-
-
Notifications
You must be signed in to change notification settings - Fork 24
Integrations
Xcodebuild.nvim is integrated with nvim-tree, neo-tree, and oil.nvim to let you manage your project and files in a convenient way.
Every change in the file tree presented by these plugins will be automatically reflected in the Xcode project file.
Additionally, the Project Manager will try predicting targets for newly created files based on their location.
If you prefer to select targets manually, you can always disable it in the configuration using
integrations.nvim_tree.guess_target
or integrations.oil_nvim.guess_target
.
nvim-tree.mp4
nvim-dap plugin lets you debug applications like in any other IDE. On top of that nvim-dap-ui extension will present for you all panels with stack, breakpoints, variables, logs, etc.
To configure DAP for development:
- Download codelldb VS Code plugin from: HERE. For macOS use
darwin
version. - Unzip
vsix
file and set paths in the configuration below. - Install also nvim-dap-ui for a nice GUI to debug.
- Make sure to enable
console
window fromnvim-dap-ui
to see simulator logs.
return {
"mfussenegger/nvim-dap",
dependencies = {
"wojciech-kulik/xcodebuild.nvim"
},
config = function()
local xcodebuild = require("xcodebuild.integrations.dap")
-- SAMPLE PATH, change it to your local codelldb path
local codelldbPath = os.getenv("HOME") .. "/tools/codelldb-aarch64-darwin/extension/adapter/codelldb"
xcodebuild.setup(codelldbPath)
vim.keymap.set("n", "<leader>dd", xcodebuild.build_and_debug, { desc = "Build & Debug" })
vim.keymap.set("n", "<leader>dr", xcodebuild.debug_without_build, { desc = "Debug Without Building" })
vim.keymap.set("n", "<leader>dt", xcodebuild.debug_tests, { desc = "Debug Tests" })
vim.keymap.set("n", "<leader>dT", xcodebuild.debug_class_tests, { desc = "Debug Class Tests" })
vim.keymap.set("n", "<leader>b", xcodebuild.toggle_breakpoint, { desc = "Toggle Breakpoint" })
vim.keymap.set("n", "<leader>B", xcodebuild.toggle_message_breakpoint, { desc = "Toggle Message Breakpoint" })
vim.keymap.set("n", "<leader>dx", xcodebuild.terminate_session, { desc = "Terminate Debugger" })
end,
}
Since iOS 17, a new secure connection between Mac and mobile devices is required.
Xcodebuild.nvim uses pymobiledevice3 to establish a special trusted tunnel that
is later used for debugging. However, this operation requires sudo
(more details).
Showing a pop-up to enter a password every time you run a debugger would be quite annoying.
That's why the plugin provides a small script (tools/remote_debugger)
that wraps the only two operations requiring sudo
(starting a secure tunnel and closing it).
This allows you to configure passwordless access just for this single file and make it work with xcodebuild.nvim.
Caution
Giving passwordless sudo access to that file, potentially opens a gate for malicious software
that could modify the file and run some evil code using root
account. The best way to protect
that file is to create a local copy, change the owner to root
, and give write permission only
to root
. The same must be applied to the parent directory. The script below automatically secures
the file.
👉 Enable integration
Update your config with:
integrations = {
pymobiledevice = {
enabled = true,
},
}
👉 Run the following command to install & protect the script
DEST="$HOME/Library/xcodebuild.nvim" && \
SOURCE="$HOME/.local/share/nvim/lazy/xcodebuild.nvim/tools/remote_debugger" && \
ME="$(whoami)" && \
sudo install -d -m 755 -o root "$DEST" && \
sudo install -m 755 -o root "$SOURCE" "$DEST" && \
sudo bash -c "echo \"$ME ALL = (ALL) NOPASSWD: $DEST/remote_debugger\" >> /etc/sudoers"
Logs without using nvim-dap
If you don't want to use nvim-dap you can always print logs directly to your terminal by calling (from your root directory):
tail -f .nvim/xcodebuild/app_logs.log
This approach works especially well if you are using tmux.
The plugin allows you to display previews for SwiftUI, UIKit, and AppKit views directly in your Neovim.
Every time you request the preview, the plugin builds & runs the app to generate it. This process can be significantly improved by adding Hot Reload to your project.
previews.mp4
Installation:
-
Make sure that your terminal supports images.
-
Install the snacks.nvim plugin to enable image support.
-
Make sure that
image
snack is enabled. -
Install Swift Package xcodebuild-nvim-preview in your project.
-
Configure the preview in a place that gets automatically called when the app starts. Examples:
SwiftUI (supports hot reload):
import SwiftUI import XcodebuildNvimPreview @main struct MyApp: App { var body: some Scene { WindowGroup { MainView() .setupNvimPreview { HomeView() } } } }
UIKit (similar for AppKit):
import XcodebuildNvimPreview func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // ... XcodebuildNvimPreview.setup(view: MainView()) // (optional) enable hot reload for preview (requires integration with `Inject`) observeHotReload() .sink { XcodebuildNvimPreview.setup(view: HomeView()) } .store(in: &cancellables) return true }
-
Run
:XcodebuildPreviewGenerateAndShow
to generate and show the preview. Alternatively, run:XcodebuildPreviewGenerateAndShow hotReload
to keep the app running for hot reloading.
Caution
snacks.nvim doesn't support clearing the in-memory cache right now, which makes it impossible to refresh previews without restarting Neovim. If you want to use this feature, you either need to wait until it is added (#1394) or you can use my fork where I removed the cache: wojciech-kulik/snacks.nvim.
Tip
If you want to use the hot reload feature, you need to integrate your app with Inject
package.
Read more about it in Tips & Tricks.
If you like to have a nice panel with issues you can try Trouble plugin. Xcodebuild.nvim adds all errors and warnings to the quickfix list, so you can just open Trouble in quickfix
mode and you should be able to see all issues.

