From 94b0fda2bcfb9f8fa580a1e9d35d6dfccd6ae072 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Thu, 18 Jun 2020 09:27:08 +1200 Subject: [PATCH 01/27] Strip BOM from first server response --- autoload/OmniSharp/proc.vim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/autoload/OmniSharp/proc.vim b/autoload/OmniSharp/proc.vim index bf3e32839..c82937da9 100644 --- a/autoload/OmniSharp/proc.vim +++ b/autoload/OmniSharp/proc.vim @@ -92,7 +92,12 @@ function! OmniSharp#proc#vimOutHandler(channel, message) abort echom printf('%s: %s', string(a:channel), string(a:message)) endif if g:OmniSharp_server_stdio - call OmniSharp#stdio#HandleResponse(s:channels[a:channel], a:message) + let message = a:message + if message =~# "^\uFEFF" + " Strip BOM + let message = substitute(message, "^\uFEFF", '', '') + endif + call OmniSharp#stdio#HandleResponse(s:channels[a:channel], message) endif endfunction From 1ca0464a96b722e60cae98770a82bc5758f138cb Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Thu, 18 Jun 2020 09:28:17 +1200 Subject: [PATCH 02/27] Always suppress 'xxxFadeOut' diagnostics --- autoload/OmniSharp/actions/diagnostics.vim | 14 ++++++++++---- autoload/OmniSharp/locations.vim | 3 +-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/autoload/OmniSharp/actions/diagnostics.vim b/autoload/OmniSharp/actions/diagnostics.vim index 0aeb50223..d749dcdb8 100644 --- a/autoload/OmniSharp/actions/diagnostics.vim +++ b/autoload/OmniSharp/actions/diagnostics.vim @@ -57,7 +57,7 @@ endfunction function! s:StdioCheckRH(Callback, response) abort if !a:response.Success | return | endif call a:Callback(OmniSharp#locations#Parse(a:response.Body.QuickFixes, - \ function("s:DiagnosticQuickfixFixup"))) + \ function('s:DiagnosticQuickfixFixup'))) endfunction function! s:DiagnosticQuickfixFixup(quickfix) abort @@ -65,16 +65,22 @@ function! s:DiagnosticQuickfixFixup(quickfix) abort if len(exclude_paths) && has_key(a:quickfix, 'FileName') for exclude_path in exclude_paths if match(a:quickfix.FileName, exclude_path) > 0 - return + return {} endif endfor endif let overrides = get(g:, 'OmniSharp_diagnostic_overrides', {}) let diag_id = get(a:quickfix, 'Id', '-') - if index(keys(overrides), diag_id) >= 0 + if diag_id =~# '.FadeOut$' + " Some analyzers such as roslynator provide 2 diagnostics: one to mark + " the start of the issue location and another to mark the end, e.g. + " `RCS1124FadeOut`. We never make use of these FadeOut diagnostics, as + " we can extract start and end locations from the main diagnostic. + return {} + elseif index(keys(overrides), diag_id) >= 0 if overrides[diag_id].type ==? 'None' - return + return {} endif call extend(a:quickfix, overrides[diag_id]) endif diff --git a/autoload/OmniSharp/locations.vim b/autoload/OmniSharp/locations.vim index 016182bfa..1ef023c04 100644 --- a/autoload/OmniSharp/locations.vim +++ b/autoload/OmniSharp/locations.vim @@ -27,7 +27,7 @@ function! OmniSharp#locations#Parse(quickfixes, ...) abort let locations = [] for quickfix in a:quickfixes let quickfix = a:0 ? a:1(quickfix) : quickfix - if type(quickfix) == 0 + if empty(quickfix) continue endif @@ -50,7 +50,6 @@ function! OmniSharp#locations#Parse(quickfixes, ...) abort let location.end_col = quickfix.EndColumn - 1 endif - if has_key(quickfix, 'type') let location.type = get(quickfix, 'type') if has_key(quickfix, 'subtype') From d3e95bab771ab96650fae6fe77c210faa03c713d Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Thu, 18 Jun 2020 09:51:20 +1200 Subject: [PATCH 03/27] Refactor project listening, maintain project count --- autoload/OmniSharp/project.vim | 105 +++++++++++++++++++++++++++ autoload/OmniSharp/stdio.vim | 125 ++++++++------------------------- doc/omnisharp-vim.txt | 9 ++- 3 files changed, 141 insertions(+), 98 deletions(-) create mode 100644 autoload/OmniSharp/project.vim diff --git a/autoload/OmniSharp/project.vim b/autoload/OmniSharp/project.vim new file mode 100644 index 000000000..61c3d0655 --- /dev/null +++ b/autoload/OmniSharp/project.vim @@ -0,0 +1,105 @@ +let s:save_cpo = &cpoptions +set cpoptions&vim + +function! OmniSharp#project#CountLoaded() abort + " TODO: stdio guard? + let host = OmniSharp#GetHost() + return get(OmniSharp#proc#GetJob(host.sln_or_dir), 'projects_loaded', 0) +endfunction + +function! OmniSharp#project#CountTotal() abort + " TODO: stdio guard? + let host = OmniSharp#GetHost() + return get(OmniSharp#proc#GetJob(host.sln_or_dir), 'projects_total', 0) +endfunction + +" Listen for stdio server-loaded events +function! OmniSharp#project#ParseEvent(job, eventBody) abort + if g:OmniSharp_server_stdio_quickload + + " Quick load: Mark server as loaded as soon as configuration is finished + if a:job.loaded | return | endif + let message = get(a:eventBody, 'Message', '') + if message ==# 'Configuration finished.' + let a:job.loaded = 1 + silent doautocmd User OmniSharpReady + call OmniSharp#stdio#ReplayRequests() + endif + + else + + " Complete load: Wait for all projects to be loaded before marking server as + " loaded + let projects_loaded = get(a:job, 'projects_loaded', 0) + let projects_total = get(a:job, 'projects_total', 0) + if a:job.loaded && projects_loaded == projects_total | return | endif + + if !has_key(a:job, 'loading_timeout') + " Create a timeout to mark a job as loaded after 30 seconds despite not + " receiving the expected server events. + let a:job.loading_timeout = timer_start( + \ g:OmniSharp_server_loading_timeout * 1000, + \ function('s:ServerLoadTimeout', [a:job])) + endif + if !has_key(a:job, 'loading') + let a:job.loading = [] + endif + let name = get(a:eventBody, 'Name', '') + let message = get(a:eventBody, 'Message', '') + if name ==# 'OmniSharp.MSBuild.ProjectManager' + let project = matchstr(message, '''\zs.*\ze''') + if message =~# '^Queue project' + call add(a:job.loading, project) + if len(a:job.loading) > projects_total + let a:job.projects_total = len(a:job.loading) + silent doautocmd User OmniSharpProjectUpdated + endif + endif + if message =~# '^Successfully loaded project' + \ || message =~# '^Failed to load project' + if message[0] ==# 'F' + echom 'Failed to load project: ' . project + endif + call filter(a:job.loading, {idx,val -> val !=# project}) + let a:job.projects_loaded = projects_loaded + 1 + silent doautocmd User OmniSharpProjectUpdated + if len(a:job.loading) == 0 + if g:OmniSharp_server_display_loading + let elapsed = reltimefloat(reltime(a:job.start_time)) + echomsg printf('Loaded server for %s in %.1fs', + \ a:job.sln_or_dir, elapsed) + endif + let a:job.loaded = 1 + silent doautocmd User OmniSharpReady + + " TODO: Remove this delay once we have better information about + " when the server is completely initialised: + " https://github.com/OmniSharp/omnisharp-roslyn/issues/1521 + call timer_start(1000, function('OmniSharp#stdio#ReplayRequests')) + " call OmniSharp#stdio#ReplayRequests() + + unlet a:job.loading + call timer_stop(a:job.loading_timeout) + unlet a:job.loading_timeout + endif + endif + endif + + endif +endfunction + +function! s:ServerLoadTimeout(job, timer) abort + if g:OmniSharp_server_display_loading + echomsg printf('Server load notification for %s not received after %d seconds - continuing.', + \ a:job.sln_or_dir, g:OmniSharp_server_loading_timeout) + endif + let a:job.loaded = 1 + unlet a:job.loading + unlet a:job.loading_timeout + silent doautocmd User OmniSharpReady +endfunction + +let &cpoptions = s:save_cpo +unlet s:save_cpo + +" vim:et:sw=2:sts=2 diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index e23bf0c91..9f64fa2f3 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -34,110 +34,41 @@ endfunction function! s:HandleServerEvent(job, res) abort if has_key(a:res, 'Body') && type(a:res.Body) == type({}) - if !a:job.loaded - - " Listen for server-loaded events - "------------------------------------------------------------------------- - if g:OmniSharp_server_stdio_quickload - " Quick load: Mark server as loaded as soon as configuration is finished - let message = get(a:res.Body, 'Message', '') - if message ==# 'Configuration finished.' - let a:job.loaded = 1 - silent doautocmd User OmniSharpReady - call s:ReplayRequests() - endif - else - " Complete load: Wait for all projects to be loaded before marking - " server as loaded - if !has_key(a:job, 'loading_timeout') - " Create a timeout to mark a job as loaded after 30 seconds despite - " not receiving the expected server events. - let a:job.loading_timeout = timer_start( - \ g:OmniSharp_server_loading_timeout * 1000, - \ function('s:ServerLoadTimeout', [a:job])) - endif - if !has_key(a:job, 'loading') - let a:job.loading = [] - endif - let name = get(a:res.Body, 'Name', '') - let message = get(a:res.Body, 'Message', '') - if name ==# 'OmniSharp.MSBuild.ProjectManager' - let project = matchstr(message, '''\zs.*\ze''') - if message =~# '^Queue project' - call add(a:job.loading, project) - endif - if message =~# '^Successfully loaded project' - \ || message =~# '^Failed to load project' - if message[0] ==# 'F' - echom 'Failed to load project: ' . project - endif - call filter(a:job.loading, {idx,val -> val !=# project}) - if len(a:job.loading) == 0 - if g:OmniSharp_server_display_loading - let elapsed = reltimefloat(reltime(a:job.start_time)) - echomsg printf('Loaded server for %s in %.1fs', - \ a:job.sln_or_dir, elapsed) - endif - let a:job.loaded = 1 - silent doautocmd User OmniSharpReady - - " TODO: Remove this delay once we have better information about - " when the server is completely initialised: - " https://github.com/OmniSharp/omnisharp-roslyn/issues/1521 - call timer_start(1000, function('s:ReplayRequests')) - " call s:ReplayRequests() - - unlet a:job.loading - call timer_stop(a:job.loading_timeout) - unlet a:job.loading_timeout - endif - endif - endif - endif - else - - " Server is loaded, listen for diagnostics - "------------------------------------------------------------------------- - if get(a:res, 'Event', '') ==# 'Diagnostic' - if has_key(g:, 'OmniSharp_ale_diagnostics_requested') - for result in get(a:res.Body, 'Results', []) - let fname = OmniSharp#util#TranslatePathForClient(result.FileName) - let bufinfo = getbufinfo(fname) - if len(bufinfo) == 0 || !has_key(bufinfo[0], 'bufnr') - continue - endif - let bufnr = bufinfo[0].bufnr - call ale#other_source#StartChecking(bufnr, 'OmniSharp') - let opts = { 'BufNum': bufnr } - let quickfixes = OmniSharp#locations#Parse(result.QuickFixes) - call ale#sources#OmniSharp#ProcessResults(opts, quickfixes) - endfor - endif - elseif get(a:res, 'Event', '') ==# 'TestMessage' - " Diagnostics received while running tests - let lines = split(a:res.Body.Message, '\n') - for line in lines - if get(a:res.Body, 'MessageLevel', '') ==# 'error' - echohl WarningMsg | echomsg line | echohl None - elseif g:OmniSharp_runtests_echo_output - echomsg line + " Handle any project loading events + call OmniSharp#project#ParseEvent(a:job, a:res.Body) + + " Listen for diagnostics + if get(a:res, 'Event', '') ==# 'Diagnostic' + if has_key(g:, 'OmniSharp_ale_diagnostics_requested') + for result in get(a:res.Body, 'Results', []) + let fname = OmniSharp#util#TranslatePathForClient(result.FileName) + let bufinfo = getbufinfo(fname) + if len(bufinfo) == 0 || !has_key(bufinfo[0], 'bufnr') + continue endif + let bufnr = bufinfo[0].bufnr + call ale#other_source#StartChecking(bufnr, 'OmniSharp') + let opts = { 'BufNum': bufnr } + let quickfixes = OmniSharp#locations#Parse(result.QuickFixes) + call ale#sources#OmniSharp#ProcessResults(opts, quickfixes) endfor endif + endif + " Diagnostics received while running tests + if get(a:res, 'Event', '') ==# 'TestMessage' + let lines = split(a:res.Body.Message, '\n') + for line in lines + if get(a:res.Body, 'MessageLevel', '') ==# 'error' + echohl WarningMsg | echomsg line | echohl None + elseif g:OmniSharp_runtests_echo_output + echomsg line + endif + endfor endif - endif -endfunction -function! s:ServerLoadTimeout(job, timer) abort - if g:OmniSharp_server_display_loading - echomsg printf('Server load notification for %s not received after %d seconds - continuing.', - \ a:job.sln_or_dir, g:OmniSharp_server_loading_timeout) endif - let a:job.loaded = 1 - unlet a:job.loading - unlet a:job.loading_timeout endfunction function! OmniSharp#stdio#Request(command, opts) abort @@ -232,7 +163,7 @@ function! OmniSharp#stdio#RequestSend(body, command, opts, ...) abort return 1 endfunction -function! s:ReplayRequests(...) abort +function! OmniSharp#stdio#ReplayRequests(...) abort for key in keys(s:pendingRequests) call OmniSharp#stdio#Request(key, s:pendingRequests[key]) unlet s:pendingRequests[key] diff --git a/doc/omnisharp-vim.txt b/doc/omnisharp-vim.txt index 942dbdbf8..12ca2a344 100644 --- a/doc/omnisharp-vim.txt +++ b/doc/omnisharp-vim.txt @@ -590,7 +590,14 @@ customise your environment depending on the server status. *OmniSharpStarted* OmniSharpStarted Fired as soon as the server is started - it will not immediately be - responsive to commands + responsive to commands. + + *OmniSharpProjectUpdated* +OmniSharpProjectUpdate + Fired when the project status changes while OmniSharp-roslyn is starting. + Used to e.g. display the total number of projects + (|OmniSharp#project#CountTotal()|), and number of loaded projects + (|OmniSharp#project#CountLoaded()|) in the statusline. *OmniSharpReady* OmniSharpReady From f605e199becf1b4c1d4f88c679bc23e43ceee020 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Fri, 19 Jun 2020 09:09:04 +1200 Subject: [PATCH 04/27] Move python variable into py.vim --- autoload/OmniSharp.vim | 1 - autoload/OmniSharp/actions/signature.vim | 2 +- autoload/OmniSharp/py.vim | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/autoload/OmniSharp.vim b/autoload/OmniSharp.vim index a740cf279..ff56c86fb 100644 --- a/autoload/OmniSharp.vim +++ b/autoload/OmniSharp.vim @@ -6,7 +6,6 @@ set cpoptions&vim if !g:OmniSharp_server_stdio " Load python helper functions call OmniSharp#py#Bootstrap() - let g:OmniSharp_py_err = {} endif function! OmniSharp#GetHost(...) abort diff --git a/autoload/OmniSharp/actions/signature.vim b/autoload/OmniSharp/actions/signature.vim index 074fa00f4..b02f75ab4 100644 --- a/autoload/OmniSharp/actions/signature.vim +++ b/autoload/OmniSharp/actions/signature.vim @@ -10,7 +10,7 @@ function! OmniSharp#actions#signature#SignatureHelp(...) abort else let response = OmniSharp#py#Eval('signatureHelp()') if OmniSharp#py#CheckForError() | return | endif - call s:CBSignatureHelp(response) + call s:CBSignatureHelp(opts, response) endif endfunction diff --git a/autoload/OmniSharp/py.vim b/autoload/OmniSharp/py.vim index 450fae6fb..f2f50cfb3 100644 --- a/autoload/OmniSharp/py.vim +++ b/autoload/OmniSharp/py.vim @@ -8,7 +8,7 @@ set cpoptions&vim let s:alive_cache = get(s:, 'alive_cache', []) let s:pycmd = has('python3') ? 'python3' : 'python' let s:pyfile = has('python3') ? 'py3file' : 'pyfile' - +let g:OmniSharp_py_err = {} let g:OmniSharp_python_path = OmniSharp#util#PathJoin(['python']) " Default map of solution files and directories to ports. From 549a54d3a8227dc49fb261e3fd8ec9a297d475b9 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Fri, 19 Jun 2020 09:14:10 +1200 Subject: [PATCH 05/27] Detect HTTP server in stdio mode and stop server --- autoload/OmniSharp/proc.vim | 11 +++++++---- autoload/OmniSharp/stdio.vim | 15 +++++++++++++++ plugin/OmniSharp.vim | 2 +- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/autoload/OmniSharp/proc.vim b/autoload/OmniSharp/proc.vim index c82937da9..f0ee28e18 100644 --- a/autoload/OmniSharp/proc.vim +++ b/autoload/OmniSharp/proc.vim @@ -97,7 +97,8 @@ function! OmniSharp#proc#vimOutHandler(channel, message) abort " Strip BOM let message = substitute(message, "^\uFEFF", '', '') endif - call OmniSharp#stdio#HandleResponse(s:channels[a:channel], message) + let job = s:channels[ch_info(a:channel).id] + call OmniSharp#stdio#HandleResponse(job, message) endif endfunction @@ -121,7 +122,7 @@ function! OmniSharp#proc#vimJobStart(command) abort \ 'start_time': reltime(), \ 'job_id': job_start(a:command, opts) \} - let channel_id = job_getchannel(job.job_id) + let channel_id = ch_info(job_getchannel(job.job_id)).id let s:channels[channel_id] = job return job endfunction @@ -182,7 +183,7 @@ function! OmniSharp#proc#Start(command, jobkey) abort if job_status(job.job_id) ==# 'run' let s:jobs[a:jobkey] = job else - call OmniSharp#util#EchoErr('Could not run command: ' . join(a:command, ' ')) + call OmniSharp#util#EchoErr('Could not run command: ' . join(a:command)) endif elseif OmniSharp#proc#supportsVimDispatch() let job = OmniSharp#proc#dispatchStart(a:command) @@ -191,7 +192,9 @@ function! OmniSharp#proc#Start(command, jobkey) abort let job = OmniSharp#proc#vimprocStart(a:command) let s:jobs[a:jobkey] = job else - call OmniSharp#util#EchoErr('Please use neovim, or vim 8.0+ or install either vim-dispatch or vimproc.vim plugin to use this feature') + call OmniSharp#util#EchoErr( + \ 'Please use neovim, or vim 8.0+ or install either vim-dispatch or ' . + \ 'vimproc.vim plugin to use this feature') endif if type(job) == type({}) let job.sln_or_dir = a:jobkey diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index 9f64fa2f3..9234a7f1c 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -9,6 +9,21 @@ function! OmniSharp#stdio#HandleResponse(job, message) abort try let res = json_decode(a:message) catch + let a:job.json_errors = get(a:job, 'json_errors', 0) + 1 + if !OmniSharp#proc#IsJobRunning(a:job) + return + endif + if a:job.json_errors >= 3 && !a:job.loaded + call OmniSharp#log#Log('3 errors caught while loading: stopping', 'info') + call OmniSharp#proc#StopJob(a:job.sln_or_dir) + echohl WarningMsg + echomsg 'You appear to be running an HTML server in stdio mode - ' . + \ 'upgrade to the stdio server with :OmniSharpInstall, or to continue ' . + \' in HTTP mode add the following to your .vimrc and restart Vim: ' + \ 'let g:OmniSharp_server_stdio = 0' + echohl None + return + endif call OmniSharp#log#Log(a:job.job_id . ' ' . a:message, 'info') call OmniSharp#log#Log(a:job.job_id . ' JSON error: ' . v:exception, 'info') return diff --git a/plugin/OmniSharp.vim b/plugin/OmniSharp.vim index 13f4180c5..972d9a65e 100644 --- a/plugin/OmniSharp.vim +++ b/plugin/OmniSharp.vim @@ -6,7 +6,7 @@ let g:OmniSharp_temp_dir = get(g:, 'OmniSharp_temp_dir', fnamemodify(tempname(), let g:OmniSharp_lookup_metadata = get(g:, 'OmniSharp_lookup_metadata', 1) -let g:OmniSharp_server_stdio = get(g:, 'OmniSharp_server_stdio', 0) +let g:OmniSharp_server_stdio = get(g:, 'OmniSharp_server_stdio', 1) " When g:OmniSharp_server_stdio_quickload = 1, consider server 'loaded' once " 'Configuration finished' is received. When this is 0, wait for notification From 8d3713fdb606c1ceafe16c4372b1361bbd423ab8 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Fri, 19 Jun 2020 09:23:38 +1200 Subject: [PATCH 06/27] Make unsolicited diagnostic listening opt-in --- autoload/OmniSharp/stdio.vim | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index 9234a7f1c..6a3b535d5 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -53,9 +53,14 @@ function! s:HandleServerEvent(job, res) abort " Handle any project loading events call OmniSharp#project#ParseEvent(a:job, a:res.Body) - " Listen for diagnostics - if get(a:res, 'Event', '') ==# 'Diagnostic' - if has_key(g:, 'OmniSharp_ale_diagnostics_requested') + " Listen for diagnostics. + " The OmniSharp-roslyn server starts sending diagnostics once projects are + " loaded, which e.g. VSCode uses to populate project-wide warnings. + " We don't do that, and it doesn't make a lot of sense in a Vim workflow, so + " parsing these diagnostics is disabled by default. + if get(g:, 'OmniSharp_diagnostics_listen', 0) + \ && has_key(g:, 'OmniSharp_ale_diagnostics_requested') + if get(a:res, 'Event', '') ==# 'Diagnostic' for result in get(a:res.Body, 'Results', []) let fname = OmniSharp#util#TranslatePathForClient(result.FileName) let bufinfo = getbufinfo(fname) From 38c1f7b3574121117951215154eafc8e8db4cdc0 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sat, 20 Jun 2020 21:29:44 +1200 Subject: [PATCH 07/27] Stop server correctly and do not restart on :e --- autoload/OmniSharp.vim | 17 +++++++++++------ autoload/OmniSharp/actions/diagnostics.vim | 3 ++- autoload/OmniSharp/proc.vim | 19 ++++++++++--------- autoload/OmniSharp/stdio.vim | 14 ++++++++------ 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/autoload/OmniSharp.vim b/autoload/OmniSharp.vim index ff56c86fb..bf0a8257f 100644 --- a/autoload/OmniSharp.vim +++ b/autoload/OmniSharp.vim @@ -14,7 +14,6 @@ function! OmniSharp#GetHost(...) abort let sln_or_dir = OmniSharp#FindSolutionOrDir(1, bufnr) if g:OmniSharp_server_stdio let host = { - \ 'job': OmniSharp#proc#GetJob(sln_or_dir), \ 'sln_or_dir': sln_or_dir \} else @@ -28,9 +27,10 @@ function! OmniSharp#GetHost(...) abort endif if g:OmniSharp_server_stdio let host = getbufvar(bufnr, 'OmniSharp_host') - if !OmniSharp#proc#IsJobRunning(host.job) - let host.job = OmniSharp#proc#GetJob(host.sln_or_dir) - endif + return { + \ 'sln_or_dir': host.sln_or_dir, + \ 'job': OmniSharp#proc#GetJob(host.sln_or_dir) + \} endif return getbufvar(bufnr, 'OmniSharp_host') endfunction @@ -294,6 +294,12 @@ function! OmniSharp#StartServer(...) abort " Optionally perform check if server is already running if check_is_running + let job = OmniSharp#proc#GetJob(sln_or_dir) + if type(job) == type({}) && get(job, 'stopped') + " The job has been manually stopped - do not start it again until + " instructed + return + endif let running = OmniSharp#proc#IsJobRunning(sln_or_dir) if !g:OmniSharp_server_stdio " If the port is hardcoded, we should check if any other vim instances @@ -323,10 +329,9 @@ function! s:StartServer(sln_or_dir) abort return endif - let job = OmniSharp#proc#Start(command, a:sln_or_dir) + call OmniSharp#proc#Start(command, a:sln_or_dir) if g:OmniSharp_server_stdio let b:OmniSharp_host = { - \ 'job': job, \ 'sln_or_dir': a:sln_or_dir \} endif diff --git a/autoload/OmniSharp/actions/diagnostics.vim b/autoload/OmniSharp/actions/diagnostics.vim index d749dcdb8..39f3880c2 100644 --- a/autoload/OmniSharp/actions/diagnostics.vim +++ b/autoload/OmniSharp/actions/diagnostics.vim @@ -51,7 +51,8 @@ function! s:StdioCheckGlobal(Callback) abort let opts = { \ 'ResponseHandler': function('s:StdioCheckRH', [a:Callback]) \} - call OmniSharp#stdio#RequestSend({}, '/codecheck', opts) + let job = OmniSharp#GetHost().job + call OmniSharp#stdio#RequestSend(job, {}, '/codecheck', opts) endfunction function! s:StdioCheckRH(Callback, response) abort diff --git a/autoload/OmniSharp/proc.vim b/autoload/OmniSharp/proc.vim index f0ee28e18..cbc36ba0d 100644 --- a/autoload/OmniSharp/proc.vim +++ b/autoload/OmniSharp/proc.vim @@ -37,10 +37,14 @@ function! OmniSharp#proc#neovimOutHandler(job_id, data, event) dict abort endfunction function! OmniSharp#proc#neovimErrHandler(job_id, data, event) dict abort - if type(a:data) == type([]) && len(a:data) && a:data[0] =~# "^\uFEFF$" + if type(a:data) == type([]) && len(a:data) == 1 && a:data[0] =~# "^\uFEFF$" " Ignore BOM return endif + if type(a:data) == type([]) && len(a:data) == 1 && a:data[0] ==# '' + " Ignore empty + return + endif let message = printf('%s: %s', a:event, string(a:data)) call OmniSharp#util#EchoErr(message) endfunction @@ -53,9 +57,6 @@ function! OmniSharp#proc#neovimExitHandler(job_id, data, event) dict abort break endif endfor - if !empty(jobkey) && has_key(s:jobs, jobkey) - call remove(s:jobs, jobkey) - endif endfunction function! OmniSharp#proc#neovimJobStart(command) abort @@ -75,6 +76,7 @@ function! OmniSharp#proc#neovimJobStart(command) abort \ 'job_id': jobstart(a:command, opts), \ 'partial': '' \} + let job.pid = jobpid(job.job_id) let s:channels[job.job_id] = job return job endfunction @@ -122,6 +124,7 @@ function! OmniSharp#proc#vimJobStart(command) abort \ 'start_time': reltime(), \ 'job_id': job_start(a:command, opts) \} + let job.pid = job_info(job.job_id).process let channel_id = ch_info(job_getchannel(job.job_id)).id let s:channels[channel_id] = job return job @@ -219,9 +222,7 @@ function! OmniSharp#proc#StopJob(jobkey) abort elseif OmniSharp#proc#supportsVimProc() call job.kill() endif - if has_key(s:jobs, a:jobkey) - call remove(s:jobs, a:jobkey) - endif + let job.stopped = 1 silent doautocmd User OmniSharpStopped endfunction @@ -240,10 +241,10 @@ function! OmniSharp#proc#IsJobRunning(jobkey) abort let job = get(s:jobs, a:jobkey) endif if OmniSharp#proc#supportsNeovimJobs() - return 1 + return !get(job, 'stopped', 0) elseif OmniSharp#proc#supportsVimJobs() let status = job_status(job.job_id) - return status ==# 'run' + return status ==# 'run' && !get(job, 'stopped', 0) elseif OmniSharp#proc#supportsVimDispatch() return dispatch#completed(job) elseif OmniSharp#proc#supportsVimProc() diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index 6a3b535d5..c299693bf 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -103,6 +103,10 @@ function! OmniSharp#stdio#Request(command, opts) abort let lnum = line('.') let cnum = col('.') endif + let job = OmniSharp#GetHost(bufnr).job + if !OmniSharp#proc#IsJobRunning(job) + return + endif if has_key(a:opts, 'SavePosition') let s:lastPosition = [bufnr, lnum, cnum] endif @@ -140,21 +144,19 @@ function! OmniSharp#stdio#Request(command, opts) abort if send_buffer let body.Arguments.Buffer = buffer endif - return OmniSharp#stdio#RequestSend(body, a:command, a:opts, sep) + return OmniSharp#stdio#RequestSend(job, body, a:command, a:opts, sep) endfunction -function! OmniSharp#stdio#RequestSend(body, command, opts, ...) abort +function! OmniSharp#stdio#RequestSend(job, body, command, opts, ...) abort let sep = a:0 ? a:1 : '' - - let job = OmniSharp#GetHost().job - if type(job) != type({}) || !has_key(job, 'job_id') || !job.loaded + if type(a:job) != type({}) || !has_key(a:job, 'job_id') || !a:job.loaded if has_key(a:opts, 'ReplayOnLoad') && !has_key(s:pendingRequests, a:command) " This request should be replayed when the server is fully loaded let s:pendingRequests[a:command] = a:opts endif return 0 endif - let job_id = job.job_id + let job_id = a:job.job_id call OmniSharp#log#Log(job_id . ' Request: ' . a:command, 'debug') let a:body['Command'] = a:command From 3bdb6001e61e611f5c723ca9569cce75f8d6657d Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sun, 21 Jun 2020 00:56:51 +1200 Subject: [PATCH 08/27] Save OmniSharp-roslyn session logs in unique files --- autoload/OmniSharp/log.vim | 21 +++++++++++++++++++-- autoload/OmniSharp/stdio.vim | 17 ++++++++--------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/autoload/OmniSharp/log.vim b/autoload/OmniSharp/log.vim index ba2e18518..5d62c6c45 100644 --- a/autoload/OmniSharp/log.vim +++ b/autoload/OmniSharp/log.vim @@ -3,7 +3,10 @@ set cpoptions&vim let s:stdiologfile = expand(':p:h:h:h') . '/log/stdio.log' -function! OmniSharp#log#Log(message, loglevel) abort +function! OmniSharp#log#Log(job, message, loglevel) abort + if !has_key(a:job, 'logfile') + let a:job.logfile = s:Init(a:job) + endif let logit = 0 if g:OmniSharp_loglevel ==? 'debug' " Log everything @@ -14,13 +17,19 @@ function! OmniSharp#log#Log(message, loglevel) abort " g:OmniSharp_loglevel ==? 'none' endif if logit - call writefile([a:message], s:stdiologfile, 'a') + call writefile([a:message], a:job.logfile, 'a') endif endfunction function! OmniSharp#log#Open(...) if g:OmniSharp_server_stdio let logfile = s:stdiologfile + if exists('b:OmniSharp_host') + let job = OmniSharp#GetHost().job + if type(job) == type({}) + let logfile = get(job, 'logfile', s:stdiologfile) + endif + endif else let logfile = OmniSharp#py#Eval('getLogFile()') if OmniSharp#py#CheckForError() | return | endif @@ -29,6 +38,14 @@ function! OmniSharp#log#Open(...) exec cmd logfile endfunction +function! s:Init(job) abort + let logfile = strftime('%Y%m%d%H%M_') . get(a:job, 'pid') . '_omnisharp.log' + let logfile = fnamemodify(s:stdiologfile, ':h') . '/' . logfile + " Add the new log filename to the standard log, so it can be opened with `gf` + call writefile([logfile], s:stdiologfile, 'a') + return logfile +endfunction + let &cpoptions = s:save_cpo unlet s:save_cpo diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index c299693bf..941c49d88 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -14,7 +14,7 @@ function! OmniSharp#stdio#HandleResponse(job, message) abort return endif if a:job.json_errors >= 3 && !a:job.loaded - call OmniSharp#log#Log('3 errors caught while loading: stopping', 'info') + call OmniSharp#log#Log(a:job, '3 errors caught while loading: stopping', 'info') call OmniSharp#proc#StopJob(a:job.sln_or_dir) echohl WarningMsg echomsg 'You appear to be running an HTML server in stdio mode - ' . @@ -24,12 +24,12 @@ function! OmniSharp#stdio#HandleResponse(job, message) abort echohl None return endif - call OmniSharp#log#Log(a:job.job_id . ' ' . a:message, 'info') - call OmniSharp#log#Log(a:job.job_id . ' JSON error: ' . v:exception, 'info') + call OmniSharp#log#Log(a:job, a:message, 'info') + call OmniSharp#log#Log(a:job, 'JSON error: ' . v:exception, 'info') return endtry let loglevel = get(res, 'Event', '') ==? 'log' ? 'info' : 'debug' - call OmniSharp#log#Log(a:job.job_id . ' ' . a:message, loglevel) + call OmniSharp#log#Log(a:job, a:message, loglevel) if get(res, 'Type', '') ==# 'event' call s:HandleServerEvent(a:job, res) return @@ -156,8 +156,7 @@ function! OmniSharp#stdio#RequestSend(job, body, command, opts, ...) abort endif return 0 endif - let job_id = a:job.job_id - call OmniSharp#log#Log(job_id . ' Request: ' . a:command, 'debug') + call OmniSharp#log#Log(a:job, ' Request: ' . a:command, 'debug') let a:body['Command'] = a:command let a:body['Seq'] = s:nextseq @@ -176,11 +175,11 @@ function! OmniSharp#stdio#RequestSend(job, body, command, opts, ...) abort let s:requests[s:nextseq].ResponseHandler = a:opts.ResponseHandler endif let s:nextseq += 1 - call OmniSharp#log#Log(encodedBody, 'debug') + call OmniSharp#log#Log(a:job, encodedBody, 'debug') if has('nvim') - call chansend(job_id, encodedBody . "\n") + call chansend(a:job.job_id, encodedBody . "\n") else - call ch_sendraw(job_id, encodedBody . "\n") + call ch_sendraw(a:job.job_id, encodedBody . "\n") endif return 1 endfunction From 0715019446a618d1d28fc7179a62145fe0c5c909 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 22 Jun 2020 00:57:19 +1200 Subject: [PATCH 09/27] Format log similar to the VSCode OmniSharp log --- autoload/OmniSharp/log.vim | 47 +++++++++++++++++++++++++++++++++--- autoload/OmniSharp/stdio.vim | 8 +++--- 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/autoload/OmniSharp/log.vim b/autoload/OmniSharp/log.vim index 5d62c6c45..9eb61967a 100644 --- a/autoload/OmniSharp/log.vim +++ b/autoload/OmniSharp/log.vim @@ -4,9 +4,7 @@ set cpoptions&vim let s:stdiologfile = expand(':p:h:h:h') . '/log/stdio.log' function! OmniSharp#log#Log(job, message, loglevel) abort - if !has_key(a:job, 'logfile') - let a:job.logfile = s:Init(a:job) - endif + call s:Init(a:job) let logit = 0 if g:OmniSharp_loglevel ==? 'debug' " Log everything @@ -21,6 +19,26 @@ function! OmniSharp#log#Log(job, message, loglevel) abort endif endfunction +" Log a decoded server message +function! OmniSharp#log#LogServer(job, raw, msg) abort + call s:Init(a:job) + if !has_key(a:msg, 'Body') || type(a:msg.Body) != type({}) + call writefile(['RAW: ' . a:raw], a:job.logfile, 'a') + elseif get(a:msg, 'Event', '') ==? 'log' + " let lines = [ + " \ printf('[%s]: %s', s:LogLevelPrefix(a:msg.Body.LogLevel), a:msg.Body.Name), + " \ ' ' . substitute(a:msg.Body.Message, '\%uD\%u0', "\r", 'g') + " \] + let lines = split(a:msg.Body.Message, '\%uD\%u0', 1) + let lines[0] = ' ' . lines[0] + let prefix = s:LogLevelPrefix(a:msg.Body.LogLevel) + call insert(lines, printf('[%s]: %s', prefix, a:msg.Body.Name)) + call writefile(lines, a:job.logfile, 'a') + else + call writefile(['ELSE: ' . a:raw], a:job.logfile, 'a') + endif +endfunction + function! OmniSharp#log#Open(...) if g:OmniSharp_server_stdio let logfile = s:stdiologfile @@ -38,12 +56,33 @@ function! OmniSharp#log#Open(...) exec cmd logfile endfunction +function! s:LogLevelPrefix(loglevel) abort + if a:loglevel ==# 'TRACE' + return 'trce' + elseif a:loglevel ==# 'DEBUG' + return 'dbug' + elseif a:loglevel ==# 'INFORMATION' + return 'info' + elseif a:loglevel ==# 'WARNING' + return 'warn' + elseif a:loglevel ==# 'ERROR' + return 'fail' + elseif a:loglevel ==# 'CRITICAL' + return 'crit' + else + return a:loglevel + endif +endfunction + function! s:Init(job) abort + if has_key(a:job, 'logfile') + return + endif let logfile = strftime('%Y%m%d%H%M_') . get(a:job, 'pid') . '_omnisharp.log' let logfile = fnamemodify(s:stdiologfile, ':h') . '/' . logfile " Add the new log filename to the standard log, so it can be opened with `gf` call writefile([logfile], s:stdiologfile, 'a') - return logfile + let a:job.logfile = logfile endfunction let &cpoptions = s:save_cpo diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index 941c49d88..518a9f1d3 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -28,8 +28,7 @@ function! OmniSharp#stdio#HandleResponse(job, message) abort call OmniSharp#log#Log(a:job, 'JSON error: ' . v:exception, 'info') return endtry - let loglevel = get(res, 'Event', '') ==? 'log' ? 'info' : 'debug' - call OmniSharp#log#Log(a:job, a:message, loglevel) + call OmniSharp#log#LogServer(a:job, a:message, res) if get(res, 'Type', '') ==# 'event' call s:HandleServerEvent(a:job, res) return @@ -175,7 +174,10 @@ function! OmniSharp#stdio#RequestSend(job, body, command, opts, ...) abort let s:requests[s:nextseq].ResponseHandler = a:opts.ResponseHandler endif let s:nextseq += 1 - call OmniSharp#log#Log(a:job, encodedBody, 'debug') + if get(g:, 'OmniSharp_proc_debug') + " The raw request is already logged by the server in debug mode. + call OmniSharp#log#Log(a:job, encodedBody, 'debug') + endif if has('nvim') call chansend(a:job.job_id, encodedBody . "\n") else From 96fb7add695523ba18d87a61b01540552119f290 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 22 Jun 2020 00:57:52 +1200 Subject: [PATCH 10/27] Add omnisharplog filetype and syntax highlighting --- ftdetect/omnisharplog.vim | 1 + ftplugin/omnisharplog/OmniSharp.vim | 1 + syntax/omnisharplog.vim | 34 +++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 ftdetect/omnisharplog.vim create mode 100644 ftplugin/omnisharplog/OmniSharp.vim create mode 100644 syntax/omnisharplog.vim diff --git a/ftdetect/omnisharplog.vim b/ftdetect/omnisharplog.vim new file mode 100644 index 000000000..6b602b7ec --- /dev/null +++ b/ftdetect/omnisharplog.vim @@ -0,0 +1 @@ +autocmd BufRead,BufNewFile *_omnisharp.log set filetype=omnisharplog diff --git a/ftplugin/omnisharplog/OmniSharp.vim b/ftplugin/omnisharplog/OmniSharp.vim new file mode 100644 index 000000000..f2b6acc5b --- /dev/null +++ b/ftplugin/omnisharplog/OmniSharp.vim @@ -0,0 +1 @@ +set foldlevel=0 diff --git a/syntax/omnisharplog.vim b/syntax/omnisharplog.vim new file mode 100644 index 000000000..852723cb9 --- /dev/null +++ b/syntax/omnisharplog.vim @@ -0,0 +1,34 @@ +if exists('b:current_syntax') + finish +endif + +let s:save_cpo = &cpoptions +set cpoptions&vim + +syn match oslName "^\[\w\{4,5\}\]: .*$"hs=s+7 contains=oslTrace,oslDebug,oslInformation,oslWarning,oslError,oslCritical + +syn match oslTrace "^\[trce\]"ms=s+1,me=e-1 contained +syn match oslDebug "^\[dbug\]"ms=s+1,me=e-1 contained +syn match oslInformation "^\[info\]"ms=s+1,me=e-1 contained +syn match oslWarning "^\[warn\]"ms=s+1,me=e-1 contained +syn match oslError "^\[trce\]"ms=s+1,me=e-1 contained +syn match oslError "^\[ERROR\]"ms=s+1,me=e-1 contained +syn match oslCritical "^\[crit\]"ms=s+1,me=e-1 contained + +syn region oslRequestResponse start="\*\{12\}\s\+\%(Request\|Response\)\s\+\*\{12\}" end="^}" transparent fold + +hi def link oslName Comment + +hi def link oslTrace Identifier +hi def link oslDebug NonText +hi def link oslInformation Type +hi def link oslWarning WarningMsg +hi def link oslError ErrorMsg +hi def link oslCritical ErrorMsg + +let b:current_syntax = 'omnisharplog' + +let &cpoptions = s:save_cpo +unlet s:save_cpo + +" vim:et:sw=2:sts=2 From 97403a2e0d8ba035ea8fe3e93de36a65b9f135f7 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 22 Jun 2020 01:17:38 +1200 Subject: [PATCH 11/27] Normalise omnisharplog newlines between OS types --- autoload/OmniSharp/log.vim | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/autoload/OmniSharp/log.vim b/autoload/OmniSharp/log.vim index 9eb61967a..34cdc1e65 100644 --- a/autoload/OmniSharp/log.vim +++ b/autoload/OmniSharp/log.vim @@ -25,11 +25,10 @@ function! OmniSharp#log#LogServer(job, raw, msg) abort if !has_key(a:msg, 'Body') || type(a:msg.Body) != type({}) call writefile(['RAW: ' . a:raw], a:job.logfile, 'a') elseif get(a:msg, 'Event', '') ==? 'log' - " let lines = [ - " \ printf('[%s]: %s', s:LogLevelPrefix(a:msg.Body.LogLevel), a:msg.Body.Name), - " \ ' ' . substitute(a:msg.Body.Message, '\%uD\%u0', "\r", 'g') - " \] - let lines = split(a:msg.Body.Message, '\%uD\%u0', 1) + " Attempt to normalise newlines, which can be \%uD\%u0 in Windows and \%u0 + " in linux + let message = substitute(a:msg.Body.Message, '\%uD\ze\%u0', '', 'g') + let lines = split(message, '\%u0', 1) let lines[0] = ' ' . lines[0] let prefix = s:LogLevelPrefix(a:msg.Body.LogLevel) call insert(lines, printf('[%s]: %s', prefix, a:msg.Body.Name)) From ecca795ed0bcf5de8847997363854f0bcb8494bc Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 22 Jun 2020 11:36:41 +1200 Subject: [PATCH 12/27] Improve log formatting and respect loglevel --- autoload/OmniSharp/log.vim | 43 ++++++++++++++++++++++-------------- autoload/OmniSharp/proc.vim | 12 ++++++++++ autoload/OmniSharp/stdio.vim | 10 ++++----- autoload/OmniSharp/util.vim | 8 +++++-- ftdetect/omnisharplog.vim | 2 +- syntax/omnisharplog.vim | 6 ++++- 6 files changed, 56 insertions(+), 25 deletions(-) diff --git a/autoload/OmniSharp/log.vim b/autoload/OmniSharp/log.vim index 34cdc1e65..7032f545d 100644 --- a/autoload/OmniSharp/log.vim +++ b/autoload/OmniSharp/log.vim @@ -3,28 +3,27 @@ set cpoptions&vim let s:stdiologfile = expand(':p:h:h:h') . '/log/stdio.log' -function! OmniSharp#log#Log(job, message, loglevel) abort +" Log from OmniSharp-vim +function! OmniSharp#log#Log(job, message, ...) abort + if g:OmniSharp_loglevel ==? 'none' | return | endif call s:Init(a:job) - let logit = 0 - if g:OmniSharp_loglevel ==? 'debug' - " Log everything - let logit = 1 - elseif g:OmniSharp_loglevel ==? 'info' - let logit = a:loglevel ==# 'info' - else - " g:OmniSharp_loglevel ==? 'none' - endif - if logit + let debug = a:0 && a:1 + if g:OmniSharp_loglevel !=? 'info' || !debug call writefile([a:message], a:job.logfile, 'a') endif endfunction " Log a decoded server message function! OmniSharp#log#LogServer(job, raw, msg) abort + if g:OmniSharp_loglevel ==? 'none' | return | endif call s:Init(a:job) - if !has_key(a:msg, 'Body') || type(a:msg.Body) != type({}) - call writefile(['RAW: ' . a:raw], a:job.logfile, 'a') - elseif get(a:msg, 'Event', '') ==? 'log' + if get(g:, 'OmniSharp_proc_debug') + call writefile([a:raw], a:job.logfile, 'a') + elseif !has_key(a:msg, 'Body') || type(a:msg.Body) != type({}) + return + elseif !has_key(a:msg, 'Event') + return + elseif get(a:msg, 'Event', '') ==# 'log' " Attempt to normalise newlines, which can be \%uD\%u0 in Windows and \%u0 " in linux let message = substitute(a:msg.Body.Message, '\%uD\ze\%u0', '', 'g') @@ -33,8 +32,20 @@ function! OmniSharp#log#LogServer(job, raw, msg) abort let prefix = s:LogLevelPrefix(a:msg.Body.LogLevel) call insert(lines, printf('[%s]: %s', prefix, a:msg.Body.Name)) call writefile(lines, a:job.logfile, 'a') - else - call writefile(['ELSE: ' . a:raw], a:job.logfile, 'a') + elseif get(a:msg, 'Event', '') ==# 'MsBuildProjectDiagnostics' + if len(a:msg.Body.Errors) == 0 && len(a:msg.Body.Warnings) == 0 + return + endif + let lines = [a:msg.Body.FileName] + for error in a:msg.Body.Errors + call add(lines, printf('%s(%d,%d): Error: %s', + \ error.FileName, error.StartLine, error.StartColumn, error.Text)) + endfor + for warn in a:msg.Body.Warnings + call add(lines, printf('%s(%d,%d): Warning: %s', + \ warn.FileName, warn.StartLine, warn.StartColumn, warn.Text)) + endfor + call writefile(lines, a:job.logfile, 'a') endif endfunction diff --git a/autoload/OmniSharp/proc.vim b/autoload/OmniSharp/proc.vim index cbc36ba0d..47ebbb502 100644 --- a/autoload/OmniSharp/proc.vim +++ b/autoload/OmniSharp/proc.vim @@ -78,6 +78,7 @@ function! OmniSharp#proc#neovimJobStart(command) abort \} let job.pid = jobpid(job.job_id) let s:channels[job.job_id] = job + call OmniSharp#log#Log(job, split(execute('version'), "\n")[0]) return job endfunction @@ -114,6 +115,9 @@ function! OmniSharp#proc#vimJobStart(command) abort call OmniSharp#util#EchoErr('Not using Vim 8.0+') return -1 endif + + + call s:debug('Using vim job_start to start the following command:') call s:debug(a:command) let opts = {'err_cb': 'OmniSharp#proc#vimErrHandler'} @@ -127,6 +131,7 @@ function! OmniSharp#proc#vimJobStart(command) abort let job.pid = job_info(job.job_id).process let channel_id = ch_info(job_getchannel(job.job_id)).id let s:channels[channel_id] = job + call OmniSharp#log#Log(job, join(split(execute('version'), "\n")[0:1], ', ')) return job endfunction @@ -202,6 +207,13 @@ function! OmniSharp#proc#Start(command, jobkey) abort if type(job) == type({}) let job.sln_or_dir = a:jobkey let job.loaded = 0 + call OmniSharp#log#Log(job, '') + call OmniSharp#log#Log(job, 'OmniSharp server started.') + call OmniSharp#log#Log(job, ' Path: ' . OmniSharp#util#GetServerPath()) + call OmniSharp#log#Log(job, ' Target: ' . a:jobkey) + call OmniSharp#log#Log(job, ' PID: ' . job.pid) + call OmniSharp#log#Log(job, ' Command: ' . join(a:command), 1) + call OmniSharp#log#Log(job, '') silent doautocmd User OmniSharpStarted endif return job diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index 518a9f1d3..da1399345 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -14,7 +14,7 @@ function! OmniSharp#stdio#HandleResponse(job, message) abort return endif if a:job.json_errors >= 3 && !a:job.loaded - call OmniSharp#log#Log(a:job, '3 errors caught while loading: stopping', 'info') + call OmniSharp#log#Log(a:job, '3 errors caught while loading: stopping') call OmniSharp#proc#StopJob(a:job.sln_or_dir) echohl WarningMsg echomsg 'You appear to be running an HTML server in stdio mode - ' . @@ -24,8 +24,8 @@ function! OmniSharp#stdio#HandleResponse(job, message) abort echohl None return endif - call OmniSharp#log#Log(a:job, a:message, 'info') - call OmniSharp#log#Log(a:job, 'JSON error: ' . v:exception, 'info') + call OmniSharp#log#Log(a:job, a:message) + call OmniSharp#log#Log(a:job, 'JSON error: ' . v:exception) return endtry call OmniSharp#log#LogServer(a:job, a:message, res) @@ -155,7 +155,7 @@ function! OmniSharp#stdio#RequestSend(job, body, command, opts, ...) abort endif return 0 endif - call OmniSharp#log#Log(a:job, ' Request: ' . a:command, 'debug') + call OmniSharp#log#Log(a:job, 'Request: ' . a:command, 1) let a:body['Command'] = a:command let a:body['Seq'] = s:nextseq @@ -176,7 +176,7 @@ function! OmniSharp#stdio#RequestSend(job, body, command, opts, ...) abort let s:nextseq += 1 if get(g:, 'OmniSharp_proc_debug') " The raw request is already logged by the server in debug mode. - call OmniSharp#log#Log(a:job, encodedBody, 'debug') + call OmniSharp#log#Log(a:job, encodedBody, 1) endif if has('nvim') call chansend(a:job.job_id, encodedBody . "\n") diff --git a/autoload/OmniSharp/util.vim b/autoload/OmniSharp/util.vim index e91688bd7..4674e153a 100644 --- a/autoload/OmniSharp/util.vim +++ b/autoload/OmniSharp/util.vim @@ -136,6 +136,10 @@ function! OmniSharp#util#EchoErr(msg) echohl ErrorMsg | echomsg a:msg | echohl None endfunction +function! OmniSharp#util#GetServerPath() abort + return get(s:, 'server_path', '') +endfunction + function! OmniSharp#util#GetStartCmd(solution_file) abort let solution_path = a:solution_file if fnamemodify(solution_path, ':t') ==? s:roslyn_server_files @@ -170,8 +174,8 @@ function! OmniSharp#util#GetStartCmd(solution_file) abort endif let command += ['-s', solution_path] - if g:OmniSharp_loglevel ==? 'debug' - let command += ['-v'] + if g:OmniSharp_loglevel !=? 'info' + let command += ['-l', g:OmniSharp_loglevel] endif if !has('win32') && !s:is_cygwin() && g:OmniSharp_server_use_mono diff --git a/ftdetect/omnisharplog.vim b/ftdetect/omnisharplog.vim index 6b602b7ec..7da569a65 100644 --- a/ftdetect/omnisharplog.vim +++ b/ftdetect/omnisharplog.vim @@ -1 +1 @@ -autocmd BufRead,BufNewFile *_omnisharp.log set filetype=omnisharplog +autocmd BufRead,BufNewFile *omnisharp.log set filetype=omnisharplog diff --git a/syntax/omnisharplog.vim b/syntax/omnisharplog.vim index 852723cb9..f077ae9d7 100644 --- a/syntax/omnisharplog.vim +++ b/syntax/omnisharplog.vim @@ -11,10 +11,12 @@ syn match oslTrace "^\[trce\]"ms=s+1,me=e-1 contained syn match oslDebug "^\[dbug\]"ms=s+1,me=e-1 contained syn match oslInformation "^\[info\]"ms=s+1,me=e-1 contained syn match oslWarning "^\[warn\]"ms=s+1,me=e-1 contained -syn match oslError "^\[trce\]"ms=s+1,me=e-1 contained +syn match oslError "^\[fail\]"ms=s+1,me=e-1 contained syn match oslError "^\[ERROR\]"ms=s+1,me=e-1 contained syn match oslCritical "^\[crit\]"ms=s+1,me=e-1 contained +syn match oslEndpoint "^Request: .*$"hs=s+9 + syn region oslRequestResponse start="\*\{12\}\s\+\%(Request\|Response\)\s\+\*\{12\}" end="^}" transparent fold hi def link oslName Comment @@ -26,6 +28,8 @@ hi def link oslWarning WarningMsg hi def link oslError ErrorMsg hi def link oslCritical ErrorMsg +hi def link oslEndpoint Identifier + let b:current_syntax = 'omnisharplog' let &cpoptions = s:save_cpo From be653b34f0cbab016d999986d665f748b54a6179 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 22 Jun 2020 22:22:06 +1200 Subject: [PATCH 13/27] Simplify OmniSharp#GetHost() and improve caching --- autoload/OmniSharp.vim | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/autoload/OmniSharp.vim b/autoload/OmniSharp.vim index bf0a8257f..2db515719 100644 --- a/autoload/OmniSharp.vim +++ b/autoload/OmniSharp.vim @@ -10,29 +10,36 @@ endif function! OmniSharp#GetHost(...) abort let bufnr = a:0 ? a:1 : bufnr('%') - if empty(getbufvar(bufnr, 'OmniSharp_host')) - let sln_or_dir = OmniSharp#FindSolutionOrDir(1, bufnr) - if g:OmniSharp_server_stdio - let host = { - \ 'sln_or_dir': sln_or_dir - \} - else + if g:OmniSharp_server_stdio + " Using the stdio server, b:OmniSharp_host is a dict containing the + " sln_or_dir: + " { 'sln_or_dir': '/path/to/solution_or_dir' } + let host = getbufvar(bufnr, 'OmniSharp_host', {}) + if get(host, 'sln_or_dir', '') ==# '' + let host.sln_or_dir = OmniSharp#FindSolutionOrDir(1, bufnr) + call setbufvar(bufnr, 'OmniSharp_host', host) + endif + " The returned dict includes the job, but the job is _not_ part of + " b:OmniSharp_host. It is important to always fetch the job from + " OmniSharp#proc#GetJob, ensuring that the job properties (job.job_id, + " job.loaded, job.pid etc.) are always correct and up-to-date. + return { + \ 'sln_or_dir': host.sln_or_dir, + \ 'job': OmniSharp#proc#GetJob(host.sln_or_dir) + \} + else + " Using the HTTP server, b:OmniSharp_host is a localhost URL + if empty(getbufvar(bufnr, 'OmniSharp_host')) + let sln_or_dir = OmniSharp#FindSolutionOrDir(1, bufnr) let port = OmniSharp#py#GetPort(sln_or_dir) if port == 0 return '' endif let host = get(g:, 'OmniSharp_host', 'http://localhost:' . port) + call setbufvar(bufnr, 'OmniSharp_host', host) endif - call setbufvar(bufnr, 'OmniSharp_host', host) - endif - if g:OmniSharp_server_stdio - let host = getbufvar(bufnr, 'OmniSharp_host') - return { - \ 'sln_or_dir': host.sln_or_dir, - \ 'job': OmniSharp#proc#GetJob(host.sln_or_dir) - \} + return getbufvar(bufnr, 'OmniSharp_host') endif - return getbufvar(bufnr, 'OmniSharp_host') endfunction From 86d9cf5896bc9638eb886e6e74306d29a3544f67 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 23 Jun 2020 11:12:01 +1200 Subject: [PATCH 14/27] Encapsulate "global" (non-buffer) stdio requests --- autoload/OmniSharp/actions/diagnostics.vim | 15 ++++++--------- autoload/OmniSharp/stdio.vim | 8 ++++++-- ftplugin/omnisharplog/OmniSharp.vim | 1 + 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/autoload/OmniSharp/actions/diagnostics.vim b/autoload/OmniSharp/actions/diagnostics.vim index 39f3880c2..360d3beb9 100644 --- a/autoload/OmniSharp/actions/diagnostics.vim +++ b/autoload/OmniSharp/actions/diagnostics.vim @@ -27,7 +27,12 @@ function! OmniSharp#actions#diagnostics#CheckGlobal(...) abort if bufname('%') ==# '' || OmniSharp#FugitiveCheck() | return [] | endif " Place the results in the quickfix window, if possible if g:OmniSharp_server_stdio - call s:StdioCheckGlobal(function('s:CBGlobalCodeCheck')) + let Callback = function('s:CBGlobalCodeCheck') + let opts = { + \ 'ResponseHandler': function('s:StdioCheckRH', [Callback]) + \} + let job = OmniSharp#GetHost().job + call OmniSharp#stdio#RequestGlobal(job, '/codecheck', opts) else let quickfixes = OmniSharp#py#Eval('globalCodeCheck()') if OmniSharp#py#CheckForError() | return | endif @@ -47,14 +52,6 @@ function! OmniSharp#actions#diagnostics#StdioCheck(opts, Callback) abort call OmniSharp#stdio#Request('/codecheck', opts) endfunction -function! s:StdioCheckGlobal(Callback) abort - let opts = { - \ 'ResponseHandler': function('s:StdioCheckRH', [a:Callback]) - \} - let job = OmniSharp#GetHost().job - call OmniSharp#stdio#RequestSend(job, {}, '/codecheck', opts) -endfunction - function! s:StdioCheckRH(Callback, response) abort if !a:response.Success | return | endif call a:Callback(OmniSharp#locations#Parse(a:response.Body.QuickFixes, diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index da1399345..0e6c0fd4e 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -143,10 +143,14 @@ function! OmniSharp#stdio#Request(command, opts) abort if send_buffer let body.Arguments.Buffer = buffer endif - return OmniSharp#stdio#RequestSend(job, body, a:command, a:opts, sep) + return s:Request(job, body, a:command, a:opts, sep) endfunction -function! OmniSharp#stdio#RequestSend(job, body, command, opts, ...) abort +function! OmniSharp#stdio#RequestGlobal(job, command, opts) abort + call s:Request(a:job, {}, a:command, a:opts) +endfunction + +function! s:Request(job, body, command, opts, ...) abort let sep = a:0 ? a:1 : '' if type(a:job) != type({}) || !has_key(a:job, 'job_id') || !a:job.loaded if has_key(a:opts, 'ReplayOnLoad') && !has_key(s:pendingRequests, a:command) diff --git a/ftplugin/omnisharplog/OmniSharp.vim b/ftplugin/omnisharplog/OmniSharp.vim index f2b6acc5b..285f0702d 100644 --- a/ftplugin/omnisharplog/OmniSharp.vim +++ b/ftplugin/omnisharplog/OmniSharp.vim @@ -1 +1,2 @@ set foldlevel=0 +set foldmethod=syntax From c41b6c4e7e200e17cdba1491eb602f51a6e4cb2c Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Fri, 26 Jun 2020 16:56:31 +1200 Subject: [PATCH 15/27] Do not try to switch to log buffer when 'nohidden' --- autoload/OmniSharp/log.vim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/autoload/OmniSharp/log.vim b/autoload/OmniSharp/log.vim index 7032f545d..6b1173c40 100644 --- a/autoload/OmniSharp/log.vim +++ b/autoload/OmniSharp/log.vim @@ -63,6 +63,9 @@ function! OmniSharp#log#Open(...) if OmniSharp#py#CheckForError() | return | endif endif let cmd = (a:0 && type(a:1) == type('') && len(a:1)) ? a:1 : 'edit' + if cmd ==# 'edit' && !&hidden + let cmd = 'split' + endif exec cmd logfile endfunction From 4af026db196f8418f8278a41960a3e69b64bfb5e Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sun, 28 Jun 2020 10:48:31 +1200 Subject: [PATCH 16/27] Use both 'Event' and 'Body' to parse events --- autoload/OmniSharp/project.vim | 2 +- autoload/OmniSharp/stdio.vim | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/autoload/OmniSharp/project.vim b/autoload/OmniSharp/project.vim index 61c3d0655..54506de43 100644 --- a/autoload/OmniSharp/project.vim +++ b/autoload/OmniSharp/project.vim @@ -14,7 +14,7 @@ function! OmniSharp#project#CountTotal() abort endfunction " Listen for stdio server-loaded events -function! OmniSharp#project#ParseEvent(job, eventBody) abort +function! OmniSharp#project#ParseEvent(job, event, eventBody) abort if g:OmniSharp_server_stdio_quickload " Quick load: Mark server as loaded as soon as configuration is finished diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index 0e6c0fd4e..24f2a3326 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -47,10 +47,15 @@ function! OmniSharp#stdio#HandleResponse(job, message) abort endfunction function! s:HandleServerEvent(job, res) abort - if has_key(a:res, 'Body') && type(a:res.Body) == type({}) + let body = get(a:res, 'Body', 0) + if type(body) != type({}) + let body = {} + endif + + " Handle any project loading events + call OmniSharp#project#ParseEvent(a:job, get(a:res, 'Event', ''), body) - " Handle any project loading events - call OmniSharp#project#ParseEvent(a:job, a:res.Body) + if !empty(body) " Listen for diagnostics. " The OmniSharp-roslyn server starts sending diagnostics once projects are @@ -60,7 +65,7 @@ function! s:HandleServerEvent(job, res) abort if get(g:, 'OmniSharp_diagnostics_listen', 0) \ && has_key(g:, 'OmniSharp_ale_diagnostics_requested') if get(a:res, 'Event', '') ==# 'Diagnostic' - for result in get(a:res.Body, 'Results', []) + for result in get(body, 'Results', []) let fname = OmniSharp#util#TranslatePathForClient(result.FileName) let bufinfo = getbufinfo(fname) if len(bufinfo) == 0 || !has_key(bufinfo[0], 'bufnr') @@ -77,9 +82,9 @@ function! s:HandleServerEvent(job, res) abort " Diagnostics received while running tests if get(a:res, 'Event', '') ==# 'TestMessage' - let lines = split(a:res.Body.Message, '\n') + let lines = split(body.Message, '\n') for line in lines - if get(a:res.Body, 'MessageLevel', '') ==# 'error' + if get(body, 'MessageLevel', '') ==# 'error' echohl WarningMsg | echomsg line | echohl None elseif g:OmniSharp_runtests_echo_output echomsg line @@ -152,7 +157,8 @@ endfunction function! s:Request(job, body, command, opts, ...) abort let sep = a:0 ? a:1 : '' - if type(a:job) != type({}) || !has_key(a:job, 'job_id') || !a:job.loaded + if !has_key(a:opts, 'AllowUnloaded') && + \ (!has_key(a:job, 'job_id') || !a:job.loaded) if has_key(a:opts, 'ReplayOnLoad') && !has_key(s:pendingRequests, a:command) " This request should be replayed when the server is fully loaded let s:pendingRequests[a:command] = a:opts From e6b30f572f87dc9144ddc5def14f3537af1e89c3 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sun, 28 Jun 2020 23:27:51 +1200 Subject: [PATCH 17/27] Use /projects endpoint as part of load process --- autoload/OmniSharp/actions/workspace.vim | 39 ++++++++++++++++++++ autoload/OmniSharp/project.vim | 46 +++++++++++++----------- autoload/OmniSharp/stdio.vim | 3 +- 3 files changed, 66 insertions(+), 22 deletions(-) create mode 100644 autoload/OmniSharp/actions/workspace.vim diff --git a/autoload/OmniSharp/actions/workspace.vim b/autoload/OmniSharp/actions/workspace.vim new file mode 100644 index 000000000..6ea9b493c --- /dev/null +++ b/autoload/OmniSharp/actions/workspace.vim @@ -0,0 +1,39 @@ +let s:save_cpo = &cpoptions +set cpoptions&vim + +let s:attempts = 0 + +function! OmniSharp#actions#workspace#Get(job) abort + let opts = { + \ 'ResponseHandler': function('s:ProjectsRH', [a:job]), + \ 'AllowUnloaded': 1 + \} + let s:attempts += 1 + call OmniSharp#stdio#RequestGlobal(a:job, '/projects', opts) +endfunction + +function! s:ProjectsRH(job, response) abort + " If this request fails, retry up to 5 times + if !a:response.Success + if s:attempts < 5 + call OmniSharp#actions#workspace#Get(a:job) + endif + return + endif + " If no projects have been loaded by the time this callback is reached, there + " are no projects and the job can be marked as ready + let projects = get(get(a:response.Body, 'MsBuild', {}), 'Projects', {}) + let a:job.projects = map(projects, + \ {_,project -> {"name": project.AssemblyName, "path": project.Path}}) + if get(a:job, 'projects_total', 0) > 0 + call OmniSharp#log#Log(a:job, 'Workspace complete: ' . a:job.projects_total . ' project(s)') + else + call OmniSharp#log#Log(a:job, 'Workspace complete: no projects') + call OmniSharp#project#RegisterLoaded(a:job) + endif +endfunction + +let &cpoptions = s:save_cpo +unlet s:save_cpo + +" vim:et:sw=2:sts=2 diff --git a/autoload/OmniSharp/project.vim b/autoload/OmniSharp/project.vim index 54506de43..ef2abcefe 100644 --- a/autoload/OmniSharp/project.vim +++ b/autoload/OmniSharp/project.vim @@ -13,23 +13,38 @@ function! OmniSharp#project#CountTotal() abort return get(OmniSharp#proc#GetJob(host.sln_or_dir), 'projects_total', 0) endfunction +function! OmniSharp#project#RegisterLoaded(job) abort + if a:job.loaded | return | endif + if g:OmniSharp_server_display_loading + let elapsed = reltimefloat(reltime(a:job.start_time)) + echomsg printf('Loaded server for %s in %.1fs', + \ a:job.sln_or_dir, elapsed) + endif + let a:job.loaded = 1 + silent doautocmd User OmniSharpReady + + " TODO: Remove this delay once we have better information about + " when the server is completely initialised: + " https://github.com/OmniSharp/omnisharp-roslyn/issues/1521 + call timer_start(1000, function('OmniSharp#stdio#ReplayRequests', [a:job])) + " call OmniSharp#stdio#ReplayRequests(a:job) +endfunction + " Listen for stdio server-loaded events function! OmniSharp#project#ParseEvent(job, event, eventBody) abort if g:OmniSharp_server_stdio_quickload - " Quick load: Mark server as loaded as soon as configuration is finished + " Quick load: Mark server as ready as soon as configuration is finished. + " WARNING: This will _not_ result in fully functional server interaction. if a:job.loaded | return | endif let message = get(a:eventBody, 'Message', '') if message ==# 'Configuration finished.' - let a:job.loaded = 1 - silent doautocmd User OmniSharpReady - call OmniSharp#stdio#ReplayRequests() + call OmniSharp#project#RegisterLoaded(a:job) endif else - " Complete load: Wait for all projects to be loaded before marking server as - " loaded + " Full load: Wait for all projects to load before marking server as ready let projects_loaded = get(a:job, 'projects_loaded', 0) let projects_total = get(a:job, 'projects_total', 0) if a:job.loaded && projects_loaded == projects_total | return | endif @@ -46,7 +61,9 @@ function! OmniSharp#project#ParseEvent(job, event, eventBody) abort endif let name = get(a:eventBody, 'Name', '') let message = get(a:eventBody, 'Message', '') - if name ==# 'OmniSharp.MSBuild.ProjectManager' + if a:event ==# 'started' + call OmniSharp#actions#workspace#Get(a:job) + elseif name ==# 'OmniSharp.MSBuild.ProjectManager' let project = matchstr(message, '''\zs.*\ze''') if message =~# '^Queue project' call add(a:job.loading, project) @@ -64,20 +81,7 @@ function! OmniSharp#project#ParseEvent(job, event, eventBody) abort let a:job.projects_loaded = projects_loaded + 1 silent doautocmd User OmniSharpProjectUpdated if len(a:job.loading) == 0 - if g:OmniSharp_server_display_loading - let elapsed = reltimefloat(reltime(a:job.start_time)) - echomsg printf('Loaded server for %s in %.1fs', - \ a:job.sln_or_dir, elapsed) - endif - let a:job.loaded = 1 - silent doautocmd User OmniSharpReady - - " TODO: Remove this delay once we have better information about - " when the server is completely initialised: - " https://github.com/OmniSharp/omnisharp-roslyn/issues/1521 - call timer_start(1000, function('OmniSharp#stdio#ReplayRequests')) - " call OmniSharp#stdio#ReplayRequests() - + call OmniSharp#project#RegisterLoaded(a:job) unlet a:job.loading call timer_stop(a:job.loading_timeout) unlet a:job.loading_timeout diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index 24f2a3326..22bb33d36 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -196,7 +196,8 @@ function! s:Request(job, body, command, opts, ...) abort return 1 endfunction -function! OmniSharp#stdio#ReplayRequests(...) abort +function! OmniSharp#stdio#ReplayRequests(job, ...) abort + call OmniSharp#log#Log(a:job, 'Replaying requests') for key in keys(s:pendingRequests) call OmniSharp#stdio#Request(key, s:pendingRequests[key]) unlet s:pendingRequests[key] From 787213ff92e276ece530884b7c2a2dc3275aaf7b Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 29 Jun 2020 13:11:01 +1200 Subject: [PATCH 18/27] Initialize buffers instead of full server load The OmniSharp-roslyn loading issues appear to stem from situations where multiple buffer-requests are sent before the project is fully initialized. This results in duplicate "miscellaneous" files, which are not correctly promoted to "project" files. Instead, this commit introduces a buffer "initialization" concept, where an `/updatebuffer` request is always sent as the first request for a buffer, and no further requests are sent for that buffer until a response is received (indicating that the server has registered the file, and there will be no duplicates created). --- autoload/OmniSharp.vim | 11 +++--- autoload/OmniSharp/actions/buffer.vim | 11 +++++- autoload/OmniSharp/actions/workspace.vim | 3 +- autoload/OmniSharp/buffer.vim | 26 +++++++++++++ autoload/OmniSharp/project.vim | 10 +---- autoload/OmniSharp/stdio.vim | 49 +++++++++++++----------- 6 files changed, 69 insertions(+), 41 deletions(-) diff --git a/autoload/OmniSharp.vim b/autoload/OmniSharp.vim index 2db515719..6354e5afa 100644 --- a/autoload/OmniSharp.vim +++ b/autoload/OmniSharp.vim @@ -12,21 +12,20 @@ function! OmniSharp#GetHost(...) abort let bufnr = a:0 ? a:1 : bufnr('%') if g:OmniSharp_server_stdio " Using the stdio server, b:OmniSharp_host is a dict containing the - " sln_or_dir: - " { 'sln_or_dir': '/path/to/solution_or_dir' } + " `sln_or_dir` and an `initialized` flag indicating whether this buffer has + " successfully been registered with the server: + " { 'sln_or_dir': '/path/to/solution_or_dir', 'initialized': 1 } let host = getbufvar(bufnr, 'OmniSharp_host', {}) if get(host, 'sln_or_dir', '') ==# '' let host.sln_or_dir = OmniSharp#FindSolutionOrDir(1, bufnr) + let host.initialized = 0 call setbufvar(bufnr, 'OmniSharp_host', host) endif " The returned dict includes the job, but the job is _not_ part of " b:OmniSharp_host. It is important to always fetch the job from " OmniSharp#proc#GetJob, ensuring that the job properties (job.job_id, " job.loaded, job.pid etc.) are always correct and up-to-date. - return { - \ 'sln_or_dir': host.sln_or_dir, - \ 'job': OmniSharp#proc#GetJob(host.sln_or_dir) - \} + return extend(copy(host), { 'job': OmniSharp#proc#GetJob(host.sln_or_dir) }) else " Using the HTTP server, b:OmniSharp_host is a localhost URL if empty(getbufvar(bufnr, 'OmniSharp_host')) diff --git a/autoload/OmniSharp/actions/buffer.vim b/autoload/OmniSharp/actions/buffer.vim index 863020e22..fbd611788 100644 --- a/autoload/OmniSharp/actions/buffer.vim +++ b/autoload/OmniSharp/actions/buffer.vim @@ -5,13 +5,17 @@ set cpoptions&vim " returned (synchronously or asynchronously) function! OmniSharp#actions#buffer#Update(...) abort let opts = a:0 && a:1 isnot 0 ? { 'Callback': a:1 } : {} - if !OmniSharp#IsServerRunning() | return | endif + let initializing = a:0 > 1 if bufname('%') ==# '' || OmniSharp#FugitiveCheck() | return | endif - if b:changedtick != get(b:, 'OmniSharp_UpdateChangeTick', -1) + if initializing || b:changedtick != get(b:, 'OmniSharp_UpdateChangeTick', -1) let b:OmniSharp_UpdateChangeTick = b:changedtick if g:OmniSharp_server_stdio + if initializing + let opts.Initializing = 1 + endif call s:StdioUpdate(opts) else + if !OmniSharp#IsServerRunning() | return | endif call OmniSharp#py#Eval('updateBuffer()') call OmniSharp#py#CheckForError() if has_key(opts, 'Callback') @@ -25,6 +29,9 @@ function! s:StdioUpdate(opts) abort let opts = { \ 'ResponseHandler': function('s:StdioUpdateRH', [a:opts]) \} + if has_key(a:opts, 'Initializing') + let opts.Initializing = 1 + endif call OmniSharp#stdio#Request('/updatebuffer', opts) endfunction diff --git a/autoload/OmniSharp/actions/workspace.vim b/autoload/OmniSharp/actions/workspace.vim index 6ea9b493c..1bb592107 100644 --- a/autoload/OmniSharp/actions/workspace.vim +++ b/autoload/OmniSharp/actions/workspace.vim @@ -5,8 +5,7 @@ let s:attempts = 0 function! OmniSharp#actions#workspace#Get(job) abort let opts = { - \ 'ResponseHandler': function('s:ProjectsRH', [a:job]), - \ 'AllowUnloaded': 1 + \ 'ResponseHandler': function('s:ProjectsRH', [a:job]) \} let s:attempts += 1 call OmniSharp#stdio#RequestGlobal(a:job, '/projects', opts) diff --git a/autoload/OmniSharp/buffer.vim b/autoload/OmniSharp/buffer.vim index 8dbdc8f36..62a32dff8 100644 --- a/autoload/OmniSharp/buffer.vim +++ b/autoload/OmniSharp/buffer.vim @@ -1,6 +1,32 @@ let s:save_cpo = &cpoptions set cpoptions&vim +function! OmniSharp#buffer#Initialize(job, bufnr, command, opts) abort + let a:job.pending_requests = get(a:job, 'pending_requests', {}) + let host = getbufvar(a:bufnr, 'OmniSharp_host') + if get(host, 'initialized') | return | endif + let a:job.pending_requests[a:bufnr] = get(a:job.pending_requests, a:bufnr, {}) + " More recent requests to the same command replace older pending requests + let a:job.pending_requests[a:bufnr][a:command] = a:opts + if has_key(OmniSharp#GetHost(a:bufnr), 'initializing') | return | endif + let host.initializing = 1 + let Callback = function('s:CBInitialize', [a:job, a:bufnr, host]) + call OmniSharp#actions#buffer#Update(Callback, 1) +endfunction + +function! s:CBInitialize(job, bufnr, host) abort + let a:host.initialized = 1 + unlet a:host.initializing + call OmniSharp#log#Log(a:job, 'Replaying requests for buffer ' . a:bufnr) + for key in keys(a:job.pending_requests[a:bufnr]) + call OmniSharp#stdio#Request(key, a:job.pending_requests[a:bufnr][key]) + unlet a:job.pending_requests[a:bufnr][key] + if empty(a:job.pending_requests[a:bufnr]) + unlet a:job.pending_requests[a:bufnr] + endif + endfor +endfunction + function! OmniSharp#buffer#PerformChanges(opts, response) abort if !a:response.Success | return | endif let changes = get(a:response.Body, 'Changes', []) diff --git a/autoload/OmniSharp/project.vim b/autoload/OmniSharp/project.vim index ef2abcefe..658634275 100644 --- a/autoload/OmniSharp/project.vim +++ b/autoload/OmniSharp/project.vim @@ -2,13 +2,13 @@ let s:save_cpo = &cpoptions set cpoptions&vim function! OmniSharp#project#CountLoaded() abort - " TODO: stdio guard? + if !g:OmniSharp_server_stdio | return 0 | endif let host = OmniSharp#GetHost() return get(OmniSharp#proc#GetJob(host.sln_or_dir), 'projects_loaded', 0) endfunction function! OmniSharp#project#CountTotal() abort - " TODO: stdio guard? + if !g:OmniSharp_server_stdio | return 0 | endif let host = OmniSharp#GetHost() return get(OmniSharp#proc#GetJob(host.sln_or_dir), 'projects_total', 0) endfunction @@ -22,12 +22,6 @@ function! OmniSharp#project#RegisterLoaded(job) abort endif let a:job.loaded = 1 silent doautocmd User OmniSharpReady - - " TODO: Remove this delay once we have better information about - " when the server is completely initialised: - " https://github.com/OmniSharp/omnisharp-roslyn/issues/1521 - call timer_start(1000, function('OmniSharp#stdio#ReplayRequests', [a:job])) - " call OmniSharp#stdio#ReplayRequests(a:job) endfunction " Listen for stdio server-loaded events diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index 22bb33d36..1a3dd59aa 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -3,7 +3,6 @@ set cpoptions&vim let s:nextseq = get(s:, 'nextseq', 1001) let s:requests = get(s:, 'requests', {}) -let s:pendingRequests = get(s:, 'pendingRequests', {}) function! OmniSharp#stdio#HandleResponse(job, message) abort try @@ -100,17 +99,37 @@ function! OmniSharp#stdio#Request(command, opts) abort let [bufnr, lnum, cnum] = s:lastPosition elseif has_key(a:opts, 'BufNum') && a:opts.BufNum != bufnr('%') let bufnr = a:opts.BufNum - let lnum = 1 - let cnum = 1 + let lnum = get(a:opts, 'LineNum', 1) + let cnum = get(a:opts, 'ColNum', 1) else let bufnr = bufnr('%') - let lnum = line('.') - let cnum = col('.') + let lnum = get(a:opts, 'LineNum', line('.')) + let cnum = get(a:opts, 'ColNum', col('.')) endif - let job = OmniSharp#GetHost(bufnr).job + let host = OmniSharp#GetHost(bufnr) + let job = host.job if !OmniSharp#proc#IsJobRunning(job) return endif + + if has_key(a:opts, 'Initializing') + " The buffer is being initialized - this request will always be sent + else + if !get(host, 'initialized') + " Replay the request when the buffer has been initialized with the server + let opts = extend(a:opts, { + \ 'BufNum': bufnr, + \ 'LineNum': lnum, + \ 'ColNum': cnum + \}) + if has_key(opts, 'UsePreviousPosition') + unlet opts.UsePreviousPosition + endif + call OmniSharp#buffer#Initialize(job, bufnr, a:command, opts) + return 0 + endif + endif + if has_key(a:opts, 'SavePosition') let s:lastPosition = [bufnr, lnum, cnum] endif @@ -156,15 +175,6 @@ function! OmniSharp#stdio#RequestGlobal(job, command, opts) abort endfunction function! s:Request(job, body, command, opts, ...) abort - let sep = a:0 ? a:1 : '' - if !has_key(a:opts, 'AllowUnloaded') && - \ (!has_key(a:job, 'job_id') || !a:job.loaded) - if has_key(a:opts, 'ReplayOnLoad') && !has_key(s:pendingRequests, a:command) - " This request should be replayed when the server is fully loaded - let s:pendingRequests[a:command] = a:opts - endif - return 0 - endif call OmniSharp#log#Log(a:job, 'Request: ' . a:command, 1) let a:body['Command'] = a:command @@ -173,6 +183,7 @@ function! s:Request(job, body, command, opts, ...) abort if has_key(a:opts, 'Parameters') call extend(a:body.Arguments, a:opts.Parameters, 'force') endif + let sep = a:0 ? a:1 : '' if sep !=# '' let encodedBody = substitute(json_encode(a:body), sep, '\\r\\n', 'g') else @@ -196,14 +207,6 @@ function! s:Request(job, body, command, opts, ...) abort return 1 endfunction -function! OmniSharp#stdio#ReplayRequests(job, ...) abort - call OmniSharp#log#Log(a:job, 'Replaying requests') - for key in keys(s:pendingRequests) - call OmniSharp#stdio#Request(key, s:pendingRequests[key]) - unlet s:pendingRequests[key] - endfor -endfunction - let &cpoptions = s:save_cpo unlet s:save_cpo From a2b4f952b144c9980156f2a47d3a6cb5cc21d799 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 29 Jun 2020 13:35:13 +1200 Subject: [PATCH 19/27] Remove g:OmniSharp_server_stdio_quickload --- autoload/OmniSharp/project.vim | 90 ++++++++++++++-------------------- plugin/OmniSharp.vim | 6 +-- 2 files changed, 39 insertions(+), 57 deletions(-) diff --git a/autoload/OmniSharp/project.vim b/autoload/OmniSharp/project.vim index 658634275..7606a1be3 100644 --- a/autoload/OmniSharp/project.vim +++ b/autoload/OmniSharp/project.vim @@ -26,63 +26,49 @@ endfunction " Listen for stdio server-loaded events function! OmniSharp#project#ParseEvent(job, event, eventBody) abort - if g:OmniSharp_server_stdio_quickload + " Full load: Wait for all projects to load before marking server as ready + let projects_loaded = get(a:job, 'projects_loaded', 0) + let projects_total = get(a:job, 'projects_total', 0) + if a:job.loaded && projects_loaded == projects_total | return | endif - " Quick load: Mark server as ready as soon as configuration is finished. - " WARNING: This will _not_ result in fully functional server interaction. - if a:job.loaded | return | endif - let message = get(a:eventBody, 'Message', '') - if message ==# 'Configuration finished.' - call OmniSharp#project#RegisterLoaded(a:job) - endif - - else - - " Full load: Wait for all projects to load before marking server as ready - let projects_loaded = get(a:job, 'projects_loaded', 0) - let projects_total = get(a:job, 'projects_total', 0) - if a:job.loaded && projects_loaded == projects_total | return | endif - - if !has_key(a:job, 'loading_timeout') - " Create a timeout to mark a job as loaded after 30 seconds despite not - " receiving the expected server events. - let a:job.loading_timeout = timer_start( - \ g:OmniSharp_server_loading_timeout * 1000, - \ function('s:ServerLoadTimeout', [a:job])) - endif - if !has_key(a:job, 'loading') - let a:job.loading = [] + if !has_key(a:job, 'loading_timeout') + " Create a timeout to mark a job as loaded after 30 seconds despite not + " receiving the expected server events. + let a:job.loading_timeout = timer_start( + \ g:OmniSharp_server_loading_timeout * 1000, + \ function('s:ServerLoadTimeout', [a:job])) + endif + if !has_key(a:job, 'loading') + let a:job.loading = [] + endif + let name = get(a:eventBody, 'Name', '') + let message = get(a:eventBody, 'Message', '') + if a:event ==# 'started' + call OmniSharp#actions#workspace#Get(a:job) + elseif name ==# 'OmniSharp.MSBuild.ProjectManager' + let project = matchstr(message, '''\zs.*\ze''') + if message =~# '^Queue project' + call add(a:job.loading, project) + if len(a:job.loading) > projects_total + let a:job.projects_total = len(a:job.loading) + silent doautocmd User OmniSharpProjectUpdated + endif endif - let name = get(a:eventBody, 'Name', '') - let message = get(a:eventBody, 'Message', '') - if a:event ==# 'started' - call OmniSharp#actions#workspace#Get(a:job) - elseif name ==# 'OmniSharp.MSBuild.ProjectManager' - let project = matchstr(message, '''\zs.*\ze''') - if message =~# '^Queue project' - call add(a:job.loading, project) - if len(a:job.loading) > projects_total - let a:job.projects_total = len(a:job.loading) - silent doautocmd User OmniSharpProjectUpdated - endif + if message =~# '^Successfully loaded project' + \ || message =~# '^Failed to load project' + if message[0] ==# 'F' + echom 'Failed to load project: ' . project endif - if message =~# '^Successfully loaded project' - \ || message =~# '^Failed to load project' - if message[0] ==# 'F' - echom 'Failed to load project: ' . project - endif - call filter(a:job.loading, {idx,val -> val !=# project}) - let a:job.projects_loaded = projects_loaded + 1 - silent doautocmd User OmniSharpProjectUpdated - if len(a:job.loading) == 0 - call OmniSharp#project#RegisterLoaded(a:job) - unlet a:job.loading - call timer_stop(a:job.loading_timeout) - unlet a:job.loading_timeout - endif + call filter(a:job.loading, {idx,val -> val !=# project}) + let a:job.projects_loaded = projects_loaded + 1 + silent doautocmd User OmniSharpProjectUpdated + if len(a:job.loading) == 0 + call OmniSharp#project#RegisterLoaded(a:job) + unlet a:job.loading + call timer_stop(a:job.loading_timeout) + unlet a:job.loading_timeout endif endif - endif endfunction diff --git a/plugin/OmniSharp.vim b/plugin/OmniSharp.vim index 972d9a65e..9e105424c 100644 --- a/plugin/OmniSharp.vim +++ b/plugin/OmniSharp.vim @@ -8,12 +8,8 @@ let g:OmniSharp_lookup_metadata = get(g:, 'OmniSharp_lookup_metadata', 1) let g:OmniSharp_server_stdio = get(g:, 'OmniSharp_server_stdio', 1) -" When g:OmniSharp_server_stdio_quickload = 1, consider server 'loaded' once -" 'Configuration finished' is received. When this is 0, wait for notification -" that all projects have been loaded. -let g:OmniSharp_server_stdio_quickload = get(g:, 'OmniSharp_server_stdio_quickload', 0) let g:OmniSharp_server_display_loading = get(g:, 'OmniSharp_server_display_loading', 1) -let g:OmniSharp_server_loading_timeout = get(g:, 'OmniSharp_server_loading_timeout', 20) +let g:OmniSharp_server_loading_timeout = get(g:, 'OmniSharp_server_loading_timeout', 30) " Use mono to start the roslyn server on *nix let g:OmniSharp_server_use_mono = get(g:, 'OmniSharp_server_use_mono', 0) From b284439c0bc4a160b68137007436f8d503c81a81 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 29 Jun 2020 13:41:00 +1200 Subject: [PATCH 20/27] Exclude declaration from FindUsages --- autoload/OmniSharp/actions/usages.vim | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/autoload/OmniSharp/actions/usages.vim b/autoload/OmniSharp/actions/usages.vim index bc819c4e6..e30ef37db 100644 --- a/autoload/OmniSharp/actions/usages.vim +++ b/autoload/OmniSharp/actions/usages.vim @@ -18,7 +18,10 @@ endfunction function! s:StdioFind(Callback) abort let opts = { - \ 'ResponseHandler': function('s:StdioFindRH', [a:Callback]) + \ 'ResponseHandler': function('s:StdioFindRH', [a:Callback]), + \ 'Parameters': { + \ 'ExcludeDefinition': 1 + \ } \} call OmniSharp#stdio#Request('/findusages', opts) endfunction From aa91d86de6758551557bd39560ef00c3ddd45984 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 6 Jul 2020 11:33:45 +1200 Subject: [PATCH 21/27] Replay highlighting (again) after server is loaded Highlighting can work well as soon as the buffer is initialised (well before the project is loaded). However, some aspects of highlighting are project-dependent (e.g. pre-processors depending on project configuration), so the _correct_ highlighting will only be applied after the project has loaded completely. --- autoload/OmniSharp/actions/highlight.vim | 25 ++++++++++++++++--- .../OmniSharp/actions/highlight_types.vim | 15 +++++------ autoload/OmniSharp/project.vim | 14 +++++++++++ autoload/OmniSharp/stdio.vim | 4 +-- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/autoload/OmniSharp/actions/highlight.vim b/autoload/OmniSharp/actions/highlight.vim index 722f190d7..fe5d42873 100644 --- a/autoload/OmniSharp/actions/highlight.vim +++ b/autoload/OmniSharp/actions/highlight.vim @@ -1,12 +1,12 @@ let s:save_cpo = &cpoptions set cpoptions&vim -function! OmniSharp#actions#highlight#Buffer() abort - if bufname('%') ==# '' || OmniSharp#FugitiveCheck() | return | endif - let opts = { 'BufNum': bufnr('%') } +function! OmniSharp#actions#highlight#Buffer(...) abort + let bufnr = a:0 ? a:1 : bufnr('%') + if bufname(bufnr) ==# '' || OmniSharp#FugitiveCheck() | return | endif if g:OmniSharp_server_stdio && \ (has('textprop') || exists('*nvim_create_namespace')) - call s:StdioHighlight(opts.BufNum) + call s:StdioHighlight(bufnr) else " Full semantic highlighting not supported - highlight types instead call OmniSharp#actions#highlight_types#Buffer() @@ -17,9 +17,26 @@ function! s:StdioHighlight(bufnr) abort let buftick = getbufvar(a:bufnr, 'changedtick') let opts = { \ 'ResponseHandler': function('s:HighlightRH', [a:bufnr, buftick]), + \ 'BufNum': a:bufnr, \ 'ReplayOnLoad': 1 \} call OmniSharp#stdio#Request('/v2/highlight', opts) + let job = OmniSharp#GetHost(a:bufnr).job + if type(job) == type({}) && !get(job, 'loaded') + " The project is still loading - it is possible to highlight but those + " highlights will be improved once loading is complete, so listen for that + " and re-run the highlighting on project load. + " TODO: If we start listening for individual project load status, then do + " this when this project finishes loading, instead of when the entire + " solution finishes loading. + let pending = get(job, 'pending_load_requests', {}) + let pending[a:bufnr] = get(pending, a:bufnr, {}) + " let opts = extend(copy(opts), { + " \ 'ResponseHandler': function('s:HighlightRH', [a:bufnr, -1]) + " \}) + let pending[a:bufnr]['/v2/highlight'] = opts + let job.pending_load_requests = pending + endif endfunction function! s:HighlightRH(bufnr, buftick, response) abort diff --git a/autoload/OmniSharp/actions/highlight_types.vim b/autoload/OmniSharp/actions/highlight_types.vim index a7e47f316..6f06f72ee 100644 --- a/autoload/OmniSharp/actions/highlight_types.vim +++ b/autoload/OmniSharp/actions/highlight_types.vim @@ -12,22 +12,23 @@ set cpoptions&vim function! OmniSharp#actions#highlight_types#Buffer() abort if bufname('%') ==# '' || OmniSharp#FugitiveCheck() | return | endif call OmniSharp#actions#highlight_types#Initialise() - let opts = { 'BufNum': bufnr('%') } + let bufnr = bufnr('%') if g:OmniSharp_server_stdio - let Callback = function('s:CBHighlightBuffer', [opts]) - call s:StdioFindHighlightTypes(Callback) + let Callback = function('s:CBHighlightBuffer', [bufnr]) + call s:StdioFindHighlightTypes(bufnr, Callback) else if !OmniSharp#IsServerRunning() | return | endif let hltypes = OmniSharp#py#Eval('findHighlightTypes()') if OmniSharp#py#CheckForError() | return | endif - call s:CBHighlightBuffer(opts, hltypes) + call s:CBHighlightBuffer(bufnr, hltypes) endif endfunction -function! s:StdioFindHighlightTypes(Callback) abort +function! s:StdioFindHighlightTypes(bufnr, Callback) abort let bufferLines = getline(1, '$') let opts = { \ 'ResponseHandler': function('s:FindHighlightTypesRH', [a:Callback, bufferLines]), + \ 'BufNum': a:bufnr, \ 'ReplayOnLoad': 1 \} call OmniSharp#stdio#Request('/highlight', opts) @@ -68,12 +69,12 @@ function! s:FindHighlightTypesRH(Callback, bufferLines, response) abort call a:Callback(hltypes) endfunction -function! s:CBHighlightBuffer(opts, hltypes) abort +function! s:CBHighlightBuffer(bufnr, hltypes) abort if has_key(a:hltypes, 'error') echohl WarningMsg | echom a:hltypes.error | echohl None return endif - if bufnr('%') != a:opts.BufNum | return | endif + if bufnr('%') != a:bufnr | return | endif let b:OmniSharp_hl_matches = get(b:, 'OmniSharp_hl_matches', []) diff --git a/autoload/OmniSharp/project.vim b/autoload/OmniSharp/project.vim index 7606a1be3..0c0041b85 100644 --- a/autoload/OmniSharp/project.vim +++ b/autoload/OmniSharp/project.vim @@ -22,6 +22,20 @@ function! OmniSharp#project#RegisterLoaded(job) abort endif let a:job.loaded = 1 silent doautocmd User OmniSharpReady + " If any requests are waiting to be replayed after the server is loaded, + " replay them now + " TODO: Remove this 1s delay if/when we get better project-laoded information + call timer_start(1000, function('s:ReplayLoadRequests', [a:job])) +endfunction + +function! s:ReplayLoadRequests(job, ...) abort + for bufnr in keys(get(a:job, 'pending_load_requests', {})) + for key in keys(a:job.pending_load_requests[bufnr]) + call OmniSharp#stdio#Request(key, a:job.pending_load_requests[bufnr][key]) + unlet a:job.pending_load_requests[bufnr][key] + endfor + unlet a:job.pending_load_requests[bufnr] + endfor endfunction " Listen for stdio server-loaded events diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index 1a3dd59aa..eefa87396 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -16,7 +16,7 @@ function! OmniSharp#stdio#HandleResponse(job, message) abort call OmniSharp#log#Log(a:job, '3 errors caught while loading: stopping') call OmniSharp#proc#StopJob(a:job.sln_or_dir) echohl WarningMsg - echomsg 'You appear to be running an HTML server in stdio mode - ' . + echomsg 'You appear to be running an HTTP server in stdio mode - ' . \ 'upgrade to the stdio server with :OmniSharpInstall, or to continue ' . \' in HTTP mode add the following to your .vimrc and restart Vim: ' \ 'let g:OmniSharp_server_stdio = 0' @@ -109,7 +109,7 @@ function! OmniSharp#stdio#Request(command, opts) abort let host = OmniSharp#GetHost(bufnr) let job = host.job if !OmniSharp#proc#IsJobRunning(job) - return + return 0 endif if has_key(a:opts, 'Initializing') From 41e3e3d53d7c0b8c05afc90c25f3800c5bc568a4 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 7 Jul 2020 00:17:14 +1200 Subject: [PATCH 22/27] Add extra DEBUG loglevel for debug without json --- autoload/OmniSharp/log.vim | 22 ++++++++++++++++++---- syntax/omnisharplog.vim | 2 ++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/autoload/OmniSharp/log.vim b/autoload/OmniSharp/log.vim index 6b1173c40..b40ea7b8d 100644 --- a/autoload/OmniSharp/log.vim +++ b/autoload/OmniSharp/log.vim @@ -28,10 +28,24 @@ function! OmniSharp#log#LogServer(job, raw, msg) abort " in linux let message = substitute(a:msg.Body.Message, '\%uD\ze\%u0', '', 'g') let lines = split(message, '\%u0', 1) - let lines[0] = ' ' . lines[0] - let prefix = s:LogLevelPrefix(a:msg.Body.LogLevel) - call insert(lines, printf('[%s]: %s', prefix, a:msg.Body.Name)) - call writefile(lines, a:job.logfile, 'a') + if g:OmniSharp_loglevel ==# 'DEBUG' && lines[0] =~# '^\*\{12\}' + " Special loglevel - DEBUG all caps. This still tells the server to pass + " full debugging requests and responses plus debugging messages, but + " OmniSharp-vim will not log the requests and responses - just record + " their commands + let prefix = matchstr(lines[0], '\*\s\+\zs\S\+\ze\s\+\*') + let commands = filter(lines, "v:val =~# '^\\s*\"Command\":'") + if len(commands) + let command = matchstr(commands[0], '"Command": "\zs[^"]\+\ze"') + let command_name = printf('Server %s: %s', prefix, command) + call writefile([command_name], a:job.logfile, 'a') + endif + else + let lines[0] = ' ' . lines[0] + let prefix = s:LogLevelPrefix(a:msg.Body.LogLevel) + call insert(lines, printf('[%s]: %s', prefix, a:msg.Body.Name)) + call writefile(lines, a:job.logfile, 'a') + endif elseif get(a:msg, 'Event', '') ==# 'MsBuildProjectDiagnostics' if len(a:msg.Body.Errors) == 0 && len(a:msg.Body.Warnings) == 0 return diff --git a/syntax/omnisharplog.vim b/syntax/omnisharplog.vim index f077ae9d7..d95379bca 100644 --- a/syntax/omnisharplog.vim +++ b/syntax/omnisharplog.vim @@ -16,6 +16,7 @@ syn match oslError "^\[ERROR\]"ms=s+1,me=e-1 contained syn match oslCritical "^\[crit\]"ms=s+1,me=e-1 contained syn match oslEndpoint "^Request: .*$"hs=s+9 +syn match oslServerEndpoint "^Server \%(Request\|Response\): .*$"hs=s+16 syn region oslRequestResponse start="\*\{12\}\s\+\%(Request\|Response\)\s\+\*\{12\}" end="^}" transparent fold @@ -29,6 +30,7 @@ hi def link oslError ErrorMsg hi def link oslCritical ErrorMsg hi def link oslEndpoint Identifier +hi def link oslServerEndpoint Constant let b:current_syntax = 'omnisharplog' From 5e8a8f6594bca75976aa7d02d3b3978b47ac0f70 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 7 Jul 2020 00:25:43 +1200 Subject: [PATCH 23/27] Make use of analyzer diagnostic results --- autoload/OmniSharp/project.vim | 7 +++++-- autoload/OmniSharp/stdio.vim | 12 ++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/autoload/OmniSharp/project.vim b/autoload/OmniSharp/project.vim index 0c0041b85..d2f614294 100644 --- a/autoload/OmniSharp/project.vim +++ b/autoload/OmniSharp/project.vim @@ -22,13 +22,16 @@ function! OmniSharp#project#RegisterLoaded(job) abort endif let a:job.loaded = 1 silent doautocmd User OmniSharpReady + call OmniSharp#log#Log(a:job, 'All projects loaded') " If any requests are waiting to be replayed after the server is loaded, - " replay them now - " TODO: Remove this 1s delay if/when we get better project-laoded information + " replay them now. + " Remove this 1s delay if/when we get better project-laoded information + " - currently we don't get any better indicators from the server. call timer_start(1000, function('s:ReplayLoadRequests', [a:job])) endfunction function! s:ReplayLoadRequests(job, ...) abort + call OmniSharp#log#Log(a:job, 'Replaying on-load requests') for bufnr in keys(get(a:job, 'pending_load_requests', {})) for key in keys(a:job.pending_load_requests[bufnr]) call OmniSharp#stdio#Request(key, a:job.pending_load_requests[bufnr][key]) diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index eefa87396..b1165bfc4 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -57,12 +57,12 @@ function! s:HandleServerEvent(job, res) abort if !empty(body) " Listen for diagnostics. - " The OmniSharp-roslyn server starts sending diagnostics once projects are - " loaded, which e.g. VSCode uses to populate project-wide warnings. - " We don't do that, and it doesn't make a lot of sense in a Vim workflow, so - " parsing these diagnostics is disabled by default. - if get(g:, 'OmniSharp_diagnostics_listen', 0) - \ && has_key(g:, 'OmniSharp_ale_diagnostics_requested') + " When OmniSharp-roslyn is configured with `EnableAnalyzersSupport`, the + " first diagnostic or code action request will trigger analysis of ALL + " solution documents. + " These diagnostics results are sent out over stdio so we might as well + " capture them and update ALE for loaded buffers. + if has_key(g:, 'OmniSharp_ale_diagnostics_requested') if get(a:res, 'Event', '') ==# 'Diagnostic' for result in get(body, 'Results', []) let fname = OmniSharp#util#TranslatePathForClient(result.FileName) From 10c64059c2d0b71f5db90f043af21ab7a8318d69 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 7 Jul 2020 11:16:20 +1200 Subject: [PATCH 24/27] Re-introduce ReplayOnLoad for selected endpoints --- autoload/OmniSharp/actions/diagnostics.vim | 6 ++-- autoload/OmniSharp/actions/highlight.vim | 16 --------- .../OmniSharp/actions/highlight_types.vim | 3 ++ autoload/OmniSharp/project.vim | 18 ++++------ autoload/OmniSharp/stdio.vim | 35 +++++++++++++++++-- autoload/ale/sources/OmniSharp.vim | 2 +- 6 files changed, 45 insertions(+), 35 deletions(-) diff --git a/autoload/OmniSharp/actions/diagnostics.vim b/autoload/OmniSharp/actions/diagnostics.vim index 360d3beb9..a1552070e 100644 --- a/autoload/OmniSharp/actions/diagnostics.vim +++ b/autoload/OmniSharp/actions/diagnostics.vim @@ -15,7 +15,7 @@ function! OmniSharp#actions#diagnostics#Check(...) abort endif if g:OmniSharp_server_stdio let Callback = function('s:CBCodeCheck', [opts]) - call OmniSharp#actions#diagnostics#StdioCheck({}, Callback) + call OmniSharp#actions#diagnostics#StdioCheck(bufnr('%'), Callback) else let codecheck = OmniSharp#py#Eval('codeCheck()') if OmniSharp#py#CheckForError() | return | endif @@ -43,12 +43,12 @@ endfunction " Normally this function would be named 's:StdioCheck`, but it is accessed " directly from autoload/ale/sources/OmniSharp.vim so requires a full autoload " function name. -function! OmniSharp#actions#diagnostics#StdioCheck(opts, Callback) abort +function! OmniSharp#actions#diagnostics#StdioCheck(bufnr, Callback) abort let opts = { \ 'ResponseHandler': function('s:StdioCheckRH', [a:Callback]), + \ 'BufNum': a:bufnr, \ 'ReplayOnLoad': 1 \} - call extend(opts, a:opts, 'force') call OmniSharp#stdio#Request('/codecheck', opts) endfunction diff --git a/autoload/OmniSharp/actions/highlight.vim b/autoload/OmniSharp/actions/highlight.vim index fe5d42873..ffd69f260 100644 --- a/autoload/OmniSharp/actions/highlight.vim +++ b/autoload/OmniSharp/actions/highlight.vim @@ -21,22 +21,6 @@ function! s:StdioHighlight(bufnr) abort \ 'ReplayOnLoad': 1 \} call OmniSharp#stdio#Request('/v2/highlight', opts) - let job = OmniSharp#GetHost(a:bufnr).job - if type(job) == type({}) && !get(job, 'loaded') - " The project is still loading - it is possible to highlight but those - " highlights will be improved once loading is complete, so listen for that - " and re-run the highlighting on project load. - " TODO: If we start listening for individual project load status, then do - " this when this project finishes loading, instead of when the entire - " solution finishes loading. - let pending = get(job, 'pending_load_requests', {}) - let pending[a:bufnr] = get(pending, a:bufnr, {}) - " let opts = extend(copy(opts), { - " \ 'ResponseHandler': function('s:HighlightRH', [a:bufnr, -1]) - " \}) - let pending[a:bufnr]['/v2/highlight'] = opts - let job.pending_load_requests = pending - endif endfunction function! s:HighlightRH(bufnr, buftick, response) abort diff --git a/autoload/OmniSharp/actions/highlight_types.vim b/autoload/OmniSharp/actions/highlight_types.vim index 6f06f72ee..4e457bf2b 100644 --- a/autoload/OmniSharp/actions/highlight_types.vim +++ b/autoload/OmniSharp/actions/highlight_types.vim @@ -74,6 +74,9 @@ function! s:CBHighlightBuffer(bufnr, hltypes) abort echohl WarningMsg | echom a:hltypes.error | echohl None return endif + " matchadd() only works in the current window/buffer, so if the user has + " navigated away from the buffer where the request was made, this response can + " not be applied if bufnr('%') != a:bufnr | return | endif let b:OmniSharp_hl_matches = get(b:, 'OmniSharp_hl_matches', []) diff --git a/autoload/OmniSharp/project.vim b/autoload/OmniSharp/project.vim index d2f614294..3d78ef515 100644 --- a/autoload/OmniSharp/project.vim +++ b/autoload/OmniSharp/project.vim @@ -25,20 +25,14 @@ function! OmniSharp#project#RegisterLoaded(job) abort call OmniSharp#log#Log(a:job, 'All projects loaded') " If any requests are waiting to be replayed after the server is loaded, " replay them now. + " + " TODO: If we start listening for individual project load status, then do + " this when this project finishes loading, instead of when the entire + " solution finishes loading. + " " Remove this 1s delay if/when we get better project-laoded information " - currently we don't get any better indicators from the server. - call timer_start(1000, function('s:ReplayLoadRequests', [a:job])) -endfunction - -function! s:ReplayLoadRequests(job, ...) abort - call OmniSharp#log#Log(a:job, 'Replaying on-load requests') - for bufnr in keys(get(a:job, 'pending_load_requests', {})) - for key in keys(a:job.pending_load_requests[bufnr]) - call OmniSharp#stdio#Request(key, a:job.pending_load_requests[bufnr][key]) - unlet a:job.pending_load_requests[bufnr][key] - endfor - unlet a:job.pending_load_requests[bufnr] - endfor + call timer_start(1000, function('OmniSharp#stdio#ReplayOnLoad', [a:job])) endfunction " Listen for stdio server-loaded events diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index b1165bfc4..f6162d941 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -163,11 +163,18 @@ function! OmniSharp#stdio#Request(command, opts) abort \ 'Column': cnum, \ } \} - if send_buffer let body.Arguments.Buffer = buffer endif - return s:Request(job, body, a:command, a:opts, sep) + + call s:Request(job, body, a:command, a:opts, sep) + + if has_key(a:opts, 'ReplayOnLoad') + let replay_opts = filter(copy(a:opts), 'v:key !=# "ReplayOnLoad"') + call s:QueueForReplayOnLoad(job, bufnr, a:command, replay_opts) + endif + + return 1 endfunction function! OmniSharp#stdio#RequestGlobal(job, command, opts) abort @@ -204,7 +211,29 @@ function! s:Request(job, body, command, opts, ...) abort else call ch_sendraw(a:job.job_id, encodedBody . "\n") endif - return 1 +endfunction + +function! s:QueueForReplayOnLoad(job, bufnr, command, opts) abort + if type(a:job) == type({}) && !get(a:job, 'loaded') + " The project is still loading - it is possible to highlight but those + " highlights will be improved once loading is complete, so listen for that + " and re-run the highlighting on project load. + let pending = get(a:job, 'pending_load_requests', {}) + let pending[a:bufnr] = get(pending, a:bufnr, {}) + let pending[a:bufnr][a:command] = a:opts + let a:job.pending_load_requests = pending + endif +endfunction + +function! OmniSharp#stdio#ReplayOnLoad(job, ...) abort + call OmniSharp#log#Log(a:job, 'Replaying on-load requests') + for bufnr in keys(get(a:job, 'pending_load_requests', {})) + for key in keys(a:job.pending_load_requests[bufnr]) + call OmniSharp#stdio#Request(key, a:job.pending_load_requests[bufnr][key]) + unlet a:job.pending_load_requests[bufnr][key] + endfor + unlet a:job.pending_load_requests[bufnr] + endfor endfunction let &cpoptions = s:save_cpo diff --git a/autoload/ale/sources/OmniSharp.vim b/autoload/ale/sources/OmniSharp.vim index f928ecee6..e4984c833 100644 --- a/autoload/ale/sources/OmniSharp.vim +++ b/autoload/ale/sources/OmniSharp.vim @@ -4,7 +4,7 @@ function! ale#sources#OmniSharp#WantResults(buffer) abort call ale#other_source#StartChecking(a:buffer, 'OmniSharp') let opts = { 'BufNum': a:buffer } let Callback = function('ale#sources#OmniSharp#ProcessResults', [opts]) - call OmniSharp#actions#diagnostics#StdioCheck(opts, Callback) + call OmniSharp#actions#diagnostics#StdioCheck(a:buffer, Callback) endfunction function! ale#sources#OmniSharp#ProcessResults(opts, locations) abort From 11e44f8c829295073391cd46bcc650920c9d5bbf Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 7 Jul 2020 15:07:34 +1200 Subject: [PATCH 25/27] Update documentation of stdio as default server --- README.md | 79 ++++++++++++++++++---------------- autoload/OmniSharp/project.vim | 2 +- doc/omnisharp-vim.txt | 32 ++++++-------- plugin/OmniSharp.vim | 4 +- 4 files changed, 60 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 5b713b543..32c268655 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,10 @@ Use Vim's popup windows and neovim's floating windows to display code/documentat ## New! Run unit tests -Using stdio mode, it is now possible to run unit tests via OmniSharp-roslyn, with success/failures listed in the quickfix window for easy navigation: +It is now possible to run unit tests via OmniSharp-roslyn, with success/failures listed in the quickfix window for easy navigation: ```vim -" Run the current unit test (the cursor should be inside the test method) +" Run the current unit test (the cursor should be on/inside the test method) :OmniSharpRunTest " Run all unit tests in the current file @@ -32,26 +32,30 @@ Using stdio mode, it is now possible to run unit tests via OmniSharp-roslyn, wit :OmniSharpRunTestsInFile % tests/test1.cs ``` -Note that this unfortunately does _not_ work in translated WSL, due to the way OmniSharp-roslyn runs the tests. +**Note:** this is only available using the stdio server, and unfortunately does _not_ work in translated WSL, due to the way OmniSharp-roslyn runs the tests. -## New! Asynchronous server interactions +## Asynchronous server interactions over stdio -For vim8 and neovim, OmniSharp-vim can now use the OmniSharp-roslyn stdio server instead of the HTTP server, using pure vimscript (no python dependency!). All server operations are asynchronous and this results in a much smoother coding experience. +For vim8 and neovim, OmniSharp-vim uses the OmniSharp-roslyn stdio server instead of the older HTTP server, using pure vimscript (no python dependency!). All server operations are asynchronous and this results in a much smoother coding experience. -This is initially opt-in only until some [user feedback](https://github.com/OmniSharp/omnisharp-vim/issues/468) is received. To switch from the HTTP server to stdio, add this to your .vimrc: +**Note:** neovim on Windows has been unable to communicate with the stdio server until a (very) recent neovim patch. +If you are using nvim-qt _or_ terminal neovim on Windows, you will require a neovim [nightly build](https://github.com/neovim/neovim/releases/nightly) `NVIM v0.5.0-556` (2020-06-11) or newer, or will need to wait for neovim 0.5 to be released. + +**Note to WSL users:** OmniSharp-roslyn stdio version 1.35.3 does _not_ work from WSL, using the Windows server, see [OmniSharp-roslyn #1844](https://github.com/OmniSharp/omnisharp-roslyn/issues/1844). +Install the previous version with `:OmniSharpInstall v1.35.2` + +To use the HTTP server instead, add this to your .vimrc: ```vim -let g:OmniSharp_server_stdio = 1 +let g:OmniSharp_server_stdio = 0 ``` -Then open vim to a .cs file and install the stdio server with `:OmniSharpInstall`. Restart vim and feel the difference! - -**Note:** neovim on Windows has been unable to communicate with the stdio server until a (very) recent neovim patch. If you are using nvim-qt _or_ terminal neovim on Windows, you will require a neovim [nightly build](https://github.com/neovim/neovim/releases/nightly) `NVIM v0.5.0-556` (2020-06-11) or newer, or will need to wait for neovim 0.5 to be released. +Any time `g:OmniSharp_server_stdio` is modified, the server needs to be re-installed with `:OmniSharpInstall`. ## Features * Contextual code completion - * Code documentation is displayed in the preview window when available (Xml Documentation for Windows, MonoDoc documentation for Mono) + * Code documentation and type lookup, displayed in popups or the preview window, when available (depends on installed SDKs) * Completion Sources are provided for: * [asyncomplete-vim](https://github.com/prabirshrestha/asyncomplete.vim) * [coc.nvim](https://github.com/neoclide/coc.nvim) @@ -69,10 +73,7 @@ Then open vim to a .cs file and install the stdio server with `:OmniSharpInstall * Find all code issues in solution and populate the quickfix window * Fix using statements for the current buffer (sort, remove and add any missing using statements where possible) * Rename refactoring -* Semantic type highlighting -* Lookup type information of an type/variable/method - * Can be printed to the status line or in the preview window - * Displays documentation for an entity when using preview window +* Full semantic highlighting * Code error checking * Code formatter * Run unit tests and navigate to failing assertions @@ -117,23 +118,23 @@ filetype indent plugin on ``` ### Server -OmniSharp-vim depends on the [OmniSharp-Roslyn](https://github.com/OmniSharp/omnisharp-roslyn) server. The first time OmniSharp-vim tries to open a C# file, it will check for the presence of the server, and if not found it will ask if it should be downloaded. Answer 'y' and the latest version will be downloaded and extracted to `~/.cache/omnisharp-vim/omnisharp-roslyn`, ready to use. *Note:* Requires [`curl`](https://curl.haxx.se/) or [`wget`](https://www.gnu.org/software/wget/) on Linux, macOS, Cygwin and WSL. +OmniSharp-vim depends on the [OmniSharp-Roslyn](https://github.com/OmniSharp/omnisharp-roslyn) server. The first time OmniSharp-vim tries to open a C# file, it will check for the presence of the server, and if not found it will ask if it should be downloaded. Answer `y` and the latest version will be downloaded and extracted to `~/.cache/omnisharp-vim/omnisharp-roslyn`, ready to use. *Note:* Requires [`curl`](https://curl.haxx.se/) or [`wget`](https://www.gnu.org/software/wget/) on Linux, macOS, Cygwin and WSL. Running the command `:OmniSharpInstall` in vim will also install/upgrade to the latest OmniSharp-roslyn release. To install a particular release, including pre-releases, specify the version number like this: ```vim -:OmniSharpInstall v1.34.2 +:OmniSharpInstall v1.35.2 ``` -*Note:* These methods depend on the `g:OmniSharp_server_stdio` variable to decide which OmniSharp-roslyn server to download. If you are unsure, try using the new stdio option first, and only fall back to HTTP if you have problems. +*Note:* These methods depend on the `g:OmniSharp_server_stdio` variable to decide which OmniSharp-roslyn server to download. If you are unsure, try using the default stdio option first, and only fall back to HTTP if you have problems. * **vim8.0+ or neovim**: Use the stdio server, it is used asynchronously and there is no python requirement. * **< vim8.0**: Use the HTTP server. Your vim must have python (2 or 3) support, and you'll need either [vim-dispatch](https://github.com/tpope/vim-dispatch) or [vimproc.vim](https://github.com/Shougo/vimproc.vim) to be installed ```vim -" Use the stdio version of OmniSharp-roslyn: +" Use the stdio version of OmniSharp-roslyn - this is the default let g:OmniSharp_server_stdio = 1 " Use the HTTP version of OmniSharp-roslyn: @@ -157,13 +158,17 @@ No special configuration is required for cygwin. The automatic installation scri #### Windows Subsystem for Linux (WSL) OmniSharp-roslyn can function perfectly well in WSL using linux binaries, if the environment is correctly configured (see [OmniSharp-roslyn](https://github.com/OmniSharp/omnisharp-roslyn) for requirements). -However, if you have the .NET Framework installed in Windows, you may have better results using the Windows binaries. To do this, follow the Manual installation instructions above, configure your vimrc to point to the `OmniSharp.exe` file, and let OmniSharp-vim know that you are operating in Cygwin/WSL mode (indicating that file paths need to be translated by OmniSharp-vim from Unix-Windows and back: +However, if you have the .NET Framework installed in Windows, you may have better results using the Windows binaries. +To do this, follow the Manual installation instructions above, configure your vimrc to point to the `OmniSharp.exe` file, and let OmniSharp-vim know that you are operating in Cygwin/WSL mode (indicating that file paths need to be translated by OmniSharp-vim from Unix-Windows and back: ```vim let g:OmniSharp_server_path = '/mnt/c/OmniSharp/omnisharp.win-x64/OmniSharp.exe' let g:OmniSharp_translate_cygwin_wsl = 1 ``` +**Note:** OmniSharp-roslyn stdio version 1.35.3 does _not_ work from WSL, using the Windows server, see [OmniSharp-roslyn #1844](https://github.com/OmniSharp/omnisharp-roslyn/issues/1844). +Install the previous version with `:OmniSharpInstall v1.35.2` + #### Linux and Mac OmniSharp-Roslyn requires Mono on Linux and OSX. The roslyn server [releases](https://github.com/OmniSharp/omnisharp-roslyn/releases) come with an embedded Mono, but this can be overridden to use the installed Mono by setting `g:OmniSharp_server_use_mono` in your vimrc. See [The Mono Project](https://www.mono-project.com/download/stable/) for installation details. @@ -171,12 +176,14 @@ OmniSharp-Roslyn requires Mono on Linux and OSX. The roslyn server [releases](ht let g:OmniSharp_server_use_mono = 1 ``` +Any time `g:OmniSharp_server_use_mono` is modified, the server needs to be re-installed with `:OmniSharpInstall`. + ##### libuv -For the HTTP version, OmniSharp-Roslyn also requires [libuv](http://libuv.org/) on Linux and Mac. This is typically a simple install step, e.g. `brew install libuv` on Mac, `apt-get install libuv1-dev` on debian/Ubuntu, `pacman -S libuv` on arch linux, `dnf install libuv libuv-devel` on Fedora/CentOS, etc. +For the HTTP server, OmniSharp-Roslyn also requires [libuv](http://libuv.org/) on Linux and Mac. This is typically a simple install step, e.g. `brew install libuv` on Mac, `apt-get install libuv1-dev` on debian/Ubuntu, `pacman -S libuv` on arch linux, `dnf install libuv libuv-devel` on Fedora/CentOS, etc. Please note that if your distro has a "dev" package (`libuv1-dev`, `libuv-devel` etc.) then you will probably need it. -**Note:** This is **not** necessary for the stdio version of OmniSharp-roslyn. +**Note:** This is **not** necessary for the default stdio version of OmniSharp-roslyn. ### Install Python (HTTP only) Install the latest version of python 3 ([Python 3.7](https://www.python.org/downloads/release/python-370/)) or 2 ([Python 2.7.15](https://www.python.org/downloads/release/python-2715/)). @@ -188,13 +195,13 @@ Verify that Python is working inside Vim with :echo has('python3') || has('python') ``` -**Note:** If you are using the stdio version of OmniSharp-roslyn, you do not need python. +**Note:** If you are using the default stdio version of OmniSharp-roslyn, you do not need python. ### Asynchronous command execution OmniSharp-vim can start the server only if any of the following criteria is met: * Vim with job control API is used (8.0+) -* [neovim](https://neovim.io) with job control API is used +* neovim with job control API is used * [vim-dispatch](https://github.com/tpope/vim-dispatch) is installed * [vimproc.vim](https://github.com/Shougo/vimproc.vim) is installed @@ -241,13 +248,8 @@ It tries to detect your solution file (.sln) and starts the OmniSharp-roslyn ser In vim8 and neovim, the server is started invisibly by a vim job. In older versions of vim, the server will be started in different ways depending on whether you are using vim-dispatch in tmux, or are using vim-proc, gvim or running vim in a terminal. -This behaviour can be disabled by setting `let g:OmniSharp_start_server = 0` in your vimrc. You can then start the server manually from within vim with `:OmniSharpStartServer`. Alternatively, the server can be manually started from outside vim: - -```sh -[mono] OmniSharp.exe -s (path/to/sln) -``` - -Add `-v` to get extra debugging output from the server. +This behaviour can be disabled by setting `let g:OmniSharp_start_server = 0` in your vimrc. +You can then start the server manually from within vim with `:OmniSharpStartServer`. To get completions, open a C# file from your solution within Vim and press `` (that is ctrl x followed by ctrl o) in Insert mode, or use a completion or autocompletion plugin. @@ -259,10 +261,11 @@ See the [wiki](https://github.com/OmniSharp/omnisharp-vim/wiki) for more custom OmniSharp-roslyn can provide highlighting information about every symbol of the document. To highlight a document, use command `:OmniSharpHighlight`. -To have `.cs` files automatically highlighted when entering a buffer and leaving insert mode, add this to your .vimrc: +By default, `.cs` files are automatically highlighted when entering a buffer and leaving insert mode. +To disable automatic highlighting, add this to your .vimrc: ```vim -let g:OmniSharp_highlighting = 2 +let g:OmniSharp_highlighting = 0 ``` To update highlighting after all text changes, even while in insert mode, use `g:OmniSharp_highlighting = 3` instead. @@ -280,7 +283,8 @@ let g:OmniSharp_highlight_groups = { The `:OmniSharpHighlightEcho` command can be used to find out what type of symbol is under the cursor. See the [wiki](https://github.com/OmniSharp/omnisharp-vim/wiki/Highlighting-configuration) for the full list of symbol types, and configuration details. -**Note:** Text property highlighting is only available when using the stdio server, not for HTTP server usage. Check the [wiki](https://github.com/OmniSharp/omnisharp-vim/wiki/Highlighting-configuration#legacy-highlighting) for how to highlight when using the HTTP server, or older Vim/neovims. +**Note:** Full semantic highlighting uses Vim's text properties and neovim's namespaces, and is only available when using the stdio server, not for HTTP server usage. +Check the [wiki](https://github.com/OmniSharp/omnisharp-vim/wiki/Highlighting-configuration#legacy-highlighting) for how to configure the simpler regex-highlighting when using the HTTP server, or older Vim/neovims. ## Diagnostics @@ -313,12 +317,15 @@ let g:OmniSharp_diagnostic_showid = 1 *Note:* Diagnostic overrides are only available in stdio mode, not HTTP mode. -Another method for filtering out diagnostic results is via path exclusion using `g:OmniSharp_diagnostic_exclude_paths`. This variable is a list of regular expressions that will exclude paths that have a match to any of its entries. +Another method for filtering out diagnostic results is via path exclusion using `g:OmniSharp_diagnostic_exclude_paths`. +This variable is a list of regular expressions that will exclude paths that have a match to any of its entries: + ```vim let g:OmniSharp_diagnostic_exclude_paths = [ \ 'obj\\', \ '[Tt]emp\\', -\ '\.nuget\\' +\ '\.nuget\\', +\ '\' \] ``` diff --git a/autoload/OmniSharp/project.vim b/autoload/OmniSharp/project.vim index 3d78ef515..a3f8487dc 100644 --- a/autoload/OmniSharp/project.vim +++ b/autoload/OmniSharp/project.vim @@ -43,7 +43,7 @@ function! OmniSharp#project#ParseEvent(job, event, eventBody) abort if a:job.loaded && projects_loaded == projects_total | return | endif if !has_key(a:job, 'loading_timeout') - " Create a timeout to mark a job as loaded after 30 seconds despite not + " Create a timeout to mark a job as loaded after 180 seconds despite not " receiving the expected server events. let a:job.loading_timeout = timer_start( \ g:OmniSharp_server_loading_timeout * 1000, diff --git a/doc/omnisharp-vim.txt b/doc/omnisharp-vim.txt index 12ca2a344..45518e659 100644 --- a/doc/omnisharp-vim.txt +++ b/doc/omnisharp-vim.txt @@ -31,7 +31,6 @@ CONTENTS *omnisharp-contents 1. DEPENDENCIES *omnisharp-dependencies* Required:~ - - Mono OR .NET Framework - Either: - Vim 8.0 or neovim or @@ -59,7 +58,8 @@ Use the stdio version of OmniSharp-roslyn. When the stdio version is used, OmniSharp-vim interacts asynchronously with it, with no other dependencies. The alternative is the HTTP version of the server, which has synchronous communication. -Default: 0 > +Note: changes require server reinstall: |:OmniSharpInstall| +Default: 1 > let g:OmniSharp_server_stdio = 1 < *'g:OmniSharp_server_install'* @@ -84,6 +84,7 @@ Default: None > The roslyn server must be run with mono on Linux and OSX. The roslyn server binaries usually come with an embedded mono, but this can be overridden to use the installed mono with this option. Note that mono must be in the $PATH. +Note: changes require server reinstall: |:OmniSharpInstall| Default: 0 > let g:OmniSharp_server_use_mono = 1 < @@ -93,8 +94,8 @@ opening a `*.cs` file. Default: 1 > let g:OmniSharp_start_server = 1 < *'g:OmniSharp_timeout'* -Use this option to specify the time (in seconds) to wait for a response from -the server. +Use this option to specify the time (in seconds) to wait for a synchronous +response from the server. Default: 1 > let g:OmniSharp_timeout = 1 < @@ -130,18 +131,10 @@ Default: 0 < *'g:OmniSharp_server_display_loading'* Stdio only -Echo "Server loaded for ...sln" message when the server is ready. +Echo "Server loaded for ...sln" message when the server has loaded all +projects. Default: 1 > let g:OmniSharp_server_display_loading = 1 -< - *'g:OmniSharp_server_loading_timeout'* - Stdio only -When the server is started, OmniSharp-vim listens for notifications from the -server to know when the server is ready for requests. However, these -notifications may not be correctly sent, in which case the server can be -considered "loaded" after a timeout period. -Default: 30 (seconds) > - let g:OmniSharp_server_loading_timeout = 30 < *'g:OmniSharp_host'* HTTP only @@ -210,7 +203,8 @@ Example: > let g:OmniSharp_diagnostic_exclude_paths = [ \ 'obj\\', \ '[Tt]emp\\', - \ '\.nuget\\' + \ '\.nuget\\', + \ '\' \] < ------------------------------------------------------------------------------- @@ -218,7 +212,9 @@ Example: > *'g:OmniSharp_highlighting'* Enable automatic semantic highlighting. -Default: 0 > +Default: 2 > + " Disable automatic highlighting + let g:OmniSharp_highlighting = 0 " Highlight on BufEnter let g:OmniSharp_highlighting = 1 " Highlight on BufEnter, InsertLeave and TextChanged @@ -389,8 +385,8 @@ Default: 0 > Use this option to specify whether OmniSharp will fetch full documentation on completion suggestions. By default, only type/method signatures are fetched for performance reasons. Full documentation can still be fetched when needed using -the |:OmniSharpDocumentation| command. Default: 0 > - let g:omnicomplete_fetch_full_documentation = 0 +the |:OmniSharpDocumentation| command. Default: 1 > + let g:omnicomplete_fetch_full_documentation = 1 < =============================================================================== 4. COMMANDS *omnisharp-commands* diff --git a/plugin/OmniSharp.vim b/plugin/OmniSharp.vim index 9e105424c..a4e06c03d 100644 --- a/plugin/OmniSharp.vim +++ b/plugin/OmniSharp.vim @@ -9,7 +9,7 @@ let g:OmniSharp_lookup_metadata = get(g:, 'OmniSharp_lookup_metadata', 1) let g:OmniSharp_server_stdio = get(g:, 'OmniSharp_server_stdio', 1) let g:OmniSharp_server_display_loading = get(g:, 'OmniSharp_server_display_loading', 1) -let g:OmniSharp_server_loading_timeout = get(g:, 'OmniSharp_server_loading_timeout', 30) +let g:OmniSharp_server_loading_timeout = get(g:, 'OmniSharp_server_loading_timeout', 180) " Use mono to start the roslyn server on *nix let g:OmniSharp_server_use_mono = get(g:, 'OmniSharp_server_use_mono', 0) @@ -63,7 +63,7 @@ let g:OmniSharp_runtests_echo_output = get(g:, 'OmniSharp_runtests_echo_output', " Set to 1 when ultisnips is installed let g:OmniSharp_want_snippet = get(g:, 'OmniSharp_want_snippet', 0) -let g:omnicomplete_fetch_full_documentation = get(g:, 'omnicomplete_fetch_full_documentation', 0) +let g:omnicomplete_fetch_full_documentation = get(g:, 'omnicomplete_fetch_full_documentation', 1) command! -bar -nargs=? OmniSharpInstall call OmniSharp#Install() command! -bar -nargs=? OmniSharpOpenLog call OmniSharp#log#Open() From 6cd92c67a6aad66d8e36f7a813c2aee30c61cbe9 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 7 Jul 2020 15:08:25 +1200 Subject: [PATCH 26/27] Simplify example .vimrc and use mappings --- README.md | 126 +++++++++++++++++++++--------------------------------- 1 file changed, 49 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index 32c268655..c9f907d51 100644 --- a/README.md +++ b/README.md @@ -439,94 +439,66 @@ Plug 'w0rp/ale' call plug#end() endif -" Note: this is required for the plugin to work -filetype indent plugin on - -" Use the stdio OmniSharp-roslyn server -let g:OmniSharp_server_stdio = 1 - -" Set the type lookup function to use the preview window instead of echoing it -"let g:OmniSharp_typeLookupInPreview = 1 - -" Timeout in seconds to wait for a response from the server -let g:OmniSharp_timeout = 5 - " Don't autoselect first omnicomplete option, show options even if there is only " one (so the preview documentation is accessible). Remove 'preview', 'popup' " and 'popuphidden' if you don't want to see any documentation whatsoever. -" Note that neovim does not support `popuphidden` or `popup` yet: +" Note that neovim does not support `popuphidden` or `popup` yet: " https://github.com/neovim/neovim/issues/10996 -set completeopt=longest,menuone,preview,popuphidden - -" Highlight the completion documentation popup background/foreground the same as -" the completion menu itself, for better readability with highlighted -" documentation. -set completepopup=highlight:Pmenu,border:off - -" Fetch full documentation during omnicomplete requests. -" By default, only Type/Method signatures are fetched. Full documentation can -" still be fetched when you need it with the :OmniSharpDocumentation command. -let g:omnicomplete_fetch_full_documentation = 1 - -" Set desired preview window height for viewing documentation. -" You might also want to look at the echodoc plugin. -set previewheight=5 +if has('patch-8.1.1880') + set completeopt=longest,menuone,popuphidden + " Highlight the completion documentation popup background/foreground the same as + " the completion menu itself, for better readability with highlighted + " documentation. + set completepopup=highlight:Pmenu,border:off +else + set completeopt=longest,menuone,preview + " Set desired preview window height for viewing documentation. + set previewheight=5 +endif " Tell ALE to use OmniSharp for linting C# files, and no other linters. let g:ale_linters = { 'cs': ['OmniSharp'] } -" Update semantic highlighting on BufEnter, InsertLeave and TextChanged -let g:OmniSharp_highlighting = 2 - augroup omnisharp_commands - autocmd! - - " Show type information automatically when the cursor stops moving. - " Note that the type is echoed to the Vim command line, and will overwrite - " any other messages in this space including e.g. ALE linting messages. - autocmd CursorHold *.cs OmniSharpTypeLookup - - " The following commands are contextual, based on the cursor position. - autocmd FileType cs nnoremap gd :OmniSharpGotoDefinition - autocmd FileType cs nnoremap fi :OmniSharpFindImplementations - autocmd FileType cs nnoremap fs :OmniSharpFindSymbol - autocmd FileType cs nnoremap fu :OmniSharpFindUsages - - " Finds members in the current buffer - autocmd FileType cs nnoremap fm :OmniSharpFindMembers - - autocmd FileType cs nnoremap fx :OmniSharpFixUsings - autocmd FileType cs nnoremap tt :OmniSharpTypeLookup - autocmd FileType cs nnoremap dc :OmniSharpDocumentation - autocmd FileType cs nnoremap :OmniSharpSignatureHelp - autocmd FileType cs inoremap :OmniSharpSignatureHelp - - " Navigate up and down by method/property/field - autocmd FileType cs nnoremap :OmniSharpNavigateUp - autocmd FileType cs nnoremap :OmniSharpNavigateDown - - " Find all code errors/warnings for the current solution and populate the quickfix window - autocmd FileType cs nnoremap cc :OmniSharpGlobalCodeCheck + autocmd! + + " Show type information automatically when the cursor stops moving. + " Note that the type is echoed to the Vim command line, and will overwrite + " any other messages in this space including e.g. ALE linting messages. + autocmd CursorHold *.cs OmniSharpTypeLookup + + " The following commands are contextual, based on the cursor position. + autocmd FileType cs nmap gd (omnisharp_go_to_definition) + autocmd FileType cs nmap osfu (omnisharp_find_usages) + autocmd FileType cs nmap osfi (omnisharp_find_implementations) + autocmd FileType cs nmap ospd (omnisharp_preview_definition) + autocmd FileType cs nmap ospi (omnisharp_preview_implementations) + autocmd FileType cs nmap ost (omnisharp_type_lookup) + autocmd FileType cs nmap osd (omnisharp_documentation) + autocmd FileType cs nmap osfs (omnisharp_find_symbol) + autocmd FileType cs nmap osfx (omnisharp_fix_usings) + autocmd FileType cs nmap (omnisharp_signature_help) + autocmd FileType cs imap (omnisharp_signature_help) + + " Navigate up and down by method/property/field + autocmd FileType cs nmap [[ (omnisharp_navigate_up) + autocmd FileType cs nmap ]] (omnisharp_navigate_down) + " Find all code errors/warnings for the current solution and populate the quickfix window + autocmd FileType cs nmap osgcc (omnisharp_global_code_check) + " Contextual code actions (uses fzf, CtrlP or unite.vim when available) + autocmd FileType cs nmap osca (omnisharp_code_actions) + autocmd FileType cs xmap osca (omnisharp_code_actions) + + autocmd FileType cs nmap os= (omnisharp_code_format) + + autocmd FileType cs nmap osnm (omnisharp_rename) + + autocmd FileType cs nmap osre (omnisharp_restart_server) + autocmd FileType cs nmap osst (omnisharp_start_server) + autocmd FileType cs nmap ossp (omnisharp_stop_server) augroup END -" Contextual code actions (uses fzf, CtrlP or unite.vim when available) -nnoremap :OmniSharpGetCodeActions -" Run code actions with text selected in visual mode to extract method -xnoremap :call OmniSharp#actions#codeactions#Get('visual') - -" Rename with dialog -nnoremap nm :OmniSharpRename -nnoremap :OmniSharpRename -" Rename without dialog - with cursor on the symbol to rename: `:Rename newname` -command! -nargs=1 Rename :call OmniSharp#actions#rename#To("") - -nnoremap cf :OmniSharpCodeFormat - -" Start the omnisharp server for the current solution -nnoremap ss :OmniSharpStartServer -nnoremap sp :OmniSharpStopServer - -" Enable snippet completion +" Enable snippet completion, using the ultisnips plugin " let g:OmniSharp_want_snippet=1 ``` From c5b41ef2526da9db3437afe1caef423bb120fdc1 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 7 Jul 2020 15:24:01 +1200 Subject: [PATCH 27/27] Make g:OmniSharp_highlighting default to 2 --- plugin/OmniSharp.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/OmniSharp.vim b/plugin/OmniSharp.vim index a4e06c03d..2fbb736e9 100644 --- a/plugin/OmniSharp.vim +++ b/plugin/OmniSharp.vim @@ -70,7 +70,7 @@ command! -bar -nargs=? OmniSharpOpenLog call OmniSharp#log#Open() " Initialise automatic type and interface highlighting " Preserve backwards compatibility with older version g:OmniSharp_highlight_types -let g:OmniSharp_highlighting = get(g:, 'OmniSharp_highlighting', get(g:, 'OmniSharp_highlight_types', 0)) +let g:OmniSharp_highlighting = get(g:, 'OmniSharp_highlighting', get(g:, 'OmniSharp_highlight_types', 2)) if g:OmniSharp_highlighting augroup OmniSharp_HighlightTypes autocmd!