Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 30 additions & 3 deletions lua/swift/core/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
77 changes: 77 additions & 0 deletions tests/test_health_speed.lua
Original file line number Diff line number Diff line change
@@ -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")
Loading