You can also configure it to appear automatically on build/test failure:
vim.api.nvim_create_autocmd("User", {
pattern = { "XcodebuildBuildFinished", "XcodebuildTestsFinished" },
callback = function(event)
if event.data.cancelled then
return
end
if event.data.success then
require("trouble").close()
elseif not event.data.failedCount or event.data.failedCount > 0 then
if next(vim.fn.getqflist()) then
require("trouble").open("quickfix")
else
require("trouble").close()
end
require("trouble").refresh()
end
end,
})
Plugin config:
return {
"folke/trouble.nvim",
dependencies = { "nvim-tree/nvim-web-devicons" },
event = { "BufReadPre", "BufNewFile" },
keys = {
{ "<leader>tt", "<cmd>Trouble quickfix toggle<cr>", { desc = "Open a quickfix" } },
},
opts = {},
config = function()
require("trouble").setup({
auto_open = false,
auto_close = false,
auto_preview = true,
auto_jump = false,
mode = "quickfix",
severity = vim.diagnostic.severity.ERROR,
cycle_results = false,
})
end,
}
Additionally, you can add extra bindings to easily jump between issues across the whole project:
vim.keymap.set("n", "<A-d>", "<cmd>silent cc | silent cn<cr>zz", { desc = "Jump to next issue" })
vim.keymap.set("n", "<A-s>", "<cmd>silent cc | silent cp<cr>zz", { desc = "Jump to previous issue" })
You can also integrate this plugin with lualine.nvim.

