From 4364a0b51ae54b155c4cf67c725888c3b5479795 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Fri, 27 May 2022 11:37:22 +1200 Subject: [PATCH 01/54] Refactor test-running code for readability --- autoload/OmniSharp/actions/test.vim | 418 ++++++++++++++-------------- 1 file changed, 204 insertions(+), 214 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 26bab3777..932d07bfc 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -3,48 +3,135 @@ set cpoptions&vim let s:runningTest = 0 -function! s:BindTest(bufnr, Callback, ...) abort +function! OmniSharp#actions#test#Debug(nobuild) abort if !s:CheckCapabilities() | return | endif - if !has_key(OmniSharp#GetHost(a:bufnr), 'project') - " Initialize the test by fetching the project for the buffer - then call - " this function again in the callback - call OmniSharp#actions#project#Get(a:bufnr, - \ function('s:BindTest', [a:bufnr, a:Callback])) - return + let s:nobuild = a:nobuild + if !OmniSharp#util#HasVimspector() + return s:Warn('Vimspector required to debug tests') + endif + call s:InitializeTestBuffers([bufnr('%')], function('s:DebugTest')) +endfunction + +function! s:DebugTest(bufferCodeStructures) abort + let bufnr = a:bufferCodeStructures[0][0] + let codeElements = a:bufferCodeStructures[0][1] + let tests = s:FindTests(codeElements) + let currentTest = s:FindTest(tests) + if type(currentTest) != type({}) + let s:runningTest = 0 + return s:Warn('No test found') + endif + let project = OmniSharp#GetHost(bufnr).project + let targetFramework = project.MsBuildProject.TargetFramework + let opts = { + \ 'ResponseHandler': function('s:DebugTestsRH', [bufnr, tests]), + \ 'Parameters': { + \ 'MethodName': currentTest.name, + \ 'NoBuild': get(s:, 'nobuild', 0), + \ 'TestFrameworkName': currentTest.framework, + \ 'TargetFrameworkVersion': targetFramework + \ }, + \ 'SendBuffer': 0 + \} + echomsg 'Debugging test ' . currentTest.name + call OmniSharp#stdio#Request('/v2/debugtest/getstartinfo', opts) +endfunction + +function! s:DebugTestsRH(bufnr, tests, response) abort + let testhost = [a:response.Body.FileName] + split(substitute(a:response.Body.Arguments, '\"', '', 'g'), ' ') + let testhost_job_pid = s:StartTestProcess(testhost) + let g:testhost_job_pid = testhost_job_pid + + let host = OmniSharp#GetHost() + let s:omnisharp_pre_debug_cwd = getcwd() + let new_cwd = fnamemodify(host.sln_or_dir, ':p:h') + call vimspector#LaunchWithConfigurations({ + \ 'attach': { + \ 'adapter': 'netcoredbg', + \ 'configuration': { + \ 'request': 'attach', + \ 'processId': testhost_job_pid + \ } + \ } + \}) + execute 'tcd' new_cwd + let opts = { + \ 'ResponseHandler': function('s:DebugComplete'), + \ 'Parameters': { + \ 'TargetProcessId': testhost_job_pid + \ } + \} + echomsg 'Launching debugged test' + call OmniSharp#stdio#Request('/v2/debugtest/launch', opts) +endfunction + +function! s:DebugComplete(response) abort + if !a:response.Success + call s:Warn(['Error debugging unit test', a:response.Message]) endif - let s:runningTest = 1 - call OmniSharp#actions#codestructure#Get(a:bufnr, - \ a:Callback) +endfunction + +function! s:StartTestProcess(command) abort + function! s:TestProcessClosed(...) abort + call OmniSharp#stdio#Request('/v2/debugtest/stop', {}) + let s:runningTest = 0 + call vimspector#Reset() + execute 'tcd' s:omnisharp_pre_debug_cwd + unlet s:omnisharp_pre_debug_cwd + endfunction + if OmniSharp#proc#supportsNeovimJobs() + let job = jobpid(jobstart(a:command, { + \ 'on_exit': function('s:TestProcessClosed') + \ })) + elseif OmniSharp#proc#supportsVimJobs() + let job = split(job_start(a:command, { + \ 'close_cb': function('s:TestProcessClosed') + \ }), ' ',)[1] + else + call s:Warn('Cannot launch test process.') + endif + return job endfunction function! OmniSharp#actions#test#Run(nobuild) abort + if !s:CheckCapabilities() | return | endif let s:nobuild = a:nobuild - call s:BindTest(bufnr('%'), function('s:RunTest', [function('s:CBRunTest')])) + call s:InitializeTestBuffers([bufnr('%')], function('s:RunTest')) endfunction -function! OmniSharp#actions#test#Debug(nobuild) abort - let s:nobuild = a:nobuild - if !OmniSharp#util#HasVimspector() - echohl WarningMsg - echomsg 'Vimspector required to debug tests' - echohl None - return +function! s:RunTest(bufferCodeStructures) abort + let bufnr = a:bufferCodeStructures[0][0] + let codeElements = a:bufferCodeStructures[0][1] + let tests = s:FindTests(codeElements) + let currentTest = s:FindTest(tests) + if type(currentTest) != type({}) + let s:runningTest = 0 + return s:Warn('No test found') endif - call s:BindTest(bufnr('%'), function('s:DebugTest', [function('s:CBDebugTest')])) + let project = OmniSharp#GetHost(bufnr).project + let targetFramework = project.MsBuildProject.TargetFramework + let opts = { + \ 'ResponseHandler': function('s:RunTestsRH', [function('s:RunComplete'), bufnr, tests]), + \ 'Parameters': { + \ 'MethodName': currentTest.name, + \ 'NoBuild': get(s:, 'nobuild', 0), + \ 'TestFrameworkName': currentTest.framework, + \ 'TargetFrameworkVersion': targetFramework + \ }, + \ 'SendBuffer': 0 + \} + echomsg 'Running test ' . currentTest.name + call OmniSharp#stdio#Request('/v2/runtest', opts) endfunction -function! s:CBRunTest(summary) abort +function! s:RunComplete(summary) abort if a:summary.pass if len(a:summary.locations) == 0 echomsg 'No tests were run' elseif get(a:summary.locations[0], 'type', '') ==# 'W' - echohl WarningMsg - echomsg a:summary.locations[0].name . ': skipped' - echohl None + call s:Warn(a:summary.locations[0].name . ': skipped') else - echohl Title - echomsg a:summary.locations[0].name . ': passed' - echohl None + call s:Emphasize(a:summary.locations[0].name . ': passed') endif else echomsg a:summary.locations[0].name . ': failed' @@ -57,15 +144,6 @@ function! s:CBRunTest(summary) abort endif endfunction -function! s:CBDebugTest(response) abort - if !a:response.Success - echohl WarningMsg - echomsg 'Error debugging unit test' - echomsg a:response.Message - echohl None - endif -endfunction - function! OmniSharp#actions#test#RunInFile(nobuild, ...) abort let s:nobuild = a:nobuild if !s:CheckCapabilities() | return | endif @@ -85,7 +163,7 @@ function! OmniSharp#actions#test#RunInFile(nobuild, ...) abort if filereadable(l:file) let nr = bufadd(l:file) else - echohl WarningMsg | echomsg 'File not found: ' . l:file | echohl None + call s:Warn('File not found: ' . l:file) continue endif endif @@ -95,12 +173,54 @@ function! OmniSharp#actions#test#RunInFile(nobuild, ...) abort return endif let s:runningTest = 1 - call OmniSharp#util#AwaitParallel( - \ map(copy(buffers), {i,b -> function('OmniSharp#actions#project#Get', [b])}), - \ function('s:FindTestsInFiles', [function('s:CBRunTestsInFile'), buffers])) + call s:InitializeTestBuffers(buffers, function('s:RunTestsInFiles')) endfunction -function! s:CBRunTestsInFile(summary) abort +function! s:RunTestsInFiles(bufferCodeStructures) abort + let Requests = [] + for bcs in a:bufferCodeStructures + let bufnr = bcs[0] + let codeElements = bcs[1] + let tests = s:FindTests(codeElements) + if len(tests) + call add(Requests, function('s:RunTestsInFile', [bufnr, tests])) + endif + endfor + if len(Requests) == 0 + let s:runningTest = 0 + return s:Warn('No tests found') + endif + if g:OmniSharp_runtests_parallel + if g:OmniSharp_runtests_echo_output + echomsg '---- Running tests ----' + endif + call OmniSharp#util#AwaitParallel(Requests, function('s:RunInFileComplete')) + else + call OmniSharp#util#AwaitSequence(Requests, function('s:RunInFileComplete')) + endif +endfunction + +function! s:RunTestsInFile(bufnr, tests, Callback) abort + if !g:OmniSharp_runtests_parallel && g:OmniSharp_runtests_echo_output + echomsg '---- Running tests: ' . bufname(a:bufnr) . ' ----' + endif + let project = OmniSharp#GetHost(a:bufnr).project + let targetFramework = project.MsBuildProject.TargetFramework + let opts = { + \ 'ResponseHandler': function('s:RunTestsRH', [a:Callback, a:bufnr, a:tests]), + \ 'BufNum': a:bufnr, + \ 'Parameters': { + \ 'MethodNames': map(copy(a:tests), {i,t -> t.name}), + \ 'NoBuild': get(s:, 'nobuild', 0), + \ 'TestFrameworkName': a:tests[0].framework, + \ 'TargetFrameworkVersion': targetFramework + \ }, + \ 'SendBuffer': 0 + \} + call OmniSharp#stdio#Request('/v2/runtestsinclass', opts) +endfunction + +function! s:RunInFileComplete(summary) abort let pass = 1 let locations = [] for summary in a:summary @@ -111,7 +231,7 @@ function! s:CBRunTestsInFile(summary) abort endfor if pass let title = len(locations) . ' tests passed' - echohl Title + call s:Emphasize(title) else let passed = 0 let noStackTrace = 0 @@ -127,46 +247,18 @@ function! s:CBRunTestsInFile(summary) abort if noStackTrace let title .= '. Check :messages for details.' endif - echohl WarningMsg + call s:Warn(title) endif - echomsg title - echohl None call OmniSharp#locations#SetQuickfix(locations, title) endfunction -function! s:RunTest(Callback, bufnr, codeElements) abort - let tests = s:FindTests(a:codeElements) - let currentTest = s:FindTest(tests) - if type(currentTest) != type({}) - echohl WarningMsg | echomsg 'No test found' | echohl None - let s:runningTest = 0 - return - endif - let project = OmniSharp#GetHost(a:bufnr).project - let targetFramework = project.MsBuildProject.TargetFramework - let opts = { - \ 'ResponseHandler': function('s:RunTestsRH', [a:Callback, a:bufnr, tests]), - \ 'Parameters': { - \ 'MethodName': currentTest.name, - \ 'NoBuild': get(s:, 'nobuild', 0), - \ 'TestFrameworkName': currentTest.framework, - \ 'TargetFrameworkVersion': targetFramework - \ }, - \ 'SendBuffer': 0 - \} - echomsg 'Running test ' . currentTest.name - call OmniSharp#stdio#Request('/v2/runtest', opts) -endfunction - +" Response handler used when running a single test, or tests in files function! s:RunTestsRH(Callback, bufnr, tests, response) abort let s:runningTest = 0 if !a:response.Success | return | endif if type(a:response.Body.Results) != type([]) - echohl WarningMsg - echomsg 'Error: "' . a:response.Body.Failure . - \ '" - this may indicate a failed build' - echohl None - return + return s:Warn('Error: "' . a:response.Body.Failure . + \ '" - this may indicate a failed build') endif let summary = { \ 'pass': a:response.Body.Pass, @@ -240,118 +332,37 @@ function! s:RunTestsRH(Callback, bufnr, tests, response) abort call a:Callback(summary) endfunction -function! s:DebugTest(Callback, bufnr, codeElements) abort - let tests = s:FindTests(a:codeElements) - let currentTest = s:FindTest(tests) - if type(currentTest) != type({}) - echohl WarningMsg | echomsg 'No test found' | echohl None - let s:runningTest = 0 - return - endif - let project = OmniSharp#GetHost(a:bufnr).project - let targetFramework = project.MsBuildProject.TargetFramework - let opts = { - \ 'ResponseHandler': function('s:DebugTestsRH', [a:Callback, a:bufnr, tests]), - \ 'Parameters': { - \ 'MethodName': currentTest.name, - \ 'NoBuild': get(s:, 'nobuild', 0), - \ 'TestFrameworkName': currentTest.framework, - \ 'TargetFrameworkVersion': targetFramework - \ }, - \ 'SendBuffer': 0 - \} - echomsg 'Debugging test ' . currentTest.name - call OmniSharp#stdio#Request('/v2/debugtest/getstartinfo', opts) -endfunction - -function! s:DebugTestsRH(Callback, bufnr, tests, response) abort - let testhost = [a:response.Body.FileName] + split(substitute(a:response.Body.Arguments, '\"', '', 'g'), ' ') - let testhost_job_pid = s:StartTestProcess(testhost) - let g:testhost_job_pid = testhost_job_pid - - let host = OmniSharp#GetHost() - let s:omnisharp_pre_debug_cwd = getcwd() - let new_cwd = fnamemodify(host.sln_or_dir, ':p:h') - call vimspector#LaunchWithConfigurations({ - \ 'attach': { - \ 'adapter': 'netcoredbg', - \ 'configuration': { - \ 'request': 'attach', - \ 'processId': testhost_job_pid - \ } - \ } - \}) - execute 'tcd '.new_cwd - - call s:LaunchDebuggedTest(a:Callback, testhost_job_pid) -endfunction - -function! s:LaunchDebuggedTest(Callback, pid) abort - let opts = { - \ 'ResponseHandler': function('s:LaunchDebuggedTestRH', [a:Callback, a:pid]), - \ 'Parameters': { - \ 'TargetProcessId': a:pid - \ } - \} - echomsg 'Launching debugged test' - call OmniSharp#stdio#Request('/v2/debugtest/launch', opts) -endfunction - -function! s:LaunchDebuggedTestRH(Callback, pid, response) abort - call a:Callback(a:response) -endfunction +" Utilities +" ========= -function! s:FindTestsInFiles(Callback, buffers, ...) abort - call OmniSharp#util#AwaitParallel( - \ map(copy(a:buffers), {i,b -> function('OmniSharp#actions#codestructure#Get', [b])}), - \ function('s:RunTestsInFiles', [a:Callback])) +function! s:CheckCapabilities() abort + if !g:OmniSharp_server_stdio + return s:Warn('stdio only, sorry') + endif + if g:OmniSharp_translate_cygwin_wsl + return s:Warn('Tests do not work in WSL unfortunately') + endif + if s:runningTest + return s:Warn('A test is already running') + endif + return 1 endfunction -function! s:RunTestsInFiles(Callback, bufferCodeStructures) abort - let Requests = [] - for bcs in a:bufferCodeStructures - let bufnr = bcs[0] - let codeElements = bcs[1] - let tests = s:FindTests(codeElements) - if len(tests) - call add(Requests, function('s:RunTestsInFile', [bufnr, tests])) - endif +function! s:EchoMessages(highlightGroup, message) abort + let messageLines = type(a:message) == type([]) ? a:message : [a:message] + execute 'echohl' a:highlightGroup + for messageLine in messageLines + echomsg messageLine endfor - if len(Requests) == 0 - echohl WarningMsg | echomsg 'No tests found' | echohl None - let s:runningTest = 0 - return - endif - if g:OmniSharp_runtests_parallel - if g:OmniSharp_runtests_echo_output - echomsg '---- Running tests ----' - endif - call OmniSharp#util#AwaitParallel(Requests, a:Callback) - else - call OmniSharp#util#AwaitSequence(Requests, a:Callback) - endif + echohl None endfunction -function! s:RunTestsInFile(bufnr, tests, Callback) abort - if !g:OmniSharp_runtests_parallel && g:OmniSharp_runtests_echo_output - echomsg '---- Running tests: ' . bufname(a:bufnr) . ' ----' - endif - let project = OmniSharp#GetHost(a:bufnr).project - let targetFramework = project.MsBuildProject.TargetFramework - let opts = { - \ 'ResponseHandler': function('s:RunTestsRH', [a:Callback, a:bufnr, a:tests]), - \ 'BufNum': a:bufnr, - \ 'Parameters': { - \ 'MethodNames': map(copy(a:tests), {i,t -> t.name}), - \ 'NoBuild': get(s:, 'nobuild', 0), - \ 'TestFrameworkName': a:tests[0].framework, - \ 'TargetFrameworkVersion': targetFramework - \ }, - \ 'SendBuffer': 0 - \} - call OmniSharp#stdio#Request('/v2/runtestsinclass', opts) +function! s:Emphasize(message) abort + call s:EchoMessages('Title', a:message) + return 1 endfunction +" Find the test in a list of tests that matches the current cursor position function! s:FindTest(tests, ...) abort for test in a:tests if a:0 @@ -367,6 +378,7 @@ function! s:FindTest(tests, ...) abort return 0 endfunction +" Find all of the test methods in a CodeStructure response function! s:FindTests(codeElements) abort if type(a:codeElements) != type([]) | return [] | endif let tests = [] @@ -387,45 +399,23 @@ function! s:FindTests(codeElements) abort return tests endfunction -function! s:CheckCapabilities() abort - if !g:OmniSharp_server_stdio - echohl WarningMsg | echomsg 'stdio only, sorry' | echohl None - return 0 - endif - if g:OmniSharp_translate_cygwin_wsl - echohl WarningMsg - echomsg 'Tests do not work in WSL unfortunately' - echohl None - return 0 - endif - if s:runningTest - echohl WarningMsg | echomsg 'A test is already running' | echohl None - return 0 - endif - return 1 +" For the given buffers, fetch the project structures, then fetch the buffer +" code structures. All operations are performed asynchronously, and the +" a:Callback is called when all buffer code structures have been fetched. +function! s:InitializeTestBuffers(buffers, Callback) abort + function! s:AwaitForBuffers(buffers, functionName, AwaitCallback, ...) abort + call OmniSharp#util#AwaitParallel( + \ map(copy(a:buffers), {i,b -> function(a:functionName, [b])}), + \ a:AwaitCallback) + endfunction + call s:AwaitForBuffers(a:buffers, 'OmniSharp#actions#project#Get', + \ function('s:AwaitForBuffers', + \ [a:buffers, 'OmniSharp#actions#codestructure#Get', a:Callback])) endfunction -function! s:TestProcessClosed(...) abort - call OmniSharp#stdio#Request('/v2/debugtest/stop', {}) - let s:runningTest = 0 - call vimspector#Reset() - execute 'tcd '.s:omnisharp_pre_debug_cwd - unlet s:omnisharp_pre_debug_cwd -endfunction - -function! s:StartTestProcess(command) abort - if OmniSharp#proc#supportsNeovimJobs() - let job = jobpid(jobstart(a:command, { - \ 'on_exit': function('s:TestProcessClosed') - \ })) - elseif OmniSharp#proc#supportsVimJobs() - let job = split(job_start(a:command, { - \ 'close_cb': function('s:TestProcessClosed') - \ }), ' ',)[1] - else - echohl WarningMsg | echomsg 'Cannot launch test process.' | echohl None - endif - return job +function! s:Warn(message) abort + call s:EchoMessages('WarningMsg', a:message) + return 0 endfunction function! s:QuickfixTextFuncStackTrace(info) abort From fa4a6c8140b81cf3de8cf14e353e5ca1284d3070 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Fri, 27 May 2022 14:18:14 +1200 Subject: [PATCH 02/54] Namespace test functions in function dictionaries --- autoload/OmniSharp/actions/test.vim | 256 ++++++++++++++-------------- autoload/OmniSharp/util.vim | 6 +- 2 files changed, 135 insertions(+), 127 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 932d07bfc..491b29554 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -1,30 +1,38 @@ let s:save_cpo = &cpoptions set cpoptions&vim -let s:runningTest = 0 + +" Function dict for debug-test functions +let s:debug = {} +let s:debug.process = {} +let s:run = {} +let s:run.running = 0 +let s:run.single = {} +let s:run.multiple = {} +let s:utils = {} +let s:utils.log = {} function! OmniSharp#actions#test#Debug(nobuild) abort - if !s:CheckCapabilities() | return | endif + if !s:utils.capabilities() | return | endif let s:nobuild = a:nobuild if !OmniSharp#util#HasVimspector() - return s:Warn('Vimspector required to debug tests') + return s:utils.log.warn('Vimspector required to debug tests') endif - call s:InitializeTestBuffers([bufnr('%')], function('s:DebugTest')) + call s:utils.initialize([bufnr('%')], s:debug.prepare) endfunction -function! s:DebugTest(bufferCodeStructures) abort +function! s:debug.prepare(bufferCodeStructures) abort let bufnr = a:bufferCodeStructures[0][0] let codeElements = a:bufferCodeStructures[0][1] - let tests = s:FindTests(codeElements) - let currentTest = s:FindTest(tests) + let tests = s:utils.extractTests(codeElements) + let currentTest = s:utils.findTest(tests) if type(currentTest) != type({}) - let s:runningTest = 0 - return s:Warn('No test found') + return s:utils.log.warn('No test found') endif let project = OmniSharp#GetHost(bufnr).project let targetFramework = project.MsBuildProject.TargetFramework let opts = { - \ 'ResponseHandler': function('s:DebugTestsRH', [bufnr, tests]), + \ 'ResponseHandler': funcref('s:debug.launch', [bufnr, tests]), \ 'Parameters': { \ 'MethodName': currentTest.name, \ 'NoBuild': get(s:, 'nobuild', 0), @@ -37,81 +45,82 @@ function! s:DebugTest(bufferCodeStructures) abort call OmniSharp#stdio#Request('/v2/debugtest/getstartinfo', opts) endfunction -function! s:DebugTestsRH(bufnr, tests, response) abort - let testhost = [a:response.Body.FileName] + split(substitute(a:response.Body.Arguments, '\"', '', 'g'), ' ') - let testhost_job_pid = s:StartTestProcess(testhost) - let g:testhost_job_pid = testhost_job_pid - +function! s:debug.launch(bufnr, tests, response) abort + let args = split(substitute(a:response.Body.Arguments, '\"', '', 'g'), ' ') + let cmd = a:response.Body.FileName + let testhost = [cmd] + args + if !s:debug.process.start(testhost) | return | endif + let s:run.running = 1 let host = OmniSharp#GetHost() let s:omnisharp_pre_debug_cwd = getcwd() let new_cwd = fnamemodify(host.sln_or_dir, ':p:h') call vimspector#LaunchWithConfigurations({ - \ 'attach': { - \ 'adapter': 'netcoredbg', - \ 'configuration': { - \ 'request': 'attach', - \ 'processId': testhost_job_pid - \ } - \ } + \ 'attach': { + \ 'adapter': 'netcoredbg', + \ 'configuration': { + \ 'request': 'attach', + \ 'processId': s:debug.process.pid + \ } + \ } \}) execute 'tcd' new_cwd let opts = { - \ 'ResponseHandler': function('s:DebugComplete'), + \ 'ResponseHandler': s:debug.complete, \ 'Parameters': { - \ 'TargetProcessId': testhost_job_pid + \ 'TargetProcessId': s:debug.process.pid \ } \} echomsg 'Launching debugged test' call OmniSharp#stdio#Request('/v2/debugtest/launch', opts) endfunction -function! s:DebugComplete(response) abort +function! s:debug.complete(response) abort if !a:response.Success - call s:Warn(['Error debugging unit test', a:response.Message]) + call s:utils.log.warn(['Error debugging unit test', a:response.Message]) endif endfunction -function! s:StartTestProcess(command) abort - function! s:TestProcessClosed(...) abort - call OmniSharp#stdio#Request('/v2/debugtest/stop', {}) - let s:runningTest = 0 - call vimspector#Reset() - execute 'tcd' s:omnisharp_pre_debug_cwd - unlet s:omnisharp_pre_debug_cwd - endfunction +function! s:debug.process.start(command) abort if OmniSharp#proc#supportsNeovimJobs() - let job = jobpid(jobstart(a:command, { - \ 'on_exit': function('s:TestProcessClosed') - \ })) + let jobid = jobstart(a:command, { 'on_exit': self.closed }) + let self.pid = jobpid(jobid) elseif OmniSharp#proc#supportsVimJobs() - let job = split(job_start(a:command, { - \ 'close_cb': function('s:TestProcessClosed') - \ }), ' ',)[1] + let job = job_start(a:command, { 'close_cb': self.closed }) + let self.pid = split(job, ' ')[1] else - call s:Warn('Cannot launch test process.') + return s:utils.log.warn('Cannot launch test process.') endif - return job + return 1 endfunction +function! s:debug.process.closed(...) abort + call OmniSharp#stdio#Request('/v2/debugtest/stop', {}) + let s:run.running = 0 + call vimspector#Reset() + execute 'tcd' s:omnisharp_pre_debug_cwd + unlet s:omnisharp_pre_debug_cwd +endfunction + + function! OmniSharp#actions#test#Run(nobuild) abort - if !s:CheckCapabilities() | return | endif + if !s:utils.capabilities() | return | endif let s:nobuild = a:nobuild - call s:InitializeTestBuffers([bufnr('%')], function('s:RunTest')) + call s:utils.initialize([bufnr('%')], s:run.single.test) endfunction -function! s:RunTest(bufferCodeStructures) abort +function! s:run.single.test(bufferCodeStructures) abort let bufnr = a:bufferCodeStructures[0][0] let codeElements = a:bufferCodeStructures[0][1] - let tests = s:FindTests(codeElements) - let currentTest = s:FindTest(tests) + let tests = s:utils.extractTests(codeElements) + let currentTest = s:utils.findTest(tests) if type(currentTest) != type({}) - let s:runningTest = 0 - return s:Warn('No test found') + return s:utils.log.warn('No test found') endif + let s:run.running = 1 let project = OmniSharp#GetHost(bufnr).project let targetFramework = project.MsBuildProject.TargetFramework let opts = { - \ 'ResponseHandler': function('s:RunTestsRH', [function('s:RunComplete'), bufnr, tests]), + \ 'ResponseHandler': funcref('s:run.process', [s:run.single.complete, bufnr, tests]), \ 'Parameters': { \ 'MethodName': currentTest.name, \ 'NoBuild': get(s:, 'nobuild', 0), @@ -124,29 +133,31 @@ function! s:RunTest(bufferCodeStructures) abort call OmniSharp#stdio#Request('/v2/runtest', opts) endfunction -function! s:RunComplete(summary) abort +function! s:run.single.complete(summary) abort if a:summary.pass if len(a:summary.locations) == 0 echomsg 'No tests were run' elseif get(a:summary.locations[0], 'type', '') ==# 'W' - call s:Warn(a:summary.locations[0].name . ': skipped') + call s:utils.log.warn(a:summary.locations[0].name . ': skipped') else - call s:Emphasize(a:summary.locations[0].name . ': passed') + call s:utils.log.emphasize(a:summary.locations[0].name . ': passed') endif else echomsg a:summary.locations[0].name . ': failed' let title = 'Test failure: ' . a:summary.locations[0].name let what = {} if len(a:summary.locations) > 1 - let what = {'quickfixtextfunc': function('s:QuickfixTextFuncStackTrace')} + let what.quickfixtextfunc = {info-> + \ map(getqflist({'id': info.id, 'items': 1}).items, {_,i -> i.text})} endif call OmniSharp#locations#SetQuickfix(a:summary.locations, title, what) endif endfunction + function! OmniSharp#actions#test#RunInFile(nobuild, ...) abort let s:nobuild = a:nobuild - if !s:CheckCapabilities() | return | endif + if !s:utils.capabilities() | return | endif if a:0 && type(a:1) == type([]) let files = a:1 elseif a:0 && type(a:1) == type('') @@ -163,51 +174,46 @@ function! OmniSharp#actions#test#RunInFile(nobuild, ...) abort if filereadable(l:file) let nr = bufadd(l:file) else - call s:Warn('File not found: ' . l:file) + call s:utils.log.warn('File not found: ' . l:file) continue endif endif call add(buffers, nr) endfor - if len(buffers) == 0 - return - endif - let s:runningTest = 1 - call s:InitializeTestBuffers(buffers, function('s:RunTestsInFiles')) + if len(buffers) == 0 | return | endif + call s:utils.initialize(buffers, s:run.multiple.prepare) endfunction -function! s:RunTestsInFiles(bufferCodeStructures) abort +function! s:run.multiple.prepare(bufferCodeStructures) abort let Requests = [] for bcs in a:bufferCodeStructures let bufnr = bcs[0] let codeElements = bcs[1] - let tests = s:FindTests(codeElements) + let tests = s:utils.extractTests(codeElements) if len(tests) - call add(Requests, function('s:RunTestsInFile', [bufnr, tests])) + call add(Requests, funcref('s:run.multiple.inBuffer', [bufnr, tests])) endif endfor - if len(Requests) == 0 - let s:runningTest = 0 - return s:Warn('No tests found') - endif + if len(Requests) == 0 | return s:utils.log.warn('No tests found') | endif + let s:run.running = 1 if g:OmniSharp_runtests_parallel if g:OmniSharp_runtests_echo_output echomsg '---- Running tests ----' endif - call OmniSharp#util#AwaitParallel(Requests, function('s:RunInFileComplete')) + call OmniSharp#util#AwaitParallel(Requests, s:run.multiple.complete) else - call OmniSharp#util#AwaitSequence(Requests, function('s:RunInFileComplete')) + call OmniSharp#util#AwaitSequence(Requests, s:run.multiple.complete) endif endfunction -function! s:RunTestsInFile(bufnr, tests, Callback) abort +function! s:run.multiple.inBuffer(bufnr, tests, Callback) abort if !g:OmniSharp_runtests_parallel && g:OmniSharp_runtests_echo_output echomsg '---- Running tests: ' . bufname(a:bufnr) . ' ----' endif let project = OmniSharp#GetHost(a:bufnr).project let targetFramework = project.MsBuildProject.TargetFramework let opts = { - \ 'ResponseHandler': function('s:RunTestsRH', [a:Callback, a:bufnr, a:tests]), + \ 'ResponseHandler': funcref('s:run.process', [a:Callback, a:bufnr, a:tests]), \ 'BufNum': a:bufnr, \ 'Parameters': { \ 'MethodNames': map(copy(a:tests), {i,t -> t.name}), @@ -220,7 +226,7 @@ function! s:RunTestsInFile(bufnr, tests, Callback) abort call OmniSharp#stdio#Request('/v2/runtestsinclass', opts) endfunction -function! s:RunInFileComplete(summary) abort +function! s:run.multiple.complete(summary) abort let pass = 1 let locations = [] for summary in a:summary @@ -231,7 +237,7 @@ function! s:RunInFileComplete(summary) abort endfor if pass let title = len(locations) . ' tests passed' - call s:Emphasize(title) + call s:utils.log.emphasize(title) else let passed = 0 let noStackTrace = 0 @@ -247,17 +253,18 @@ function! s:RunInFileComplete(summary) abort if noStackTrace let title .= '. Check :messages for details.' endif - call s:Warn(title) + call s:utils.log.warn(title) endif call OmniSharp#locations#SetQuickfix(locations, title) endfunction -" Response handler used when running a single test, or tests in files -function! s:RunTestsRH(Callback, bufnr, tests, response) abort - let s:runningTest = 0 + +" Response handler used when running a single test, or multiple tests in files +function! s:run.process(Callback, bufnr, tests, response) abort + let s:run.running = 0 if !a:response.Success | return | endif if type(a:response.Body.Results) != type([]) - return s:Warn('Error: "' . a:response.Body.Failure . + return s:utils.log.warn('Error: "' . a:response.Body.Failure . \ '" - this may indicate a failed build') endif let summary = { @@ -318,7 +325,7 @@ function! s:RunTestsRH(Callback, bufnr, tests, response) abort endif if !has_key(location, 'lnum') " Success, or unexpected test failure. - let test = s:FindTest(a:tests, result.MethodName) + let test = s:utils.findTest(a:tests, result.MethodName) if type(test) == type({}) let location.lnum = test.nameRange.Start.Line let location.col = test.nameRange.Start.Column @@ -332,54 +339,26 @@ function! s:RunTestsRH(Callback, bufnr, tests, response) abort call a:Callback(summary) endfunction -" Utilities -" ========= -function! s:CheckCapabilities() abort +function! OmniSharp#actions#test#Validate() abort + return s:utils.capabilities() +endfunction + +function! s:utils.capabilities() abort if !g:OmniSharp_server_stdio - return s:Warn('stdio only, sorry') + return self.log.warn('stdio only, sorry') endif if g:OmniSharp_translate_cygwin_wsl - return s:Warn('Tests do not work in WSL unfortunately') + return self.log.warn('Tests do not work in WSL unfortunately') endif - if s:runningTest - return s:Warn('A test is already running') + if s:run.running + return self.log.warn('A test is already running') endif return 1 endfunction -function! s:EchoMessages(highlightGroup, message) abort - let messageLines = type(a:message) == type([]) ? a:message : [a:message] - execute 'echohl' a:highlightGroup - for messageLine in messageLines - echomsg messageLine - endfor - echohl None -endfunction - -function! s:Emphasize(message) abort - call s:EchoMessages('Title', a:message) - return 1 -endfunction - -" Find the test in a list of tests that matches the current cursor position -function! s:FindTest(tests, ...) abort - for test in a:tests - if a:0 - if test.name ==# a:1 - return test - endif - else - if line('.') >= test.range.Start.Line && line('.') <= test.range.End.Line - return test - endif - endif - endfor - return 0 -endfunction - " Find all of the test methods in a CodeStructure response -function! s:FindTests(codeElements) abort +function! s:utils.extractTests(codeElements) abort if type(a:codeElements) != type([]) | return [] | endif let tests = [] for element in a:codeElements @@ -394,15 +373,31 @@ function! s:FindTests(codeElements) abort \ 'nameRange': element.Ranges.name, \}) endif - call extend(tests, s:FindTests(get(element, 'Children', []))) + call extend(tests, self.extractTests(get(element, 'Children', []))) endfor return tests endfunction +" Find the test in a list of tests that matches the current cursor position +function! s:utils.findTest(tests, ...) abort + for test in a:tests + if a:0 + if test.name ==# a:1 + return test + endif + else + if line('.') >= test.range.Start.Line && line('.') <= test.range.End.Line + return test + endif + endif + endfor + return 0 +endfunction + " For the given buffers, fetch the project structures, then fetch the buffer " code structures. All operations are performed asynchronously, and the " a:Callback is called when all buffer code structures have been fetched. -function! s:InitializeTestBuffers(buffers, Callback) abort +function! s:utils.initialize(buffers, Callback) abort function! s:AwaitForBuffers(buffers, functionName, AwaitCallback, ...) abort call OmniSharp#util#AwaitParallel( \ map(copy(a:buffers), {i,b -> function(a:functionName, [b])}), @@ -413,14 +408,23 @@ function! s:InitializeTestBuffers(buffers, Callback) abort \ [a:buffers, 'OmniSharp#actions#codestructure#Get', a:Callback])) endfunction -function! s:Warn(message) abort - call s:EchoMessages('WarningMsg', a:message) - return 0 +function! s:utils.log.echo(highlightGroup, message) abort + let messageLines = type(a:message) == type([]) ? a:message : [a:message] + execute 'echohl' a:highlightGroup + for messageLine in messageLines + echomsg messageLine + endfor + echohl None endfunction -function! s:QuickfixTextFuncStackTrace(info) abort - let items = getqflist({'id' : a:info.id, 'items' : 1}).items - return map(items, {_,i -> i.text}) +function! s:utils.log.emphasize(message) abort + call self.echo('Title', a:message) + return 1 +endfunction + +function! s:utils.log.warn(message) abort + call self.echo('WarningMsg', a:message) + return 0 endfunction let &cpoptions = s:save_cpo diff --git a/autoload/OmniSharp/util.vim b/autoload/OmniSharp/util.vim index 897af5c8e..ea3cc71c8 100644 --- a/autoload/OmniSharp/util.vim +++ b/autoload/OmniSharp/util.vim @@ -46,7 +46,11 @@ function! OmniSharp#util#AwaitParallel(Funcs, OnAllComplete) abort \ 'OnAllComplete': a:OnAllComplete \} for Func in a:Funcs - call Func(function('s:AwaitFuncComplete', [state])) + " If the Func has been declared as a dictionary function, then it must be + " called as a dictionary function: + " function s:run.test() + let dict = { 'f': Func } + call dict.f(function('s:AwaitFuncComplete', [state])) endfor endfunction From d768ddfde96bc4cb8e35394f3144c80072ee10ed Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sun, 29 May 2022 20:03:34 +1200 Subject: [PATCH 03/54] Refactor test extraction into initialisation --- autoload/OmniSharp/actions/test.vim | 53 ++++++++++++++++------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 491b29554..1734cbcfe 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -10,6 +10,7 @@ let s:run.running = 0 let s:run.single = {} let s:run.multiple = {} let s:utils = {} +let s:utils.init = {} let s:utils.log = {} function! OmniSharp#actions#test#Debug(nobuild) abort @@ -21,10 +22,9 @@ function! OmniSharp#actions#test#Debug(nobuild) abort call s:utils.initialize([bufnr('%')], s:debug.prepare) endfunction -function! s:debug.prepare(bufferCodeStructures) abort - let bufnr = a:bufferCodeStructures[0][0] - let codeElements = a:bufferCodeStructures[0][1] - let tests = s:utils.extractTests(codeElements) +function! s:debug.prepare(bufferTests) abort + let bufnr = a:bufferTests[0].bufnr + let tests = a:bufferTests[0].tests let currentTest = s:utils.findTest(tests) if type(currentTest) != type({}) return s:utils.log.warn('No test found') @@ -51,9 +51,7 @@ function! s:debug.launch(bufnr, tests, response) abort let testhost = [cmd] + args if !s:debug.process.start(testhost) | return | endif let s:run.running = 1 - let host = OmniSharp#GetHost() let s:omnisharp_pre_debug_cwd = getcwd() - let new_cwd = fnamemodify(host.sln_or_dir, ':p:h') call vimspector#LaunchWithConfigurations({ \ 'attach': { \ 'adapter': 'netcoredbg', @@ -63,7 +61,8 @@ function! s:debug.launch(bufnr, tests, response) abort \ } \ } \}) - execute 'tcd' new_cwd + let project_dir = fnamemodify(OmniSharp#GetHost(a:bufnr).sln_or_dir, ':p:h') + execute 'tcd' project_dir let opts = { \ 'ResponseHandler': s:debug.complete, \ 'Parameters': { @@ -108,10 +107,9 @@ function! OmniSharp#actions#test#Run(nobuild) abort call s:utils.initialize([bufnr('%')], s:run.single.test) endfunction -function! s:run.single.test(bufferCodeStructures) abort - let bufnr = a:bufferCodeStructures[0][0] - let codeElements = a:bufferCodeStructures[0][1] - let tests = s:utils.extractTests(codeElements) +function! s:run.single.test(bufferTests) abort + let bufnr = a:bufferTests[0].bufnr + let tests = a:bufferTests[0].tests let currentTest = s:utils.findTest(tests) if type(currentTest) != type({}) return s:utils.log.warn('No test found') @@ -184,12 +182,11 @@ function! OmniSharp#actions#test#RunInFile(nobuild, ...) abort call s:utils.initialize(buffers, s:run.multiple.prepare) endfunction -function! s:run.multiple.prepare(bufferCodeStructures) abort +function! s:run.multiple.prepare(bufferTests) abort let Requests = [] - for bcs in a:bufferCodeStructures - let bufnr = bcs[0] - let codeElements = bcs[1] - let tests = s:utils.extractTests(codeElements) + for btests in a:bufferTests + let bufnr = btests.bufnr + let tests = btests.tests if len(tests) call add(Requests, funcref('s:run.multiple.inBuffer', [bufnr, tests])) endif @@ -398,14 +395,22 @@ endfunction " code structures. All operations are performed asynchronously, and the " a:Callback is called when all buffer code structures have been fetched. function! s:utils.initialize(buffers, Callback) abort - function! s:AwaitForBuffers(buffers, functionName, AwaitCallback, ...) abort - call OmniSharp#util#AwaitParallel( - \ map(copy(a:buffers), {i,b -> function(a:functionName, [b])}), - \ a:AwaitCallback) - endfunction - call s:AwaitForBuffers(a:buffers, 'OmniSharp#actions#project#Get', - \ function('s:AwaitForBuffers', - \ [a:buffers, 'OmniSharp#actions#codestructure#Get', a:Callback])) + call s:utils.init.await(a:buffers, 'OmniSharp#actions#project#Get', + \ funcref('s:utils.init.await', [a:buffers, 'OmniSharp#actions#codestructure#Get', + \ funcref('s:utils.init.extract', [a:Callback])])) +endfunction + +function! s:utils.init.await(buffers, functionName, Callback, ...) abort + let Funcs = map(copy(a:buffers), {i,b -> function(a:functionName, [b])}) + call OmniSharp#util#AwaitParallel(Funcs, a:Callback) +endfunction + +function! s:utils.init.extract(Callback, codeStructures) abort + let bufferTests = map(a:codeStructures, {i, cs -> { + \ 'bufnr': cs[0], + \ 'tests': s:utils.extractTests(cs[1]) + \}}) + call a:Callback(bufferTests) endfunction function! s:utils.log.echo(highlightGroup, message) abort From 42c942633acc4564aa6f411d7c06e7ecd9ef190a Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Fri, 3 Jun 2022 00:35:50 +1200 Subject: [PATCH 04/54] Add new test runner buffer --- autoload/OmniSharp/actions/test.vim | 1 + autoload/OmniSharp/preview.vim | 6 +-- autoload/OmniSharp/testrunner.vim | 80 +++++++++++++++++++++++++++++ plugin/OmniSharp.vim | 1 + 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 autoload/OmniSharp/testrunner.vim diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 1734cbcfe..f80c2a0da 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -410,6 +410,7 @@ function! s:utils.init.extract(Callback, codeStructures) abort \ 'bufnr': cs[0], \ 'tests': s:utils.extractTests(cs[1]) \}}) + call OmniSharp#testrunner#SetTests(bufferTests) call a:Callback(bufferTests) endfunction diff --git a/autoload/OmniSharp/preview.vim b/autoload/OmniSharp/preview.vim index 4f5ec532d..0a7399b6c 100644 --- a/autoload/OmniSharp/preview.vim +++ b/autoload/OmniSharp/preview.vim @@ -6,10 +6,10 @@ function! OmniSharp#preview#Display(content, title) abort silent wincmd P setlocal modifiable noreadonly setlocal nobuflisted buftype=nofile bufhidden=wipe - 0,$d + 0,$delete silent put =a:content - 0d_ - setfiletype omnisharpdoc + 0delete _ + set filetype=omnisharpdoc setlocal conceallevel=3 setlocal nomodifiable readonly let winid = winnr() diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim new file mode 100644 index 000000000..43729ce59 --- /dev/null +++ b/autoload/OmniSharp/testrunner.vim @@ -0,0 +1,80 @@ +let s:save_cpo = &cpoptions +set cpoptions&vim + +function! OmniSharp#testrunner#Open() abort + if !OmniSharp#actions#test#Validate() | return | endif + call s:Open() +endfunction + +function s:Open() abort + let ft = 'omnisharptest' + let title = 'OmniSharp Test Runner' + " If the buffer is listed in a window in the current tab, then focus it + for winnr in range(1, winnr('$')) + if getbufvar(winbufnr(winnr), '&filetype') ==# ft + call win_gotoid(win_getid(winnr)) + break + endif + endfor + if &filetype !=# ft + " If a buffer with filetype omnisharptest exists, open it in a new split + for buffer in getbufinfo() + if getbufvar(buffer.bufnr, '&filetype') ==# ft + botright split + execute 'buffer' buffer.bufnr + break + endif + endfor + endif + if &filetype !=# ft + botright new + endif + + silent setlocal noswapfile signcolumn=no + set bufhidden=hide + let &filetype = ft + execute 'file' title + call s:Paint() +endfunction + +function! s:Paint() abort + setlocal modifiable + let winview = winsaveview() + 0,$delete _ + put ='OmniSharp Test Runner' + 0delete _ + put ='' + + for sln_or_dir in OmniSharp#proc#ListRunningJobs() + put =fnamemodify(sln_or_dir, ':t') + let job = OmniSharp#proc#GetJob(sln_or_dir) + if !has_key(job, 'tests') | continue | endif + for testfile in keys(job.tests) + put =' ' . fnamemodify(testfile, ':.') + for test in job.tests[testfile] + put =' ' . test.name + endfor + endfor + put ='' + endfor + + call winrestview(winview) + setlocal nomodifiable nomodified +endfunction + +function! OmniSharp#testrunner#SetTests(bufferTests) abort + let winid = win_getid() + for buffer in a:bufferTests + let job = OmniSharp#GetHost(buffer.bufnr).job + let job.tests = get(job, 'tests', {}) + let filename = fnamemodify(bufname(buffer.bufnr), ':p') + let job.tests[filename] = buffer.tests + endfor + call s:Open() + call win_gotoid(winid) +endfunction + +let &cpoptions = s:save_cpo +unlet s:save_cpo + +" vim:et:sw=2:sts=2 diff --git a/plugin/OmniSharp.vim b/plugin/OmniSharp.vim index 77e04a50f..f30ca0bbb 100644 --- a/plugin/OmniSharp.vim +++ b/plugin/OmniSharp.vim @@ -54,6 +54,7 @@ let g:omnicomplete_fetch_full_documentation = get(g:, 'omnicomplete_fetch_full_d command! -bar -nargs=? OmniSharpInstall call OmniSharp#Install() command! -bar -nargs=? OmniSharpOpenLog call OmniSharp#log#Open() +command! -bar -nargs=? OmniSharpOpenTestRunner call OmniSharp#testrunner#Open() command! -bar -bang OmniSharpStatus call OmniSharp#Status(0) " Preserve backwards compatibility with older version g:OmniSharp_highlight_types From b4bab8c85a7abc8c9a421e519fb5e7ab1b53e13b Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sat, 4 Jun 2022 00:35:56 +1200 Subject: [PATCH 05/54] Add initial omnisharptest syntax file --- autoload/OmniSharp/testrunner.vim | 23 +++++++++++++++++++---- syntax/omnisharptest.vim | 24 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 syntax/omnisharptest.vim diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 43729ce59..962ce0736 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -1,6 +1,13 @@ let s:save_cpo = &cpoptions set cpoptions&vim +let s:state2char = { +\ 'Not run': '|', +\ 'Running': '-', +\ 'Passed': '*', +\ 'Failed': '#' +\} + function! OmniSharp#testrunner#Open() abort if !OmniSharp#actions#test#Validate() | return | endif call s:Open() @@ -30,7 +37,7 @@ function s:Open() abort botright new endif - silent setlocal noswapfile signcolumn=no + silent setlocal noswapfile signcolumn=no conceallevel=3 concealcursor=nv set bufhidden=hide let &filetype = ft execute 'file' title @@ -51,8 +58,9 @@ function! s:Paint() abort if !has_key(job, 'tests') | continue | endif for testfile in keys(job.tests) put =' ' . fnamemodify(testfile, ':.') - for test in job.tests[testfile] - put =' ' . test.name + for name in keys(job.tests[testfile]) + let test = job.tests[testfile][name] + put =printf('%s %s', s:state2char[test.state], name) endfor endfor put ='' @@ -68,7 +76,14 @@ function! OmniSharp#testrunner#SetTests(bufferTests) abort let job = OmniSharp#GetHost(buffer.bufnr).job let job.tests = get(job, 'tests', {}) let filename = fnamemodify(bufname(buffer.bufnr), ':p') - let job.tests[filename] = buffer.tests + let existing = get(job.tests, filename, {}) + let job.tests[filename] = existing + for test in buffer.tests + let extest = get(existing, test.name, { 'state': 'Not run' }) + let existing[test.name] = extest + let extest.framework = test.framework + let extest.lnum = test.nameRange.Start.Line + endfor endfor call s:Open() call win_gotoid(winid) diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim new file mode 100644 index 000000000..98572edb9 --- /dev/null +++ b/syntax/omnisharptest.vim @@ -0,0 +1,24 @@ +if exists('b:current_syntax') + finish +endif + +let s:save_cpo = &cpoptions +set cpoptions&vim + +syn match ostStateNotRun "^|.*" contains=ostStateChar +syn match ostStateRunning "^-.*" contains=ostStateChar +syn match ostStatePassed "^\*.*" contains=ostStateChar +syn match ostStateFailed "^#.*" contains=ostStateChar +syn match ostStateChar "^[|\*#-]" conceal contained + +hi def link ostStateNotRun Comment +hi def link ostStateRunning Identifier +hi def link ostStatePassed Title +hi def link ostStateFailed WarningMsg + +let b:current_syntax = 'omnisharptest' + +let &cpoptions = s:save_cpo +unlet s:save_cpo + +" vim:et:sw=2:sts=2 From f1f0db9cde4236e5bb35df6dd6a6d99da569e989 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sat, 4 Jun 2022 01:49:55 +1200 Subject: [PATCH 06/54] Add testrunner Repaint function for updating state --- autoload/OmniSharp/testrunner.vim | 51 ++++++++++++++++++++++--------- syntax/omnisharptest.vim | 9 ++++-- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 962ce0736..b194e73c4 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -5,7 +5,7 @@ let s:state2char = { \ 'Not run': '|', \ 'Running': '-', \ 'Passed': '*', -\ 'Failed': '#' +\ 'Failed': '!' \} function! OmniSharp#testrunner#Open() abort @@ -36,38 +36,61 @@ function s:Open() abort if &filetype !=# ft botright new endif - + let s:testrunner_bufnr = bufnr() silent setlocal noswapfile signcolumn=no conceallevel=3 concealcursor=nv + setlocal comments=:# commentstring=#\ %s set bufhidden=hide let &filetype = ft execute 'file' title call s:Paint() endfunction +function! OmniSharp#testrunner#Repaint() abort + " Check that the test runner has been initialised and is still a loaded buffer + if !exists('s:testrunner_bufnr') | return | endif + if getbufvar(s:testrunner_bufnr, '&ft') !=# 'omnisharptest' | return | endif + " If the buffer is listed in a window in the current tab, then focus it + for winnr in range(1, winnr('$')) + if winbufnr(winnr) == s:testrunner_bufnr + let l:winid = win_getid() + call win_gotoid(win_getid(winnr)) + break + endif + endfor + call s:Paint() + if exists('l:winid') + call win_gotoid(l:winid) + endif +endfunction + function! s:Paint() abort - setlocal modifiable - let winview = winsaveview() - 0,$delete _ - put ='OmniSharp Test Runner' - 0delete _ - put ='' + let lines = [] + call add(lines, repeat('=', 80)) + call add(lines, ' OmniSharp Test Runner') + call add(lines, repeat('=', 80)) + call add(lines, '') for sln_or_dir in OmniSharp#proc#ListRunningJobs() - put =fnamemodify(sln_or_dir, ':t') + call add(lines, fnamemodify(sln_or_dir, ':t')) let job = OmniSharp#proc#GetJob(sln_or_dir) if !has_key(job, 'tests') | continue | endif for testfile in keys(job.tests) - put =' ' . fnamemodify(testfile, ':.') + call add(lines, ' ' . fnamemodify(testfile, ':.')) for name in keys(job.tests[testfile]) let test = job.tests[testfile][name] - put =printf('%s %s', s:state2char[test.state], name) + call add(lines, printf('%s %s', s:state2char[test.state], name)) endfor endfor - put ='' + call add(lines, '') endfor - call winrestview(winview) - setlocal nomodifiable nomodified + if bufnr() == s:testrunner_bufnr | let winview = winsaveview() | endif + call setbufvar(s:testrunner_bufnr, '&modifiable', 1) + call deletebufline(s:testrunner_bufnr, 1, '$') + call setbufline(s:testrunner_bufnr, 1, lines) + call setbufvar(s:testrunner_bufnr, '&modifiable', 0) + call setbufvar(s:testrunner_bufnr, '&modified', 0) + if bufnr() == s:testrunner_bufnr |call winrestview(winview) | endif endfunction function! OmniSharp#testrunner#SetTests(bufferTests) abort diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim index 98572edb9..218644473 100644 --- a/syntax/omnisharptest.vim +++ b/syntax/omnisharptest.vim @@ -5,11 +5,16 @@ endif let s:save_cpo = &cpoptions set cpoptions&vim +syn region ostIntro start="\%1l" end="^$" contains=ostIntroDelim transparent +syn match ostIntroDelim "^=\+$" contained + syn match ostStateNotRun "^|.*" contains=ostStateChar syn match ostStateRunning "^-.*" contains=ostStateChar syn match ostStatePassed "^\*.*" contains=ostStateChar -syn match ostStateFailed "^#.*" contains=ostStateChar -syn match ostStateChar "^[|\*#-]" conceal contained +syn match ostStateFailed "^!.*" contains=ostStateChar +syn match ostStateChar "^[|\*!-]" conceal contained + +hi def link ostIntroDelim PreProc hi def link ostStateNotRun Comment hi def link ostStateRunning Identifier From 9acd572bf9ef5cf1d5350617e6009c7d1a268224 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sun, 5 Jun 2022 00:46:28 +1200 Subject: [PATCH 07/54] Update test state when starting/completing a test --- autoload/OmniSharp/actions/test.vim | 37 +++++++++++++++++++++++------ autoload/OmniSharp/testrunner.vim | 36 ++++++++++++++++++++++++++-- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index f80c2a0da..6262d8c2b 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -115,6 +115,7 @@ function! s:run.single.test(bufferTests) abort return s:utils.log.warn('No test found') endif let s:run.running = 1 + call OmniSharp#testrunner#StateRunning(bufnr, currentTest.name) let project = OmniSharp#GetHost(bufnr).project let targetFramework = project.MsBuildProject.TargetFramework let opts = { @@ -132,17 +133,22 @@ function! s:run.single.test(bufferTests) abort endfunction function! s:run.single.complete(summary) abort + if a:summary.pass && len(a:summary.locations) == 0 + echomsg 'No tests were run' + " Do we ever reach here? + " call OmniSharp#testrunner#StateSkipped(bufnr) + endif + let location = a:summary.locations[0] + call s:run.updatestate(location) if a:summary.pass - if len(a:summary.locations) == 0 - echomsg 'No tests were run' - elseif get(a:summary.locations[0], 'type', '') ==# 'W' - call s:utils.log.warn(a:summary.locations[0].name . ': skipped') + if get(location, 'type', '') ==# 'W' + call s:utils.log.warn(location.name . ': skipped') else - call s:utils.log.emphasize(a:summary.locations[0].name . ': passed') + call s:utils.log.emphasize(location.name . ': passed') endif else - echomsg a:summary.locations[0].name . ': failed' - let title = 'Test failure: ' . a:summary.locations[0].name + echomsg location.name . ': failed' + let title = 'Test failure: ' . location.name let what = {} if len(a:summary.locations) > 1 let what.quickfixtextfunc = {info-> @@ -187,7 +193,9 @@ function! s:run.multiple.prepare(bufferTests) abort for btests in a:bufferTests let bufnr = btests.bufnr let tests = btests.tests + let testnames = map(copy(tests), {_,t -> t.name}) if len(tests) + call OmniSharp#testrunner#StateRunning(bufnr, testnames) call add(Requests, funcref('s:run.multiple.inBuffer', [bufnr, tests])) endif endfor @@ -232,6 +240,9 @@ function! s:run.multiple.complete(summary) abort let pass = 0 endif endfor + for location in locations + call s:run.updatestate(location) + endfor if pass let title = len(locations) . ' tests passed' call s:utils.log.emphasize(title) @@ -271,6 +282,8 @@ function! s:run.process(Callback, bufnr, tests, response) abort for result in a:response.Body.Results " Strip namespace and classname from test method name let location = { + \ 'bufnr': a:bufnr, + \ 'fullname': result.MethodName, \ 'filename': bufname(a:bufnr), \ 'name': substitute(result.MethodName, '^.*\.', '', '') \} @@ -336,6 +349,16 @@ function! s:run.process(Callback, bufnr, tests, response) abort call a:Callback(summary) endfunction +function! s:run.updatestate(location) abort + if get(a:location, 'type', '') ==# 'E' + call OmniSharp#testrunner#StateFailed(a:location.bufnr, a:location.fullname) + elseif get(a:location, 'type', '') ==# 'W' + call OmniSharp#testrunner#StateSkipped(a:location.bufnr, a:location.fullname) + else + call OmniSharp#testrunner#StatePassed(a:location.bufnr, a:location.fullname) + endif +endfunction + function! OmniSharp#actions#test#Validate() abort return s:utils.capabilities() diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index b194e73c4..8885dcee4 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -45,8 +45,7 @@ function s:Open() abort call s:Paint() endfunction -function! OmniSharp#testrunner#Repaint() abort - " Check that the test runner has been initialised and is still a loaded buffer +function! s:Repaint() abort if !exists('s:testrunner_bufnr') | return | endif if getbufvar(s:testrunner_bufnr, '&ft') !=# 'omnisharptest' | return | endif " If the buffer is listed in a window in the current tab, then focus it @@ -112,6 +111,39 @@ function! OmniSharp#testrunner#SetTests(bufferTests) abort call win_gotoid(winid) endfunction +function! s:UpdateState(bufnr, testnames, state) abort + let job = OmniSharp#GetHost(a:bufnr).job + let filename = fnamemodify(bufname(a:bufnr), ':p') + let tests = get(job.tests, filename, {}) + for testname in a:testnames + if has_key(tests, testname) + let tests[testname].state = a:state + endif + endfor + call s:Repaint() +endfunction + +function! OmniSharp#testrunner#StateRunning(bufnr, testnames) abort + let testnames = type(a:testnames) == type([]) ? a:testnames : [a:testnames] + let s:lasttestnames = testnames + call s:UpdateState(a:bufnr, testnames, 'Running') +endfunction + +function! OmniSharp#testrunner#StateSkipped(bufnr, ...) abort + let testnames = a:0 ? (type(a:1) == type([]) ? a:1 : [a:1]) : s:lasttestnames + call s:UpdateState(a:bufnr, testnames, 'Not run') +endfunction + +function! OmniSharp#testrunner#StatePassed(bufnr, ...) abort + let testnames = a:0 ? (type(a:1) == type([]) ? a:1 : [a:1]) : s:lasttestnames + call s:UpdateState(a:bufnr, testnames, 'Passed') +endfunction + +function! OmniSharp#testrunner#StateFailed(bufnr, ...) abort + let testnames = a:0 ? (type(a:1) == type([]) ? a:1 : [a:1]) : s:lasttestnames + call s:UpdateState(a:bufnr, testnames, 'Failed') +endfunction + let &cpoptions = s:save_cpo unlet s:save_cpo From 0fba289967b4a4c56824aa080f2780131f442cb8 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 6 Jun 2022 00:20:12 +1200 Subject: [PATCH 08/54] Add spinner for running tests --- autoload/OmniSharp/testrunner.vim | 42 ++++++++++++++++++++++++++++++- syntax/omnisharptest.vim | 6 ++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 8885dcee4..65707a34c 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -77,7 +77,11 @@ function! s:Paint() abort call add(lines, ' ' . fnamemodify(testfile, ':.')) for name in keys(job.tests[testfile]) let test = job.tests[testfile][name] - call add(lines, printf('%s %s', s:state2char[test.state], name)) + let state = s:state2char[test.state] + call add(lines, printf('%s %s', state, name)) + if state ==# '-' && !has_key(test, 'spintimer') + call s:SpinnerStart(test, len(lines)) + endif endfor endfor call add(lines, '') @@ -92,6 +96,42 @@ function! s:Paint() abort if bufnr() == s:testrunner_bufnr |call winrestview(winview) | endif endfunction +function! s:SpinnerSpin(test, lnum, timer) abort + if s:state2char[a:test.state] !=# '-' + call timer_stop(a:timer) + return + endif + let lines = getbufline(s:testrunner_bufnr, a:lnum) + if len(lines) == 0 + call timer_stop(a:timer) + return + endif + let line = lines[0] + let steps = get(g:, 'OmniSharp_testrunner_spinnersteps', [ + \ '<*---->', '<-*--->', '<--*-->', '<---*->', + \ '<----*>', '<---*->', '<--*-->', '<-*--->']) + if !has_key(a:test.spinner, 'index') + let line .= ' -- ' . steps[0] + let a:test.spinner.index = 0 + else + let a:test.spinner.index += 1 + if a:test.spinner.index >= len(steps) + let a:test.spinner.index = 0 + endif + let line = substitute(line, ' -- \zs.*$', steps[a:test.spinner.index], '') + endif + call setbufvar(s:testrunner_bufnr, '&modifiable', 1) + call setbufline(s:testrunner_bufnr, a:lnum, line) + call setbufvar(s:testrunner_bufnr, '&modifiable', 0) +endfunction + +function! s:SpinnerStart(test, lnum) abort + let a:test.spinner = {} + let a:test.spinner.timer = timer_start(300, + \ funcref('s:SpinnerSpin', [a:test, a:lnum]), + \ {'repeat': -1}) +endfunction + function! OmniSharp#testrunner#SetTests(bufferTests) abort let winid = win_getid() for buffer in a:bufferTests diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim index 218644473..ed0feb961 100644 --- a/syntax/omnisharptest.vim +++ b/syntax/omnisharptest.vim @@ -9,15 +9,19 @@ syn region ostIntro start="\%1l" end="^$" contains=ostIntroDelim transparent syn match ostIntroDelim "^=\+$" contained syn match ostStateNotRun "^|.*" contains=ostStateChar -syn match ostStateRunning "^-.*" contains=ostStateChar +syn match ostStateRunning "^-.*" contains=ostStateChar,ostRunningSuffix syn match ostStatePassed "^\*.*" contains=ostStateChar syn match ostStateFailed "^!.*" contains=ostStateChar syn match ostStateChar "^[|\*!-]" conceal contained +syn match ostRunningSuffix " -- .*" contained contains=ostRunningSpinner,ostRunningSuffixDivider +syn match ostRunningSuffixDivider " \zs--" conceal contained +syn match ostRunningSpinner " -- \zs.*" contained hi def link ostIntroDelim PreProc hi def link ostStateNotRun Comment hi def link ostStateRunning Identifier +hi def link ostRunningSpinner Normal hi def link ostStatePassed Title hi def link ostStateFailed WarningMsg From 3b2d25658689aabeee5973ba660cc4795312bad9 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 6 Jun 2022 00:30:52 +1200 Subject: [PATCH 09/54] Add option to disable quickfix for test results --- autoload/OmniSharp/actions/test.vim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 6262d8c2b..8a98a25e6 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -149,6 +149,7 @@ function! s:run.single.complete(summary) abort else echomsg location.name . ': failed' let title = 'Test failure: ' . location.name + if get(g:, 'OmniSharp_test_quickfix', 1) == 0 | return | endif let what = {} if len(a:summary.locations) > 1 let what.quickfixtextfunc = {info-> @@ -263,6 +264,7 @@ function! s:run.multiple.complete(summary) abort endif call s:utils.log.warn(title) endif + if get(g:, 'OmniSharp_test_quickfix', 1) == 0 | return | endif call OmniSharp#locations#SetQuickfix(locations, title) endfunction From 109d923559267d923dee92781a49e5882d9dcec7 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 6 Jun 2022 00:45:04 +1200 Subject: [PATCH 10/54] Update testrunner state when debugging test --- autoload/OmniSharp/actions/test.vim | 7 +++++-- autoload/OmniSharp/testrunner.vim | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 8a98a25e6..67cc733c2 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -32,7 +32,7 @@ function! s:debug.prepare(bufferTests) abort let project = OmniSharp#GetHost(bufnr).project let targetFramework = project.MsBuildProject.TargetFramework let opts = { - \ 'ResponseHandler': funcref('s:debug.launch', [bufnr, tests]), + \ 'ResponseHandler': funcref('s:debug.launch', [bufnr, currentTest.name]), \ 'Parameters': { \ 'MethodName': currentTest.name, \ 'NoBuild': get(s:, 'nobuild', 0), @@ -45,12 +45,14 @@ function! s:debug.prepare(bufferTests) abort call OmniSharp#stdio#Request('/v2/debugtest/getstartinfo', opts) endfunction -function! s:debug.launch(bufnr, tests, response) abort +function! s:debug.launch(bufnr, testname, response) abort let args = split(substitute(a:response.Body.Arguments, '\"', '', 'g'), ' ') let cmd = a:response.Body.FileName let testhost = [cmd] + args if !s:debug.process.start(testhost) | return | endif let s:run.running = 1 + call OmniSharp#testrunner#StateRunning(a:bufnr, a:testname) + let s:debug.bufnr = a:bufnr let s:omnisharp_pre_debug_cwd = getcwd() call vimspector#LaunchWithConfigurations({ \ 'attach': { @@ -77,6 +79,7 @@ function! s:debug.complete(response) abort if !a:response.Success call s:utils.log.warn(['Error debugging unit test', a:response.Message]) endif + call OmniSharp#testrunner#StateSkipped(s:debug.bufnr) endfunction function! s:debug.process.start(command) abort diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 65707a34c..769b41c45 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -123,6 +123,7 @@ function! s:SpinnerSpin(test, lnum, timer) abort call setbufvar(s:testrunner_bufnr, '&modifiable', 1) call setbufline(s:testrunner_bufnr, a:lnum, line) call setbufvar(s:testrunner_bufnr, '&modifiable', 0) + call setbufvar(s:testrunner_bufnr, '&modified', 0) endfunction function! s:SpinnerStart(test, lnum) abort From 1b60e7f069becc1b4ce56752aba65f90075662b2 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Fri, 10 Jun 2022 22:54:10 +1200 Subject: [PATCH 11/54] Display Console output and fold syntax regions --- autoload/OmniSharp/actions/test.vim | 18 +-- autoload/OmniSharp/testrunner.vim | 179 ++++++++++++++++----------- ftplugin/omnisharptest/OmniSharp.vim | 7 ++ syntax/omnisharptest.vim | 22 +++- 4 files changed, 135 insertions(+), 91 deletions(-) create mode 100644 ftplugin/omnisharptest/OmniSharp.vim diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 67cc733c2..dc7508966 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -1,8 +1,6 @@ let s:save_cpo = &cpoptions set cpoptions&vim - -" Function dict for debug-test functions let s:debug = {} let s:debug.process = {} let s:run = {} @@ -142,7 +140,7 @@ function! s:run.single.complete(summary) abort " call OmniSharp#testrunner#StateSkipped(bufnr) endif let location = a:summary.locations[0] - call s:run.updatestate(location) + call OmniSharp#testrunner#StateComplete(location) if a:summary.pass if get(location, 'type', '') ==# 'W' call s:utils.log.warn(location.name . ': skipped') @@ -245,7 +243,7 @@ function! s:run.multiple.complete(summary) abort endif endfor for location in locations - call s:run.updatestate(location) + call OmniSharp#testrunner#StateComplete(location) endfor if pass let title = len(locations) . ' tests passed' @@ -295,9 +293,11 @@ function! s:run.process(Callback, bufnr, tests, response) abort let locations = [location] " Write any standard output to message-history if len(get(result, 'StandardOutput', [])) + let location.output = [] echomsg 'Standard output from test ' . location.name . ':' for output in result.StandardOutput for line in split(trim(output), '\r\?\n', 1) + call add(location.output, line) echomsg ' ' . line endfor endfor @@ -354,16 +354,6 @@ function! s:run.process(Callback, bufnr, tests, response) abort call a:Callback(summary) endfunction -function! s:run.updatestate(location) abort - if get(a:location, 'type', '') ==# 'E' - call OmniSharp#testrunner#StateFailed(a:location.bufnr, a:location.fullname) - elseif get(a:location, 'type', '') ==# 'W' - call OmniSharp#testrunner#StateSkipped(a:location.bufnr, a:location.fullname) - else - call OmniSharp#testrunner#StatePassed(a:location.bufnr, a:location.fullname) - endif -endfunction - function! OmniSharp#actions#test#Validate() abort return s:utils.capabilities() diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 769b41c45..f9bf2b5db 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -1,13 +1,6 @@ let s:save_cpo = &cpoptions set cpoptions&vim -let s:state2char = { -\ 'Not run': '|', -\ 'Running': '-', -\ 'Passed': '*', -\ 'Failed': '!' -\} - function! OmniSharp#testrunner#Open() abort if !OmniSharp#actions#test#Validate() | return | endif call s:Open() @@ -37,9 +30,6 @@ function s:Open() abort botright new endif let s:testrunner_bufnr = bufnr() - silent setlocal noswapfile signcolumn=no conceallevel=3 concealcursor=nv - setlocal comments=:# commentstring=#\ %s - set bufhidden=hide let &filetype = ft execute 'file' title call s:Paint() @@ -70,18 +60,28 @@ function! s:Paint() abort call add(lines, '') for sln_or_dir in OmniSharp#proc#ListRunningJobs() - call add(lines, fnamemodify(sln_or_dir, ':t')) let job = OmniSharp#proc#GetJob(sln_or_dir) if !has_key(job, 'tests') | continue | endif - for testfile in keys(job.tests) - call add(lines, ' ' . fnamemodify(testfile, ':.')) - for name in keys(job.tests[testfile]) - let test = job.tests[testfile][name] - let state = s:state2char[test.state] - call add(lines, printf('%s %s', state, name)) - if state ==# '-' && !has_key(test, 'spintimer') - call s:SpinnerStart(test, len(lines)) - endif + for testproject in sort(keys(job.tests)) + call add(lines, testproject) + for testfile in sort(keys(job.tests[testproject])) + call add(lines, ' ' . fnamemodify(testfile, ':.')) + let tests = job.tests[testproject][testfile] + for name in sort(keys(tests), {a,b -> tests[a].lnum > tests[b].lnum}) + let test = tests[name] + let state = s:utils.state2char[test.state] + call add(lines, printf('%s %s', state, name)) + if state ==# '-' && !has_key(test, 'spintimer') + call s:spinner.start(test, len(lines)) + endif + let output = get(test, 'output', []) + if len(output) + for outputline in output + call add(lines, '// ' . outputline) + endfor + endif + endfor + call add(lines, '__') endfor endfor call add(lines, '') @@ -93,54 +93,24 @@ function! s:Paint() abort call setbufline(s:testrunner_bufnr, 1, lines) call setbufvar(s:testrunner_bufnr, '&modifiable', 0) call setbufvar(s:testrunner_bufnr, '&modified', 0) - if bufnr() == s:testrunner_bufnr |call winrestview(winview) | endif -endfunction - -function! s:SpinnerSpin(test, lnum, timer) abort - if s:state2char[a:test.state] !=# '-' - call timer_stop(a:timer) - return - endif - let lines = getbufline(s:testrunner_bufnr, a:lnum) - if len(lines) == 0 - call timer_stop(a:timer) - return - endif - let line = lines[0] - let steps = get(g:, 'OmniSharp_testrunner_spinnersteps', [ - \ '<*---->', '<-*--->', '<--*-->', '<---*->', - \ '<----*>', '<---*->', '<--*-->', '<-*--->']) - if !has_key(a:test.spinner, 'index') - let line .= ' -- ' . steps[0] - let a:test.spinner.index = 0 - else - let a:test.spinner.index += 1 - if a:test.spinner.index >= len(steps) - let a:test.spinner.index = 0 - endif - let line = substitute(line, ' -- \zs.*$', steps[a:test.spinner.index], '') + if bufnr() == s:testrunner_bufnr + call winrestview(winview) + syn sync fromstart endif - call setbufvar(s:testrunner_bufnr, '&modifiable', 1) - call setbufline(s:testrunner_bufnr, a:lnum, line) - call setbufvar(s:testrunner_bufnr, '&modifiable', 0) - call setbufvar(s:testrunner_bufnr, '&modified', 0) endfunction -function! s:SpinnerStart(test, lnum) abort - let a:test.spinner = {} - let a:test.spinner.timer = timer_start(300, - \ funcref('s:SpinnerSpin', [a:test, a:lnum]), - \ {'repeat': -1}) -endfunction function! OmniSharp#testrunner#SetTests(bufferTests) abort let winid = win_getid() for buffer in a:bufferTests let job = OmniSharp#GetHost(buffer.bufnr).job let job.tests = get(job, 'tests', {}) + let projectname = s:utils.getProjectName(buffer.bufnr) + let testproject = get(job.tests, projectname, {}) + let job.tests[projectname] = testproject let filename = fnamemodify(bufname(buffer.bufnr), ':p') - let existing = get(job.tests, filename, {}) - let job.tests[filename] = existing + let existing = get(testproject, filename, {}) + let testproject[filename] = existing for test in buffer.tests let extest = get(existing, test.name, { 'state': 'Not run' }) let existing[test.name] = extest @@ -152,13 +122,14 @@ function! OmniSharp#testrunner#SetTests(bufferTests) abort call win_gotoid(winid) endfunction -function! s:UpdateState(bufnr, testnames, state) abort - let job = OmniSharp#GetHost(a:bufnr).job +function! s:UpdateState(bufnr, testnames, state, output) abort + let projectname = s:utils.getProjectName(a:bufnr) let filename = fnamemodify(bufname(a:bufnr), ':p') - let tests = get(job.tests, filename, {}) + let tests = OmniSharp#GetHost(a:bufnr).job.tests[projectname][filename] for testname in a:testnames if has_key(tests, testname) let tests[testname].state = a:state + let tests[testname].output = a:output endif endfor call s:Repaint() @@ -167,24 +138,90 @@ endfunction function! OmniSharp#testrunner#StateRunning(bufnr, testnames) abort let testnames = type(a:testnames) == type([]) ? a:testnames : [a:testnames] let s:lasttestnames = testnames - call s:UpdateState(a:bufnr, testnames, 'Running') + call s:UpdateState(a:bufnr, testnames, 'Running', []) endfunction -function! OmniSharp#testrunner#StateSkipped(bufnr, ...) abort - let testnames = a:0 ? (type(a:1) == type([]) ? a:1 : [a:1]) : s:lasttestnames - call s:UpdateState(a:bufnr, testnames, 'Not run') +function! OmniSharp#testrunner#StateComplete(location) abort + if get(a:location, 'type', '') ==# 'E' + let state = 'Failed' + elseif get(a:location, 'type', '') ==# 'W' + let state = 'Not run' + else + let state = 'Passed' + endif + let output = get(a:location, 'output', []) + call s:UpdateState(a:.location.bufnr, [a:location.fullname], state, output) endfunction -function! OmniSharp#testrunner#StatePassed(bufnr, ...) abort - let testnames = a:0 ? (type(a:1) == type([]) ? a:1 : [a:1]) : s:lasttestnames - call s:UpdateState(a:bufnr, testnames, 'Passed') +function! OmniSharp#testrunner#StateSkipped(bufnr) abort + call s:UpdateState(a:bufnr, s:lasttestnames, 'Not run', []) endfunction -function! OmniSharp#testrunner#StateFailed(bufnr, ...) abort - let testnames = a:0 ? (type(a:1) == type([]) ? a:1 : [a:1]) : s:lasttestnames - call s:UpdateState(a:bufnr, testnames, 'Failed') + +let s:spinner = {} +let s:spinner.steps = get(g:, 'OmniSharp_testrunner_spinnersteps', [ +\ '<*---->', +\ '<-*--->', +\ '<--*-->', +\ '<---*->', +\ '<----*>', +\ '<---*->', +\ '<--*-->', +\ '<-*--->']) + +function! s:spinner.spin(test, lnum, timer) abort + if s:utils.state2char[a:test.state] !=# '-' + call timer_stop(a:timer) + return + endif + let lines = getbufline(s:testrunner_bufnr, a:lnum) + if len(lines) == 0 + call timer_stop(a:timer) + return + endif + let line = lines[0] + if !has_key(a:test.spinner, 'index') + let line .= ' -- ' . s:spinner.steps[0] + let a:test.spinner.index = 0 + else + let a:test.spinner.index += 1 + if a:test.spinner.index >= len(s:spinner.steps) + let a:test.spinner.index = 0 + endif + let step = s:spinner.steps[a:test.spinner.index] + let line = substitute(line, ' -- \zs.*$', step, '') + endif + call setbufvar(s:testrunner_bufnr, '&modifiable', 1) + call setbufline(s:testrunner_bufnr, a:lnum, line) + call setbufvar(s:testrunner_bufnr, '&modifiable', 0) + call setbufvar(s:testrunner_bufnr, '&modified', 0) endfunction +function! s:spinner.start(test, lnum) abort + if !get(g:, 'OmniSharp_testrunner_spinner', 1) | return | endif + let a:test.spinner = {} + let a:test.spinner.timer = timer_start(300, + \ funcref('s:spinner.spin', [a:test, a:lnum], self), + \ {'repeat': -1}) +endfunction + + +let s:utils = {} + +let s:utils.state2char = { +\ 'Not run': '|', +\ 'Running': '-', +\ 'Passed': '*', +\ 'Failed': '!' +\} + +function! s:utils.getProjectName(bufnr) abort + let project = OmniSharp#GetHost(a:bufnr).project + let msbuildproject = get(project, 'MsBuildProject', {}) + return get(msbuildproject, 'AssemblyName', '_Default') +endfunction + + let &cpoptions = s:save_cpo unlet s:save_cpo diff --git a/ftplugin/omnisharptest/OmniSharp.vim b/ftplugin/omnisharptest/OmniSharp.vim new file mode 100644 index 000000000..abac9666c --- /dev/null +++ b/ftplugin/omnisharptest/OmniSharp.vim @@ -0,0 +1,7 @@ +set bufhidden=hide +set noswapfile +set conceallevel=3 +set concealcursor=nv +set foldlevel=2 +set foldmethod=syntax +set signcolumn=no diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim index ed0feb961..5c1d0b850 100644 --- a/syntax/omnisharptest.vim +++ b/syntax/omnisharptest.vim @@ -8,22 +8,32 @@ set cpoptions&vim syn region ostIntro start="\%1l" end="^$" contains=ostIntroDelim transparent syn match ostIntroDelim "^=\+$" contained -syn match ostStateNotRun "^|.*" contains=ostStateChar -syn match ostStateRunning "^-.*" contains=ostStateChar,ostRunningSuffix -syn match ostStatePassed "^\*.*" contains=ostStateChar -syn match ostStateFailed "^!.*" contains=ostStateChar -syn match ostStateChar "^[|\*!-]" conceal contained +syn region ostProject matchgroup=ostProjectName start="^\a.*" end="^$"me=s-1 contains=TOP transparent fold +syn region ostFile matchgroup=ostFileName start="^ \S.*" end="^__$"me=s-1 contains=TOP transparent fold +syn match ostFileDivider "^__$" conceal + +syn match ostStateNotRun "^|.*" contains=ostStatePrefix +syn match ostStateRunning "^-.*" contains=ostStatePrefix,ostRunningSuffix +syn match ostStatePassed "^\*.*" contains=ostStatePrefix +syn match ostStateFailed "^!.*" contains=ostStatePrefix +syn match ostStatePrefix "^[|\*!-]" conceal contained + syn match ostRunningSuffix " -- .*" contained contains=ostRunningSpinner,ostRunningSuffixDivider syn match ostRunningSuffixDivider " \zs--" conceal contained syn match ostRunningSpinner " -- \zs.*" contained -hi def link ostIntroDelim PreProc +syn region ostOutput start="^//" end="^[^/]"me=s-1 contains=ostOutputPrefix fold +syn match ostOutputPrefix "^//" conceal contained +hi def link ostIntroDelim PreProc +hi def link ostProjectName Identifier +hi def link ostFileName TypeDef hi def link ostStateNotRun Comment hi def link ostStateRunning Identifier hi def link ostRunningSpinner Normal hi def link ostStatePassed Title hi def link ostStateFailed WarningMsg +hi def link ostOutput Comment let b:current_syntax = 'omnisharptest' From d5e247ae40b776f59da9322c3ef7c638d03bf61e Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sun, 12 Jun 2022 00:12:13 +1200 Subject: [PATCH 12/54] Display failure message and exception stack trace --- autoload/OmniSharp/actions/test.vim | 2 ++ autoload/OmniSharp/testrunner.vim | 35 ++++++++++++++++++++++------- syntax/omnisharptest.vim | 2 ++ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index dc7508966..d151a3ae4 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -305,6 +305,8 @@ function! s:run.process(Callback, bufnr, tests, response) abort if result.Outcome =~? 'failed' let location.type = 'E' let location.text = location.name . ': ' . result.ErrorMessage + let location.message = split(result.ErrorMessage, '\r\?\n') + let location.stacktrace = split(result.ErrorStackTrace, '\r\?\n') let st = result.ErrorStackTrace let parsed = matchlist(st, '.* in \(.\+\):line \(\d\+\)') if len(parsed) > 0 diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index f9bf2b5db..6765e4ca1 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -69,15 +69,27 @@ function! s:Paint() abort let tests = job.tests[testproject][testfile] for name in sort(keys(tests), {a,b -> tests[a].lnum > tests[b].lnum}) let test = tests[name] - let state = s:utils.state2char[test.state] + let state = s:utils.state2char[test.state] call add(lines, printf('%s %s', state, name)) if state ==# '-' && !has_key(test, 'spintimer') call s:spinner.start(test, len(lines)) endif + let message = get(test, 'message', []) + if len(message) + for messageline in message + call add(lines, '> ' . trim(messageline, ' ', 2)) + endfor + endif + let stacktrace = get(test, 'stacktrace', []) + if len(stacktrace) + for stacktraceline in stacktrace + call add(lines, '> ' . trim(stacktraceline, ' ', 2)) + endfor + endif let output = get(test, 'output', []) if len(output) for outputline in output - call add(lines, '// ' . outputline) + call add(lines, '// ' . trim(outputline, ' ', 2)) endfor endif endfor @@ -122,14 +134,19 @@ function! OmniSharp#testrunner#SetTests(bufferTests) abort call win_gotoid(winid) endfunction -function! s:UpdateState(bufnr, testnames, state, output) abort +function! s:UpdateState(bufnr, testnames, state, ...) abort + let message = a:0 ? a:1 : [] + let stacktrace = a:0 > 1 ? a:2 : [] + let output = a:0 > 2 ? a:3 : [] let projectname = s:utils.getProjectName(a:bufnr) let filename = fnamemodify(bufname(a:bufnr), ':p') let tests = OmniSharp#GetHost(a:bufnr).job.tests[projectname][filename] for testname in a:testnames if has_key(tests, testname) let tests[testname].state = a:state - let tests[testname].output = a:output + let tests[testname].message = message + let tests[testname].stacktrace = stacktrace + let tests[testname].output = output endif endfor call s:Repaint() @@ -138,7 +155,7 @@ endfunction function! OmniSharp#testrunner#StateRunning(bufnr, testnames) abort let testnames = type(a:testnames) == type([]) ? a:testnames : [a:testnames] let s:lasttestnames = testnames - call s:UpdateState(a:bufnr, testnames, 'Running', []) + call s:UpdateState(a:bufnr, testnames, 'Running') endfunction function! OmniSharp#testrunner#StateComplete(location) abort @@ -149,12 +166,14 @@ function! OmniSharp#testrunner#StateComplete(location) abort else let state = 'Passed' endif - let output = get(a:location, 'output', []) - call s:UpdateState(a:.location.bufnr, [a:location.fullname], state, output) + call s:UpdateState(a:.location.bufnr, [a:location.fullname], state, + \ get(a:location, 'message', []), + \ get(a:location, 'stacktrace', []), + \ get(a:location, 'output', [])) endfunction function! OmniSharp#testrunner#StateSkipped(bufnr) abort - call s:UpdateState(a:bufnr, s:lasttestnames, 'Not run', []) + call s:UpdateState(a:bufnr, s:lasttestnames, 'Not run') endfunction diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim index 5c1d0b850..2b58fd939 100644 --- a/syntax/omnisharptest.vim +++ b/syntax/omnisharptest.vim @@ -22,6 +22,8 @@ syn match ostRunningSuffix " -- .*" contained contains=ostRunningSpinner,ostRun syn match ostRunningSuffixDivider " \zs--" conceal contained syn match ostRunningSpinner " -- \zs.*" contained +syn region ostFailure start="^>" end="^[^>]"me=s-1 contains=ostFailurePrefix fold +syn match ostFailurePrefix "^>" conceal contained syn region ostOutput start="^//" end="^[^/]"me=s-1 contains=ostOutputPrefix fold syn match ostOutputPrefix "^//" conceal contained From 939147bacfdbb06cf008182f96c632af4fcf5d6a Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sun, 12 Jun 2022 10:41:55 +1200 Subject: [PATCH 13/54] Add default utf-8 running spinner --- autoload/OmniSharp/testrunner.vim | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 6765e4ca1..8888868bc 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -1,3 +1,4 @@ +scriptencoding utf-8 let s:save_cpo = &cpoptions set cpoptions&vim @@ -178,7 +179,7 @@ endfunction let s:spinner = {} -let s:spinner.steps = get(g:, 'OmniSharp_testrunner_spinnersteps', [ +let s:spinner.steps_ascii = [ \ '<*---->', \ '<-*--->', \ '<--*-->', @@ -186,7 +187,14 @@ let s:spinner.steps = get(g:, 'OmniSharp_testrunner_spinnersteps', [ \ '<----*>', \ '<---*->', \ '<--*-->', -\ '<-*--->']) +\ '<-*--->'] +let s:spinner.steps_utf8 = [ +\ '∙∙∙', +\ '●∙∙', +\ '∙●∙', +\ '∙∙●', +\ '∙∙∙' +\] function! s:spinner.spin(test, lnum, timer) abort if s:utils.state2char[a:test.state] !=# '-' @@ -199,15 +207,18 @@ function! s:spinner.spin(test, lnum, timer) abort return endif let line = lines[0] + let steps = get(g:, 'OmniSharp_testrunner_spinnersteps', + \ get(g:, 'OmniSharp_testrunner_spinner_ascii') + \ ? self.steps_ascii : self.steps_utf8) if !has_key(a:test.spinner, 'index') - let line .= ' -- ' . s:spinner.steps[0] + let line .= ' -- ' . steps[0] let a:test.spinner.index = 0 else let a:test.spinner.index += 1 - if a:test.spinner.index >= len(s:spinner.steps) + if a:test.spinner.index >= len(steps) let a:test.spinner.index = 0 endif - let step = s:spinner.steps[a:test.spinner.index] + let step = steps[a:test.spinner.index] let line = substitute(line, ' -- \zs.*$', step, '') endif call setbufvar(s:testrunner_bufnr, '&modifiable', 1) From c4b0840bcf4f446d72d754d10f9fd23bae170fa4 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 13 Jun 2022 00:41:47 +1200 Subject: [PATCH 14/54] Clean up output and conceal namespaces --- autoload/OmniSharp/testrunner.vim | 40 ++++++++++++++++++++++++++----- syntax/omnisharptest.vim | 22 ++++++++++++----- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 8888868bc..8dc9af596 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -66,25 +66,34 @@ function! s:Paint() abort for testproject in sort(keys(job.tests)) call add(lines, testproject) for testfile in sort(keys(job.tests[testproject])) - call add(lines, ' ' . fnamemodify(testfile, ':.')) + call add(lines, ' ' . fnamemodify(testfile, ':.')) let tests = job.tests[testproject][testfile] for name in sort(keys(tests), {a,b -> tests[a].lnum > tests[b].lnum}) let test = tests[name] let state = s:utils.state2char[test.state] - call add(lines, printf('%s %s', state, name)) + call add(lines, printf('%s %s', state, name)) if state ==# '-' && !has_key(test, 'spintimer') call s:spinner.start(test, len(lines)) endif let message = get(test, 'message', []) if len(message) for messageline in message - call add(lines, '> ' . trim(messageline, ' ', 2)) + call add(lines, '> ' . trim(messageline, ' ', 2)) endfor endif let stacktrace = get(test, 'stacktrace', []) if len(stacktrace) - for stacktraceline in stacktrace - call add(lines, '> ' . trim(stacktraceline, ' ', 2)) + for st in stacktrace + let line = trim(st.text) + if has_key(st, 'filename') + let line = '__ ' . line . ' __' + else + let line = '_._ ' . line . ' _._' + endif + if has_key(st, 'lnum') + let line .= ' line ' . st.lnum + endif + call add(lines, '> ' . line) endfor endif let output = get(test, 'output', []) @@ -137,13 +146,32 @@ endfunction function! s:UpdateState(bufnr, testnames, state, ...) abort let message = a:0 ? a:1 : [] - let stacktrace = a:0 > 1 ? a:2 : [] + let stacktraceraw = a:0 > 1 ? a:2 : [] let output = a:0 > 2 ? a:3 : [] let projectname = s:utils.getProjectName(a:bufnr) let filename = fnamemodify(bufname(a:bufnr), ':p') let tests = OmniSharp#GetHost(a:bufnr).job.tests[projectname][filename] for testname in a:testnames if has_key(tests, testname) + let stacktrace = [] + for st in stacktraceraw + let parsed = matchlist(st, 'at \(.\+\) in \([^:]\+\)\(:line \(\d\+\)\)\?') + if len(parsed) + call add(stacktrace, { + \ 'text': parsed[1], + \ 'filename': parsed[2], + \ 'lnum': str2nr(parsed[4]) + \}) + else + let parsed = matchlist(st, 'at \(.\+\)') + if len(parsed) + call add(stacktrace, {'text': parsed[1]}) + else + call add(stacktrace, {'text': st}) + endif + endif + endfor + let tests[testname].state = a:state let tests[testname].message = message let tests[testname].stacktrace = stacktrace diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim index 2b58fd939..d77c6ce9b 100644 --- a/syntax/omnisharptest.vim +++ b/syntax/omnisharptest.vim @@ -9,21 +9,30 @@ syn region ostIntro start="\%1l" end="^$" contains=ostIntroDelim transparent syn match ostIntroDelim "^=\+$" contained syn region ostProject matchgroup=ostProjectName start="^\a.*" end="^$"me=s-1 contains=TOP transparent fold -syn region ostFile matchgroup=ostFileName start="^ \S.*" end="^__$"me=s-1 contains=TOP transparent fold +syn region ostFile start="^ \S.*" end="^__$"me=s-1 contains=TOP transparent fold +syn match ostFileName "^ \S.*" contains=ostFilePath,ostFileExt +syn match ostFilePath "^ \zs\%(\%(\w\+\.\)*\w\+\/\)*\ze\w\+\." conceal contained +syn match ostFileExt "\%(\.\w\+\)\+" conceal contained syn match ostFileDivider "^__$" conceal -syn match ostStateNotRun "^|.*" contains=ostStatePrefix -syn match ostStateRunning "^-.*" contains=ostStatePrefix,ostRunningSuffix -syn match ostStatePassed "^\*.*" contains=ostStatePrefix -syn match ostStateFailed "^!.*" contains=ostStatePrefix +syn match ostStateNotRun "^|.*" contains=ostStatePrefix,ostTestNamespace +syn match ostStateRunning "^-.*" contains=ostStatePrefix,ostTestNamespace,ostRunningSuffix +syn match ostStatePassed "^\*.*" contains=ostStatePrefix,ostTestNamespace +syn match ostStateFailed "^!.*" contains=ostStatePrefix,ostTestNamespace syn match ostStatePrefix "^[|\*!-]" conceal contained +syn match ostTestNamespace "\%(\w\+\.\)*\ze\w\+" conceal contained syn match ostRunningSuffix " -- .*" contained contains=ostRunningSpinner,ostRunningSuffixDivider syn match ostRunningSuffixDivider " \zs--" conceal contained syn match ostRunningSpinner " -- \zs.*" contained -syn region ostFailure start="^>" end="^[^>]"me=s-1 contains=ostFailurePrefix fold +syn region ostFailure start="^>" end="^[^>]"me=s-1 contains=ostFailurePrefix,ostStackFile,ostStackFileNoLoc fold syn match ostFailurePrefix "^>" conceal contained +syn region ostStackFile start=" __ "hs=e+1 end=" __" contains=ostStackFileDelimiter,ostStackFileNamespace contained keepend +syn match ostStackFileDelimiter " __" conceal contained +syn region ostStackFileNoLoc start=" _._ "hs=e+1 end=" _._" contains=ostStackFileNoLocDelimiter,ostStackFileNamespace contained keepend +syn match ostStackFileNoLocDelimiter " _._" conceal contained +syn match ostStackFileNamespace "\%(\w\+\.\)*\ze\w\+\.\w\+(" conceal contained syn region ostOutput start="^//" end="^[^/]"me=s-1 contains=ostOutputPrefix fold syn match ostOutputPrefix "^//" conceal contained @@ -35,6 +44,7 @@ hi def link ostStateRunning Identifier hi def link ostRunningSpinner Normal hi def link ostStatePassed Title hi def link ostStateFailed WarningMsg +hi def link ostStackFile Underlined hi def link ostOutput Comment let b:current_syntax = 'omnisharptest' From 2e3411a4d158f7ad4a4c190f4cd37bef94aedd87 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 14 Jun 2022 00:04:15 +1200 Subject: [PATCH 15/54] Extend testrunner quick-help banner --- autoload/OmniSharp/testrunner.vim | 18 +++++++++++++----- syntax/omnisharptest.vim | 16 +++++++++++++--- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 8dc9af596..b92e1ebec 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -55,10 +55,17 @@ endfunction function! s:Paint() abort let lines = [] - call add(lines, repeat('=', 80)) - call add(lines, ' OmniSharp Test Runner') - call add(lines, repeat('=', 80)) - call add(lines, '') + if get(g:, 'OmniSharp_testrunner_banner', 1) + let delimiter = get(g:, 'OmniSharp_testrunner_banner_delimeter', '─') + call add(lines, repeat(delimiter, 80)) + call add(lines, ' OmniSharp Test Runner') + call add(lines, ' ' . repeat(delimiter, 76)) + call add(lines, ' Toggle this menu (:help omnisharp-test-runner for more)') + call add(lines, ' Run test or tests in file under cursor') + call add(lines, ' Debug test under cursor') + call add(lines, ' Navigate to test or stack trace') + call add(lines, repeat(delimiter, 80)) + endif for sln_or_dir in OmniSharp#proc#ListRunningJobs() let job = OmniSharp#proc#GetJob(sln_or_dir) @@ -215,7 +222,8 @@ let s:spinner.steps_ascii = [ \ '<----*>', \ '<---*->', \ '<--*-->', -\ '<-*--->'] +\ '<-*--->' +\] let s:spinner.steps_utf8 = [ \ '∙∙∙', \ '●∙∙', diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim index d77c6ce9b..f6151328b 100644 --- a/syntax/omnisharptest.vim +++ b/syntax/omnisharptest.vim @@ -5,8 +5,14 @@ endif let s:save_cpo = &cpoptions set cpoptions&vim -syn region ostIntro start="\%1l" end="^$" contains=ostIntroDelim transparent -syn match ostIntroDelim "^=\+$" contained +syn region ostBanner start="\%1l" end="\%8l$" contains=ostBannerDelim,ostBannerTitle,ostBannerHelp transparent keepend +syn match ostBannerHelp "^ .\+$" contained contains=ostBannerMap,ostBannerLink +syn match ostBannerMap "^ \S\+" contained +syn match ostBannerLink ":help [[:alnum:]-]\+" contained +syn match ostBannerTitle "\%2l^.\+$" contained +syn match ostBannerDelim "\%1l^.*$" contained +syn match ostBannerDelim "\%3l^.*$" contained +syn match ostBannerDelim "\%8l^.*$" contained syn region ostProject matchgroup=ostProjectName start="^\a.*" end="^$"me=s-1 contains=TOP transparent fold syn region ostFile start="^ \S.*" end="^__$"me=s-1 contains=TOP transparent fold @@ -36,7 +42,11 @@ syn match ostStackFileNamespace "\%(\w\+\.\)*\ze\w\+\.\w\+(" conceal contained syn region ostOutput start="^//" end="^[^/]"me=s-1 contains=ostOutputPrefix fold syn match ostOutputPrefix "^//" conceal contained -hi def link ostIntroDelim PreProc +hi def link ostBannerDelim PreProc +hi def link ostBannerTitle Normal +hi def link ostBannerHelp Comment +hi def link ostBannerMap PreProc +hi def link ostBannerLink helpHyperTextJump hi def link ostProjectName Identifier hi def link ostFileName TypeDef hi def link ostStateNotRun Comment From a8319627a0fc604dcbdcff344f76e9f9b9892420 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 14 Jun 2022 00:04:45 +1200 Subject: [PATCH 16/54] Add mapping and variable to toggle banner --- autoload/OmniSharp/testrunner.vim | 14 +++++++++++--- ftplugin/omnisharptest/OmniSharp.vim | 2 ++ syntax/omnisharptest.vim | 6 +++--- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index b92e1ebec..3e101ca6e 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -213,6 +213,12 @@ function! OmniSharp#testrunner#StateSkipped(bufnr) abort endfunction +function! OmniSharp#testrunner#toggleBanner() abort + let g:OmniSharp_testrunner_banner = 1 - get(g:, 'OmniSharp_testrunner_banner', 1) + call s:Paint() +endfunction + + let s:spinner = {} let s:spinner.steps_ascii = [ \ '<*---->', @@ -237,7 +243,8 @@ function! s:spinner.spin(test, lnum, timer) abort call timer_stop(a:timer) return endif - let lines = getbufline(s:testrunner_bufnr, a:lnum) + let lnum = a:lnum + (get(g:, 'OmniSharp_testrunner_banner', 1) ? 8 : 0) + let lines = getbufline(s:testrunner_bufnr, lnum) if len(lines) == 0 call timer_stop(a:timer) return @@ -258,16 +265,17 @@ function! s:spinner.spin(test, lnum, timer) abort let line = substitute(line, ' -- \zs.*$', step, '') endif call setbufvar(s:testrunner_bufnr, '&modifiable', 1) - call setbufline(s:testrunner_bufnr, a:lnum, line) + call setbufline(s:testrunner_bufnr, lnum, line) call setbufvar(s:testrunner_bufnr, '&modifiable', 0) call setbufvar(s:testrunner_bufnr, '&modified', 0) endfunction function! s:spinner.start(test, lnum) abort if !get(g:, 'OmniSharp_testrunner_spinner', 1) | return | endif + let lnum = a:lnum - (get(g:, 'OmniSharp_testrunner_banner', 1) ? 8 : 0) let a:test.spinner = {} let a:test.spinner.timer = timer_start(300, - \ funcref('s:spinner.spin', [a:test, a:lnum], self), + \ funcref('s:spinner.spin', [a:test, lnum], self), \ {'repeat': -1}) endfunction diff --git a/ftplugin/omnisharptest/OmniSharp.vim b/ftplugin/omnisharptest/OmniSharp.vim index abac9666c..0d9123fd0 100644 --- a/ftplugin/omnisharptest/OmniSharp.vim +++ b/ftplugin/omnisharptest/OmniSharp.vim @@ -5,3 +5,5 @@ set concealcursor=nv set foldlevel=2 set foldmethod=syntax set signcolumn=no + +nnoremap :call OmniSharp#testrunner#toggleBanner() diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim index f6151328b..bbadbf4c4 100644 --- a/syntax/omnisharptest.vim +++ b/syntax/omnisharptest.vim @@ -6,8 +6,8 @@ let s:save_cpo = &cpoptions set cpoptions&vim syn region ostBanner start="\%1l" end="\%8l$" contains=ostBannerDelim,ostBannerTitle,ostBannerHelp transparent keepend -syn match ostBannerHelp "^ .\+$" contained contains=ostBannerMap,ostBannerLink -syn match ostBannerMap "^ \S\+" contained +syn match ostBannerHelp "^ .*$" contained contains=ostBannerMap,ostBannerLink +syn match ostBannerMap "^ \S\+" contained syn match ostBannerLink ":help [[:alnum:]-]\+" contained syn match ostBannerTitle "\%2l^.\+$" contained syn match ostBannerDelim "\%1l^.*$" contained @@ -46,7 +46,7 @@ hi def link ostBannerDelim PreProc hi def link ostBannerTitle Normal hi def link ostBannerHelp Comment hi def link ostBannerMap PreProc -hi def link ostBannerLink helpHyperTextJump +hi def link ostBannerLink Identifier hi def link ostProjectName Identifier hi def link ostFileName TypeDef hi def link ostStateNotRun Comment From 063b3b9c6039310fab45183e3fb612c85285df5c Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 14 Jun 2022 12:10:58 +1200 Subject: [PATCH 17/54] Handle server-side errors and failed builds --- autoload/OmniSharp/actions/test.vim | 13 +++- autoload/OmniSharp/testrunner.vim | 99 +++++++++++++++-------------- syntax/omnisharptest.vim | 9 ++- 3 files changed, 69 insertions(+), 52 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index d151a3ae4..289658b9f 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -76,8 +76,11 @@ endfunction function! s:debug.complete(response) abort if !a:response.Success call s:utils.log.warn(['Error debugging unit test', a:response.Message]) + call OmniSharp#testrunner#StateError(s:debug.bufnr, + \ split(trim(a:response.Message), '\r\?\n', 1)) + else + call OmniSharp#testrunner#StateSkipped(s:debug.bufnr) endif - call OmniSharp#testrunner#StateSkipped(s:debug.bufnr) endfunction function! s:debug.process.start(command) abort @@ -273,8 +276,14 @@ endfunction " Response handler used when running a single test, or multiple tests in files function! s:run.process(Callback, bufnr, tests, response) abort let s:run.running = 0 - if !a:response.Success | return | endif + if !a:response.Success + call OmniSharp#testrunner#StateError(a:bufnr, + \ split(trim(eval(a:response.Message)), '\r\?\n', 1)) + return s:utils.log.warn('An error has occurred. This may indicate a failed build') + endif if type(a:response.Body.Results) != type([]) + call OmniSharp#testrunner#StateError(a:bufnr, + \ split(trim(a:response.Body.Failure), '\r\?\n', 1)) return s:utils.log.warn('Error: "' . a:response.Body.Failure . \ '" - this may indicate a failed build') endif diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 3e101ca6e..05a85f037 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -71,7 +71,11 @@ function! s:Paint() abort let job = OmniSharp#proc#GetJob(sln_or_dir) if !has_key(job, 'tests') | continue | endif for testproject in sort(keys(job.tests)) - call add(lines, testproject) + let errors = get(get(job, 'testerrors', {}), testproject, []) + call add(lines, testproject . (len(errors) ? ' - ERROR' : '')) + for errorline in errors + call add(lines, '< ' . trim(errorline, ' ', 2)) + endfor for testfile in sort(keys(job.tests[testproject])) call add(lines, ' ' . fnamemodify(testfile, ':.')) let tests = job.tests[testproject][testfile] @@ -82,33 +86,24 @@ function! s:Paint() abort if state ==# '-' && !has_key(test, 'spintimer') call s:spinner.start(test, len(lines)) endif - let message = get(test, 'message', []) - if len(message) - for messageline in message - call add(lines, '> ' . trim(messageline, ' ', 2)) - endfor - endif - let stacktrace = get(test, 'stacktrace', []) - if len(stacktrace) - for st in stacktrace - let line = trim(st.text) - if has_key(st, 'filename') - let line = '__ ' . line . ' __' - else - let line = '_._ ' . line . ' _._' - endif - if has_key(st, 'lnum') - let line .= ' line ' . st.lnum - endif - call add(lines, '> ' . line) - endfor - endif - let output = get(test, 'output', []) - if len(output) - for outputline in output - call add(lines, '// ' . trim(outputline, ' ', 2)) - endfor - endif + for messageline in get(test, 'message', []) + call add(lines, '> ' . trim(messageline, ' ', 2)) + endfor + for stacktraceline in get(test, 'stacktrace', []) + let line = trim(stacktraceline.text) + if has_key(stacktraceline, 'filename') + let line = '__ ' . line . ' __' + else + let line = '_._ ' . line . ' _._' + endif + if has_key(stacktraceline, 'lnum') + let line .= ' line ' . stacktraceline.lnum + endif + call add(lines, '> ' . line) + endfor + for outputline in get(test, 'output', []) + call add(lines, '// ' . trim(outputline, ' ', 2)) + endfor endfor call add(lines, '__') endfor @@ -151,17 +146,19 @@ function! OmniSharp#testrunner#SetTests(bufferTests) abort call win_gotoid(winid) endfunction -function! s:UpdateState(bufnr, testnames, state, ...) abort - let message = a:0 ? a:1 : [] - let stacktraceraw = a:0 > 1 ? a:2 : [] - let output = a:0 > 2 ? a:3 : [] +function! s:UpdateState(bufnr, state, ...) abort + let opts = a:0 ? a:1 : {} + let job = OmniSharp#GetHost(a:bufnr).job let projectname = s:utils.getProjectName(a:bufnr) + let job.testerrors = get(job, 'testerrors', {}) + let job.testerrors[projectname] = get(opts, 'errors', []) + " TODO: parse errors like the stacktrace let filename = fnamemodify(bufname(a:bufnr), ':p') - let tests = OmniSharp#GetHost(a:bufnr).job.tests[projectname][filename] - for testname in a:testnames + let tests = job.tests[projectname][filename] + for testname in get(opts, 'testnames', s:lasttestnames) if has_key(tests, testname) let stacktrace = [] - for st in stacktraceraw + for st in get(opts, 'stacktrace', []) let parsed = matchlist(st, 'at \(.\+\) in \([^:]\+\)\(:line \(\d\+\)\)\?') if len(parsed) call add(stacktrace, { @@ -180,20 +177,14 @@ function! s:UpdateState(bufnr, testnames, state, ...) abort endfor let tests[testname].state = a:state - let tests[testname].message = message + let tests[testname].message = get(opts, 'message', []) let tests[testname].stacktrace = stacktrace - let tests[testname].output = output + let tests[testname].output = get(opts, 'output', []) endif endfor call s:Repaint() endfunction -function! OmniSharp#testrunner#StateRunning(bufnr, testnames) abort - let testnames = type(a:testnames) == type([]) ? a:testnames : [a:testnames] - let s:lasttestnames = testnames - call s:UpdateState(a:bufnr, testnames, 'Running') -endfunction - function! OmniSharp#testrunner#StateComplete(location) abort if get(a:location, 'type', '') ==# 'E' let state = 'Failed' @@ -202,14 +193,26 @@ function! OmniSharp#testrunner#StateComplete(location) abort else let state = 'Passed' endif - call s:UpdateState(a:.location.bufnr, [a:location.fullname], state, - \ get(a:location, 'message', []), - \ get(a:location, 'stacktrace', []), - \ get(a:location, 'output', [])) + call s:UpdateState(a:.location.bufnr, state, { + \ 'testnames': [a:location.fullname], + \ 'message': get(a:location, 'message', []), + \ 'stacktrace': get(a:location, 'stacktrace', []), + \ 'output': get(a:location, 'output', []) + \}) +endfunction + +function! OmniSharp#testrunner#StateError(bufnr, messages) abort + call s:UpdateState(a:bufnr, 'Not run', {'errors': a:messages}) +endfunction + +function! OmniSharp#testrunner#StateRunning(bufnr, testnames) abort + let testnames = type(a:testnames) == type([]) ? a:testnames : [a:testnames] + let s:lasttestnames = testnames + call s:UpdateState(a:bufnr, 'Running', {'testnames': testnames}) endfunction function! OmniSharp#testrunner#StateSkipped(bufnr) abort - call s:UpdateState(a:bufnr, s:lasttestnames, 'Not run') + call s:UpdateState(a:bufnr, 'Not run') endfunction diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim index bbadbf4c4..f069de297 100644 --- a/syntax/omnisharptest.vim +++ b/syntax/omnisharptest.vim @@ -14,10 +14,12 @@ syn match ostBannerDelim "\%1l^.*$" contained syn match ostBannerDelim "\%3l^.*$" contained syn match ostBannerDelim "\%8l^.*$" contained -syn region ostProject matchgroup=ostProjectName start="^\a.*" end="^$"me=s-1 contains=TOP transparent fold +syn region ostProject start="^\a.*" end="^$"me=s-1 contains=TOP transparent fold +syn match ostProjectName "^\a.*" contains=ostProjectError +syn match ostProjectError " - ERROR$"hs=s+2 contained syn region ostFile start="^ \S.*" end="^__$"me=s-1 contains=TOP transparent fold syn match ostFileName "^ \S.*" contains=ostFilePath,ostFileExt -syn match ostFilePath "^ \zs\%(\%(\w\+\.\)*\w\+\/\)*\ze\w\+\." conceal contained +syn match ostFilePath "^ \zs\%(\%(\f\+\.\)*\f\+\/\)*\ze\f\+\." conceal contained syn match ostFileExt "\%(\.\w\+\)\+" conceal contained syn match ostFileDivider "^__$" conceal @@ -32,6 +34,8 @@ syn match ostRunningSuffix " -- .*" contained contains=ostRunningSpinner,ostRun syn match ostRunningSuffixDivider " \zs--" conceal contained syn match ostRunningSpinner " -- \zs.*" contained +syn region ostError start="^<" end="^[^<]"me=s-1 contains=ostErrorPrefix,ostStackFile,ostStackFileNoLoc +syn match ostErrorPrefix "^<" conceal contained syn region ostFailure start="^>" end="^[^>]"me=s-1 contains=ostFailurePrefix,ostStackFile,ostStackFileNoLoc fold syn match ostFailurePrefix "^>" conceal contained syn region ostStackFile start=" __ "hs=e+1 end=" __" contains=ostStackFileDelimiter,ostStackFileNamespace contained keepend @@ -48,6 +52,7 @@ hi def link ostBannerHelp Comment hi def link ostBannerMap PreProc hi def link ostBannerLink Identifier hi def link ostProjectName Identifier +hi def link ostProjectError WarningMsg hi def link ostFileName TypeDef hi def link ostStateNotRun Comment hi def link ostStateRunning Identifier From f682f1eb83089b364729544e9975f7864cdfd9cb Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Wed, 15 Jun 2022 00:12:59 +1200 Subject: [PATCH 18/54] Display diagnostic test output (build status) --- autoload/OmniSharp/actions/test.vim | 1 + autoload/OmniSharp/stdio.vim | 3 +- autoload/OmniSharp/testrunner.vim | 62 +++++++++++++++++++--------- ftplugin/omnisharptest/OmniSharp.vim | 2 +- syntax/omnisharptest.vim | 8 ++-- 5 files changed, 50 insertions(+), 26 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 289658b9f..332ff6a91 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -424,6 +424,7 @@ endfunction " code structures. All operations are performed asynchronously, and the " a:Callback is called when all buffer code structures have been fetched. function! s:utils.initialize(buffers, Callback) abort + call OmniSharp#testrunner#Init(a:buffers) call s:utils.init.await(a:buffers, 'OmniSharp#actions#project#Get', \ funcref('s:utils.init.await', [a:buffers, 'OmniSharp#actions#codestructure#Get', \ funcref('s:utils.init.extract', [a:Callback])])) diff --git a/autoload/OmniSharp/stdio.vim b/autoload/OmniSharp/stdio.vim index 034313ece..123d97cf2 100644 --- a/autoload/OmniSharp/stdio.vim +++ b/autoload/OmniSharp/stdio.vim @@ -106,7 +106,8 @@ function! s:HandleServerEvent(job, res) abort " Diagnostics received while running tests if get(a:res, 'Event', '') ==# 'TestMessage' - let lines = split(body.Message, '\n') + let lines = split(body.Message, '\r\?\n') + call OmniSharp#testrunner#Log(lines) for line in lines if get(body, 'MessageLevel', '') ==# 'error' echohl WarningMsg | echomsg line | echohl None diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 05a85f037..8f7ea5444 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -2,6 +2,19 @@ scriptencoding utf-8 let s:save_cpo = &cpoptions set cpoptions&vim +let s:current = get(s:, 'current', {}) +let s:runner = get(s:, 'runner', {}) + +function! OmniSharp#testrunner#Init(buffers) abort + let s:current.log = [] + let s:current.singlebuffer = len(a:buffers) == 1 ? a:buffers[0] : -1 + let s:current.testnames = {} +endfunction + +function! OmniSharp#testrunner#Log(message) abort + call extend(s:current.log, a:message) +endfunction + function! OmniSharp#testrunner#Open() abort if !OmniSharp#actions#test#Validate() | return | endif call s:Open() @@ -30,18 +43,18 @@ function s:Open() abort if &filetype !=# ft botright new endif - let s:testrunner_bufnr = bufnr() + let s:runner.bufnr = bufnr() let &filetype = ft execute 'file' title call s:Paint() endfunction function! s:Repaint() abort - if !exists('s:testrunner_bufnr') | return | endif - if getbufvar(s:testrunner_bufnr, '&ft') !=# 'omnisharptest' | return | endif + if !has_key(s:runner, 'bufnr') | return | endif + if getbufvar(s:runner.bufnr, '&ft') !=# 'omnisharptest' | return | endif " If the buffer is listed in a window in the current tab, then focus it for winnr in range(1, winnr('$')) - if winbufnr(winnr) == s:testrunner_bufnr + if winbufnr(winnr) == s:runner.bufnr let l:winid = win_getid() call win_gotoid(win_getid(winnr)) break @@ -76,6 +89,16 @@ function! s:Paint() abort for errorline in errors call add(lines, '< ' . trim(errorline, ' ', 2)) endfor + " The diagnostic logs (build output) are only displayed when a single file + " is tested, otherwise multiple build outputs are intermingled + if OmniSharp#GetHost(s:current.singlebuffer).sln_or_dir ==# sln_or_dir + if len(errors) > 0 && len(s:current.log) > 1 + call add(lines, '< ' . repeat(delimiter, 10)) + endif + for log in s:current.log + call add(lines, '< ' . trim(log, ' ', 2)) + endfor + endif for testfile in sort(keys(job.tests[testproject])) call add(lines, ' ' . fnamemodify(testfile, ':.')) let tests = job.tests[testproject][testfile] @@ -111,13 +134,13 @@ function! s:Paint() abort call add(lines, '') endfor - if bufnr() == s:testrunner_bufnr | let winview = winsaveview() | endif - call setbufvar(s:testrunner_bufnr, '&modifiable', 1) - call deletebufline(s:testrunner_bufnr, 1, '$') - call setbufline(s:testrunner_bufnr, 1, lines) - call setbufvar(s:testrunner_bufnr, '&modifiable', 0) - call setbufvar(s:testrunner_bufnr, '&modified', 0) - if bufnr() == s:testrunner_bufnr + if bufnr() == s:runner.bufnr | let winview = winsaveview() | endif + call setbufvar(s:runner.bufnr, '&modifiable', 1) + call deletebufline(s:runner.bufnr, 1, '$') + call setbufline(s:runner.bufnr, 1, lines) + call setbufvar(s:runner.bufnr, '&modifiable', 0) + call setbufvar(s:runner.bufnr, '&modified', 0) + if bufnr() == s:runner.bufnr call winrestview(winview) syn sync fromstart endif @@ -152,10 +175,9 @@ function! s:UpdateState(bufnr, state, ...) abort let projectname = s:utils.getProjectName(a:bufnr) let job.testerrors = get(job, 'testerrors', {}) let job.testerrors[projectname] = get(opts, 'errors', []) - " TODO: parse errors like the stacktrace let filename = fnamemodify(bufname(a:bufnr), ':p') let tests = job.tests[projectname][filename] - for testname in get(opts, 'testnames', s:lasttestnames) + for testname in get(opts, 'testnames', s:current.testnames[a:bufnr]) if has_key(tests, testname) let stacktrace = [] for st in get(opts, 'stacktrace', []) @@ -207,7 +229,7 @@ endfunction function! OmniSharp#testrunner#StateRunning(bufnr, testnames) abort let testnames = type(a:testnames) == type([]) ? a:testnames : [a:testnames] - let s:lasttestnames = testnames + let s:current.testnames[a:bufnr] = testnames call s:UpdateState(a:bufnr, 'Running', {'testnames': testnames}) endfunction @@ -216,7 +238,7 @@ function! OmniSharp#testrunner#StateSkipped(bufnr) abort endfunction -function! OmniSharp#testrunner#toggleBanner() abort +function! OmniSharp#testrunner#ToggleBanner() abort let g:OmniSharp_testrunner_banner = 1 - get(g:, 'OmniSharp_testrunner_banner', 1) call s:Paint() endfunction @@ -247,7 +269,7 @@ function! s:spinner.spin(test, lnum, timer) abort return endif let lnum = a:lnum + (get(g:, 'OmniSharp_testrunner_banner', 1) ? 8 : 0) - let lines = getbufline(s:testrunner_bufnr, lnum) + let lines = getbufline(s:runner.bufnr, lnum) if len(lines) == 0 call timer_stop(a:timer) return @@ -267,10 +289,10 @@ function! s:spinner.spin(test, lnum, timer) abort let step = steps[a:test.spinner.index] let line = substitute(line, ' -- \zs.*$', step, '') endif - call setbufvar(s:testrunner_bufnr, '&modifiable', 1) - call setbufline(s:testrunner_bufnr, lnum, line) - call setbufvar(s:testrunner_bufnr, '&modifiable', 0) - call setbufvar(s:testrunner_bufnr, '&modified', 0) + call setbufvar(s:runner.bufnr, '&modifiable', 1) + call setbufline(s:runner.bufnr, lnum, line) + call setbufvar(s:runner.bufnr, '&modifiable', 0) + call setbufvar(s:runner.bufnr, '&modified', 0) endfunction function! s:spinner.start(test, lnum) abort diff --git a/ftplugin/omnisharptest/OmniSharp.vim b/ftplugin/omnisharptest/OmniSharp.vim index 0d9123fd0..9bfeff62e 100644 --- a/ftplugin/omnisharptest/OmniSharp.vim +++ b/ftplugin/omnisharptest/OmniSharp.vim @@ -6,4 +6,4 @@ set foldlevel=2 set foldmethod=syntax set signcolumn=no -nnoremap :call OmniSharp#testrunner#toggleBanner() +nnoremap :call OmniSharp#testrunner#ToggleBanner() diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim index f069de297..622a0300d 100644 --- a/syntax/omnisharptest.vim +++ b/syntax/omnisharptest.vim @@ -14,13 +14,15 @@ syn match ostBannerDelim "\%1l^.*$" contained syn match ostBannerDelim "\%3l^.*$" contained syn match ostBannerDelim "\%8l^.*$" contained -syn region ostProject start="^\a.*" end="^$"me=s-1 contains=TOP transparent fold syn match ostProjectName "^\a.*" contains=ostProjectError syn match ostProjectError " - ERROR$"hs=s+2 contained -syn region ostFile start="^ \S.*" end="^__$"me=s-1 contains=TOP transparent fold +syn region ostProject start="^\a.*" end="^$"me=s-1 contains=TOP transparent fold +syn region ostError start="^<" end="^[^<]"me=s-1 contains=ostErrorPrefix,ostStackFile,ostStackFileNoLoc fold +syn match ostErrorPrefix "^<" conceal contained syn match ostFileName "^ \S.*" contains=ostFilePath,ostFileExt syn match ostFilePath "^ \zs\%(\%(\f\+\.\)*\f\+\/\)*\ze\f\+\." conceal contained syn match ostFileExt "\%(\.\w\+\)\+" conceal contained +syn region ostFile start="^ \S.*" end="^__$"me=s-1 contains=TOP transparent fold syn match ostFileDivider "^__$" conceal syn match ostStateNotRun "^|.*" contains=ostStatePrefix,ostTestNamespace @@ -34,8 +36,6 @@ syn match ostRunningSuffix " -- .*" contained contains=ostRunningSpinner,ostRun syn match ostRunningSuffixDivider " \zs--" conceal contained syn match ostRunningSpinner " -- \zs.*" contained -syn region ostError start="^<" end="^[^<]"me=s-1 contains=ostErrorPrefix,ostStackFile,ostStackFileNoLoc -syn match ostErrorPrefix "^<" conceal contained syn region ostFailure start="^>" end="^[^>]"me=s-1 contains=ostFailurePrefix,ostStackFile,ostStackFileNoLoc fold syn match ostFailurePrefix "^>" conceal contained syn region ostStackFile start=" __ "hs=e+1 end=" __" contains=ostStackFileDelimiter,ostStackFileNamespace contained keepend From a3771d027264f547d69eb076a25ee4843569aace Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Wed, 15 Jun 2022 20:40:57 +1200 Subject: [PATCH 19/54] Add option for when to display build output --- autoload/OmniSharp/testrunner.vim | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 8f7ea5444..5c17b4668 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -68,8 +68,8 @@ endfunction function! s:Paint() abort let lines = [] + let delimiter = get(g:, 'OmniSharp_testrunner_banner_delimeter', '─') if get(g:, 'OmniSharp_testrunner_banner', 1) - let delimiter = get(g:, 'OmniSharp_testrunner_banner_delimeter', '─') call add(lines, repeat(delimiter, 80)) call add(lines, ' OmniSharp Test Runner') call add(lines, ' ' . repeat(delimiter, 76)) @@ -89,15 +89,18 @@ function! s:Paint() abort for errorline in errors call add(lines, '< ' . trim(errorline, ' ', 2)) endfor - " The diagnostic logs (build output) are only displayed when a single file - " is tested, otherwise multiple build outputs are intermingled - if OmniSharp#GetHost(s:current.singlebuffer).sln_or_dir ==# sln_or_dir - if len(errors) > 0 && len(s:current.log) > 1 - call add(lines, '< ' . repeat(delimiter, 10)) + let loglevel = get(g:, 'OmniSharp_testrunner_loglevel', 'error') + if loglevel ==? 'all' || (loglevel ==? 'error' && len(errors)) + " The diagnostic logs (build output) are only displayed when a single file + " is tested, otherwise multiple build outputs are intermingled + if OmniSharp#GetHost(s:current.singlebuffer).sln_or_dir ==# sln_or_dir + if len(errors) > 0 && len(s:current.log) > 1 + call add(lines, '< ' . repeat(delimiter, 10)) + endif + for log in s:current.log + call add(lines, '< ' . trim(log, ' ', 2)) + endfor endif - for log in s:current.log - call add(lines, '< ' . trim(log, ' ', 2)) - endfor endif for testfile in sort(keys(job.tests[testproject])) call add(lines, ' ' . fnamemodify(testfile, ':.')) From ebbd022d06615f2b5b6c03a5988d4c4cbdb7cc6c Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Thu, 16 Jun 2022 22:49:22 +1200 Subject: [PATCH 20/54] Improve highlighting of concealed elements --- autoload/OmniSharp/testrunner.vim | 7 ++++--- syntax/omnisharptest.vim | 28 +++++++++++++++++++++------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 5c17b4668..605d22b62 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -118,12 +118,12 @@ function! s:Paint() abort for stacktraceline in get(test, 'stacktrace', []) let line = trim(stacktraceline.text) if has_key(stacktraceline, 'filename') - let line = '__ ' . line . ' __' + let line = '__ ' . line . ' ___ ' . stacktraceline.filename . ' __ ' else - let line = '_._ ' . line . ' _._' + let line = '_._ ' . line . ' _._ ' endif if has_key(stacktraceline, 'lnum') - let line .= ' line ' . stacktraceline.lnum + let line .= 'line ' . stacktraceline.lnum endif call add(lines, '> ' . line) endfor @@ -172,6 +172,7 @@ function! OmniSharp#testrunner#SetTests(bufferTests) abort call win_gotoid(winid) endfunction + function! s:UpdateState(bufnr, state, ...) abort let opts = a:0 ? a:1 : {} let job = OmniSharp#GetHost(a:bufnr).job diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim index 622a0300d..19c25edda 100644 --- a/syntax/omnisharptest.vim +++ b/syntax/omnisharptest.vim @@ -36,13 +36,15 @@ syn match ostRunningSuffix " -- .*" contained contains=ostRunningSpinner,ostRun syn match ostRunningSuffixDivider " \zs--" conceal contained syn match ostRunningSpinner " -- \zs.*" contained -syn region ostFailure start="^>" end="^[^>]"me=s-1 contains=ostFailurePrefix,ostStackFile,ostStackFileNoLoc fold +syn region ostFailure start="^>" end="^[^>]"me=s-1 contains=ostFailurePrefix,ostStackLoc,ostStackNoLoc fold syn match ostFailurePrefix "^>" conceal contained -syn region ostStackFile start=" __ "hs=e+1 end=" __" contains=ostStackFileDelimiter,ostStackFileNamespace contained keepend -syn match ostStackFileDelimiter " __" conceal contained -syn region ostStackFileNoLoc start=" _._ "hs=e+1 end=" _._" contains=ostStackFileNoLocDelimiter,ostStackFileNamespace contained keepend -syn match ostStackFileNoLocDelimiter " _._" conceal contained -syn match ostStackFileNamespace "\%(\w\+\.\)*\ze\w\+\.\w\+(" conceal contained +syn region ostStackLoc start=" __ "hs=e+1 end=" __ "he=e-1 contains=ostStackFile,ostStackDelimiter,ostStackNamespace contained keepend +syn region ostStackFile start=" ___ " end=" __ "he=e-1 contains=ostStackFileDelimiter,ostStackDelimiter conceal contained +syn match ostStackDelimiter " __ "he=e-1 conceal contained +syn match ostStackFileDelimiter " ___ " conceal contained +syn region ostStackNoLoc start=" _._ "hs=e+1 end=" _._" contains=ostStackNoLocDelimiter,ostStackNamespace contained keepend +syn match ostStackNoLocDelimiter " _._" conceal contained +syn match ostStackNamespace "\%(\w\+\.\)*\ze\w\+\.\w\+(" conceal contained syn region ostOutput start="^//" end="^[^/]"me=s-1 contains=ostOutputPrefix fold syn match ostOutputPrefix "^//" conceal contained @@ -59,9 +61,21 @@ hi def link ostStateRunning Identifier hi def link ostRunningSpinner Normal hi def link ostStatePassed Title hi def link ostStateFailed WarningMsg -hi def link ostStackFile Underlined +hi def link ostStackLoc Identifier hi def link ostOutput Comment +" Highlights for normally concealed elements +hi def link ostErrorPrefix NonText +hi def link ostFileDivider NonText +hi def link ostStatePrefix NonText +hi def link ostFailurePrefix NonText +hi def link ostRunningSuffixDivider NonText +hi def link ostStackDelimiter NonText +hi def link ostStackFileDelimiter NonText +hi def link ostStackNoLocDelimiter NonText +hi def link ostOutputPrefix NonText +hi def link ostStackFile WarningMsg + let b:current_syntax = 'omnisharptest' let &cpoptions = s:save_cpo From 09e3f91bb2fbf2730155a0f38f9a2add44ea3707 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Fri, 17 Jun 2022 00:17:59 +1200 Subject: [PATCH 21/54] Navigate to test locations --- autoload/OmniSharp/testrunner.vim | 86 +++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 605d22b62..fed63c11f 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -5,16 +5,102 @@ set cpoptions&vim let s:current = get(s:, 'current', {}) let s:runner = get(s:, 'runner', {}) + +function! OmniSharp#testrunner#Debug() abort +endfunction + + function! OmniSharp#testrunner#Init(buffers) abort let s:current.log = [] let s:current.singlebuffer = len(a:buffers) == 1 ? a:buffers[0] : -1 let s:current.testnames = {} endfunction + function! OmniSharp#testrunner#Log(message) abort call extend(s:current.log, a:message) endfunction + +function! OmniSharp#testrunner#Run() abort +endfunction + + +function! OmniSharp#testrunner#Navigate() abort + if &filetype !=# 'omnisharptest' | return | endif + let bufnr = -1 + let filename = '' + let lnum = -1 + let col = -1 + let line = getline('.') + if line =~# '^\a' + " Project selected - do nothing + elseif line =~# '^ \f' + " File selected + let filename = trim(line) + let bufnr = bufnr(filename) + else + " Stack trace with valid location (filename and possible line number) + let parsed = matchlist(line, '^> \+__ .* ___ \(.*\) __ \%(line \(\d\+\)\)\?$') + if len(parsed) + let filename = parsed[1] + if parsed[2] !=# '' + let lnum = str2nr(parsed[2]) + endif + endif + if filename ==# '' + " Search for test + let testpattern = '[-|*!] \S' + if line =~# testpattern + let testline = line('.') + else + let testline = search(testpattern, 'bcnWz') + endif + if testline > 0 + let testname = matchlist(getline(testline), '[-|*!] \zs.*$')[0] + let projectline = search('^\a', 'bcnWz') + let projectname = matchlist(getline(projectline), '^\S\+')[0] + let fileline = search('^ \f', 'bcnWz') + let filename = matchlist(getline(fileline), '^ \zs.*$')[0] + let filename = fnamemodify(filename, ':p') + for sln_or_dir in OmniSharp#proc#ListRunningJobs() + let job = OmniSharp#proc#GetJob(sln_or_dir) + if has_key(job, 'tests') && has_key(job.tests, projectname) + let lnum = job.tests[projectname][filename][testname].lnum + break + endif + endfor + endif + endif + endif + if bufnr == -1 + if filename !=# '' + let bufnr = bufnr(filename) + if bufnr == -1 + let bufnr = bufadd(filename) + call bufload(bufnr) + endif + endif + if bufnr == -1 | return | endif + endif + for winnr in range(1, winnr('$')) + if winbufnr(winnr) == bufnr + call win_gotoid(win_getid(winnr)) + break + endif + endfor + if bufnr() != bufnr + execute 'aboveleft split' filename + endif + if lnum != -1 + call cursor(lnum, max([col, 0])) + if col == -1 + normal! ^ + endif + endif +endfunction + + function! OmniSharp#testrunner#Open() abort if !OmniSharp#actions#test#Validate() | return | endif call s:Open() From 05a2fb5f8628c1581a003cd71845088915939369 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Fri, 17 Jun 2022 00:18:40 +1200 Subject: [PATCH 22/54] Add and default testrunner mappings --- ftplugin/omnisharptest/OmniSharp.vim | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/ftplugin/omnisharptest/OmniSharp.vim b/ftplugin/omnisharptest/OmniSharp.vim index 9bfeff62e..bb7face78 100644 --- a/ftplugin/omnisharptest/OmniSharp.vim +++ b/ftplugin/omnisharptest/OmniSharp.vim @@ -6,4 +6,19 @@ set foldlevel=2 set foldmethod=syntax set signcolumn=no -nnoremap :call OmniSharp#testrunner#ToggleBanner() +nnoremap (omnisharp_testrunner_togglebanner) :call OmniSharp#testrunner#ToggleBanner() +nnoremap (omnisharp_testrunner_run) :call OmniSharp#testrunner#Run() +nnoremap (omnisharp_testrunner_debug) :call OmniSharp#testrunner#Debug() +nnoremap (omnisharp_testrunner_navigate) :call OmniSharp#testrunner#Navigate() + +function! s:map(mode, lhs, plug) abort + let l:rhs = '(' . a:plug . ')' + if !hasmapto(l:rhs, substitute(a:mode, 'x', 'v', '')) + execute a:mode . 'map ' a:lhs l:rhs + endif +endfunction + +call s:map('n', '', 'omnisharp_testrunner_togglebanner') +call s:map('n', '', 'omnisharp_testrunner_run') +call s:map('n', '', 'omnisharp_testrunner_debug') +call s:map('n', '', 'omnisharp_testrunner_navigate') From 2793374ca67f70e13374226c9be4b2a205b68a5e Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 20 Jun 2022 22:53:55 +1200 Subject: [PATCH 23/54] Add space between projects to fix fold levels --- autoload/OmniSharp/testrunner.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index fed63c11f..ec54bd3bd 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -219,8 +219,8 @@ function! s:Paint() abort endfor call add(lines, '__') endfor + call add(lines, '') endfor - call add(lines, '') endfor if bufnr() == s:runner.bufnr | let winview = winsaveview() | endif From ca1da95e35f2ddf7ce6879cdbbe24a58891ab665 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Fri, 24 Jun 2022 00:09:56 +1200 Subject: [PATCH 24/54] Run test/tests in file/tests in project --- autoload/OmniSharp/actions/test.vim | 22 ++++--- autoload/OmniSharp/testrunner.vim | 89 ++++++++++++++++++++--------- 2 files changed, 74 insertions(+), 37 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 332ff6a91..514252f01 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -23,7 +23,7 @@ endfunction function! s:debug.prepare(bufferTests) abort let bufnr = a:bufferTests[0].bufnr let tests = a:bufferTests[0].tests - let currentTest = s:utils.findTest(tests) + let currentTest = s:utils.findTest(tests, '') if type(currentTest) != type({}) return s:utils.log.warn('No test found') endif @@ -105,16 +105,18 @@ function! s:debug.process.closed(...) abort endfunction -function! OmniSharp#actions#test#Run(nobuild) abort +function! OmniSharp#actions#test#Run(nobuild, ...) abort if !s:utils.capabilities() | return | endif let s:nobuild = a:nobuild - call s:utils.initialize([bufnr('%')], s:run.single.test) + let bufnr = a:0 ? (type(a:1) == type('') ? bufnr(a:1) : a:1) : bufnr('%') + let RunTest = funcref('s:run.single.test', [a:0 > 1 ? a:2 : '']) + call s:utils.initialize([bufnr], RunTest) endfunction -function! s:run.single.test(bufferTests) abort +function! s:run.single.test(testName, bufferTests) abort let bufnr = a:bufferTests[0].bufnr let tests = a:bufferTests[0].tests - let currentTest = s:utils.findTest(tests) + let currentTest = s:utils.findTest(tests, a:testName) if type(currentTest) != type({}) return s:utils.log.warn('No test found') endif @@ -124,6 +126,7 @@ function! s:run.single.test(bufferTests) abort let targetFramework = project.MsBuildProject.TargetFramework let opts = { \ 'ResponseHandler': funcref('s:run.process', [s:run.single.complete, bufnr, tests]), + \ 'BufNum': bufnr, \ 'Parameters': { \ 'MethodName': currentTest.name, \ 'NoBuild': get(s:, 'nobuild', 0), @@ -405,10 +408,10 @@ function! s:utils.extractTests(codeElements) abort endfunction " Find the test in a list of tests that matches the current cursor position -function! s:utils.findTest(tests, ...) abort +function! s:utils.findTest(tests, testName) abort for test in a:tests - if a:0 - if test.name ==# a:1 + if a:testName !=# '' + if test.name ==# a:testName return test endif else @@ -441,7 +444,8 @@ function! s:utils.init.extract(Callback, codeStructures) abort \ 'tests': s:utils.extractTests(cs[1]) \}}) call OmniSharp#testrunner#SetTests(bufferTests) - call a:Callback(bufferTests) + let dict = { 'f': a:Callback } + call dict.f(bufferTests) endfunction function! s:utils.log.echo(highlightGroup, message) abort diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index ec54bd3bd..f6e10ef32 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -23,6 +23,28 @@ endfunction function! OmniSharp#testrunner#Run() abort + let filename = '' + let line = getline('.') + if line =~# '^\a' + " Project selected - run all tests + let projectname = getline('.') + for sln_or_dir in OmniSharp#proc#ListRunningJobs() + let job = OmniSharp#proc#GetJob(sln_or_dir) + if has_key(job, 'tests') && has_key(job.tests, projectname) + call OmniSharp#actions#test#RunInFile(1, keys(job.tests[projectname])) + endif + endfor + elseif line =~# '^ \f' + " File selected + let filename = trim(line) + call OmniSharp#actions#test#RunInFile(0, filename) + return + else + let test = s:utils.findTest() + if has_key(test, 'filename') + call OmniSharp#actions#test#Run(0, test.filename, test.name) + endif + endif endfunction @@ -49,27 +71,10 @@ function! OmniSharp#testrunner#Navigate() abort endif endif if filename ==# '' - " Search for test - let testpattern = '[-|*!] \S' - if line =~# testpattern - let testline = line('.') - else - let testline = search(testpattern, 'bcnWz') - endif - if testline > 0 - let testname = matchlist(getline(testline), '[-|*!] \zs.*$')[0] - let projectline = search('^\a', 'bcnWz') - let projectname = matchlist(getline(projectline), '^\S\+')[0] - let fileline = search('^ \f', 'bcnWz') - let filename = matchlist(getline(fileline), '^ \zs.*$')[0] - let filename = fnamemodify(filename, ':p') - for sln_or_dir in OmniSharp#proc#ListRunningJobs() - let job = OmniSharp#proc#GetJob(sln_or_dir) - if has_key(job, 'tests') && has_key(job.tests, projectname) - let lnum = job.tests[projectname][filename][testname].lnum - break - endif - endfor + let test = s:utils.findTest() + if has_key(test, 'filename') + let filename = test.filename + let lnum = test.lnum endif endif endif @@ -245,13 +250,15 @@ function! OmniSharp#testrunner#SetTests(bufferTests) abort let testproject = get(job.tests, projectname, {}) let job.tests[projectname] = testproject let filename = fnamemodify(bufname(buffer.bufnr), ':p') - let existing = get(testproject, filename, {}) - let testproject[filename] = existing - for test in buffer.tests - let extest = get(existing, test.name, { 'state': 'Not run' }) - let existing[test.name] = extest - let extest.framework = test.framework - let extest.lnum = test.nameRange.Start.Line + let filetests = get(testproject, filename, {}) + let testproject[filename] = filetests + for buffertest in buffer.tests + let test = get(filetests, buffertest.name, { 'state': 'Not run' }) + let filetests[buffertest.name] = test + let test.name = buffertest.name + let test.filename = filename + let test.framework = buffertest.framework + let test.lnum = buffertest.nameRange.Start.Line endfor endfor call s:Open() @@ -404,6 +411,32 @@ let s:utils.state2char = { \ 'Failed': '!' \} +function! s:utils.findTest() abort + if &filetype !=# 'omnisharptest' | return {} | endif + let testpattern = '[-|*!] \S' + let line = getline('.') + if line =~# testpattern + let testline = line('.') + else + let testline = search(testpattern, 'bcnWz') + endif + if testline > 0 + let testname = matchlist(getline(testline), '[-|*!] \zs.*$')[0] + let projectline = search('^\a', 'bcnWz') + let projectname = matchlist(getline(projectline), '^\S\+')[0] + let fileline = search('^ \f', 'bcnWz') + let filename = matchlist(getline(fileline), '^ \zs.*$')[0] + let filename = fnamemodify(filename, ':p') + for sln_or_dir in OmniSharp#proc#ListRunningJobs() + let job = OmniSharp#proc#GetJob(sln_or_dir) + if has_key(job, 'tests') && has_key(job.tests, projectname) + return job.tests[projectname][filename][testname] + endif + endfor + endif + return {} +endfunction + function! s:utils.getProjectName(bufnr) abort let project = OmniSharp#GetHost(a:bufnr).project let msbuildproject = get(project, 'MsBuildProject', {}) From 7aafe930e11cc986b1c1afe8c3809f0ddf088520 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sat, 25 Jun 2022 16:50:52 +1200 Subject: [PATCH 25/54] Add F9 mapping to set breakpoints on stacktrace --- autoload/OmniSharp/testrunner.vim | 23 +++++++++++++++++++++++ ftplugin/omnisharptest/OmniSharp.vim | 2 ++ 2 files changed, 25 insertions(+) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index f6e10ef32..5952c64b1 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -241,6 +241,29 @@ function! s:Paint() abort endfunction +function! OmniSharp#testrunner#SetBreakpoints() abort + if !OmniSharp#util#HasVimspector() + echohl WarningMsg + echomsg 'Vimspector required to set breakpoints' + echohl None + return + endif + let test = s:utils.findTest() + if !has_key(test, 'stacktrace') + echo 'No breakpoints added' + return + endif + let bps = filter(copy(test.stacktrace), + \ "has_key(v:val, 'filename') && has_key(v:val, 'lnum')") + for bp in bps + call vimspector#SetLineBreakpoint(bp.filename, bp.lnum) + endfor + let n = len(bps) + let message = printf('%d break point%s set', n, n == 1 ? '' : 's') + echomsg message +endfunction + + function! OmniSharp#testrunner#SetTests(bufferTests) abort let winid = win_getid() for buffer in a:bufferTests diff --git a/ftplugin/omnisharptest/OmniSharp.vim b/ftplugin/omnisharptest/OmniSharp.vim index bb7face78..32331ebe7 100644 --- a/ftplugin/omnisharptest/OmniSharp.vim +++ b/ftplugin/omnisharptest/OmniSharp.vim @@ -9,6 +9,7 @@ set signcolumn=no nnoremap (omnisharp_testrunner_togglebanner) :call OmniSharp#testrunner#ToggleBanner() nnoremap (omnisharp_testrunner_run) :call OmniSharp#testrunner#Run() nnoremap (omnisharp_testrunner_debug) :call OmniSharp#testrunner#Debug() +nnoremap (omnisharp_testrunner_set_breakpoints) :call OmniSharp#testrunner#SetBreakpoints() nnoremap (omnisharp_testrunner_navigate) :call OmniSharp#testrunner#Navigate() function! s:map(mode, lhs, plug) abort @@ -21,4 +22,5 @@ endfunction call s:map('n', '', 'omnisharp_testrunner_togglebanner') call s:map('n', '', 'omnisharp_testrunner_run') call s:map('n', '', 'omnisharp_testrunner_debug') +call s:map('n', '', 'omnisharp_testrunner_set_breakpoints') call s:map('n', '', 'omnisharp_testrunner_navigate') From 6450ab82f1fdffcc54e535868ebf1c3e58366030 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sat, 25 Jun 2022 23:25:23 +1200 Subject: [PATCH 26/54] Add single break-point when error is under cursor --- autoload/OmniSharp/testrunner.vim | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 5952c64b1..29926569c 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -248,6 +248,14 @@ function! OmniSharp#testrunner#SetBreakpoints() abort echohl None return endif + let line = getline('.') + " Stack trace with valid location (filename and possible line number) + let parsed = matchlist(line, '^> \+__ .* ___ \(.*\) __ \%(line \(\d\+\)\)\?$') + if len(parsed) && parsed[2] !=# '' + call vimspector#SetLineBreakpoint(parsed[1], str2nr(parsed[2])) + echomsg 'Break point set' + return + endif let test = s:utils.findTest() if !has_key(test, 'stacktrace') echo 'No breakpoints added' From 4642e118adfae0f022ce23a193924cee9cb7594d Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 27 Jun 2022 00:17:15 +1200 Subject: [PATCH 27/54] Debug test from test runner --- autoload/OmniSharp/actions/test.vim | 11 +++++--- autoload/OmniSharp/testrunner.vim | 42 ++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 514252f01..05167f33b 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -11,19 +11,21 @@ let s:utils = {} let s:utils.init = {} let s:utils.log = {} -function! OmniSharp#actions#test#Debug(nobuild) abort +function! OmniSharp#actions#test#Debug(nobuild, ...) abort if !s:utils.capabilities() | return | endif let s:nobuild = a:nobuild if !OmniSharp#util#HasVimspector() return s:utils.log.warn('Vimspector required to debug tests') endif - call s:utils.initialize([bufnr('%')], s:debug.prepare) + let bufnr = a:0 ? (type(a:1) == type('') ? bufnr(a:1) : a:1) : bufnr('%') + let DebugTest = funcref('s:debug.prepare', [a:0 > 1 ? a:2 : '']) + call s:utils.initialize([bufnr], DebugTest) endfunction -function! s:debug.prepare(bufferTests) abort +function! s:debug.prepare(testName, bufferTests) abort let bufnr = a:bufferTests[0].bufnr let tests = a:bufferTests[0].tests - let currentTest = s:utils.findTest(tests, '') + let currentTest = s:utils.findTest(tests, a:testName) if type(currentTest) != type({}) return s:utils.log.warn('No test found') endif @@ -31,6 +33,7 @@ function! s:debug.prepare(bufferTests) abort let targetFramework = project.MsBuildProject.TargetFramework let opts = { \ 'ResponseHandler': funcref('s:debug.launch', [bufnr, currentTest.name]), + \ 'BufNum': bufnr, \ 'Parameters': { \ 'MethodName': currentTest.name, \ 'NoBuild': get(s:, 'nobuild', 0), diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 29926569c..44d2d47bf 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -7,6 +7,16 @@ let s:runner = get(s:, 'runner', {}) function! OmniSharp#testrunner#Debug() abort + let filename = '' + let line = getline('.') + if line =~# '^\a' || line =~# '^ \f' + return s:utils.log.warn('Select a test to debug') + else + let test = s:utils.findTest() + if has_key(test, 'filename') + call OmniSharp#actions#test#Debug(0, test.filename, test.name) + endif + endif endfunction @@ -243,23 +253,18 @@ endfunction function! OmniSharp#testrunner#SetBreakpoints() abort if !OmniSharp#util#HasVimspector() - echohl WarningMsg - echomsg 'Vimspector required to set breakpoints' - echohl None - return + return s:utils.log.warn('Vimspector required to set breakpoints') endif let line = getline('.') " Stack trace with valid location (filename and possible line number) let parsed = matchlist(line, '^> \+__ .* ___ \(.*\) __ \%(line \(\d\+\)\)\?$') if len(parsed) && parsed[2] !=# '' call vimspector#SetLineBreakpoint(parsed[1], str2nr(parsed[2])) - echomsg 'Break point set' - return + return s:utils.log.emphasize('Break point set') endif let test = s:utils.findTest() if !has_key(test, 'stacktrace') - echo 'No breakpoints added' - return + return s:utils.log.emphasize('No breakpoints added') endif let bps = filter(copy(test.stacktrace), \ "has_key(v:val, 'filename') && has_key(v:val, 'lnum')") @@ -268,7 +273,7 @@ function! OmniSharp#testrunner#SetBreakpoints() abort endfor let n = len(bps) let message = printf('%d break point%s set', n, n == 1 ? '' : 's') - echomsg message + return s:utils.log.emphasize(message) endfunction @@ -434,6 +439,7 @@ endfunction let s:utils = {} +let s:utils.log = {} let s:utils.state2char = { \ 'Not run': '|', @@ -474,6 +480,24 @@ function! s:utils.getProjectName(bufnr) abort return get(msbuildproject, 'AssemblyName', '_Default') endfunction +function! s:utils.log.echo(highlightGroup, message) abort + let messageLines = type(a:message) == type([]) ? a:message : [a:message] + execute 'echohl' a:highlightGroup + for messageLine in messageLines + echomsg messageLine + endfor + echohl None +endfunction + +function! s:utils.log.emphasize(message) abort + call self.echo('Title', a:message) + return 1 +endfunction + +function! s:utils.log.warn(message) abort + call self.echo('WarningMsg', a:message) + return 0 +endfunction let &cpoptions = s:save_cpo unlet s:save_cpo From 0f34524a8e1864425dc4fc6c3454dbc070784f66 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 28 Jun 2022 00:04:45 +1200 Subject: [PATCH 28/54] Add mapping to remove test/file/proj from runner --- autoload/OmniSharp/testrunner.vim | 52 ++++++++++++++++++++++++++-- ftplugin/omnisharptest/OmniSharp.vim | 2 ++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 44d2d47bf..13c2047e5 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -41,7 +41,9 @@ function! OmniSharp#testrunner#Run() abort for sln_or_dir in OmniSharp#proc#ListRunningJobs() let job = OmniSharp#proc#GetJob(sln_or_dir) if has_key(job, 'tests') && has_key(job.tests, projectname) - call OmniSharp#actions#test#RunInFile(1, keys(job.tests[projectname])) + let filenames = filter(keys(job.tests[projectname]), + \ {_,f -> !get(job.tests[projectname][f], '__OmniSharp__removed')}) + call OmniSharp#actions#test#RunInFile(1, filenames) endif endfor elseif line =~# '^ \f' @@ -58,6 +60,39 @@ function! OmniSharp#testrunner#Run() abort endfunction +function! OmniSharp#testrunner#Remove() abort + let filename = '' + let line = getline('.') + if line =~# '^\a' + " Project selected - run all tests + let projectname = getline('.') + for sln_or_dir in OmniSharp#proc#ListRunningJobs() + let job = OmniSharp#proc#GetJob(sln_or_dir) + if has_key(job, 'tests') && has_key(job.tests, projectname) + let job.tests[projectname].__OmniSharp__removed = 1 + break + endif + endfor + elseif line =~# '^ \f' + " File selected + let filename = fnamemodify(trim(line), ':p') + let projectline = search('^\a', 'bcnWz') + let projectname = matchlist(getline(projectline), '^\S\+')[0] + for sln_or_dir in OmniSharp#proc#ListRunningJobs() + let job = OmniSharp#proc#GetJob(sln_or_dir) + if has_key(job, 'tests') && has_key(job.tests, projectname) + let job.tests[projectname][filename].__OmniSharp__removed = 1 + break + endif + endfor + else + let test = s:utils.findTest() + let test.__OmniSharp__removed = 1 + endif + call s:Paint() +endfunction + + function! OmniSharp#testrunner#Navigate() abort if &filetype !=# 'omnisharptest' | return | endif let bufnr = -1 @@ -185,6 +220,7 @@ function! s:Paint() abort let job = OmniSharp#proc#GetJob(sln_or_dir) if !has_key(job, 'tests') | continue | endif for testproject in sort(keys(job.tests)) + if get(job.tests[testproject], '__OmniSharp__removed') | continue | endif let errors = get(get(job, 'testerrors', {}), testproject, []) call add(lines, testproject . (len(errors) ? ' - ERROR' : '')) for errorline in errors @@ -204,10 +240,12 @@ function! s:Paint() abort endif endif for testfile in sort(keys(job.tests[testproject])) - call add(lines, ' ' . fnamemodify(testfile, ':.')) let tests = job.tests[testproject][testfile] + if get(tests, '__OmniSharp__removed') | continue | endif + call add(lines, ' ' . fnamemodify(testfile, ':.')) for name in sort(keys(tests), {a,b -> tests[a].lnum > tests[b].lnum}) let test = tests[name] + if get(test, '__OmniSharp__removed') | continue | endif let state = s:utils.state2char[test.state] call add(lines, printf('%s %s', state, name)) if state ==# '-' && !has_key(test, 'spintimer') @@ -284,15 +322,25 @@ function! OmniSharp#testrunner#SetTests(bufferTests) abort let job.tests = get(job, 'tests', {}) let projectname = s:utils.getProjectName(buffer.bufnr) let testproject = get(job.tests, projectname, {}) + if has_key(testproject, '__OmniSharp__removed') + unlet testproject.__OmniSharp__removed + endif let job.tests[projectname] = testproject let filename = fnamemodify(bufname(buffer.bufnr), ':p') let filetests = get(testproject, filename, {}) + if has_key(testproject, '__OmniSharp__removed') + unlet testproject.__OmniSharp__removed + endif let testproject[filename] = filetests for buffertest in buffer.tests let test = get(filetests, buffertest.name, { 'state': 'Not run' }) + if has_key(test, '__OmniSharp__removed') + unlet test.__OmniSharp__removed + endif let filetests[buffertest.name] = test let test.name = buffertest.name let test.filename = filename + let test.projectname = projectname let test.framework = buffertest.framework let test.lnum = buffertest.nameRange.Start.Line endfor diff --git a/ftplugin/omnisharptest/OmniSharp.vim b/ftplugin/omnisharptest/OmniSharp.vim index 32331ebe7..fa8919be0 100644 --- a/ftplugin/omnisharptest/OmniSharp.vim +++ b/ftplugin/omnisharptest/OmniSharp.vim @@ -11,6 +11,7 @@ nnoremap (omnisharp_testrunner_run) :call OmniSharp#testrunner#Ru nnoremap (omnisharp_testrunner_debug) :call OmniSharp#testrunner#Debug() nnoremap (omnisharp_testrunner_set_breakpoints) :call OmniSharp#testrunner#SetBreakpoints() nnoremap (omnisharp_testrunner_navigate) :call OmniSharp#testrunner#Navigate() +nnoremap (omnisharp_testrunner_remove) :call OmniSharp#testrunner#Remove() function! s:map(mode, lhs, plug) abort let l:rhs = '(' . a:plug . ')' @@ -24,3 +25,4 @@ call s:map('n', '', 'omnisharp_testrunner_run') call s:map('n', '', 'omnisharp_testrunner_debug') call s:map('n', '', 'omnisharp_testrunner_set_breakpoints') call s:map('n', '', 'omnisharp_testrunner_navigate') +call s:map('n', 'dd', 'omnisharp_testrunner_remove') From 4d38a676984330812365613a2d3aafc53922ed15 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Wed, 29 Jun 2022 23:27:30 +1200 Subject: [PATCH 29/54] Refactor test dict from job.tests to s:tests --- autoload/OmniSharp/testrunner.vim | 202 +++++++++++++----------------- syntax/omnisharptest.vim | 18 ++- 2 files changed, 101 insertions(+), 119 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 13c2047e5..630ca4d0e 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -4,12 +4,18 @@ set cpoptions&vim let s:current = get(s:, 'current', {}) let s:runner = get(s:, 'runner', {}) +let s:tests = get(s:, 'tests', {}) + +" Expose s:tests for custom scripting +function! OmniSharp#testrunner#GetTests() abort + return s:tests +endfunction function! OmniSharp#testrunner#Debug() abort let filename = '' let line = getline('.') - if line =~# '^\a' || line =~# '^ \f' + if line =~# '^;' || line =~# '^ \f' return s:utils.log.warn('Select a test to debug') else let test = s:utils.findTest() @@ -35,17 +41,12 @@ endfunction function! OmniSharp#testrunner#Run() abort let filename = '' let line = getline('.') - if line =~# '^\a' + if line =~# '^;' " Project selected - run all tests - let projectname = getline('.') - for sln_or_dir in OmniSharp#proc#ListRunningJobs() - let job = OmniSharp#proc#GetJob(sln_or_dir) - if has_key(job, 'tests') && has_key(job.tests, projectname) - let filenames = filter(keys(job.tests[projectname]), - \ {_,f -> !get(job.tests[projectname][f], '__OmniSharp__removed')}) - call OmniSharp#actions#test#RunInFile(1, filenames) - endif - endfor + let projectname = matchlist(getline('.'), '^\S\+')[0] + let filenames = filter(keys(s:tests[projectname].files), + \ {_,f -> s:tests[projectname].files[f].visible}) + call OmniSharp#actions#test#RunInFile(1, filenames) elseif line =~# '^ \f' " File selected let filename = trim(line) @@ -63,31 +64,19 @@ endfunction function! OmniSharp#testrunner#Remove() abort let filename = '' let line = getline('.') - if line =~# '^\a' + if line =~# '^;' " Project selected - run all tests - let projectname = getline('.') - for sln_or_dir in OmniSharp#proc#ListRunningJobs() - let job = OmniSharp#proc#GetJob(sln_or_dir) - if has_key(job, 'tests') && has_key(job.tests, projectname) - let job.tests[projectname].__OmniSharp__removed = 1 - break - endif - endfor + let projectname = matchlist(getline('.'), '^\S\+')[0] + let s:tests[projectname].visible = 0 elseif line =~# '^ \f' " File selected let filename = fnamemodify(trim(line), ':p') - let projectline = search('^\a', 'bcnWz') + let projectline = search('^;', 'bcnWz') let projectname = matchlist(getline(projectline), '^\S\+')[0] - for sln_or_dir in OmniSharp#proc#ListRunningJobs() - let job = OmniSharp#proc#GetJob(sln_or_dir) - if has_key(job, 'tests') && has_key(job.tests, projectname) - let job.tests[projectname][filename].__OmniSharp__removed = 1 - break - endif - endfor + let s:tests[projectname].files[filename].visible = 0 else let test = s:utils.findTest() - let test.__OmniSharp__removed = 1 + let test.state = 'hidden' endif call s:Paint() endfunction @@ -100,7 +89,7 @@ function! OmniSharp#testrunner#Navigate() abort let lnum = -1 let col = -1 let line = getline('.') - if line =~# '^\a' + if line =~# '^;' " Project selected - do nothing elseif line =~# '^ \f' " File selected @@ -216,22 +205,21 @@ function! s:Paint() abort call add(lines, repeat(delimiter, 80)) endif - for sln_or_dir in OmniSharp#proc#ListRunningJobs() - let job = OmniSharp#proc#GetJob(sln_or_dir) - if !has_key(job, 'tests') | continue | endif - for testproject in sort(keys(job.tests)) - if get(job.tests[testproject], '__OmniSharp__removed') | continue | endif - let errors = get(get(job, 'testerrors', {}), testproject, []) - call add(lines, testproject . (len(errors) ? ' - ERROR' : '')) - for errorline in errors - call add(lines, '< ' . trim(errorline, ' ', 2)) - endfor - let loglevel = get(g:, 'OmniSharp_testrunner_loglevel', 'error') - if loglevel ==? 'all' || (loglevel ==? 'error' && len(errors)) - " The diagnostic logs (build output) are only displayed when a single file - " is tested, otherwise multiple build outputs are intermingled - if OmniSharp#GetHost(s:current.singlebuffer).sln_or_dir ==# sln_or_dir - if len(errors) > 0 && len(s:current.log) > 1 + for key in sort(keys(s:tests)) + let [assembly, sln] = split(key, ';') + if !s:tests[key].visible | continue | endif + call add(lines, key . (len(s:tests[key].errors) ? ' ERROR' : '')) + for errorline in s:tests[key].errors + call add(lines, '< ' . trim(errorline, ' ', 2)) + endfor + let loglevel = get(g:, 'OmniSharp_testrunner_loglevel', 'error') + if loglevel ==? 'all' || (loglevel ==? 'error' && len(s:tests[key].errors)) + " The diagnostic logs (build output) are only displayed when a single file + " is tested, otherwise multiple build outputs are intermingled + if s:current.singlebuffer != -1 + let [ssln, sass, _] = s:utils.getProject(s:current.singlebuffer) + if ssln ==# sln && sass ==# assembly + if len(s:tests[key].errors) > 0 && len(s:current.log) > 1 call add(lines, '< ' . repeat(delimiter, 10)) endif for log in s:current.log @@ -239,41 +227,41 @@ function! s:Paint() abort endfor endif endif - for testfile in sort(keys(job.tests[testproject])) - let tests = job.tests[testproject][testfile] - if get(tests, '__OmniSharp__removed') | continue | endif - call add(lines, ' ' . fnamemodify(testfile, ':.')) - for name in sort(keys(tests), {a,b -> tests[a].lnum > tests[b].lnum}) - let test = tests[name] - if get(test, '__OmniSharp__removed') | continue | endif - let state = s:utils.state2char[test.state] - call add(lines, printf('%s %s', state, name)) - if state ==# '-' && !has_key(test, 'spintimer') - call s:spinner.start(test, len(lines)) + endif + for testfile in sort(keys(s:tests[key].files)) + if !s:tests[key].files[testfile].visible | continue | endif + let tests = s:tests[key].files[testfile].tests + call add(lines, ' ' . fnamemodify(testfile, ':.')) + for name in sort(keys(tests), {a,b -> tests[a].lnum > tests[b].lnum}) + let test = tests[name] + if test.state ==# 'hidden' | continue | endif + let state = s:utils.state2char[test.state] + call add(lines, printf('%s %s', state, name)) + if state ==# '-' && !has_key(test, 'spintimer') + call s:spinner.start(test, len(lines)) + endif + for messageline in get(test, 'message', []) + call add(lines, '> ' . trim(messageline, ' ', 2)) + endfor + for stacktraceline in get(test, 'stacktrace', []) + let line = trim(stacktraceline.text) + if has_key(stacktraceline, 'filename') + let line = '__ ' . line . ' ___ ' . stacktraceline.filename . ' __ ' + else + let line = '_._ ' . line . ' _._ ' endif - for messageline in get(test, 'message', []) - call add(lines, '> ' . trim(messageline, ' ', 2)) - endfor - for stacktraceline in get(test, 'stacktrace', []) - let line = trim(stacktraceline.text) - if has_key(stacktraceline, 'filename') - let line = '__ ' . line . ' ___ ' . stacktraceline.filename . ' __ ' - else - let line = '_._ ' . line . ' _._ ' - endif - if has_key(stacktraceline, 'lnum') - let line .= 'line ' . stacktraceline.lnum - endif - call add(lines, '> ' . line) - endfor - for outputline in get(test, 'output', []) - call add(lines, '// ' . trim(outputline, ' ', 2)) - endfor + if has_key(stacktraceline, 'lnum') + let line .= 'line ' . stacktraceline.lnum + endif + call add(lines, '> ' . line) + endfor + for outputline in get(test, 'output', []) + call add(lines, '// ' . trim(outputline, ' ', 2)) endfor - call add(lines, '__') endfor - call add(lines, '') + call add(lines, '__') endfor + call add(lines, '') endfor if bufnr() == s:runner.bufnr | let winview = winsaveview() | endif @@ -318,29 +306,22 @@ endfunction function! OmniSharp#testrunner#SetTests(bufferTests) abort let winid = win_getid() for buffer in a:bufferTests - let job = OmniSharp#GetHost(buffer.bufnr).job - let job.tests = get(job, 'tests', {}) - let projectname = s:utils.getProjectName(buffer.bufnr) - let testproject = get(job.tests, projectname, {}) - if has_key(testproject, '__OmniSharp__removed') - unlet testproject.__OmniSharp__removed - endif - let job.tests[projectname] = testproject + let [sln, assembly, key] = s:utils.getProject(buffer.bufnr) + let project = get(s:tests, key, { 'files': {}, 'errors': [] }) + let project.visible = 1 + let s:tests[key] = project let filename = fnamemodify(bufname(buffer.bufnr), ':p') - let filetests = get(testproject, filename, {}) - if has_key(testproject, '__OmniSharp__removed') - unlet testproject.__OmniSharp__removed - endif - let testproject[filename] = filetests + let testfile = get(project.files, filename, { 'tests': {} }) + let testfile.visible = 1 + let project.files[filename] = testfile for buffertest in buffer.tests - let test = get(filetests, buffertest.name, { 'state': 'Not run' }) - if has_key(test, '__OmniSharp__removed') - unlet test.__OmniSharp__removed - endif - let filetests[buffertest.name] = test - let test.name = buffertest.name + let name = buffertest.name + let test = get(testfile.tests, name, { 'state': 'Not run' }) + let testfile.tests[name] = test + let test.name = name let test.filename = filename - let test.projectname = projectname + let test.assembly = assembly + let test.sln = sln let test.framework = buffertest.framework let test.lnum = buffertest.nameRange.Start.Line endfor @@ -352,12 +333,10 @@ endfunction function! s:UpdateState(bufnr, state, ...) abort let opts = a:0 ? a:1 : {} - let job = OmniSharp#GetHost(a:bufnr).job - let projectname = s:utils.getProjectName(a:bufnr) - let job.testerrors = get(job, 'testerrors', {}) - let job.testerrors[projectname] = get(opts, 'errors', []) + let [sln, assembly, key] = s:utils.getProject(a:bufnr) + let s:tests[key].errors = get(opts, 'errors', []) let filename = fnamemodify(bufname(a:bufnr), ':p') - let tests = job.tests[projectname][filename] + let tests = s:tests[key].files[filename].tests for testname in get(opts, 'testnames', s:current.testnames[a:bufnr]) if has_key(tests, testname) let stacktrace = [] @@ -507,25 +486,22 @@ function! s:utils.findTest() abort endif if testline > 0 let testname = matchlist(getline(testline), '[-|*!] \zs.*$')[0] - let projectline = search('^\a', 'bcnWz') + let projectline = search('^;', 'bcnWz') let projectname = matchlist(getline(projectline), '^\S\+')[0] let fileline = search('^ \f', 'bcnWz') let filename = matchlist(getline(fileline), '^ \zs.*$')[0] let filename = fnamemodify(filename, ':p') - for sln_or_dir in OmniSharp#proc#ListRunningJobs() - let job = OmniSharp#proc#GetJob(sln_or_dir) - if has_key(job, 'tests') && has_key(job.tests, projectname) - return job.tests[projectname][filename][testname] - endif - endfor + return s:tests[projectname].files[filename].tests[testname] endif return {} endfunction -function! s:utils.getProjectName(bufnr) abort - let project = OmniSharp#GetHost(a:bufnr).project - let msbuildproject = get(project, 'MsBuildProject', {}) - return get(msbuildproject, 'AssemblyName', '_Default') +function! s:utils.getProject(bufnr) abort + let host = OmniSharp#GetHost(a:bufnr) + let msbuildproject = get(host.project, 'MsBuildProject', {}) + let sln = host.sln_or_dir + let assembly = get(msbuildproject, 'AssemblyName', '_Default') + return [sln, assembly, printf(';%s;%s;', assembly, sln)] endfunction function! s:utils.log.echo(highlightGroup, message) abort diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim index 19c25edda..0cd27e967 100644 --- a/syntax/omnisharptest.vim +++ b/syntax/omnisharptest.vim @@ -14,14 +14,18 @@ syn match ostBannerDelim "\%1l^.*$" contained syn match ostBannerDelim "\%3l^.*$" contained syn match ostBannerDelim "\%8l^.*$" contained -syn match ostProjectName "^\a.*" contains=ostProjectError -syn match ostProjectError " - ERROR$"hs=s+2 contained -syn region ostProject start="^\a.*" end="^$"me=s-1 contains=TOP transparent fold +syn match ostProjectKey ";[^;]*;[^;]*;.*" contains=ostSolution,ostAssembly,ostProjectDelimiter,ostProjectError +syn match ostSolution "\%(^;[^;]\+;\)\@<=[^;]\+" contained conceal +syn match ostAssembly "\%(^;\)\@<=[^;]\+\ze;[^;]\+;" contained +syn match ostProjectDelimiter ";" contained conceal +syn match ostProjectError "ERROR$" contained +syn region ostProject start="^;" end="^$"me=s-1 contains=TOP transparent fold + syn region ostError start="^<" end="^[^<]"me=s-1 contains=ostErrorPrefix,ostStackFile,ostStackFileNoLoc fold syn match ostErrorPrefix "^<" conceal contained syn match ostFileName "^ \S.*" contains=ostFilePath,ostFileExt -syn match ostFilePath "^ \zs\%(\%(\f\+\.\)*\f\+\/\)*\ze\f\+\." conceal contained -syn match ostFileExt "\%(\.\w\+\)\+" conceal contained +syn match ostFilePath "\%(^ \)\@<=\f\{-}\ze[^/\\]\+\.csx\?$" conceal contained +syn match ostFileExt "\.csx\?$" conceal contained syn region ostFile start="^ \S.*" end="^__$"me=s-1 contains=TOP transparent fold syn match ostFileDivider "^__$" conceal @@ -53,7 +57,8 @@ hi def link ostBannerTitle Normal hi def link ostBannerHelp Comment hi def link ostBannerMap PreProc hi def link ostBannerLink Identifier -hi def link ostProjectName Identifier +hi def link ostAssembly Identifier +hi def link ostSolution Normal hi def link ostProjectError WarningMsg hi def link ostFileName TypeDef hi def link ostStateNotRun Comment @@ -65,6 +70,7 @@ hi def link ostStackLoc Identifier hi def link ostOutput Comment " Highlights for normally concealed elements +hi def link ostProjectDelimiter NonText hi def link ostErrorPrefix NonText hi def link ostFileDivider NonText hi def link ostStatePrefix NonText From 7d3b6900bdfa94d850ac15275d2eb486a4a3abde Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sun, 3 Jul 2022 23:28:14 +1200 Subject: [PATCH 30/54] Add foldtext for testrunner folds --- autoload/OmniSharp/testrunner.vim | 54 +++++++++++++++++++++++----- ftplugin/omnisharptest/OmniSharp.vim | 2 ++ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 630ca4d0e..0d8a07716 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -33,6 +33,42 @@ function! OmniSharp#testrunner#Init(buffers) abort endfunction +function! OmniSharp#testrunner#FoldText() abort + let line = getline(v:foldstart) + if line =~# '^;' + " Project + let projectkey = matchlist(line, '^\S\+')[0] + let [assembly, _] = split(projectkey, ';') + let ntests = 0 + for filename in keys(s:tests[projectkey].files) + let ntests += len(s:tests[projectkey].files[filename].tests) + endfor + let err = match(line, '; ERROR$') == -1 ? '' : ' ERROR' + return printf('%s [%d]%s', assembly, ntests, err) + elseif line =~# '^ \f' + " File + let filename = trim(line) + let fullpath = fnamemodify(filename, ':p') + let displayname = matchlist(filename, '^\f\{-}\([^/\\]\+\)\.csx\?$')[1] + " Position the cursor so that search() is relative to the fold, not the + " actual cursor position + let winview = winsaveview() + call cursor(v:foldstart, 0) + let projectline = search('^;', 'bcnWz') + call winrestview(winview) + let projectkey = matchlist(getline(projectline), '^\S\+')[0] + let ntests = len(s:tests[projectkey].files[fullpath].tests) + return printf(' %s [%d]', displayname, ntests) + elseif line =~# '^<' + return printf(' Error details (%d lines)', v:foldend - v:foldstart + 1) + elseif line =~# '^>' + return printf(' Results (%d lines)', v:foldend - v:foldstart + 1) + elseif line =~# '^//' + return printf(' Output (%d lines)', v:foldend - v:foldstart + 1) + endif + return printf('%s (%d lines)', line, v:foldend - v:foldstart + 1) +endfunction + function! OmniSharp#testrunner#Log(message) abort call extend(s:current.log, a:message) endfunction @@ -43,9 +79,9 @@ function! OmniSharp#testrunner#Run() abort let line = getline('.') if line =~# '^;' " Project selected - run all tests - let projectname = matchlist(getline('.'), '^\S\+')[0] - let filenames = filter(keys(s:tests[projectname].files), - \ {_,f -> s:tests[projectname].files[f].visible}) + let projectkey = matchlist(getline('.'), '^\S\+')[0] + let filenames = filter(keys(s:tests[projectkey].files), + \ {_,f -> s:tests[projectkey].files[f].visible}) call OmniSharp#actions#test#RunInFile(1, filenames) elseif line =~# '^ \f' " File selected @@ -66,14 +102,14 @@ function! OmniSharp#testrunner#Remove() abort let line = getline('.') if line =~# '^;' " Project selected - run all tests - let projectname = matchlist(getline('.'), '^\S\+')[0] - let s:tests[projectname].visible = 0 + let projectkey = matchlist(getline('.'), '^\S\+')[0] + let s:tests[projectkey].visible = 0 elseif line =~# '^ \f' " File selected let filename = fnamemodify(trim(line), ':p') let projectline = search('^;', 'bcnWz') - let projectname = matchlist(getline(projectline), '^\S\+')[0] - let s:tests[projectname].files[filename].visible = 0 + let projectkey = matchlist(getline(projectline), '^\S\+')[0] + let s:tests[projectkey].files[filename].visible = 0 else let test = s:utils.findTest() let test.state = 'hidden' @@ -487,11 +523,11 @@ function! s:utils.findTest() abort if testline > 0 let testname = matchlist(getline(testline), '[-|*!] \zs.*$')[0] let projectline = search('^;', 'bcnWz') - let projectname = matchlist(getline(projectline), '^\S\+')[0] + let projectkey = matchlist(getline(projectline), '^\S\+')[0] let fileline = search('^ \f', 'bcnWz') let filename = matchlist(getline(fileline), '^ \zs.*$')[0] let filename = fnamemodify(filename, ':p') - return s:tests[projectname].files[filename].tests[testname] + return s:tests[projectkey].files[filename].tests[testname] endif return {} endfunction diff --git a/ftplugin/omnisharptest/OmniSharp.vim b/ftplugin/omnisharptest/OmniSharp.vim index fa8919be0..0c4e0b860 100644 --- a/ftplugin/omnisharptest/OmniSharp.vim +++ b/ftplugin/omnisharptest/OmniSharp.vim @@ -26,3 +26,5 @@ call s:map('n', '', 'omnisharp_testrunner_debug') call s:map('n', '', 'omnisharp_testrunner_set_breakpoints') call s:map('n', '', 'omnisharp_testrunner_navigate') call s:map('n', 'dd', 'omnisharp_testrunner_remove') + +set foldtext=OmniSharp#testrunner#FoldText() From fe9bff83217fc204fd703e27eaf0ae215ca17ac0 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Thu, 14 Jul 2022 20:15:38 +1200 Subject: [PATCH 31/54] Toggle banner without repainting buffer --- autoload/OmniSharp/testrunner.vim | 61 ++++++++++++++++++---------- ftplugin/omnisharptest/OmniSharp.vim | 1 + syntax/omnisharptest.vim | 13 +++--- 3 files changed, 48 insertions(+), 27 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 0d8a07716..627dd8941 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -114,7 +114,7 @@ function! OmniSharp#testrunner#Remove() abort let test = s:utils.findTest() let test.state = 'hidden' endif - call s:Paint() + call s:buffer.paint() endfunction @@ -207,40 +207,44 @@ function s:Open() abort let s:runner.bufnr = bufnr() let &filetype = ft execute 'file' title - call s:Paint() + call s:buffer.paint() endfunction -function! s:Repaint() abort + +let s:buffer = {} +function! s:buffer.focus() abort if !has_key(s:runner, 'bufnr') | return | endif if getbufvar(s:runner.bufnr, '&ft') !=# 'omnisharptest' | return | endif " If the buffer is listed in a window in the current tab, then focus it for winnr in range(1, winnr('$')) if winbufnr(winnr) == s:runner.bufnr - let l:winid = win_getid() call win_gotoid(win_getid(winnr)) - break + return v:true endif endfor - call s:Paint() - if exists('l:winid') - call win_gotoid(l:winid) - endif + return v:false endfunction -function! s:Paint() abort +function! s:buffer.bannerlines() abort let lines = [] let delimiter = get(g:, 'OmniSharp_testrunner_banner_delimeter', '─') + call add(lines, '`' . repeat(delimiter, 80)) + call add(lines, '` OmniSharp Test Runner') + call add(lines, '` ' . repeat(delimiter, 76)) + call add(lines, '` Toggle this menu (:help omnisharp-test-runner for more)') + call add(lines, '` Run test or tests in file under cursor') + call add(lines, '` Debug test under cursor') + call add(lines, '` Navigate to test or stack trace') + call add(lines, '`' . repeat(delimiter, 80)) + return lines +endfunction + +function! s:buffer.paint() abort if get(g:, 'OmniSharp_testrunner_banner', 1) - call add(lines, repeat(delimiter, 80)) - call add(lines, ' OmniSharp Test Runner') - call add(lines, ' ' . repeat(delimiter, 76)) - call add(lines, ' Toggle this menu (:help omnisharp-test-runner for more)') - call add(lines, ' Run test or tests in file under cursor') - call add(lines, ' Debug test under cursor') - call add(lines, ' Navigate to test or stack trace') - call add(lines, repeat(delimiter, 80)) + let lines = self.bannerlines() + else + let lines = [] endif - for key in sort(keys(s:tests)) let [assembly, sln] = split(key, ';') if !s:tests[key].visible | continue | endif @@ -400,7 +404,12 @@ function! s:UpdateState(bufnr, state, ...) abort let tests[testname].output = get(opts, 'output', []) endif endfor - call s:Repaint() + let l:winid = win_getid() + let l:focused = s:buffer.focus() + call s:buffer.paint() + if l:focused + call win_gotoid(l:winid) + endif endfunction function! OmniSharp#testrunner#StateComplete(location) abort @@ -436,7 +445,17 @@ endfunction function! OmniSharp#testrunner#ToggleBanner() abort let g:OmniSharp_testrunner_banner = 1 - get(g:, 'OmniSharp_testrunner_banner', 1) - call s:Paint() + if s:buffer.focus() + let displayed = getline(1) =~# '`' + call setbufvar(s:runner.bufnr, '&modifiable', 1) + if g:OmniSharp_testrunner_banner && !displayed + call appendbufline(s:runner.bufnr, 0, s:buffer.bannerlines()) + elseif !g:OmniSharp_testrunner_banner && displayed + call deletebufline(s:runner.bufnr, 1, len(s:buffer.bannerlines())) + endif + call setbufvar(s:runner.bufnr, '&modifiable', 0) + call setbufvar(s:runner.bufnr, '&modified', 0) + endif endfunction diff --git a/ftplugin/omnisharptest/OmniSharp.vim b/ftplugin/omnisharptest/OmniSharp.vim index 0c4e0b860..554198d2e 100644 --- a/ftplugin/omnisharptest/OmniSharp.vim +++ b/ftplugin/omnisharptest/OmniSharp.vim @@ -5,6 +5,7 @@ set concealcursor=nv set foldlevel=2 set foldmethod=syntax set signcolumn=no +set synmaxcol=3000 nnoremap (omnisharp_testrunner_togglebanner) :call OmniSharp#testrunner#ToggleBanner() nnoremap (omnisharp_testrunner_run) :call OmniSharp#testrunner#Run() diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim index 0cd27e967..24daaa8ec 100644 --- a/syntax/omnisharptest.vim +++ b/syntax/omnisharptest.vim @@ -6,13 +6,14 @@ let s:save_cpo = &cpoptions set cpoptions&vim syn region ostBanner start="\%1l" end="\%8l$" contains=ostBannerDelim,ostBannerTitle,ostBannerHelp transparent keepend -syn match ostBannerHelp "^ .*$" contained contains=ostBannerMap,ostBannerLink -syn match ostBannerMap "^ \S\+" contained +syn match ostBannerHelp "^` .*$" contained contains=ostBannerMap,ostBannerLink,ostBannerPrefix +syn match ostBannerMap "^` \S\+" contained contains=ostBannerPrefix syn match ostBannerLink ":help [[:alnum:]-]\+" contained -syn match ostBannerTitle "\%2l^.\+$" contained -syn match ostBannerDelim "\%1l^.*$" contained -syn match ostBannerDelim "\%3l^.*$" contained -syn match ostBannerDelim "\%8l^.*$" contained +syn match ostBannerTitle "\%2l^`.\+$" contained contains=ostBannerPrefix +syn match ostBannerDelim "\%1l^`.*$" contained contains=ostBannerPrefix +syn match ostBannerDelim "\%3l^`.*$" contained contains=ostBannerPrefix +syn match ostBannerDelim "\%8l^`.*$" contained contains=ostBannerPrefix +syn match ostBannerPrefix "^`" conceal contained syn match ostProjectKey ";[^;]*;[^;]*;.*" contains=ostSolution,ostAssembly,ostProjectDelimiter,ostProjectError syn match ostSolution "\%(^;[^;]\+;\)\@<=[^;]\+" contained conceal From 47192138065c30c56b215797ea02ec39fbc3640f Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Fri, 15 Jul 2022 15:39:13 +1200 Subject: [PATCH 32/54] Update test without repainting buffer --- autoload/OmniSharp/testrunner.vim | 141 ++++++++++++++++++------------ syntax/omnisharptest.vim | 1 + 2 files changed, 84 insertions(+), 58 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 627dd8941..f98c63231 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -48,7 +48,6 @@ function! OmniSharp#testrunner#FoldText() abort elseif line =~# '^ \f' " File let filename = trim(line) - let fullpath = fnamemodify(filename, ':p') let displayname = matchlist(filename, '^\f\{-}\([^/\\]\+\)\.csx\?$')[1] " Position the cursor so that search() is relative to the fold, not the " actual cursor position @@ -57,7 +56,7 @@ function! OmniSharp#testrunner#FoldText() abort let projectline = search('^;', 'bcnWz') call winrestview(winview) let projectkey = matchlist(getline(projectline), '^\S\+')[0] - let ntests = len(s:tests[projectkey].files[fullpath].tests) + let ntests = len(s:tests[projectkey].files[filename].tests) return printf(' %s [%d]', displayname, ntests) elseif line =~# '^<' return printf(' Error details (%d lines)', v:foldend - v:foldstart + 1) @@ -106,7 +105,7 @@ function! OmniSharp#testrunner#Remove() abort let s:tests[projectkey].visible = 0 elseif line =~# '^ \f' " File selected - let filename = fnamemodify(trim(line), ':p') + let filename = trim(line) let projectline = search('^;', 'bcnWz') let projectkey = matchlist(getline(projectline), '^\S\+')[0] let s:tests[projectkey].files[filename].visible = 0 @@ -225,23 +224,9 @@ function! s:buffer.focus() abort return v:false endfunction -function! s:buffer.bannerlines() abort - let lines = [] - let delimiter = get(g:, 'OmniSharp_testrunner_banner_delimeter', '─') - call add(lines, '`' . repeat(delimiter, 80)) - call add(lines, '` OmniSharp Test Runner') - call add(lines, '` ' . repeat(delimiter, 76)) - call add(lines, '` Toggle this menu (:help omnisharp-test-runner for more)') - call add(lines, '` Run test or tests in file under cursor') - call add(lines, '` Debug test under cursor') - call add(lines, '` Navigate to test or stack trace') - call add(lines, '`' . repeat(delimiter, 80)) - return lines -endfunction - function! s:buffer.paint() abort if get(g:, 'OmniSharp_testrunner_banner', 1) - let lines = self.bannerlines() + let lines = self.paintbanner() else let lines = [] endif @@ -271,33 +256,10 @@ function! s:buffer.paint() abort for testfile in sort(keys(s:tests[key].files)) if !s:tests[key].files[testfile].visible | continue | endif let tests = s:tests[key].files[testfile].tests - call add(lines, ' ' . fnamemodify(testfile, ':.')) + call add(lines, ' ' . testfile) for name in sort(keys(tests), {a,b -> tests[a].lnum > tests[b].lnum}) let test = tests[name] - if test.state ==# 'hidden' | continue | endif - let state = s:utils.state2char[test.state] - call add(lines, printf('%s %s', state, name)) - if state ==# '-' && !has_key(test, 'spintimer') - call s:spinner.start(test, len(lines)) - endif - for messageline in get(test, 'message', []) - call add(lines, '> ' . trim(messageline, ' ', 2)) - endfor - for stacktraceline in get(test, 'stacktrace', []) - let line = trim(stacktraceline.text) - if has_key(stacktraceline, 'filename') - let line = '__ ' . line . ' ___ ' . stacktraceline.filename . ' __ ' - else - let line = '_._ ' . line . ' _._ ' - endif - if has_key(stacktraceline, 'lnum') - let line .= 'line ' . stacktraceline.lnum - endif - call add(lines, '> ' . line) - endfor - for outputline in get(test, 'output', []) - call add(lines, '// ' . trim(outputline, ' ', 2)) - endfor + call extend(lines, self.painttest(test, len(lines) + 1)) endfor call add(lines, '__') endfor @@ -316,6 +278,51 @@ function! s:buffer.paint() abort endif endfunction +function! s:buffer.paintbanner() abort + let lines = [] + let delimiter = get(g:, 'OmniSharp_testrunner_banner_delimeter', '─') + call add(lines, '`' . repeat(delimiter, 80)) + call add(lines, '` OmniSharp Test Runner') + call add(lines, '` ' . repeat(delimiter, 76)) + call add(lines, '` Toggle this menu (:help omnisharp-test-runner for more)') + call add(lines, '` Run test or tests in file under cursor') + call add(lines, '` Debug test under cursor') + call add(lines, '` Navigate to test or stack trace') + call add(lines, '`' . repeat(delimiter, 80)) + return lines +endfunction + +function! s:buffer.painttest(test, lnum) abort + if a:test.state ==# 'hidden' + return [] + endif + let lines = [] + let state = s:utils.state2char[a:test.state] + call add(lines, printf('%s %s', state, a:test.name)) + if state ==# '-' && !has_key(a:test, 'spintimer') + call s:spinner.start(a:test, a:lnum) + endif + for messageline in get(a:test, 'message', []) + call add(lines, '> ' . trim(messageline, ' ', 2)) + endfor + for stacktraceline in get(a:test, 'stacktrace', []) + let line = trim(stacktraceline.text) + if has_key(stacktraceline, 'filename') + let line = '__ ' . line . ' ___ ' . stacktraceline.filename . ' __ ' + else + let line = '_._ ' . line . ' _._ ' + endif + if has_key(stacktraceline, 'lnum') + let line .= 'line ' . stacktraceline.lnum + endif + call add(lines, '> ' . line) + endfor + for outputline in get(a:test, 'output', []) + call add(lines, '// ' . trim(outputline, ' ', 2)) + endfor + return lines +endfunction + function! OmniSharp#testrunner#SetBreakpoints() abort if !OmniSharp#util#HasVimspector() @@ -397,19 +404,33 @@ function! s:UpdateState(bufnr, state, ...) abort endif endif endfor - - let tests[testname].state = a:state - let tests[testname].message = get(opts, 'message', []) - let tests[testname].stacktrace = stacktrace - let tests[testname].output = get(opts, 'output', []) + let test = tests[testname] + let test.state = a:state + let test.message = get(opts, 'message', []) + let test.stacktrace = stacktrace + let test.output = get(opts, 'output', []) + + call setbufvar(s:runner.bufnr, '&modifiable', 1) + let lines = getbufline(s:runner.bufnr, 1, '$') + let pattern = '^ ' . substitute(filename, '/', '\\/', 'g') + let fileline = match(lines, pattern) + 1 + let pattern = '^[-|*!] ' . testname + let testline = match(lines, pattern, fileline) + 1 + + let patterns = ['^[-|*!] \S', '^__$', '^$'] + let endline = min( + \ filter( + \ map( + \ patterns, + \ {_,pattern -> match(lines, pattern, testline)}), + \ {_,matchline -> matchline >= testline})) + let testlines = s:buffer.painttest(test, testline) + call deletebufline(s:runner.bufnr, testline, endline) + call appendbufline(s:runner.bufnr, testline - 1, testlines) + call setbufvar(s:runner.bufnr, '&modifiable', 0) + call setbufvar(s:runner.bufnr, '&modified', 0) endif endfor - let l:winid = win_getid() - let l:focused = s:buffer.focus() - call s:buffer.paint() - if l:focused - call win_gotoid(l:winid) - endif endfunction function! OmniSharp#testrunner#StateComplete(location) abort @@ -449,9 +470,9 @@ function! OmniSharp#testrunner#ToggleBanner() abort let displayed = getline(1) =~# '`' call setbufvar(s:runner.bufnr, '&modifiable', 1) if g:OmniSharp_testrunner_banner && !displayed - call appendbufline(s:runner.bufnr, 0, s:buffer.bannerlines()) + call appendbufline(s:runner.bufnr, 0, s:buffer.paintbanner()) elseif !g:OmniSharp_testrunner_banner && displayed - call deletebufline(s:runner.bufnr, 1, len(s:buffer.bannerlines())) + call deletebufline(s:runner.bufnr, 1, len(s:buffer.paintbanner())) endif call setbufvar(s:runner.bufnr, '&modifiable', 0) call setbufvar(s:runner.bufnr, '&modified', 0) @@ -481,7 +502,6 @@ let s:spinner.steps_utf8 = [ function! s:spinner.spin(test, lnum, timer) abort if s:utils.state2char[a:test.state] !=# '-' call timer_stop(a:timer) - return endif let lnum = a:lnum + (get(g:, 'OmniSharp_testrunner_banner', 1) ? 8 : 0) let lines = getbufline(s:runner.bufnr, lnum) @@ -489,14 +509,20 @@ function! s:spinner.spin(test, lnum, timer) abort call timer_stop(a:timer) return endif + " TODO: find the test by name, instead of line number let line = lines[0] let steps = get(g:, 'OmniSharp_testrunner_spinnersteps', \ get(g:, 'OmniSharp_testrunner_spinner_ascii') \ ? self.steps_ascii : self.steps_utf8) if !has_key(a:test.spinner, 'index') + " Starting let line .= ' -- ' . steps[0] let a:test.spinner.index = 0 + elseif s:utils.state2char[a:test.state] !=# '-' + " Stopping + let line = substitute(line, ' -- .*$', '', '') else + " Stepping let a:test.spinner.index += 1 if a:test.spinner.index >= len(steps) let a:test.spinner.index = 0 @@ -545,7 +571,6 @@ function! s:utils.findTest() abort let projectkey = matchlist(getline(projectline), '^\S\+')[0] let fileline = search('^ \f', 'bcnWz') let filename = matchlist(getline(fileline), '^ \zs.*$')[0] - let filename = fnamemodify(filename, ':p') return s:tests[projectkey].files[filename].tests[testname] endif return {} diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim index 24daaa8ec..db0c158af 100644 --- a/syntax/omnisharptest.vim +++ b/syntax/omnisharptest.vim @@ -71,6 +71,7 @@ hi def link ostStackLoc Identifier hi def link ostOutput Comment " Highlights for normally concealed elements +hi def link ostBannerPrefix NonText hi def link ostProjectDelimiter NonText hi def link ostErrorPrefix NonText hi def link ostFileDivider NonText From 37967f64abb69de29717b50ae0f35ab2aa69595f Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Fri, 15 Jul 2022 21:47:11 +1200 Subject: [PATCH 33/54] Only repaint when initializing new tests --- autoload/OmniSharp/testrunner.vim | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index f98c63231..3c5ca3fef 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -351,18 +351,27 @@ endfunction function! OmniSharp#testrunner#SetTests(bufferTests) abort - let winid = win_getid() + let hasNew = v:false for buffer in a:bufferTests let [sln, assembly, key] = s:utils.getProject(buffer.bufnr) + if !has_key(s:tests, key) || !s:tests[key].visible + let hasNew = v:true + endif let project = get(s:tests, key, { 'files': {}, 'errors': [] }) let project.visible = 1 let s:tests[key] = project let filename = fnamemodify(bufname(buffer.bufnr), ':p') let testfile = get(project.files, filename, { 'tests': {} }) + if !get(testfile, 'visible', 0) + let hasNew = v:true + endif let testfile.visible = 1 let project.files[filename] = testfile for buffertest in buffer.tests let name = buffertest.name + if !has_key(testfile.tests, name) + let hasNew = v:true + endif let test = get(testfile.tests, name, { 'state': 'Not run' }) let testfile.tests[name] = test let test.name = name @@ -373,8 +382,19 @@ function! OmniSharp#testrunner#SetTests(bufferTests) abort let test.lnum = buffertest.nameRange.Start.Line endfor endfor - call s:Open() - call win_gotoid(winid) + let winid = win_getid() + if hasNew + call s:Open() + call win_gotoid(winid) + elseif s:buffer.focus() + for buffer in a:bufferTests + let filename = fnamemodify(bufname(buffer.bufnr), ':p') + let pattern = '^ ' . substitute(filename, '/', '\\/', 'g') + call search(pattern, 'cw') + normal! 5zo + endfor + call win_gotoid(winid) + endif endfunction @@ -431,6 +451,11 @@ function! s:UpdateState(bufnr, state, ...) abort call setbufvar(s:runner.bufnr, '&modified', 0) endif endfor + let winid = win_getid() + if s:buffer.focus() + syn sync fromstart + call win_gotoid(winid) + endif endfunction function! OmniSharp#testrunner#StateComplete(location) abort From 7f561c7f5793be1b5f7fbafdf40ff7b7e111394c Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sat, 16 Jul 2022 22:31:04 +1200 Subject: [PATCH 34/54] Add success/failure glyph beside completed tests --- autoload/OmniSharp/testrunner.vim | 16 +++++++++++++--- syntax/omnisharptest.vim | 11 +++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 3c5ca3fef..8d1baad8a 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -298,7 +298,16 @@ function! s:buffer.painttest(test, lnum) abort endif let lines = [] let state = s:utils.state2char[a:test.state] - call add(lines, printf('%s %s', state, a:test.name)) + let glyph = '' + if state ==# '*' && get(g:, 'OmniSharp_testrunner_glyph', 1) + let glyph = get(g:, 'OmniSharp_testrunner_glyph_passed', '✔') + elseif state ==# '!' && get(g:, 'OmniSharp_testrunner_glyph', 1) + let glyph = get(g:, 'OmniSharp_testrunner_glyph_failed', '✘') + endif + if glyph !=# '' + let glyph = printf('|| %s || ', glyph) + endif + call add(lines, printf('%s %s%s', state, glyph, a:test.name)) if state ==# '-' && !has_key(a:test, 'spintimer') call s:spinner.start(a:test, a:lnum) endif @@ -434,7 +443,7 @@ function! s:UpdateState(bufnr, state, ...) abort let lines = getbufline(s:runner.bufnr, 1, '$') let pattern = '^ ' . substitute(filename, '/', '\\/', 'g') let fileline = match(lines, pattern) + 1 - let pattern = '^[-|*!] ' . testname + let pattern = '^[-|*!] \%(|| .\{-} || \)\?' . testname let testline = match(lines, pattern, fileline) + 1 let patterns = ['^[-|*!] \S', '^__$', '^$'] @@ -591,7 +600,8 @@ function! s:utils.findTest() abort let testline = search(testpattern, 'bcnWz') endif if testline > 0 - let testname = matchlist(getline(testline), '[-|*!] \zs.*$')[0] + let line = getline(testline) + let testname = matchlist(line, '[-|*!] \%(|| .\{-} || \)\?\zs.*$')[0] let projectline = search('^;', 'bcnWz') let projectkey = matchlist(getline(projectline), '^\S\+')[0] let fileline = search('^ \f', 'bcnWz') diff --git a/syntax/omnisharptest.vim b/syntax/omnisharptest.vim index db0c158af..357fd7677 100644 --- a/syntax/omnisharptest.vim +++ b/syntax/omnisharptest.vim @@ -32,8 +32,8 @@ syn match ostFileDivider "^__$" conceal syn match ostStateNotRun "^|.*" contains=ostStatePrefix,ostTestNamespace syn match ostStateRunning "^-.*" contains=ostStatePrefix,ostTestNamespace,ostRunningSuffix -syn match ostStatePassed "^\*.*" contains=ostStatePrefix,ostTestNamespace -syn match ostStateFailed "^!.*" contains=ostStatePrefix,ostTestNamespace +syn match ostStatePassed "^\*.*" contains=ostStatePrefix,ostTestNamespace,ostStatePassedGlyph,ostCompletePrefixDivider +syn match ostStateFailed "^!.*" contains=ostStatePrefix,ostTestNamespace,ostStateFailedGlyph,ostCompletePrefixDivider syn match ostStatePrefix "^[|\*!-]" conceal contained syn match ostTestNamespace "\%(\w\+\.\)*\ze\w\+" conceal contained @@ -41,6 +41,10 @@ syn match ostRunningSuffix " -- .*" contained contains=ostRunningSpinner,ostRun syn match ostRunningSuffixDivider " \zs--" conceal contained syn match ostRunningSpinner " -- \zs.*" contained +syn match ostStatePassedGlyph "\%(|| \)\@<=.\{-}\ze || " contained +syn match ostStateFailedGlyph "\%(|| \)\@<=.\{-}\ze || " contained +syn match ostCompletePrefixDivider "|| " conceal contained + syn region ostFailure start="^>" end="^[^>]"me=s-1 contains=ostFailurePrefix,ostStackLoc,ostStackNoLoc fold syn match ostFailurePrefix "^>" conceal contained syn region ostStackLoc start=" __ "hs=e+1 end=" __ "he=e-1 contains=ostStackFile,ostStackDelimiter,ostStackNamespace contained keepend @@ -66,7 +70,9 @@ hi def link ostStateNotRun Comment hi def link ostStateRunning Identifier hi def link ostRunningSpinner Normal hi def link ostStatePassed Title +hi def link ostStatePassedGlyph Title hi def link ostStateFailed WarningMsg +hi def link ostStateFailedGlyph WarningMsg hi def link ostStackLoc Identifier hi def link ostOutput Comment @@ -78,6 +84,7 @@ hi def link ostFileDivider NonText hi def link ostStatePrefix NonText hi def link ostFailurePrefix NonText hi def link ostRunningSuffixDivider NonText +hi def link ostCompletePrefixDivider NonText hi def link ostStackDelimiter NonText hi def link ostStackFileDelimiter NonText hi def link ostStackNoLocDelimiter NonText From d95a0413409cccce63bcbb3442f4736be42ec432 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 19 Jul 2022 18:03:05 +1200 Subject: [PATCH 35/54] Set g:OmniSharp_runtests_echo_output default to 0 --- doc/omnisharp-vim.txt | 8 ++++---- plugin/OmniSharp.vim | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/omnisharp-vim.txt b/doc/omnisharp-vim.txt index 79fe7dbbe..e1150466c 100644 --- a/doc/omnisharp-vim.txt +++ b/doc/omnisharp-vim.txt @@ -374,10 +374,10 @@ Default: atcursor > 3.5 TESTS *omnisharp-test-options* *g:OmniSharp_runtests_echo_output* -When running unit tests with |:OmniSharpRunTest| and |:OmniSharpRunTestsInFile|, -echo all test runner output to the |message-history|, so it can be viewed with -|:messages|. -Default: 1 +When running unit tests with |:OmniSharpRunTest| and |:OmniSharpRunTestsInFile| +or from the test runner, echo all test runner output to the |message-history|, +so it can be viewed with |:messages|. +Default: 0 *g:OmniSharp_runtests_parallel* When running multiple unit test files with |:OmniSharpRunTestsInFile|, run diff --git a/plugin/OmniSharp.vim b/plugin/OmniSharp.vim index f30ca0bbb..531b68c71 100644 --- a/plugin/OmniSharp.vim +++ b/plugin/OmniSharp.vim @@ -39,7 +39,7 @@ let g:OmniSharp_loglevel = get(g:, 'OmniSharp_loglevel', defaultlevel) let g:OmniSharp_diagnostic_listen = get(g:, 'OmniSharp_diagnostic_listen', 2) let g:OmniSharp_runtests_parallel = get(g:, 'OmniSharp_runtests_parallel', 1) -let g:OmniSharp_runtests_echo_output = get(g:, 'OmniSharp_runtests_echo_output', 1) +let g:OmniSharp_runtests_echo_output = get(g:, 'OmniSharp_runtests_echo_output', 0) " Set to 1 when ultisnips is installed let g:OmniSharp_want_snippet = get(g:, 'OmniSharp_want_snippet', 0) From 00f7b41b855e51ee2d879e14caab37b944027531 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 19 Jul 2022 18:27:34 +1200 Subject: [PATCH 36/54] Namespace test functions in function dictionaries --- autoload/OmniSharp/util.vim | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/autoload/OmniSharp/util.vim b/autoload/OmniSharp/util.vim index ea3cc71c8..ada389f3c 100644 --- a/autoload/OmniSharp/util.vim +++ b/autoload/OmniSharp/util.vim @@ -68,9 +68,12 @@ function! OmniSharp#util#AwaitSequence(Funcs, OnAllComplete, ...) abort \} endif - let Func = remove(a:Funcs, 0) + " If the Func has been declared as a dictionary function, then it must be + " called as a dictionary function: + " function s:run.test() + let dict = { 'f': remove(a:Funcs, 0) } let state.OnComplete = function('OmniSharp#util#AwaitSequence', [a:Funcs, a:OnAllComplete]) - call Func(function('s:AwaitFuncComplete', [state])) + call dict.f(function('s:AwaitFuncComplete', [state])) endfunction function! s:AwaitFuncComplete(state, ...) abort From 7a6b45a73ee9368402f3111e261d8865d95a802c Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 19 Jul 2022 18:45:13 +1200 Subject: [PATCH 37/54] Document g:OmniSharp_runtests_quickfix --- autoload/OmniSharp/actions/test.vim | 4 ++-- doc/omnisharp-vim.txt | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 05167f33b..b6fe5fa90 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -159,7 +159,7 @@ function! s:run.single.complete(summary) abort else echomsg location.name . ': failed' let title = 'Test failure: ' . location.name - if get(g:, 'OmniSharp_test_quickfix', 1) == 0 | return | endif + if get(g:, 'OmniSharp_runtests_quickfix', 0) == 0 | return | endif let what = {} if len(a:summary.locations) > 1 let what.quickfixtextfunc = {info-> @@ -274,7 +274,7 @@ function! s:run.multiple.complete(summary) abort endif call s:utils.log.warn(title) endif - if get(g:, 'OmniSharp_test_quickfix', 1) == 0 | return | endif + if get(g:, 'OmniSharp_runtests_quickfix', 0) == 0 | return | endif call OmniSharp#locations#SetQuickfix(locations, title) endfunction diff --git a/doc/omnisharp-vim.txt b/doc/omnisharp-vim.txt index e1150466c..85b0083fc 100644 --- a/doc/omnisharp-vim.txt +++ b/doc/omnisharp-vim.txt @@ -389,6 +389,17 @@ test files in sequence instead, resulting in readable output in the |message-history|. Default: 1 + *g:OmniSharp_runtests_quickfix* +When running unit tests with |:OmniSharpRunTest| and |:OmniSharpRunTestsInFile| +or from the |omnisharp-testrunner|, populate the quickfix list with test +results. +When a single test is run and an exception occurs, the quickfix will be +populated with the exception stack trace. +When multiple tests are run then each test will get a quickfix location: +failed test locations point to the failed assertion; successful test locations +point to the test method declaration. +Default: 0 + ------------------------------------------------------------------------------- 3.6 INTEGRATIONS *omnisharp-integration-options* From 8cd8375c7d716a90bb577ce78be4b1ba551235a7 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 19 Jul 2022 18:46:21 +1200 Subject: [PATCH 38/54] Add option not to automatically open testrunner --- autoload/OmniSharp/testrunner.vim | 3 +++ doc/omnisharp-vim.txt | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 8d1baad8a..87b59ea13 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -391,6 +391,7 @@ function! OmniSharp#testrunner#SetTests(bufferTests) abort let test.lnum = buffertest.nameRange.Start.Line endfor endfor + if !get(g:, 'OmniSharp_testrunner', 1) | return | endif let winid = win_getid() if hasNew call s:Open() @@ -439,6 +440,7 @@ function! s:UpdateState(bufnr, state, ...) abort let test.stacktrace = stacktrace let test.output = get(opts, 'output', []) + if !has_key(s:runner, 'bufnr') | continue | endif call setbufvar(s:runner.bufnr, '&modifiable', 1) let lines = getbufline(s:runner.bufnr, 1, '$') let pattern = '^ ' . substitute(filename, '/', '\\/', 'g') @@ -460,6 +462,7 @@ function! s:UpdateState(bufnr, state, ...) abort call setbufvar(s:runner.bufnr, '&modified', 0) endif endfor + if !get(g:, 'OmniSharp_testrunner', 1) | return | endif let winid = win_getid() if s:buffer.focus() syn sync fromstart diff --git a/doc/omnisharp-vim.txt b/doc/omnisharp-vim.txt index 85b0083fc..ca4d71b2a 100644 --- a/doc/omnisharp-vim.txt +++ b/doc/omnisharp-vim.txt @@ -400,6 +400,10 @@ failed test locations point to the failed assertion; successful test locations point to the test method declaration. Default: 0 + *g:OmniSharp_testrunner* +Open the |omnisharp-testrunner| automatically when running tests. +Default: 1 + ------------------------------------------------------------------------------- 3.6 INTEGRATIONS *omnisharp-integration-options* From e8221e3ab795c59679c1643d4185cc9768547c2b Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Wed, 20 Jul 2022 23:54:26 +1200 Subject: [PATCH 39/54] Repaint project output/errors on UpdateState --- autoload/OmniSharp/testrunner.vim | 122 ++++++++++++++++++------------ 1 file changed, 75 insertions(+), 47 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 87b59ea13..515e29d10 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -211,6 +211,10 @@ endfunction let s:buffer = {} +function! s:buffer.delimiter() abort + return get(g:, 'OmniSharp_testrunner_banner_delimeter', '─') +endfunction + function! s:buffer.focus() abort if !has_key(s:runner, 'bufnr') | return | endif if getbufvar(s:runner.bufnr, '&ft') !=# 'omnisharptest' | return | endif @@ -231,28 +235,7 @@ function! s:buffer.paint() abort let lines = [] endif for key in sort(keys(s:tests)) - let [assembly, sln] = split(key, ';') - if !s:tests[key].visible | continue | endif - call add(lines, key . (len(s:tests[key].errors) ? ' ERROR' : '')) - for errorline in s:tests[key].errors - call add(lines, '< ' . trim(errorline, ' ', 2)) - endfor - let loglevel = get(g:, 'OmniSharp_testrunner_loglevel', 'error') - if loglevel ==? 'all' || (loglevel ==? 'error' && len(s:tests[key].errors)) - " The diagnostic logs (build output) are only displayed when a single file - " is tested, otherwise multiple build outputs are intermingled - if s:current.singlebuffer != -1 - let [ssln, sass, _] = s:utils.getProject(s:current.singlebuffer) - if ssln ==# sln && sass ==# assembly - if len(s:tests[key].errors) > 0 && len(s:current.log) > 1 - call add(lines, '< ' . repeat(delimiter, 10)) - endif - for log in s:current.log - call add(lines, '< ' . trim(log, ' ', 2)) - endfor - endif - endif - endif + call extend(lines, self.paintproject(key)) for testfile in sort(keys(s:tests[key].files)) if !s:tests[key].files[testfile].visible | continue | endif let tests = s:tests[key].files[testfile].tests @@ -280,18 +263,61 @@ endfunction function! s:buffer.paintbanner() abort let lines = [] - let delimiter = get(g:, 'OmniSharp_testrunner_banner_delimeter', '─') - call add(lines, '`' . repeat(delimiter, 80)) + call add(lines, '`' . repeat(self.delimiter(), 80)) call add(lines, '` OmniSharp Test Runner') - call add(lines, '` ' . repeat(delimiter, 76)) + call add(lines, '` ' . repeat(self.delimiter(), 76)) call add(lines, '` Toggle this menu (:help omnisharp-test-runner for more)') call add(lines, '` Run test or tests in file under cursor') call add(lines, '` Debug test under cursor') call add(lines, '` Navigate to test or stack trace') - call add(lines, '`' . repeat(delimiter, 80)) + call add(lines, '`' . repeat(self.delimiter(), 80)) + return lines +endfunction + +function! s:buffer.paintproject(key) abort + let [assembly, sln] = split(a:key, ';') + if !s:tests[a:key].visible + return [] + endif + let lines = [] + call add(lines, a:key . (len(s:tests[a:key].errors) ? ' ERROR' : '')) + for errorline in s:tests[a:key].errors + call add(lines, '< ' . trim(errorline, ' ', 2)) + endfor + let loglevel = get(g:, 'OmniSharp_testrunner_loglevel', 'error') + if loglevel ==? 'all' || (loglevel ==? 'error' && len(s:tests[a:key].errors)) + " The diagnostic logs (build output) are only displayed when a single file + " is tested, otherwise multiple build outputs are intermingled + if s:current.singlebuffer != -1 + let [ssln, sass, _] = s:utils.getProject(s:current.singlebuffer) + if ssln ==# sln && sass ==# assembly + if len(s:tests[a:key].errors) > 0 && len(s:current.log) > 1 + call add(lines, '< ' . repeat(self.delimiter(), 10)) + endif + for log in s:current.log + call add(lines, '< ' . trim(log, ' ', 2)) + endfor + endif + endif + endif return lines endfunction +function! s:buffer.repaintproject(key) abort + if !has_key(s:runner, 'bufnr') | return | endif + let projectlines = s:buffer.paintproject(a:key) + call setbufvar(s:runner.bufnr, '&modifiable', 1) + let lines = getbufline(s:runner.bufnr, 1, '$') + let pattern = '^' . substitute(a:key, '/', '\\/', 'g') + let projectline = match(lines, pattern) + 1 + let pattern = '^ ' + let endline = match(lines, '^ ', projectline) + call deletebufline(s:runner.bufnr, projectline, endline) + call appendbufline(s:runner.bufnr, projectline - 1, projectlines) + call setbufvar(s:runner.bufnr, '&modifiable', 0) + call setbufvar(s:runner.bufnr, '&modified', 0) +endfunction + function! s:buffer.painttest(test, lnum) abort if a:test.state ==# 'hidden' return [] @@ -332,6 +358,27 @@ function! s:buffer.painttest(test, lnum) abort return lines endfunction +function! s:buffer.repainttest(filename, testname, test) abort + if !has_key(s:runner, 'bufnr') | return | endif + call setbufvar(s:runner.bufnr, '&modifiable', 1) + let lines = getbufline(s:runner.bufnr, 1, '$') + let pattern = '^ ' . substitute(a:filename, '/', '\\/', 'g') + let fileline = match(lines, pattern) + 1 + let pattern = '^[-|*!] \%(|| .\{-} || \)\?' . a:testname + let testline = match(lines, pattern, fileline) + 1 + let endline = min( + \ filter( + \ map( + \ ['^[-|*!] \S', '^__$', '^$'], + \ {_,pattern -> match(lines, pattern, testline)}), + \ {_,matchline -> matchline >= testline})) + let testlines = s:buffer.painttest(a:test, testline) + call deletebufline(s:runner.bufnr, testline, endline) + call appendbufline(s:runner.bufnr, testline - 1, testlines) + call setbufvar(s:runner.bufnr, '&modifiable', 0) + call setbufvar(s:runner.bufnr, '&modified', 0) +endfunction + function! OmniSharp#testrunner#SetBreakpoints() abort if !OmniSharp#util#HasVimspector() @@ -439,27 +486,8 @@ function! s:UpdateState(bufnr, state, ...) abort let test.message = get(opts, 'message', []) let test.stacktrace = stacktrace let test.output = get(opts, 'output', []) - - if !has_key(s:runner, 'bufnr') | continue | endif - call setbufvar(s:runner.bufnr, '&modifiable', 1) - let lines = getbufline(s:runner.bufnr, 1, '$') - let pattern = '^ ' . substitute(filename, '/', '\\/', 'g') - let fileline = match(lines, pattern) + 1 - let pattern = '^[-|*!] \%(|| .\{-} || \)\?' . testname - let testline = match(lines, pattern, fileline) + 1 - - let patterns = ['^[-|*!] \S', '^__$', '^$'] - let endline = min( - \ filter( - \ map( - \ patterns, - \ {_,pattern -> match(lines, pattern, testline)}), - \ {_,matchline -> matchline >= testline})) - let testlines = s:buffer.painttest(test, testline) - call deletebufline(s:runner.bufnr, testline, endline) - call appendbufline(s:runner.bufnr, testline - 1, testlines) - call setbufvar(s:runner.bufnr, '&modifiable', 0) - call setbufvar(s:runner.bufnr, '&modified', 0) + call s:buffer.repaintproject(key) + call s:buffer.repainttest(filename, testname, test) endif endfor if !get(g:, 'OmniSharp_testrunner', 1) | return | endif From 44c338ed24796bf664b9a01dd580e7da533b970c Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Thu, 28 Jul 2022 12:11:47 +1200 Subject: [PATCH 40/54] Center banner title --- autoload/OmniSharp/testrunner.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 515e29d10..1e459e5c7 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -264,7 +264,7 @@ endfunction function! s:buffer.paintbanner() abort let lines = [] call add(lines, '`' . repeat(self.delimiter(), 80)) - call add(lines, '` OmniSharp Test Runner') + call add(lines, '` OmniSharp Test Runner') call add(lines, '` ' . repeat(self.delimiter(), 76)) call add(lines, '` Toggle this menu (:help omnisharp-test-runner for more)') call add(lines, '` Run test or tests in file under cursor') From 7df4b20056e788ae1f3fb175bdf613fe3765f4d7 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Fri, 29 Jul 2022 11:58:36 +1200 Subject: [PATCH 41/54] Repaint correctly after hiding (removing) project --- autoload/OmniSharp/testrunner.vim | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 1e459e5c7..064731126 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -235,6 +235,7 @@ function! s:buffer.paint() abort let lines = [] endif for key in sort(keys(s:tests)) + if !s:tests[key].visible | continue | endif call extend(lines, self.paintproject(key)) for testfile in sort(keys(s:tests[key].files)) if !s:tests[key].files[testfile].visible | continue | endif @@ -276,9 +277,6 @@ endfunction function! s:buffer.paintproject(key) abort let [assembly, sln] = split(a:key, ';') - if !s:tests[a:key].visible - return [] - endif let lines = [] call add(lines, a:key . (len(s:tests[a:key].errors) ? ' ERROR' : '')) for errorline in s:tests[a:key].errors From 27dd9283b5067f3108f328b4d0f5c01b887573c6 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sat, 13 Aug 2022 22:36:15 +1200 Subject: [PATCH 42/54] Allow selection of running test --- autoload/OmniSharp/testrunner.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 064731126..bae5f22b6 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -630,7 +630,7 @@ function! s:utils.findTest() abort endif if testline > 0 let line = getline(testline) - let testname = matchlist(line, '[-|*!] \%(|| .\{-} || \)\?\zs.*$')[0] + let testname = matchlist(line, '[-|*!] \%(|| .\{-} || \)\?\zs\S*')[0] let projectline = search('^;', 'bcnWz') let projectkey = matchlist(getline(projectline), '^\S\+')[0] let fileline = search('^ \f', 'bcnWz') From a01214596759d847c90e349b0c85575ab8cd3469 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 29 Aug 2022 05:41:30 +0200 Subject: [PATCH 43/54] Document the omnisharp-testrunner feature --- doc/omnisharp-vim.txt | 140 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 128 insertions(+), 12 deletions(-) diff --git a/doc/omnisharp-vim.txt b/doc/omnisharp-vim.txt index ca4d71b2a..5b26e3aa9 100644 --- a/doc/omnisharp-vim.txt +++ b/doc/omnisharp-vim.txt @@ -25,7 +25,9 @@ CONTENTS *omnisharp-contents 3.7 Miscellaneous ....................... |omnisharp-miscellaneous-options| 4. Commands ............................... |omnisharp-commands| 5. Autocmds ............................... |omnisharp-autocmds| - 6. Integrations ........................... |omnisharp-integrations| + 6. Test runner ............................ |omnisharp-testrunner| + 6.1 Mappings ............................ |omnisharp-testrunner-mappings| + 7. Integrations ........................... |omnisharp-integrations| =============================================================================== 1. DEPENDENCIES *omnisharp-dependencies* @@ -43,7 +45,7 @@ Optional:~ 2. USAGE *omnisharp-usage* Opening a `*.cs` file will automatically start an instance of omnisharp-server -(if using vim 8.0+, neovim or vim-dispatch). Symantic completions are triggered +(if using vim 8.0+, neovim or vim-dispatch). Semantic completions are triggered using omni-completion (CTRL-X_CTRL-O in insert mode), or using an autocompletion plugin such as asyncomplete or Deoplete. @@ -375,8 +377,8 @@ Default: atcursor > *g:OmniSharp_runtests_echo_output* When running unit tests with |:OmniSharpRunTest| and |:OmniSharpRunTestsInFile| -or from the test runner, echo all test runner output to the |message-history|, -so it can be viewed with |:messages|. +or from the |omnisharp-testrunner|, echo all test runner output to the +|message-history|, so it can be viewed with |:messages|. Default: 0 *g:OmniSharp_runtests_parallel* @@ -404,6 +406,47 @@ Default: 0 Open the |omnisharp-testrunner| automatically when running tests. Default: 1 + *g:OmniSharp_testrunner_banner* +Display the |omnisharp-testrunner| help/introduction banner when opening the +testrunner. When this option is set to 0, the banner may still be toggled with +the (default) toggle mapping. +Default: 1 + + *g:OmniSharp_testrunner_glyph* +Display a passed/failed "glyph" string beside completed test results in the +|omnisharp-testrunner|. +Default: 1 + + *g:OmniSharp_testrunner_glyph_failed* +The "glyph" string to display beside failed completed tests in the testrunner. +Default: ✘ + + *g:OmniSharp_testrunner_glyph_passed* +The "glyph" string to display beside passed completed tests in the testrunner. +Default: ✔ + + *g:OmniSharp_testrunner_loglevel* +The type of build and test logs to output in the |omnisharp-testrunner|. + + all All build output and test runner output is displayed. + + error When a build error occurs, the error message and stack + trace are output. + + none Only build error messages will be output. + +Default: error > + let g:OmniSharp_testrunner_loglevel = 'all' +< + *g:OmniSharp_testrunner_spinner* +Display a "running" spinner animation when running tests in the testrunner. +Default: 1 + + *g:OmniSharp_testrunner_spinnersteps* +A list of "step" strings to be displayed as the |omnisharp-testrunner| +"spinner" animation. +Default: ['∙∙∙', '●∙∙', '∙●∙', '∙∙●', '∙∙∙'] + ------------------------------------------------------------------------------- 3.6 INTEGRATIONS *omnisharp-integration-options* @@ -623,18 +666,21 @@ convenient user re-mapping. These can be used like so: > *(omnisharp_run_test_no_build)* :OmniSharpRunTest[!] Run the current unit test. The cursor can be anywhere in the test method. + The |omnisharp-testrunner| window will be opened to display the test + status and results. If the test fails, the failure message and location will be displayed in - the quickfix list, along with the error stack trace, if one exists. + the testrunner, or optionally (see |g:OmniSharp_runtests_quickfix|) in the + quickfix list, along with the error stack trace if one exists. When called with ! or the |(omnisharp_run_test_no_build)| mapping, the project is not built before running the test. - *:OmniSharpDebugTest* - *(omnisharp_debug_test)* - *(omnisharp_debug_test_no_build)* + *:OmniSharpDebugTest* + *(omnisharp_debug_test)* + *(omnisharp_debug_test_no_build)* :OmniSharpDebugTest[!] Debug the current unit test with Vimspector. The cursor can be anywhere in - the test method. The quickfix list is not populated with the test result. + the test method. When called with ! or the |(omnisharp_debug_test_no_build)| mapping, the project is not built before starting the debugger. @@ -643,8 +689,9 @@ convenient user re-mapping. These can be used like so: > *(omnisharp_run_tests_in_file)* *(omnisharp_run_tests_in_file_no_build)* :OmniSharpRunTestsInFile[!] - Run all unit tests in the current file. The quickfix list will be - populated with the test results of all tests. + Run all unit tests in the current file. When |g:OmniSharp_runtests_quickfix| + is enabled, the quickfix list will be populated with the results of all + tests. Optionally accepts one or more filenames of files to run tests for. > " Run all unit tests in the current file @@ -668,6 +715,9 @@ convenient user re-mapping. These can be used like so: > :OmniSharpOpenLog vsplit :OmniSharpOpenLog tabedit < + *:OmniSharpOpenTestRunner* + Open the |omnisharp-testrunner| window + *:OmniSharpGetCodeActions* *(omnisharp_code_actions)* :OmniSharpGetCodeActions @@ -809,7 +859,73 @@ OmniSharpStopped Fired when the server process is stopped. =============================================================================== -6. INTEGRATIONS *omnisharp-integrations* +6. TEST RUNNER *omnisharp-testrunner* + +The test runner provides an overview of unit tests which have been run, along +with the test status (running/passed/failed/not run) and any outputs that may +be produced (exceptions and Console output). + +Open the test runner window by either running a test (with e.g. +|:OmniSharpRunTest|) or with the |:OmniSharpOpenTestRunner| command. + +Any time a new test is run, it (along with all other tests in the same file) +is added to the runner. Tests remain visible in the test runner window until +they are manually removed. + +Tests are grouped under the files they are contained in, which are in turn +grouped by project. Folds are used to collapse and expand sections, so a +project/file/output section can be hidden or displayed using standard vim +|folding-commands|. + +To disable the testrunner and instead use the quickfix list to navigate and +display test results, use the following settings: > + let g:OmniSharp_testrunner = 0 + let g:OmniSharp_runtests_quickfix = 1 +< +------------------------------------------------------------------------------- +6.1 MAPPINGS *omnisharp-testrunner-mappings* + +The following || mappings and associated default recursive mappings are +provided in the test runner window. + + *(omnisharp_testrunner_navigate)* + Default: +Navigate to the test under the cursor. When the used on a error stack trace +location in a failed test result, navigate to the stack location. + + *(omnisharp_testrunner_togglebanner)* + Default: +Toggle display of the help/intro banner. Note that the banner can be hidden +initially using the |g:OmniSharp_testrunner_banner| setting. + + *(omnisharp_testrunner_run)* + Default: +Run the test(s) under the cursor. When the cursor is on a line representing a +single test (the test itself or part of its output) then just that single test +is run. When the cursor is on a file name, then all tests in the file are run. +When the cursor is on a project name, all previously run tests in the project +will be run. +Note: Running tests in a project only runs previous run tests. This command +does not automatically discover tests in the project. + + *(omnisharp_testrunner_debug)* + Default: +Debug the test under the cursor in Vimspector. + + *(omnisharp_testrunner_set_breakpoints)* + Default: +When used on a failed test resulting in an error stack, the stack trace +locations are set in Vimspector as breakpoints. + + *(omnisharp_testrunner_remove)* + Default: dd +Remove the item under the cursor from the test runner. Can be used at the +test, file or project level. Note however that individual tests cannot be +completely removed: subsequent runs of tests in the containing file/project +will still run the removed test. + +=============================================================================== +7. INTEGRATIONS *omnisharp-integrations* 6.1 fzf, vim-clap, CtrlP, unite.vim~ From fc53ee2e2df897f697125e8290926eb40c63cdfb Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Wed, 21 Sep 2022 21:13:42 +1200 Subject: [PATCH 44/54] Strip method signature from NUnit test name --- autoload/OmniSharp/actions/test.vim | 7 +++++-- autoload/OmniSharp/testrunner.vim | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index b6fe5fa90..112fc115c 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -298,12 +298,15 @@ function! s:run.process(Callback, bufnr, tests, response) abort \ 'locations': [] \} for result in a:response.Body.Results + " Strip method signature from test method fullname + let fullname = substitute(result.MethodName, '(.*)$', '', '') " Strip namespace and classname from test method name + let name = substitute(result.MethodName, '^.*\.', '', '') let location = { \ 'bufnr': a:bufnr, - \ 'fullname': result.MethodName, + \ 'fullname': fullname, \ 'filename': bufname(a:bufnr), - \ 'name': substitute(result.MethodName, '^.*\.', '', '') + \ 'name': name \} let locations = [location] " Write any standard output to message-history diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index bae5f22b6..57ce127f7 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -504,7 +504,7 @@ function! OmniSharp#testrunner#StateComplete(location) abort else let state = 'Passed' endif - call s:UpdateState(a:.location.bufnr, state, { + call s:UpdateState(a:location.bufnr, state, { \ 'testnames': [a:location.fullname], \ 'message': get(a:location, 'message', []), \ 'stacktrace': get(a:location, 'stacktrace', []), From 76d05a998e5ca44cc70e0a8e851be6337a54fe3d Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 4 Oct 2022 20:49:45 +1300 Subject: [PATCH 45/54] Add test Discover function --- autoload/OmniSharp/testrunner.vim | 43 +++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 57ce127f7..be5661c19 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -12,6 +12,49 @@ function! OmniSharp#testrunner#GetTests() abort endfunction +" Discover all tests in the project. +" Optional argument: A dict containing the following optional items: +" Callback: funcref to be called after the response is returned +" Display: flag indicating that the tests should immediately be displayed in +" the testrunner +function! OmniSharp#testrunner#Discover(bufnr, ...) abort + if a:0 && type(a:1) == type(function('tr')) + let opts = { 'Callback': a:1 } + else + let opts = a:0 ? a:1 : {} + endif + let opts.Display = get(opts, 'Display', 1) + if !has_key(OmniSharp#GetHost(a:bufnr), 'project') + " Fetch the project structure, then call this function again + call OmniSharp#actions#project#Get(a:bufnr, + \ function('OmniSharp#testrunner#Discover', [a:bufnr, opts])) + return + endif + let project = OmniSharp#GetHost(a:bufnr).project + let opts = { + \ 'ResponseHandler': function('s:DiscoverRH', [a:bufnr, opts]), + \ 'BufNum': a:bufnr, + \ 'Parameters': { + \ 'TargetFrameworkVersion': project['MsBuildProject']['TargetFramework'] + \ }, + \ 'SendBuffer': 0 + \} + call OmniSharp#stdio#Request('/v2/discovertests', opts) +endfunction + +function! s:DiscoverRH(bufnr, opts, response) abort + if !a:response.Success | return | endif + let project = OmniSharp#GetHost(a:bufnr).project + let project.tests = a:response.Body.Tests + if a:opts.Display + " TODO: add tests to testrunner display + endif + if has_key(a:opts, 'Callback') + call a:opts.Callback(a:response) + endif +endfunction + + function! OmniSharp#testrunner#Debug() abort let filename = '' let line = getline('.') From 94460213a579fc82fdcf5e507d611cc54dde4e76 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 4 Oct 2022 20:52:46 +1300 Subject: [PATCH 46/54] "Discover" tests before calling test runners --- autoload/OmniSharp/actions/test.vim | 55 +++++++++++++++++++---------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 112fc115c..39f886380 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -298,15 +298,11 @@ function! s:run.process(Callback, bufnr, tests, response) abort \ 'locations': [] \} for result in a:response.Body.Results - " Strip method signature from test method fullname - let fullname = substitute(result.MethodName, '(.*)$', '', '') - " Strip namespace and classname from test method name - let name = substitute(result.MethodName, '^.*\.', '', '') let location = { \ 'bufnr': a:bufnr, - \ 'fullname': fullname, + \ 'fullname': result.MethodName, \ 'filename': bufname(a:bufnr), - \ 'name': name + \ 'name': substitute(result.MethodName, '^.*\.', '', '') \} let locations = [location] " Write any standard output to message-history @@ -393,22 +389,44 @@ function! s:utils.capabilities() abort endfunction " Find all of the test methods in a CodeStructure response -function! s:utils.extractTests(codeElements) abort +function! s:utils.extractTests(bufnr, codeElements) abort if type(a:codeElements) != type([]) | return [] | endif + let filename = fnamemodify(bufname(a:bufnr), ':p') + let testlines = map( + \ filter( + \ copy(OmniSharp#GetHost(a:bufnr).project.tests), + \ {_,dt -> dt.CodeFilePath ==# filename}), + \ {_,dt -> dt.LineNumber}) let tests = [] for element in a:codeElements if has_key(element, 'Properties') \ && type(element.Properties) == type({}) \ && has_key(element.Properties, 'testMethodName') \ && has_key(element.Properties, 'testFramework') - call add(tests, { - \ 'name': element.Properties.testMethodName, - \ 'framework': element.Properties.testFramework, - \ 'range': element.Ranges.full, - \ 'nameRange': element.Ranges.name, - \}) + " Compare with project discovered tests. Note that test discovery may + " include a test multiple times, if the test can be run with different + " arguments (e.g. NUnit TestCaseSource) + + " Discovered test line numbers begin at the first line of code, not the + " line containing the test name, so when the method opening brace is not + " on the same line as the test method name, the line numbers will not + " match. We therefore search ahead for the closest line number, and use + " that. + let testStart = element.Ranges.name.Start.Line + let testStart = min(filter(copy(testlines), {_,l -> l >= testStart})) + for dt in OmniSharp#GetHost(a:bufnr).project.tests + if dt.CodeFilePath ==# filename && dt.LineNumber == testStart + " \ 'name': element.Properties.testMethodName, + call add(tests, { + \ 'name': dt.FullyQualifiedName, + \ 'framework': element.Properties.testFramework, + \ 'range': element.Ranges.full, + \ 'nameRange': element.Ranges.name, + \}) + endif + endfor endif - call extend(tests, self.extractTests(get(element, 'Children', []))) + call extend(tests, self.extractTests(a:bufnr, get(element, 'Children', []))) endfor return tests endfunction @@ -429,12 +447,13 @@ function! s:utils.findTest(tests, testName) abort return 0 endfunction -" For the given buffers, fetch the project structures, then fetch the buffer -" code structures. All operations are performed asynchronously, and the +" For the given buffers, discover the project's tests (which includes fetching +" the project structure if it hasn't already been fetched. Finally, fetch the +" buffer code structures. All operations are performed asynchronously, and the " a:Callback is called when all buffer code structures have been fetched. function! s:utils.initialize(buffers, Callback) abort call OmniSharp#testrunner#Init(a:buffers) - call s:utils.init.await(a:buffers, 'OmniSharp#actions#project#Get', + call s:utils.init.await(a:buffers, 'OmniSharp#testrunner#Discover', \ funcref('s:utils.init.await', [a:buffers, 'OmniSharp#actions#codestructure#Get', \ funcref('s:utils.init.extract', [a:Callback])])) endfunction @@ -447,7 +466,7 @@ endfunction function! s:utils.init.extract(Callback, codeStructures) abort let bufferTests = map(a:codeStructures, {i, cs -> { \ 'bufnr': cs[0], - \ 'tests': s:utils.extractTests(cs[1]) + \ 'tests': s:utils.extractTests(cs[0], cs[1]) \}}) call OmniSharp#testrunner#SetTests(bufferTests) let dict = { 'f': a:Callback } From 969c34accef1c3de4d029b740bc1f50dc1c0ef5c Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 4 Oct 2022 20:53:50 +1300 Subject: [PATCH 47/54] Update state and repaint multiple NUnit sources --- autoload/OmniSharp/actions/test.vim | 43 +++++++++++++++++++---------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 39f886380..b1f3d5aaf 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -26,9 +26,10 @@ function! s:debug.prepare(testName, bufferTests) abort let bufnr = a:bufferTests[0].bufnr let tests = a:bufferTests[0].tests let currentTest = s:utils.findTest(tests, a:testName) - if type(currentTest) != type({}) + if type(currentTest) != type([]) || len(currentTest) == 0 return s:utils.log.warn('No test found') endif + let currentTest = currentTest[0] let project = OmniSharp#GetHost(bufnr).project let targetFramework = project.MsBuildProject.TargetFramework let opts = { @@ -120,18 +121,21 @@ function! s:run.single.test(testName, bufferTests) abort let bufnr = a:bufferTests[0].bufnr let tests = a:bufferTests[0].tests let currentTest = s:utils.findTest(tests, a:testName) - if type(currentTest) != type({}) + if type(currentTest) != type([]) || len(currentTest) == 0 return s:utils.log.warn('No test found') endif let s:run.running = 1 - call OmniSharp#testrunner#StateRunning(bufnr, currentTest.name) + for ct in currentTest + call OmniSharp#testrunner#StateRunning(bufnr, ct.name) + endfor + let currentTest = currentTest[0] let project = OmniSharp#GetHost(bufnr).project let targetFramework = project.MsBuildProject.TargetFramework let opts = { \ 'ResponseHandler': funcref('s:run.process', [s:run.single.complete, bufnr, tests]), \ 'BufNum': bufnr, \ 'Parameters': { - \ 'MethodName': currentTest.name, + \ 'MethodName': substitute(currentTest.name, '(.*)$', '', ''), \ 'NoBuild': get(s:, 'nobuild', 0), \ 'TestFrameworkName': currentTest.framework, \ 'TargetFrameworkVersion': targetFramework @@ -143,6 +147,13 @@ function! s:run.single.test(testName, bufferTests) abort endfunction function! s:run.single.complete(summary) abort + if len(a:summary.locations) > 1 + " A single test was run, but multiple test results were returned. This can + " happen when using e.g. NUnit TestCaseSources which re-run the test using + " different arguments. + call s:run.multiple.complete([a:summary]) + return + endif if a:summary.pass && len(a:summary.locations) == 0 echomsg 'No tests were run' " Do we ever reach here? @@ -357,9 +368,9 @@ function! s:run.process(Callback, bufnr, tests, response) abort if !has_key(location, 'lnum') " Success, or unexpected test failure. let test = s:utils.findTest(a:tests, result.MethodName) - if type(test) == type({}) - let location.lnum = test.nameRange.Start.Line - let location.col = test.nameRange.Start.Column + if type(test) == type([]) && len(test) > 0 + let location.lnum = test[0].nameRange.Start.Line + let location.col = test[0].nameRange.Start.Column let location.vcol = 0 endif endif @@ -433,17 +444,21 @@ endfunction " Find the test in a list of tests that matches the current cursor position function! s:utils.findTest(tests, testName) abort - for test in a:tests - if a:testName !=# '' + if a:testName !=# '' + for test in a:tests if test.name ==# a:testName - return test + return [test] endif - else + endfor + else + let found = [] + for test in a:tests if line('.') >= test.range.Start.Line && line('.') <= test.range.End.Line - return test + call add(found, test) endif - endif - endfor + endfor + return found + endif return 0 endfunction From 034dea5e3685ba0d8650f5f12a92cb0291a98bd3 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Sat, 29 Oct 2022 22:48:53 +1300 Subject: [PATCH 48/54] Correctly run multiple NUnit tests (RunInFile) --- autoload/OmniSharp/actions/test.vim | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index b1f3d5aaf..061c3a8b4 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -131,18 +131,19 @@ function! s:run.single.test(testName, bufferTests) abort let currentTest = currentTest[0] let project = OmniSharp#GetHost(bufnr).project let targetFramework = project.MsBuildProject.TargetFramework + let currentTestName = substitute(currentTest.name, '(.*)$', '', '') let opts = { \ 'ResponseHandler': funcref('s:run.process', [s:run.single.complete, bufnr, tests]), \ 'BufNum': bufnr, \ 'Parameters': { - \ 'MethodName': substitute(currentTest.name, '(.*)$', '', ''), + \ 'MethodName': currentTestName, \ 'NoBuild': get(s:, 'nobuild', 0), \ 'TestFrameworkName': currentTest.framework, \ 'TargetFrameworkVersion': targetFramework \ }, \ 'SendBuffer': 0 \} - echomsg 'Running test ' . currentTest.name + echomsg 'Running test ' . currentTestName call OmniSharp#stdio#Request('/v2/runtest', opts) endfunction @@ -243,7 +244,7 @@ function! s:run.multiple.inBuffer(bufnr, tests, Callback) abort \ 'ResponseHandler': funcref('s:run.process', [a:Callback, a:bufnr, a:tests]), \ 'BufNum': a:bufnr, \ 'Parameters': { - \ 'MethodNames': map(copy(a:tests), {i,t -> t.name}), + \ 'MethodNames': map(copy(a:tests), {i,t -> substitute(t.name, '(.*)$', '', '')}), \ 'NoBuild': get(s:, 'nobuild', 0), \ 'TestFrameworkName': a:tests[0].framework, \ 'TargetFrameworkVersion': targetFramework @@ -427,7 +428,6 @@ function! s:utils.extractTests(bufnr, codeElements) abort let testStart = min(filter(copy(testlines), {_,l -> l >= testStart})) for dt in OmniSharp#GetHost(a:bufnr).project.tests if dt.CodeFilePath ==# filename && dt.LineNumber == testStart - " \ 'name': element.Properties.testMethodName, call add(tests, { \ 'name': dt.FullyQualifiedName, \ 'framework': element.Properties.testFramework, From 4d2b31507ac828c2084ba8ea1a31bffd4455fe37 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 28 Feb 2023 10:57:31 +1300 Subject: [PATCH 49/54] Exclude quickfix stack locations from runner --- autoload/OmniSharp/actions/test.vim | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 061c3a8b4..2eb8fe7b8 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -148,19 +148,20 @@ function! s:run.single.test(testName, bufferTests) abort endfunction function! s:run.single.complete(summary) abort - if len(a:summary.locations) > 1 + let locations = filter(copy(a:summary.locations), 'has_key(v:val, "bufnr")') + if len(locations) > 1 " A single test was run, but multiple test results were returned. This can " happen when using e.g. NUnit TestCaseSources which re-run the test using " different arguments. call s:run.multiple.complete([a:summary]) return endif - if a:summary.pass && len(a:summary.locations) == 0 + if a:summary.pass && len(locations) == 0 echomsg 'No tests were run' " Do we ever reach here? " call OmniSharp#testrunner#StateSkipped(bufnr) endif - let location = a:summary.locations[0] + let location = locations[0] call OmniSharp#testrunner#StateComplete(location) if a:summary.pass if get(location, 'type', '') ==# 'W' From ce1c0f484205482bb0dce66cb5c70af9f79f1007 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Tue, 28 Feb 2023 11:07:21 +1300 Subject: [PATCH 50/54] Allow opening test runner while test is running --- autoload/OmniSharp/actions/test.vim | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index 2eb8fe7b8..a46fbf8ea 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -13,6 +13,7 @@ let s:utils.log = {} function! OmniSharp#actions#test#Debug(nobuild, ...) abort if !s:utils.capabilities() | return | endif + if !s:utils.isrunning() | return | endif let s:nobuild = a:nobuild if !OmniSharp#util#HasVimspector() return s:utils.log.warn('Vimspector required to debug tests') @@ -111,6 +112,7 @@ endfunction function! OmniSharp#actions#test#Run(nobuild, ...) abort if !s:utils.capabilities() | return | endif + if !s:utils.isrunning() | return | endif let s:nobuild = a:nobuild let bufnr = a:0 ? (type(a:1) == type('') ? bufnr(a:1) : a:1) : bufnr('%') let RunTest = funcref('s:run.single.test', [a:0 > 1 ? a:2 : '']) @@ -186,6 +188,7 @@ endfunction function! OmniSharp#actions#test#RunInFile(nobuild, ...) abort let s:nobuild = a:nobuild if !s:utils.capabilities() | return | endif + if !s:utils.isrunning() | return | endif if a:0 && type(a:1) == type([]) let files = a:1 elseif a:0 && type(a:1) == type('') @@ -395,6 +398,10 @@ function! s:utils.capabilities() abort if g:OmniSharp_translate_cygwin_wsl return self.log.warn('Tests do not work in WSL unfortunately') endif + return 1 +endfunction + +function! s:utils.isrunning() abort if s:run.running return self.log.warn('A test is already running') endif From e97eab6dab6e89432acf72e964aa737cbd304576 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 24 Apr 2023 14:21:22 +1200 Subject: [PATCH 51/54] Add :OmniSharpTestRunnerReset command --- autoload/OmniSharp/actions/test.vim | 5 +++++ autoload/OmniSharp/testrunner.vim | 9 +++++++++ plugin/OmniSharp.vim | 3 ++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/autoload/OmniSharp/actions/test.vim b/autoload/OmniSharp/actions/test.vim index a46fbf8ea..6cce08588 100644 --- a/autoload/OmniSharp/actions/test.vim +++ b/autoload/OmniSharp/actions/test.vim @@ -110,6 +110,11 @@ function! s:debug.process.closed(...) abort endfunction +function! OmniSharp#actions#test#Reset() abort + let s:run.running = 0 +endfunction + + function! OmniSharp#actions#test#Run(nobuild, ...) abort if !s:utils.capabilities() | return | endif if !s:utils.isrunning() | return | endif diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index be5661c19..44514609d 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -421,6 +421,15 @@ function! s:buffer.repainttest(filename, testname, test) abort endfunction +function! OmniSharp#testrunner#Reset() abort + let s:current = {} + let s:runner = {} + let s:tests = {} + call OmniSharp#actions#test#Reset() + call s:Open() +endfunction + + function! OmniSharp#testrunner#SetBreakpoints() abort if !OmniSharp#util#HasVimspector() return s:utils.log.warn('Vimspector required to set breakpoints') diff --git a/plugin/OmniSharp.vim b/plugin/OmniSharp.vim index 531b68c71..a088ff573 100644 --- a/plugin/OmniSharp.vim +++ b/plugin/OmniSharp.vim @@ -54,8 +54,9 @@ let g:omnicomplete_fetch_full_documentation = get(g:, 'omnicomplete_fetch_full_d command! -bar -nargs=? OmniSharpInstall call OmniSharp#Install() command! -bar -nargs=? OmniSharpOpenLog call OmniSharp#log#Open() -command! -bar -nargs=? OmniSharpOpenTestRunner call OmniSharp#testrunner#Open() command! -bar -bang OmniSharpStatus call OmniSharp#Status(0) +command! -bar OmniSharpTestRunner call OmniSharp#testrunner#Open() +command! -bar OmniSharpTestRunnerReset call OmniSharp#testrunner#Reset() " Preserve backwards compatibility with older version g:OmniSharp_highlight_types let g:OmniSharp_highlighting = get(g:, 'OmniSharp_highlighting', get(g:, 'OmniSharp_highlight_types', 2)) From 57f602a123db293d052be261d6eb4bf94f053a17 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 24 Apr 2023 14:21:43 +1200 Subject: [PATCH 52/54] Refactor documentation --- doc/omnisharp-vim.txt | 267 ++++++++++++++++++++++-------------------- 1 file changed, 137 insertions(+), 130 deletions(-) diff --git a/doc/omnisharp-vim.txt b/doc/omnisharp-vim.txt index 5b26e3aa9..c736230bb 100644 --- a/doc/omnisharp-vim.txt +++ b/doc/omnisharp-vim.txt @@ -20,13 +20,14 @@ CONTENTS *omnisharp-contents 3.2 Diagnostics ......................... |omnisharp-diagnostic-options| 3.3 Highlights .......................... |omnisharp-highlight-options| 3.4 Popups .............................. |omnisharp-popup-options| - 3.5 Tests ............................... |omnisharp-test-options| 3.6 Integrations ........................ |omnisharp-integration-options| 3.7 Miscellaneous ....................... |omnisharp-miscellaneous-options| - 4. Commands ............................... |omnisharp-commands| + 4. General Commands ....................... |omnisharp-commands| 5. Autocmds ............................... |omnisharp-autocmds| 6. Test runner ............................ |omnisharp-testrunner| - 6.1 Mappings ............................ |omnisharp-testrunner-mappings| + 6.1 Options ............................. |omnisharp-test-options| + 6.1 Commands ............................ |omnisharp-testrunner-commands| + 6.2 Mappings ............................ |omnisharp-testrunner-mappings| 7. Integrations ........................... |omnisharp-integrations| =============================================================================== @@ -373,82 +374,7 @@ Default: atcursor > let g:OmniSharp_popup_position = 'peek' < ------------------------------------------------------------------------------- -3.5 TESTS *omnisharp-test-options* - - *g:OmniSharp_runtests_echo_output* -When running unit tests with |:OmniSharpRunTest| and |:OmniSharpRunTestsInFile| -or from the |omnisharp-testrunner|, echo all test runner output to the -|message-history|, so it can be viewed with |:messages|. -Default: 0 - - *g:OmniSharp_runtests_parallel* -When running multiple unit test files with |:OmniSharpRunTestsInFile|, run -tests in all files simultaneously - this is the fastest way to run multiple -test files. The disadvantage is that when |g:OmniSharp_runtests_echo_output| -is set to 1, tests from multiple files will be interspersed, and therefore -difficult to read. Set |g:OmniSharp_runtests_parallel| to 0 in order to run -test files in sequence instead, resulting in readable output in the -|message-history|. -Default: 1 - - *g:OmniSharp_runtests_quickfix* -When running unit tests with |:OmniSharpRunTest| and |:OmniSharpRunTestsInFile| -or from the |omnisharp-testrunner|, populate the quickfix list with test -results. -When a single test is run and an exception occurs, the quickfix will be -populated with the exception stack trace. -When multiple tests are run then each test will get a quickfix location: -failed test locations point to the failed assertion; successful test locations -point to the test method declaration. -Default: 0 - - *g:OmniSharp_testrunner* -Open the |omnisharp-testrunner| automatically when running tests. -Default: 1 - - *g:OmniSharp_testrunner_banner* -Display the |omnisharp-testrunner| help/introduction banner when opening the -testrunner. When this option is set to 0, the banner may still be toggled with -the (default) toggle mapping. -Default: 1 - - *g:OmniSharp_testrunner_glyph* -Display a passed/failed "glyph" string beside completed test results in the -|omnisharp-testrunner|. -Default: 1 - - *g:OmniSharp_testrunner_glyph_failed* -The "glyph" string to display beside failed completed tests in the testrunner. -Default: ✘ - - *g:OmniSharp_testrunner_glyph_passed* -The "glyph" string to display beside passed completed tests in the testrunner. -Default: ✔ - - *g:OmniSharp_testrunner_loglevel* -The type of build and test logs to output in the |omnisharp-testrunner|. - - all All build output and test runner output is displayed. - - error When a build error occurs, the error message and stack - trace are output. - - none Only build error messages will be output. - -Default: error > - let g:OmniSharp_testrunner_loglevel = 'all' -< - *g:OmniSharp_testrunner_spinner* -Display a "running" spinner animation when running tests in the testrunner. -Default: 1 - - *g:OmniSharp_testrunner_spinnersteps* -A list of "step" strings to be displayed as the |omnisharp-testrunner| -"spinner" animation. -Default: ['∙∙∙', '●∙∙', '∙●∙', '∙∙●', '∙∙∙'] - -------------------------------------------------------------------------------- -3.6 INTEGRATIONS *omnisharp-integration-options* +3.5 INTEGRATIONS *omnisharp-integration-options* *g:OmniSharp_selector_ui* Use this option to specify a selector UI for choosing code actions and @@ -509,7 +435,7 @@ Use this option to enable syntastic integration > let g:syntastic_cs_checkers = ['code_checker'] < ------------------------------------------------------------------------------- -3.7 MISCELLANEOUS *omnisharp-miscellaneous-options* +3.6 MISCELLANEOUS *omnisharp-miscellaneous-options* *g:OmniSharp_filename_modifiers* File paths returned from the server are normalized using Vim @@ -557,7 +483,7 @@ the |:OmniSharpDocumentation| command. Default: 1 > let g:omnicomplete_fetch_full_documentation = 1 < =============================================================================== -4. COMMANDS *omnisharp-commands* +4. GENERAL COMMANDS *omnisharp-commands* Most of the OmniSharp-vim commands have associated plug mappings defined, for convenient user re-mapping. These can be used like so: > @@ -575,8 +501,8 @@ convenient user re-mapping. These can be used like so: > :OmniSharpGotoDefinition vsplit :OmniSharpGotoDefinition tabedit < - *:OmniSharpGotoTypeDefinition* - *(omnisharp_go_to_type_definition)* + *:OmniSharpGotoTypeDefinition* + *(omnisharp_go_to_type_definition)* :OmniSharpGotoTypeDefinition [{cmd}] Navigates to the type definition of the symbol under the cursor. By default the definition is opened in the current window. To open it in a @@ -661,48 +587,6 @@ convenient user re-mapping. These can be used like so: > :OmniSharpNavigateDown Navigates to next method or class - *:OmniSharpRunTest* - *(omnisharp_run_test)* - *(omnisharp_run_test_no_build)* -:OmniSharpRunTest[!] - Run the current unit test. The cursor can be anywhere in the test method. - The |omnisharp-testrunner| window will be opened to display the test - status and results. - If the test fails, the failure message and location will be displayed in - the testrunner, or optionally (see |g:OmniSharp_runtests_quickfix|) in the - quickfix list, along with the error stack trace if one exists. - - When called with ! or the |(omnisharp_run_test_no_build)| mapping, the - project is not built before running the test. - - *:OmniSharpDebugTest* - *(omnisharp_debug_test)* - *(omnisharp_debug_test_no_build)* -:OmniSharpDebugTest[!] - Debug the current unit test with Vimspector. The cursor can be anywhere in - the test method. - - When called with ! or the |(omnisharp_debug_test_no_build)| mapping, - the project is not built before starting the debugger. - - *:OmniSharpRunTestsInFile* - *(omnisharp_run_tests_in_file)* - *(omnisharp_run_tests_in_file_no_build)* -:OmniSharpRunTestsInFile[!] - Run all unit tests in the current file. When |g:OmniSharp_runtests_quickfix| - is enabled, the quickfix list will be populated with the results of all - tests. - Optionally accepts one or more filenames of files to run tests for. -> - " Run all unit tests in the current file - :OmniSharpRunTestsInFile - - " Run all unit tests in the current file, and file `tests/test1.cs` - :OmniSharpRunTestsInFile % tests/test1.cs -< - When called with ! or the |(omnisharp_run_tests_in_file_no_build)| - mapping, the project is not built before running the tests. - *:OmniSharpOpenLog* *(omnisharp_open_log)* :OmniSharpOpenLog [{cmd}] @@ -715,9 +599,6 @@ convenient user re-mapping. These can be used like so: > :OmniSharpOpenLog vsplit :OmniSharpOpenLog tabedit < - *:OmniSharpOpenTestRunner* - Open the |omnisharp-testrunner| window - *:OmniSharpGetCodeActions* *(omnisharp_code_actions)* :OmniSharpGetCodeActions @@ -866,7 +747,7 @@ with the test status (running/passed/failed/not run) and any outputs that may be produced (exceptions and Console output). Open the test runner window by either running a test (with e.g. -|:OmniSharpRunTest|) or with the |:OmniSharpOpenTestRunner| command. +|:OmniSharpRunTest|) or with the |:OmniSharpTestRunner| command. Any time a new test is run, it (along with all other tests in the same file) is added to the runner. Tests remain visible in the test runner window until @@ -883,7 +764,133 @@ display test results, use the following settings: > let g:OmniSharp_runtests_quickfix = 1 < ------------------------------------------------------------------------------- -6.1 MAPPINGS *omnisharp-testrunner-mappings* +6.1 OPTIONS *omnisharp-test-options* + + *g:OmniSharp_runtests_echo_output* +When running unit tests with |:OmniSharpRunTest| and |:OmniSharpRunTestsInFile| +or from the |omnisharp-testrunner|, echo all test runner output to the +|message-history|, so it can be viewed with |:messages|. +Default: 0 + + *g:OmniSharp_runtests_parallel* +When running multiple unit test files with |:OmniSharpRunTestsInFile|, run +tests in all files simultaneously - this is the fastest way to run multiple +test files. The disadvantage is that when |g:OmniSharp_runtests_echo_output| +is set to 1, tests from multiple files will be interspersed, and therefore +difficult to read. Set |g:OmniSharp_runtests_parallel| to 0 in order to run +test files in sequence instead, resulting in readable output in the +|message-history|. +Default: 1 + + *g:OmniSharp_runtests_quickfix* +When running unit tests with |:OmniSharpRunTest| and |:OmniSharpRunTestsInFile| +or from the |omnisharp-testrunner|, populate the quickfix list with test +results. +When a single test is run and an exception occurs, the quickfix will be +populated with the exception stack trace. +When multiple tests are run then each test will get a quickfix location: +failed test locations point to the failed assertion; successful test locations +point to the test method declaration. +Default: 0 + + *g:OmniSharp_testrunner* +Open the |omnisharp-testrunner| automatically when running tests. +Default: 1 + + *g:OmniSharp_testrunner_banner* +Display the |omnisharp-testrunner| help/introduction banner when opening the +testrunner. When this option is set to 0, the banner may still be toggled with +the (default) toggle mapping. +Default: 1 + + *g:OmniSharp_testrunner_glyph* +Display a passed/failed "glyph" string beside completed test results in the +|omnisharp-testrunner|. +Default: 1 + + *g:OmniSharp_testrunner_glyph_failed* +The "glyph" string to display beside failed completed tests in the testrunner. +Default: ✘ + + *g:OmniSharp_testrunner_glyph_passed* +The "glyph" string to display beside passed completed tests in the testrunner. +Default: ✔ + + *g:OmniSharp_testrunner_loglevel* +The type of build and test logs to output in the |omnisharp-testrunner|. + + all All build output and test runner output is displayed. + + error When a build error occurs, the error message and stack + trace are output. + + none Only build error messages will be output. + +Default: error > + let g:OmniSharp_testrunner_loglevel = 'all' +< + *g:OmniSharp_testrunner_spinner* +Display a "running" spinner animation when running tests in the testrunner. +Default: 1 + + *g:OmniSharp_testrunner_spinnersteps* +A list of "step" strings to be displayed as the |omnisharp-testrunner| +"spinner" animation. +Default: ['∙∙∙', '●∙∙', '∙●∙', '∙∙●', '∙∙∙'] + +------------------------------------------------------------------------------- +6.2 COMMANDS *omnisharp-testrunner-commands* + + *:OmniSharpRunTest* + *(omnisharp_run_test)* + *(omnisharp_run_test_no_build)* +:OmniSharpRunTest[!] + Run the current unit test. The cursor can be anywhere in the test method. + The |omnisharp-testrunner| window will be opened to display the test + status and results. + If the test fails, the failure message and location will be displayed in + the testrunner, or optionally (see |g:OmniSharp_runtests_quickfix|) in the + quickfix list, along with the error stack trace if one exists. + + When called with ! or the |(omnisharp_run_test_no_build)| mapping, the + project is not built before running the test. + + *:OmniSharpDebugTest* + *(omnisharp_debug_test)* + *(omnisharp_debug_test_no_build)* +:OmniSharpDebugTest[!] + Debug the current unit test with Vimspector. The cursor can be anywhere in + the test method. + + When called with ! or the |(omnisharp_debug_test_no_build)| mapping, + the project is not built before starting the debugger. + + *:OmniSharpRunTestsInFile* + *(omnisharp_run_tests_in_file)* + *(omnisharp_run_tests_in_file_no_build)* +:OmniSharpRunTestsInFile[!] + Run all unit tests in the current file. When |g:OmniSharp_runtests_quickfix| + is enabled, the quickfix list will be populated with the results of all + tests. + Optionally accepts one or more filenames of files to run tests for. +> + " Run all unit tests in the current file + :OmniSharpRunTestsInFile + + " Run all unit tests in the current file, and file `tests/test1.cs` + :OmniSharpRunTestsInFile % tests/test1.cs +< + When called with ! or the |(omnisharp_run_tests_in_file_no_build)| + mapping, the project is not built before running the tests. + + *:OmniSharpTestRunner* + Open the |omnisharp-testrunner| window + + *:OmniSharpTestRunnerReset* + Forget all test history and clear the test runner window + +------------------------------------------------------------------------------- +6.3 MAPPINGS *omnisharp-testrunner-mappings* The following || mappings and associated default recursive mappings are provided in the test runner window. From d5a141539c458d628d7dd0bdb5a153ba4108e03a Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Mon, 24 Apr 2023 14:22:08 +1200 Subject: [PATCH 53/54] Wrap popups at word boundaries by default --- autoload/OmniSharp/popup.vim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/autoload/OmniSharp/popup.vim b/autoload/OmniSharp/popup.vim index a684277ef..479a4d6d9 100644 --- a/autoload/OmniSharp/popup.vim +++ b/autoload/OmniSharp/popup.vim @@ -318,6 +318,8 @@ function! s:VimOpen(what, opts) abort endif " Prevent popup buffer from being listed in buffer list (`:ls`) call setbufvar(winbufnr(winid), '&buflisted', 0) + " Make wrapping occur at word boundaries + call setwinvar(winid, '&linebreak', 1) return winid endfunction From 66a2d623e5aa98d37ce7bd9d6f78f5792178f1a5 Mon Sep 17 00:00:00 2001 From: Nick Jensen Date: Thu, 17 Oct 2024 09:33:12 +1300 Subject: [PATCH 54/54] Fix incorrect help tag --- autoload/OmniSharp/testrunner.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/OmniSharp/testrunner.vim b/autoload/OmniSharp/testrunner.vim index 44514609d..b46802abc 100644 --- a/autoload/OmniSharp/testrunner.vim +++ b/autoload/OmniSharp/testrunner.vim @@ -310,7 +310,7 @@ function! s:buffer.paintbanner() abort call add(lines, '`' . repeat(self.delimiter(), 80)) call add(lines, '` OmniSharp Test Runner') call add(lines, '` ' . repeat(self.delimiter(), 76)) - call add(lines, '` Toggle this menu (:help omnisharp-test-runner for more)') + call add(lines, '` Toggle this menu (:help omnisharp-testrunner for more)') call add(lines, '` Run test or tests in file under cursor') call add(lines, '` Debug test under cursor') call add(lines, '` Navigate to test or stack trace')