diff --git a/lua/swift/core/utils.lua b/lua/swift/core/utils.lua index ade244e..78411c9 100644 --- a/lua/swift/core/utils.lua +++ b/lua/swift/core/utils.lua @@ -16,13 +16,40 @@ function M.find_file_upwards(filename, start_path) return nil end --- Find files matching pattern in parent directories +-- Find files matching a suffix pattern in parent directories +-- Uses vim.uv.fs_scandir instead of vim.fn.glob to avoid blocking on large dirs function M.find_pattern_upwards(pattern, start_path) start_path = start_path or vim.fn.getcwd() local path = start_path - while path ~= "/" do - local matches = vim.fn.glob(path .. "/" .. pattern, false, true) + -- Convert glob pattern like "*.xcworkspace" to a suffix match + local suffix = pattern:match("^%*(.+)$") + + -- Limit traversal depth to avoid scanning all the way to / + local max_depth = 10 + local depth = 0 + + while path ~= "/" and depth < max_depth do + depth = depth + 1 + local matches = {} + + if suffix then + -- Fast suffix scan using libuv (non-blocking within coroutine, instant in practice) + local handle = vim.uv.fs_scandir(path) + if handle then + while true do + local name, ftype = vim.uv.fs_scandir_next(handle) + if not name then break end + if (ftype == "directory" or ftype == "link") and name:sub(-#suffix) == suffix then + table.insert(matches, path .. "/" .. name) + end + end + end + else + -- Fallback to glob for complex patterns + matches = vim.fn.glob(path .. "/" .. pattern, false, true) + end + if #matches > 0 then return matches end diff --git a/tests/test_health_speed.lua b/tests/test_health_speed.lua new file mode 100644 index 0000000..6042bce --- /dev/null +++ b/tests/test_health_speed.lua @@ -0,0 +1,77 @@ +-- Test script: validate all shell calls in health.lua are fast +-- Run with: nvim --headless -u tests/minimal_init.lua -l tests/test_health_speed.lua + +local function time_call(label, fn) + local start = vim.loop.hrtime() + local ok, result = pcall(fn) + local elapsed_ms = (vim.loop.hrtime() - start) / 1e6 + local status = ok and "OK" or "ERROR" + print(string.format("[%s] %-50s %.0fms", status, label, elapsed_ms)) + if not ok then + print(" Error: " .. tostring(result)) + end + return result +end + +print("=== swift.nvim checkhealth speed test ===") +print("") + +-- Bootstrap plugin +local plugin_ok = pcall(require, "swift") +if not plugin_ok then + -- minimal setup without lspconfig + vim.opt.runtimepath:prepend(".") + plugin_ok = pcall(require, "swift") +end + +-- 1. swift --version +time_call("swift --version (via version_validator)", function() + local v = require("swift.version_validator") + return v.get_installed_swift_version() +end) + +-- 2. xcrun --find sourcekit-lsp +time_call("xcrun --find sourcekit-lsp (via lsp.find_sourcekit_lsp)", function() + local lsp = require("swift.features.lsp") + return lsp.find_sourcekit_lsp() +end) + +-- 3. swift-format --version +time_call("swift-format --version (via validator.is_formatter_compatible)", function() + local v = require("swift.version_validator") + return v.is_formatter_compatible() +end) + +-- 4a. Project detection — xcworkspace scan (was the culprit) +time_call("detect_xcode_workspace (find_pattern_upwards *.xcworkspace)", function() + local d = require("swift.features.project_detector") + return d.detect_xcode_workspace(vim.fn.getcwd()) +end) + +-- 4b. Project detection — xcodeproj scan +time_call("detect_xcode_project (find_pattern_upwards *.xcodeproj)", function() + local d = require("swift.features.project_detector") + return d.detect_xcode_project(vim.fn.getcwd()) +end) + +-- 4c. Project detection — SPM (io.open Package.swift, fast) +time_call("detect_spm (find_file_upwards Package.swift)", function() + local d = require("swift.features.project_detector") + return d.detect_spm(vim.fn.getcwd()) +end) + +-- 5. lsp.status() — should be instant (just checks active clients) +time_call("lsp.status() — check active clients", function() + local lsp = require("swift.features.lsp") + return lsp.status() +end) + +-- 6. debugger.find_lldb() — just exepath, instant +time_call("debugger.find_lldb() — exepath/executable", function() + local d = require("swift.features.debugger") + return d.find_lldb() +end) + +print("") +print("=== Done. All calls above 1000ms are potential hangs. ===") +vim.cmd("quit")