local function xcodebuild_device()
if vim.g.xcodebuild_platform == "macOS" then
return " macOS"
end
local deviceIcon = ""
if vim.g.xcodebuild_platform:match("watch") then
deviceIcon = ""
elseif vim.g.xcodebuild_platform:match("tv") then
deviceIcon = " "
elseif vim.g.xcodebuild_platform:match("vision") then
deviceIcon = " "
end
if vim.g.xcodebuild_os then
return deviceIcon .. " " .. vim.g.xcodebuild_device_name .. " (" .. vim.g.xcodebuild_os .. ")"
end
return deviceIcon .. " " .. vim.g.xcodebuild_device_name
end
------
lualine_x = {
{ "' ' .. vim.g.xcodebuild_last_status", color = { fg = "Gray" } },
{ "' ' .. vim.g.xcodebuild_test_plan", color = { fg = "#a6e3a1", bg = "#161622" } },
{ xcodebuild_device, color = { fg = "#f9e2af", bg = "#161622" } },
},
Global variables that you can use:
Variable | Description |
---|---|
vim.g.xcodebuild_device_name |
Device name (ex. iPhone 15 Pro) |
vim.g.xcodebuild_os |
OS version (ex. 16.4) |
vim.g.xcodebuild_platform |
Device platform (ex. iPhone Simulator) |
vim.g.xcodebuild_scheme |
Selected project scheme (ex. MyApp) |
vim.g.xcodebuild_test_plan |
Selected Test Plan (ex. MyAppTests) |
vim.g.xcodebuild_last_status |
Last build/test status (ex. Build Succeeded [15s]) |
Below you can find a sample configuration that intercepts xcodebuild.nvim events and sends them to fidget.nvim.
return {
"j-hui/fidget.nvim",
event = "VeryLazy",
config = function()
local fidget = require("fidget")
fidget.setup({
notification = {
window = {
normal_hl = "String", -- Base highlight group in the notification window
winblend = 0, -- Background color opacity in the notification window
border = "rounded", -- Border around the notification window
zindex = 45, -- Stacking priority of the notification window
max_width = 0, -- Maximum width of the notification window
max_height = 0, -- Maximum height of the notification window
x_padding = 1, -- Padding from right edge of window boundary
y_padding = 1, -- Padding from bottom edge of window boundary
align = "bottom", -- How to align the notification window
relative = "editor", -- What the notification window position is relative to
},
},
})
end,
}
Top of the file with xcodebuild.nvim config:
local progress_handle
xcodebuild.nvim setup options:
show_build_progress_bar = false,
logs = {
notify = function(message, severity)
local fidget = require("fidget")
if progress_handle then
progress_handle.message = message
if not message:find("Loading") then
progress_handle:finish()
progress_handle = nil
if vim.trim(message) ~= "" then
fidget.notify(message, severity)
end
end
else
fidget.notify(message, severity)
end
end,
notify_progress = function(message)
local progress = require("fidget.progress")
if progress_handle then
progress_handle.title = ""
progress_handle.message = message
else
progress_handle = progress.handle.create({
message = message,
lsp_client = { name = "xcodebuild.nvim" },
})
end
end,
},
Demo:
demo.mov
Tip
Key messages like build/test finished are published as notifications, so if you miss one, you can check out :Fidget history
.
If you installed nvim-dap and nvim-dap-ui, you can easily track your app logs. The plugin automatically
sends logs to the console
window provided by nvim-dap-ui.
To see SIMULATOR logs you don't need to run the debugger. You can just show the console
and run the app
(remember that the app must be launched using xcodebuild.nvim).
:lua require("dapui").toggle()
Important
Logs printed by NSLog
will appear only if the debugger is NOT attached.
You can always clear the console window by calling:
:lua require("xcodebuild.integrations.dap").clear_console()

Using xcodebuild.nvim you can also check the code coverage after running tests.
- Make sure that you enabled code coverage for desired targets in your test plan.
- Enable code coverage in xcodebuild config:
code_coverage = {
enabled = true,
}
- Toggle code coverage
:XcodebuildToggleCodeCoverage
or:lua require("xcodebuild.actions").toggle_code_coverage(true)
. - Run tests - once they're finished, code coverage should appear on the sidebar with line numbers.
- You can jump between code coverage marks using
:XcodebuildJumpToPrevCoverage
and:XcodebuildJumpToNextCoverage
. - You can also check out the report using
:XcodebuildShowCodeCoverageReport
command.
The plugin sends XcodebuildCoverageToggled
event that you can use to disable other plugins presenting
lines on the side bar (like gitsigns
). Example:
vim.api.nvim_create_autocmd("User", {
pattern = "XcodebuildCoverageToggled",
callback = function(event)
local isOn = event.data
require("gitsigns").toggle_signs(not isOn)
end,
})
Coverage Report Keys:
Key | Description |
---|---|
enter or tab
|
Expand or collapse the current node |
o |
Open source file |
This plugin offers a nice list of failing snapshot tests. For each test it generates a preview image combining reference, failure, and difference images into one. It works with swift-snapshot-testing library.
Run :XcodebuildFailingSnapshots
to see the list.
