diff --git a/README.md b/README.md index 3e83c06..a058c72 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ It provides all essential actions for development, including building, launching - [x] [nvim-dap] integration to let you easily build, run, and debug apps. - [x] [nvim-dap-ui] integration to show app logs in the console window. - [x] [lualine.nvim] integration to show selected device, test plan, and other project settings. +- [x] [Quick] integration to show test results for tests written using [Quick] framework. - [x] Auto-detection of the target membership for new files. - [x] Picker with all available plugin actions. - [x] Highly customizable (many config options, auto commands, highlights, and user commands). @@ -58,6 +59,7 @@ https://github.com/wojciech-kulik/xcodebuild.nvim/assets/3128467/ed7d2d2e-eaa4-4 - [nui.nvim] to present floating code coverage report. - [nvim-tree] or [oil.nvim] to visually manage your project files. - [nvim-dap] and [nvim-dap-ui] to debug apps. +- [nvim-treesitter] + Swift parser to show test results for tests written using [Quick] framework. ##### External tools @@ -104,6 +106,7 @@ return { "MunifTanjim/nui.nvim", "nvim-tree/nvim-tree.lua", -- (optional) to manage project files "stevearc/oil.nvim", -- (optional) to manage project files + "nvim-treesitter/nvim-treesitter", -- (optional) for Quick tests support (required Swift parser) }, config = function() require("xcodebuild").setup({ @@ -363,7 +366,6 @@ vim.keymap.set("n", "xa", "XcodebuildCodeActions", { desc = "Sh failure_sign = "✖", -- failed test icon show_test_duration = true, -- show each test duration next to its declaration show_diagnostics = true, -- add test failures to diagnostics - file_pattern = "*Tests.swift", -- test diagnostics will be loaded in files matching this pattern (if available) }, quickfix = { show_errors_on_quickfixlist = true, -- add build/test errors to quickfix list @@ -419,6 +421,9 @@ vim.keymap.set("n", "xa", "XcodebuildCodeActions", { desc = "Sh return true end, }, + quick = { -- integration with Swift test framework: github.com/Quick/Quick + enabled = true, -- enable Quick tests support (requires Swift parser for nvim-treesitter) + }, }, highlights = { -- you can override here any highlight group used by this plugin @@ -829,6 +834,7 @@ and run `xcode-build-server config` again. [oil.nvim]: https://github.com/stevearc/oil.nvim [nvim-dap]: https://github.com/mfussenegger/nvim-dap [nvim-dap-ui]: https://github.com/rcarriga/nvim-dap-ui +[nvim-treesitter]: https://github.com/nvim-treesitter/nvim-treesitter [nui.nvim]: https://github.com/MunifTanjim/nui.nvim [telescope.nvim]: https://github.com/nvim-telescope/telescope.nvim [neovim]: https://neovim.io @@ -846,5 +852,6 @@ and run `xcode-build-server config` again. [tips-and-tricks]: https://github.com/wojciech-kulik/xcodebuild.nvim/discussions/categories/tips-tricks [ios-guide]: https://wojciechkulik.pl/ios/the-complete-guide-to-ios-macos-development-in-neovim [ios-dev-starter-nvim]: https://github.com/wojciech-kulik/ios-dev-starter-nvim -[swift-snapshot-testing]: https://github.com/pointfreeco/swift-snapshot-testing [test-file-search]: https://github.com/wojciech-kulik/xcodebuild.nvim/discussions/41 +[swift-snapshot-testing]: https://github.com/pointfreeco/swift-snapshot-testing +[Quick]: https://github.com/Quick/Quick diff --git a/doc/xcodebuild.txt b/doc/xcodebuild.txt index 0f45cc6..607240e 100644 --- a/doc/xcodebuild.txt +++ b/doc/xcodebuild.txt @@ -41,6 +41,7 @@ Remote Debugger Integration ·········· |xcodebuild.integrations.remote LSP Integration ·································· |xcodebuild.integrations.lsp| nvim-tree Integration ······················ |xcodebuild.integrations.nvim_tree| oil.nvim Integration ························ |xcodebuild.integrations.oil-nvim| +Quick Test Framework Integration ··············· |xcodebuild.integrations.quick| xcode-build-server Integration ···· |xcodebuild.integrations.xcode-build-server| Pickers ················································ |xcodebuild.ui.pickers| Helpers ··················································· |xcodebuild.helpers| @@ -125,7 +126,6 @@ M.setup({options}) *xcodebuild.setup* failure_sign = "✖", -- failed test icon show_test_duration = true, -- show each test duration next to its declaration show_diagnostics = true, -- add test failures to diagnostics - file_pattern = "*Tests.swift", -- test diagnostics will be loaded in files matching this pattern (if available) }, quickfix = { show_errors_on_quickfixlist = true, -- add build/test errors to quickfix list @@ -181,6 +181,9 @@ M.setup({options}) *xcodebuild.setup* return true end, }, + quick = { -- integration with Swift test framework: github.com/Quick/Quick + enabled = true, -- enable Quick tests support (requires Swift parser for nvim-treesitter) + }, }, highlights = { -- you can override here any highlight group used by this plugin @@ -213,6 +216,7 @@ Features *xcodebuild.features* - `nvim-dap` integration to let you easily build, run, and debug apps. - `nvim-dap-ui` integration to show app logs in the console window. - `lualine.nvim` integration to show selected device, test plan, + - `Quick` integration to show test results for tests written using `Quick` framework. and other project settings. - Auto-detection of the target membership for new files. - Picker with all available plugin actions. @@ -250,7 +254,8 @@ Neovim environment - `telescope.nvim` to present pickers. - `nui.nvim` to present floating code coverage report. - `nvim-tree` or `oil.nvim` to visually manage your project files. - - `nvim-dap` and nvim-dap-ui to debug apps. + - `nvim-dap` and `nvim-dap-ui` to debug apps. + - `nvim-treesitter` + Swift parser to show test results for tests written using `Quick` framework. External tools - `xcbeautify` to format Xcode logs (you can set a different tool or disable formatting in the config). @@ -2537,10 +2542,6 @@ Test Diagnostics *xcodebuild.tests.diagnostics* This module is responsible for handling diagnostics and marks for test files. -M.setup() *xcodebuild.tests.diagnostics.setup* - Set up highlights for tests. - - *xcodebuild.tests.diagnostics.refresh_test_buffer* M.refresh_test_buffer({bufnr}, {report}) Refreshes the diagnostics and marks for the given buffer. @@ -2553,9 +2554,25 @@ M.refresh_test_buffer({bufnr}, {report}) |xcodebuild.xcode_logs.parser.ParsedReport| + *xcodebuild.tests.diagnostics.refresh_test_buffer_by_name* +M.refresh_test_buffer_by_name({name}, {report}) + Refreshes diagnostics and marks for the test buffer with the given name. + + It implements a debounce mechanism to avoid refreshing + the same buffer multiple times.The window is 1 second. + + Note: this function will affect the buffer after 1 second. + To refresh the buffer instantly use `refresh_test_buffer`. + + + Parameters: ~ + {name} (string) + {report} (ParsedReport) + + *xcodebuild.tests.diagnostics.refresh_all_test_buffers* M.refresh_all_test_buffers({report}) - Refreshes the diagnostics and marks for all test buffers. + Refreshes diagnostics and marks for all test buffers. Parameters: ~ {report} (ParsedReport) @@ -2564,6 +2581,18 @@ M.refresh_all_test_buffers({report}) |xcodebuild.xcode_logs.parser.ParsedReport| +M.clear() *xcodebuild.tests.diagnostics.clear* + Clears marks and diagnostics. + + +M.clear_marks() *xcodebuild.tests.diagnostics.clear_marks* + Clears marks. + + +M.setup() *xcodebuild.tests.diagnostics.setup* + Set up highlights for tests. + + ============================================================================== Test Enumeration Parser *xcodebuild.tests.enumeration_parser* @@ -3429,6 +3458,72 @@ M.setup() *xcodebuild.integrations.oil-nvim.setup* |xcodebuild.project-manager| +============================================================================== +Quick Test Framework Integration *xcodebuild.integrations.quick* + +This module is responsible for parsing tests written using `Quick` framework. + +It provides functions to get a list of `Quick` tests and their locations. + +Note: it requires the Swift parser to be installed (using `nvim-treesitter`). + +See: https://github.com/Quick/Quick + + +QuickTest *xcodebuild.integrations.quick.QuickTest* + + Fields: ~ + {id} (string) test id (matching xcodebuild test name) + {row} (number) 1-based row number + + + *xcodebuild.integrations.quick.find_quick_tests* +M.find_quick_tests({bufnr}) + Returns a list of tests and their locations. + + The result is a table where keys are test ids and values are `QuickTest` objects. + + Each test represents a single test case (e.g. `it` block). + + + Parameters: ~ + {bufnr} (number) + + Returns: ~ + (table|nil) + + + *xcodebuild.integrations.quick.contains_quick_tests* +M.contains_quick_tests({bufnr}) + Returns whether the given buffer contains Quick tests. + + It checks if the buffer contains a subclass of `QuickSpec` or + if there is `import Quick`. + + Parameters: ~ + {bufnr} (number) + + Returns: ~ + (boolean) + + + *xcodebuild.integrations.quick.is_swift_parser_installed* +M.is_swift_parser_installed() + Returns whether the Swift parser is installed. + + It caches the result for future calls. + + Returns: ~ + (boolean) + + +M.is_enabled() *xcodebuild.integrations.quick.is_enabled* + Returns whether the Quick integration is enabled. + + Returns: ~ + (boolean) + + ============================================================================== xcode-build-server Integration *xcodebuild.integrations.xcode-build-server* @@ -3651,7 +3746,7 @@ M.is_not_empty({table}) *xcodebuild.util.is_not_empty* M.get_buffers({opts}) *xcodebuild.util.get_buffers* Gets all buffers in the current neovim instance. - If `opts.returnNotLoaded` is true, it will + If {opts.returnNotLoaded} is true, it will return all buffers, including the ones that are not loaded. Parameters: ~ @@ -3695,6 +3790,7 @@ M.get_buf_by_filename({filename}, {opts}) M.get_buf_by_name({name}) *xcodebuild.util.get_buf_by_name* Gets a buffer by its name. + Returns also buffers that are not loaded. Parameters: ~ {name} (string) @@ -3705,6 +3801,7 @@ M.get_buf_by_name({name}) *xcodebuild.util.get_buf_by_name* M.get_buf_by_filetype({filetype}) *xcodebuild.util.get_buf_by_filetype* Gets a buffer by its filetype. + Returns also buffers that are not loaded. Parameters: ~ {filetype} (string) diff --git a/lua/xcodebuild/core/autocmd.lua b/lua/xcodebuild/core/autocmd.lua index 2a75004..c09c92a 100644 --- a/lua/xcodebuild/core/autocmd.lua +++ b/lua/xcodebuild/core/autocmd.lua @@ -41,10 +41,20 @@ function M.setup() if config.marks.show_diagnostics or config.marks.show_signs then vim.api.nvim_create_autocmd({ "BufReadPost" }, { group = autogroup, - pattern = config.marks.file_pattern, + pattern = "*.swift", callback = function(ev) - if projectConfig.is_project_configured() then - diagnostics.refresh_test_buffer(ev.buf, appdata.report) + if projectConfig.is_project_configured() and appdata.report and appdata.report.tests then + local filepath = vim.api.nvim_buf_get_name(ev.buf) + + -- refresh diagnostics if the file is in the report + for _, testClass in pairs(appdata.report.tests) do + for _, test in ipairs(testClass) do + if test.filepath == filepath then + diagnostics.refresh_test_buffer(ev.buf, appdata.report) + break + end + end + end end end, }) diff --git a/lua/xcodebuild/core/config.lua b/lua/xcodebuild/core/config.lua index 6794f0b..734417c 100644 --- a/lua/xcodebuild/core/config.lua +++ b/lua/xcodebuild/core/config.lua @@ -64,7 +64,6 @@ local defaults = { failure_sign = "✖", -- failed test icon show_test_duration = true, -- show each test duration next to its declaration show_diagnostics = true, -- add test failures to diagnostics - file_pattern = "*Tests.swift", -- test diagnostics will be loaded in files matching this pattern (if available) }, quickfix = { show_errors_on_quickfixlist = true, -- add build/test errors to quickfix list @@ -120,6 +119,9 @@ local defaults = { return true end, }, + quick = { -- integration with Swift test framework: github.com/Quick/Quick + enabled = true, -- enable Quick tests support (requires Swift parser for nvim-treesitter) + }, }, highlights = { -- you can override here any highlight group used by this plugin diff --git a/lua/xcodebuild/docs/features.lua b/lua/xcodebuild/docs/features.lua index 004944b..3ce041d 100644 --- a/lua/xcodebuild/docs/features.lua +++ b/lua/xcodebuild/docs/features.lua @@ -18,6 +18,7 @@ --- - `nvim-dap` integration to let you easily build, run, and debug apps. --- - `nvim-dap-ui` integration to show app logs in the console window. --- - `lualine.nvim` integration to show selected device, test plan, +--- - `Quick` integration to show test results for tests written using `Quick` framework. --- and other project settings. --- - Auto-detection of the target membership for new files. --- - Picker with all available plugin actions. diff --git a/lua/xcodebuild/docs/requirements.lua b/lua/xcodebuild/docs/requirements.lua index 95061b5..7ff245d 100644 --- a/lua/xcodebuild/docs/requirements.lua +++ b/lua/xcodebuild/docs/requirements.lua @@ -7,7 +7,8 @@ --- - `telescope.nvim` to present pickers. --- - `nui.nvim` to present floating code coverage report. --- - `nvim-tree` or `oil.nvim` to visually manage your project files. ---- - `nvim-dap` and nvim-dap-ui to debug apps. +--- - `nvim-dap` and `nvim-dap-ui` to debug apps. +--- - `nvim-treesitter` + Swift parser to show test results for tests written using `Quick` framework. --- ---External tools --- - `xcbeautify` to format Xcode logs (you can set a different tool or disable formatting in the config). diff --git a/lua/xcodebuild/health.lua b/lua/xcodebuild/health.lua index 0362794..303b9f8 100644 --- a/lua/xcodebuild/health.lua +++ b/lua/xcodebuild/health.lua @@ -50,7 +50,7 @@ local required_dependencies = { }, } -local required_plugins = { +local plugins = { { name = "telescope.nvim", lib = "telescope", @@ -87,6 +87,12 @@ local required_plugins = { optional = true, info = "(Optional to present debugger UI)", }, + { + name = "nvim-treesitter", + lib = "nvim-treesitter", + optional = true, + info = "(Optional to present results for tests written using Quick framework)", + }, } ---@param binary string @@ -148,7 +154,7 @@ local function check_debugger() end local function check_plugins() - for _, plugin in ipairs(required_plugins) do + for _, plugin in ipairs(plugins) do if plugin_installed(plugin.lib) then ok(plugin.name .. " installed.") else @@ -311,6 +317,18 @@ local function check_plugin_commit() end end +local function check_swift_parser() + local quick = require("xcodebuild.integrations.quick") + + if quick.is_swift_parser_installed() then + ok("nvim-treesitter: Swift parser installed.") + else + warn( + "nvim-treesitter: Swift parser not installed. It is required to present results for tests written using Quick framework." + ) + end +end + local M = {} M.check = function() @@ -320,8 +338,9 @@ M.check = function() start("Checking OS") check_os() - start("Checking required plugins") + start("Checking plugins") check_plugins() + check_swift_parser() start("Checking required dependencies") check_xcodebuild_version() diff --git a/lua/xcodebuild/init.lua b/lua/xcodebuild/init.lua index e4e60cb..6564431 100644 --- a/lua/xcodebuild/init.lua +++ b/lua/xcodebuild/init.lua @@ -144,7 +144,6 @@ end --- failure_sign = "✖", -- failed test icon --- show_test_duration = true, -- show each test duration next to its declaration --- show_diagnostics = true, -- add test failures to diagnostics ---- file_pattern = "*Tests.swift", -- test diagnostics will be loaded in files matching this pattern (if available) --- }, --- quickfix = { --- show_errors_on_quickfixlist = true, -- add build/test errors to quickfix list @@ -200,6 +199,9 @@ end --- return true --- end, --- }, +--- quick = { -- integration with Swift test framework: github.com/Quick/Quick +--- enabled = true, -- enable Quick tests support (requires Swift parser for nvim-treesitter) +--- }, --- }, --- highlights = { --- -- you can override here any highlight group used by this plugin diff --git a/lua/xcodebuild/integrations/quick.lua b/lua/xcodebuild/integrations/quick.lua new file mode 100644 index 0000000..3a78b7b --- /dev/null +++ b/lua/xcodebuild/integrations/quick.lua @@ -0,0 +1,280 @@ +---@mod xcodebuild.integrations.quick Quick Test Framework Integration +---@brief [[ +---This module is responsible for parsing tests written using `Quick` framework. +--- +---It provides functions to get a list of `Quick` tests and their locations. +--- +---Note: it requires the Swift parser to be installed (using `nvim-treesitter`). +--- +---See: https://github.com/Quick/Quick +--- +---@brief ]] + +local M = {} + +local ts = vim.treesitter + +---Cached result of the Swift parser check. +---@type boolean|nil +local cachedSwiftParserResult = nil + +---@class QuickTest +---@field id string test id (matching xcodebuild test name) +---@field row number 1-based row number + +---@private +---@class TestTreeNode +---@field id string node type +---@field testId string|nil test id (matching xcodebuild test name) +---@field name string group name +---@field row number 0-based row number +---@field endRow number 0-based row number +---@field parent TestTreeNode|nil +---@field children TestTreeNode[] + +---@private +---@class TestQueryMatch +---@field id string node type +---@field name string group name +---@field row number 0-based row number +---@field endRow number 0-based row number + +---Returns a list of captured nodes with two main types: +---`quick.context` and `quick.it`. +---@param bufnr number +---@return TestQueryMatch[][] +local function parse_test_file(bufnr) + local result = {} + local tree = ts.get_parser(bufnr, "swift"):parse() + local root = tree[1]:root() + + local quickQueries = ts.query.parse( + "swift", + [[ + ((call_expression + (simple_identifier) @quick-func + (call_suffix + (value_arguments + (value_argument + value: (line_string_literal + text: (line_str_text) @quick.context + (#any-of? @quick-func "describe" "fdescribe" "xdescribe" "context" "fcontext" "xcontext" "when") + )))))) @quick.context.definition + + ((call_expression + (simple_identifier) @quick-func + (call_suffix + (value_arguments + (value_argument + value: (line_string_literal + text: (line_str_text) @quick.it + (#any-of? @quick-func "it" "fit" "xit" "then") + )))))) @quick.it.definition +]] + ) + + for _, match in quickQueries:iter_matches(root, bufnr) do + local capturedNodes = {} + + for i, capture in ipairs(quickQueries.captures) do + local currentMatch = match[i] + + if currentMatch and capture ~= "quick-func" then + local startRow, _, endRow, _ = currentMatch:range() + table.insert(capturedNodes, { + id = capture, + name = not capture:match("definition") and ts.get_node_text(currentMatch, bufnr) or nil, + row = startRow, + endRow = endRow, + }) + end + end + + table.insert(result, capturedNodes) + end + + return result +end + +---Returns a full test id by concatenating all parent names and +---replacing non-alphanumeric characters with underscores. +--- +---The result should match the test name printed by xcodebuild. +---@param node TestTreeNode +---@return string +local function get_test_id(node) + assert(node.id == "quick.it", "Incorrect node type") + + local components = {} + local current = node + + local function normalize(str) + local result = str:gsub("([^%w])", "_") + return result + end + + while current and current.id ~= "root" do + table.insert(components, normalize(current.name)) + current = current.parent + end + + return table.concat(vim.fn.reverse(components), "__") +end + +---Returns a tree structure of tests and a list of tests +---based on the flat tree structure received from tree-sitter queries. +---@param flatTree TestQueryMatch[][] +---@return { tree: TestTreeNode[], tests: table } +local function make_tree(flatTree) + ---@type TestTreeNode + local root = { id = "root", name = "", row = 0, endRow = 0, parent = nil, children = {} } + local tree = root + + ---@type QuickTest[] + local tests = {} + + ---@type TestTreeNode|nil + local parent = root + local updateParent = false + + for _, match in ipairs(flatTree) do + ---@type TestTreeNode + local newNode = { + id = match[1].id, + name = match[1].name, + row = match[1].row, + endRow = match[2].endRow, + parent = parent, + children = {}, + } + + if updateParent then + while parent and parent.id ~= "root" do + if parent.row <= newNode.row and parent.endRow >= newNode.endRow then + break + end + + parent = parent.parent + end + + newNode.parent = parent + updateParent = false + end + + assert(parent, "Parent is nil") + + if newNode.id == "quick.it" then + newNode.testId = get_test_id(newNode) + table.insert(parent.children, newNode) + tests[newNode.testId] = { id = newNode.testId, row = newNode.row + 1 } + updateParent = true + else + table.insert(parent.children, newNode) + parent = newNode + end + end + + return { + tree = tree, + tests = tests, + } +end + +---Returns a list of tests and their locations. +--- +---The result is a table where keys are test ids and values are `QuickTest` objects. +--- +---Each test represents a single test case (e.g. `it` block). +--- +---@param bufnr number +---@return table|nil +function M.find_quick_tests(bufnr) + if not M.is_swift_parser_installed() then + return nil + end + + return make_tree(parse_test_file(bufnr)).tests +end + +---Returns whether the given buffer contains Quick tests. +--- +---It checks if the buffer contains a subclass of `QuickSpec` or +---if there is `import Quick`. +---@param bufnr number +---@return boolean +function M.contains_quick_tests(bufnr) + if not M.is_swift_parser_installed() then + return false + end + + local tree = ts.get_parser(bufnr, "swift"):parse() + local root = tree[1]:root() + + local testClassQuery = ts.query.parse( + "swift", + [[ +(class_declaration + name: (type_identifier) @test-class + (inheritance_specifier + inherits_from: (user_type + (type_identifier) @test-class-type + (#eq? @test-class-type "QuickSpec")))) +]] + ) + + local importQuery = ts.query.parse( + "swift", + [[ +(import_declaration + (identifier) @quick-import (#eq? @quick-import "Quick")) +]] + ) + + for _, match in testClassQuery:iter_matches(root, bufnr) do + if match[2] then + return true + end + end + + for _, match in importQuery:iter_matches(root, bufnr) do + if match[1] then + return true + end + end + + return false +end + +---Returns whether the Swift parser is installed. +--- +---It caches the result for future calls. +---@return boolean +function M.is_swift_parser_installed() + if cachedSwiftParserResult ~= nil then + return cachedSwiftParserResult + end + + local success, _ = pcall(require, "nvim-treesitter") + if not success then + return false + end + + local parsers = vim.api.nvim_get_runtime_file("parser/*.so", true) + local result = vim.tbl_contains(parsers, function(path) + local filename = vim.fn.fnamemodify(path, ":t") + return filename == "swift.so" + end, { predicate = true }) + + cachedSwiftParserResult = result + + return result +end + +---Returns whether the Quick integration is enabled. +---@return boolean +function M.is_enabled() + return require("xcodebuild.core.config").options.integrations.quick.enabled + and M.is_swift_parser_installed() +end + +return M diff --git a/lua/xcodebuild/tests/diagnostics.lua b/lua/xcodebuild/tests/diagnostics.lua index 621f018..b6449b4 100644 --- a/lua/xcodebuild/tests/diagnostics.lua +++ b/lua/xcodebuild/tests/diagnostics.lua @@ -6,9 +6,16 @@ local util = require("xcodebuild.util") local config = require("xcodebuild.core.config").options.marks local testSearch = require("xcodebuild.tests.search") +local quick = require("xcodebuild.integrations.quick") local M = {} +local diagnosticsNamespace = vim.api.nvim_create_namespace("xcodebuild-diagnostics") +local marksNamespace = vim.api.nvim_create_namespace("xcodebuild-marks") + +---@type string|nil +local requestedRefreshForFile + ---Returns the test class key for the given buffer. ---@param bufnr number ---@param report ParsedReport @@ -40,16 +47,18 @@ end ---@param bufnr number ---@param testClass string ---@param report ParsedReport -local function refresh_buf_diagnostics(bufnr, testClass, report) +---@param quickTests table|nil +local function refresh_buf_diagnostics(bufnr, testClass, report, quickTests) if not report.tests or not config.show_diagnostics then return end - local ns = vim.api.nvim_create_namespace("xcodebuild-diagnostics") local diagnostics = {} local duplicates = {} for _, test in ipairs(report.tests[testClass] or {}) do + test.lineNumber = test.lineNumber or (quickTests and quickTests[test.name] and quickTests[test.name].row) + if not test.success and test.filepath @@ -69,20 +78,21 @@ local function refresh_buf_diagnostics(bufnr, testClass, report) end end - vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) - vim.diagnostic.set(ns, bufnr, diagnostics, {}) + vim.diagnostic.reset(diagnosticsNamespace, bufnr) + vim.api.nvim_buf_clear_namespace(bufnr, diagnosticsNamespace, 0, -1) + vim.diagnostic.set(diagnosticsNamespace, bufnr, diagnostics, {}) end ---Refreshes the marks for the given buffer. ---@param bufnr number ---@param testClass string ---@param tests ParsedTest[] -local function refresh_buf_marks(bufnr, testClass, tests) +---@param quickTests table|nil +local function refresh_buf_marks(bufnr, testClass, tests, quickTests) if not tests or not (config.show_test_duration or config.show_signs) then return end - local ns = vim.api.nvim_create_namespace("xcodebuild-marks") local bufLines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) local findTestLine = function(testName) for lineNumber, line in ipairs(bufLines) do @@ -94,10 +104,11 @@ local function refresh_buf_marks(bufnr, testClass, tests) return nil end - vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) + vim.api.nvim_buf_clear_namespace(bufnr, marksNamespace, 0, -1) for _, test in ipairs(tests[testClass] or {}) do local lineNumber = findTestLine(test.name) + or (quickTests and quickTests[test.name] and quickTests[test.name].row - 1) local testDuration = nil local signText = nil local signHighlight = nil @@ -119,7 +130,7 @@ local function refresh_buf_marks(bufnr, testClass, tests) end if test.filepath and lineNumber then - vim.api.nvim_buf_set_extmark(bufnr, ns, lineNumber, 0, { + vim.api.nvim_buf_set_extmark(bufnr, marksNamespace, lineNumber, 0, { virt_text = { testDuration }, sign_text = signText, sign_hl_group = signHighlight, @@ -128,16 +139,6 @@ local function refresh_buf_marks(bufnr, testClass, tests) end end ----Set up highlights for tests. -function M.setup() - -- stylua: ignore start - vim.api.nvim_set_hl(0, "XcodebuildTestSuccessSign", { link = "DiagnosticSignOk", default = true }) - vim.api.nvim_set_hl(0, "XcodebuildTestFailureSign", { link = "DiagnosticSignError", default = true }) - vim.api.nvim_set_hl(0, "XcodebuildTestSuccessDurationSign", { link = "DiagnosticSignWarn", default = true }) - vim.api.nvim_set_hl(0, "XcodebuildTestFailureDurationSign", { link = "DiagnosticSignError", default = true }) - -- stylua: ignore end -end - ---Refreshes the diagnostics and marks for the given buffer. ---@param bufnr number ---@param report ParsedReport @@ -145,12 +146,44 @@ end function M.refresh_test_buffer(bufnr, report) local testClass = find_test_class(bufnr, report) if testClass then - refresh_buf_diagnostics(bufnr, testClass, report) - refresh_buf_marks(bufnr, testClass, report.tests) + local quickTests + if quick.is_enabled() and quick.contains_quick_tests(bufnr) then + quickTests = quick.find_quick_tests(bufnr) + end + + refresh_buf_diagnostics(bufnr, testClass, report, quickTests) + refresh_buf_marks(bufnr, testClass, report.tests, quickTests) end end ----Refreshes the diagnostics and marks for all test buffers. +---Refreshes diagnostics and marks for the test buffer with the given name. +--- +---It implements a debounce mechanism to avoid refreshing +---the same buffer multiple times.The window is 1 second. +--- +---Note: this function will affect the buffer after 1 second. +---To refresh the buffer instantly use `refresh_test_buffer`. +--- +---@param name string +---@param report ParsedReport +function M.refresh_test_buffer_by_name(name, report) + if requestedRefreshForFile == name then + return + end + + requestedRefreshForFile = name + + vim.defer_fn(function() + requestedRefreshForFile = nil + + local bufnr = util.get_buf_by_name(name) + if bufnr and vim.api.nvim_buf_is_loaded(bufnr) then + M.refresh_test_buffer(bufnr, report) + end + end, 1000) +end + +---Refreshes diagnostics and marks for all test buffers. ---@param report ParsedReport ---@see xcodebuild.xcode_logs.parser.ParsedReport function M.refresh_all_test_buffers(report) @@ -158,18 +191,48 @@ function M.refresh_all_test_buffers(report) return end - -- TODO: improve gsub - the conversion from wildcard to regex might not be reliable - local filePattern = config.file_pattern - local regexPattern = string.gsub(string.gsub(filePattern, "%.", "%%."), "%*", "%.%*") - local testBuffers = util.get_bufs_by_matching_name(regexPattern) + M.clear_marks() + + local filePathToBufnr = {} + for _, bufnr in ipairs(util.get_buffers()) do + local filepath = vim.api.nvim_buf_get_name(bufnr) + filePathToBufnr[filepath] = bufnr + end - for _, buffer in ipairs(testBuffers or {}) do - local testClass = find_test_class(buffer.bufnr, report) - if testClass then - refresh_buf_diagnostics(buffer.bufnr, testClass, report) - refresh_buf_marks(buffer.bufnr, testClass, report.tests) + for _, testsPerClass in pairs(report.tests) do + for _, test in ipairs(testsPerClass) do + if test.filepath and filePathToBufnr[test.filepath] then + M.refresh_test_buffer(filePathToBufnr[test.filepath], report) + break -- all tests within the class are in the same file + end end end end +---Clears marks and diagnostics. +function M.clear() + for _, bufnr in ipairs(util.get_buffers()) do + vim.diagnostic.reset(diagnosticsNamespace, bufnr) + vim.api.nvim_buf_clear_namespace(bufnr, marksNamespace, 0, -1) + vim.api.nvim_buf_clear_namespace(bufnr, diagnosticsNamespace, 0, -1) + end +end + +---Clears marks. +function M.clear_marks() + for _, bufnr in ipairs(util.get_buffers()) do + vim.api.nvim_buf_clear_namespace(bufnr, marksNamespace, 0, -1) + end +end + +---Set up highlights for tests. +function M.setup() + -- stylua: ignore start + vim.api.nvim_set_hl(0, "XcodebuildTestSuccessSign", { link = "DiagnosticSignOk", default = true }) + vim.api.nvim_set_hl(0, "XcodebuildTestFailureSign", { link = "DiagnosticSignError", default = true }) + vim.api.nvim_set_hl(0, "XcodebuildTestSuccessDurationSign", { link = "DiagnosticSignWarn", default = true }) + vim.api.nvim_set_hl(0, "XcodebuildTestFailureDurationSign", { link = "DiagnosticSignError", default = true }) + -- stylua: ignore end +end + return M diff --git a/lua/xcodebuild/tests/explorer.lua b/lua/xcodebuild/tests/explorer.lua index 3eb014a..68139ea 100644 --- a/lua/xcodebuild/tests/explorer.lua +++ b/lua/xcodebuild/tests/explorer.lua @@ -582,6 +582,7 @@ function M.open_selected_test() end local filepath = line_to_test[currentRow].filepath + local quick = require("xcodebuild.integrations.quick") if filepath then local searchPhrase = line_to_test[currentRow].name @@ -591,6 +592,20 @@ function M.open_selected_test() end vim.cmd("wincmd p | e " .. filepath) + + if + quick.is_enabled() + and line_to_test[currentRow].kind == KIND_TEST + and quick.contains_quick_tests(0) + then + local quickTests = quick.find_quick_tests(0) + if quickTests and quickTests[searchPhrase] then + vim.cmd(tostring(quickTests[searchPhrase].row)) + vim.cmd("normal! zz") + return + end + end + vim.fn.search(searchPhrase, "") vim.cmd("execute 'normal! zt'") end diff --git a/lua/xcodebuild/tests/runner.lua b/lua/xcodebuild/tests/runner.lua index 701dca2..9b952c1 100644 --- a/lua/xcodebuild/tests/runner.lua +++ b/lua/xcodebuild/tests/runner.lua @@ -130,6 +130,7 @@ function M.run_tests(testsToRun, opts) notifications.send_tests_started() helpers.clear_state() + diagnostics.clear_marks() local show_finish = function() notifications.send_tests_finished(appdata.report, false) @@ -163,7 +164,7 @@ function M.run_tests(testsToRun, opts) local logsParser = require("xcodebuild.xcode_logs.parser") appdata.report = logsParser.parse_logs(output) notifications.show_tests_progress(appdata.report) - diagnostics.refresh_all_test_buffers(appdata.report) + events.tests_status( appdata.report.testsCount - appdata.report.failedTestsCount, appdata.report.failedTestsCount diff --git a/lua/xcodebuild/util.lua b/lua/xcodebuild/util.lua index 97eaa6b..f4b0c04 100644 --- a/lua/xcodebuild/util.lua +++ b/lua/xcodebuild/util.lua @@ -41,7 +41,7 @@ function M.is_not_empty(table) end ---Gets all buffers in the current neovim instance. ----If `opts.returnNotLoaded` is true, it will +---If {opts.returnNotLoaded} is true, it will ---return all buffers, including the ones that are not loaded. ---@param opts table|nil --- @@ -50,12 +50,12 @@ end function M.get_buffers(opts) local result = {} - for i, buf in ipairs(vim.api.nvim_list_bufs()) do + for _, buf in ipairs(vim.api.nvim_list_bufs()) do if (opts or {}).returnNotLoaded == true and vim.api.nvim_buf_is_valid(buf) or vim.api.nvim_buf_is_loaded(buf) then - result[i] = buf + table.insert(result, buf) end end @@ -92,7 +92,7 @@ end function M.get_buf_by_filename(filename, opts) local allBuffers = M.get_buffers(opts) - for _, buf in pairs(allBuffers) do + for _, buf in ipairs(allBuffers) do if string.match(vim.api.nvim_buf_get_name(buf), ".*/([^/]*)$") == filename then return buf end @@ -102,6 +102,7 @@ function M.get_buf_by_filename(filename, opts) end ---Gets a buffer by its name. +---Returns also buffers that are not loaded. ---@param name string ---@return number|nil function M.get_buf_by_name(name) @@ -115,6 +116,7 @@ function M.get_buf_by_name(name) end ---Gets a buffer by its filetype. +---Returns also buffers that are not loaded. ---@param filetype string ---@return number|nil function M.get_buf_by_filetype(filetype) @@ -134,7 +136,7 @@ function M.get_bufs_by_matching_name(pattern) local allBuffers = M.get_buffers() local result = {} - for _, buf in pairs(allBuffers) do + for _, buf in ipairs(allBuffers) do local bufName = vim.api.nvim_buf_get_name(buf) if string.find(bufName, pattern) then table.insert(result, { bufnr = buf, file = bufName }) diff --git a/lua/xcodebuild/xcode_logs/parser.lua b/lua/xcodebuild/xcode_logs/parser.lua index ca89b16..2ce1e63 100644 --- a/lua/xcodebuild/xcode_logs/parser.lua +++ b/lua/xcodebuild/xcode_logs/parser.lua @@ -125,11 +125,28 @@ local function flush_test(message) table.insert(tests[key], lineData) end + -- refresh test explorer require("xcodebuild.tests.explorer").update_test_status( lineData.target .. "/" .. lineData.class .. "/" .. lineData.name, lineData.success and "passed" or "failed" ) + -- refresh diagnostics and marks for the test file + if lineData.filepath then + local report = { + tests = tests, + testsCount = testsCount, + failedTestsCount = failedTestsCount, + buildErrors = buildErrors, + buildWarnings = buildWarnings, + testErrors = testErrors, + xcresultFilepath = xcresultFilepath, + } + + local diagnostics = require("xcodebuild.tests.diagnostics") + diagnostics.refresh_test_buffer_by_name(lineData.filepath, report) + end + lastTest = lineData lineType = BEGIN lineData = {} diff --git a/scripts/help-update.sh b/scripts/help-update.sh index 461216f..7ead185 100755 --- a/scripts/help-update.sh +++ b/scripts/help-update.sh @@ -44,6 +44,7 @@ lemmy-help \ ./lua/xcodebuild/integrations/lsp.lua \ ./lua/xcodebuild/integrations/nvim-tree.lua \ ./lua/xcodebuild/integrations/oil-nvim.lua \ + ./lua/xcodebuild/integrations/quick.lua \ ./lua/xcodebuild/integrations/xcode-build-server.lua \ ./lua/xcodebuild/ui/pickers.lua \ ./lua/xcodebuild/helpers.lua \