Skip to content

Integrations

Wojciech Kulik edited this page Nov 25, 2024 · 29 revisions

🌳  File Tree Integration

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

 

🔬  Debugger

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 from nvim-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,
}

 

📲  Debugging On iOS 17+

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.

 

🚦  Trouble

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.

image

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" })

 

🚥  Lualine

You can also integrate this plugin with lualine.nvim.

xcodebuild.nvim - lualine
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])

 

💫  Fidget

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.

 

🐛  Application Logs

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()

 

💯  Code Coverage

xcodebuild.nvim - coverage report

Using xcodebuild.nvim you can also check the code coverage after running tests.

  1. Make sure that you enabled code coverage for desired targets in your test plan.
  2. Enable code coverage in xcodebuild config:
code_coverage = {
  enabled = true,
}
  1. Toggle code coverage :XcodebuildToggleCodeCoverage or :lua require("xcodebuild.actions").toggle_code_coverage(true).
  2. Run tests - once they're finished, code coverage should appear on the sidebar with line numbers.
  3. You can jump between code coverage marks using :XcodebuildJumpToPrevCoverage and :XcodebuildJumpToNextCoverage.
  4. 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

 

📸  Snapshot Tests Preview

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.

xcodebuild.nvim - snapshot testing