-
-
Notifications
You must be signed in to change notification settings - Fork 47
Neovim Configuration
Below you will find basic information on how to prepare your Neovim for iOS development.
If you'd like to get more details, you can read my article: The complete guide to iOS & macOS development in Neovim.
Here you can find my sample config for iOS development. You can try it out without touching your own config.
Apple provides together with Xcode an LSP server called sourcekit-lsp. You can integrate it by using nvim-lspconfig plugin. To properly display code completion you will also need nvim-cmp or blink.cmp. On top of that, you also need Build Server Protocol (BSP), which will let the LSP understand the project structure (xcodeproj / xcworkspace). For that purpose, you need to install xcode-build-server.
👉 nvim-lspconfig configuration
return {
"neovim/nvim-lspconfig",
dependencies = {
{ "hrsh7th/cmp-nvim-lsp" }, -- choose which plugin you use
{ "saghen/blink.cmp" }, -- choose which plugin you use
{ "antosha417/nvim-lsp-file-operations", config = true },
},
config = function()
-- choose which plugin you use:
local capabilities = require("cmp_nvim_lsp").default_capabilities()
-- local capabilities = require("blink.cmp").get_lsp_capabilities()
local lspconfig = vim.lsp.config
local opts = { noremap = true, silent = true }
local on_attach = function(_, bufnr)
opts.buffer = bufnr
opts.desc = "Show line diagnostics"
vim.keymap.set("n", "<leader>d", vim.diagnostic.open_float, opts)
opts.desc = "Show documentation for what is under cursor"
vim.keymap.set("n", "K", vim.lsp.buf.hover, opts)
opts.desc = "Show LSP definition"
vim.keymap.set("n", "gd", "<cmd>Telescope lsp_definitions trim_text=true<cr>", opts)
end
lspconfig("sourcekit", {
capabilities = capabilities,
on_attach = on_attach,
root_dir = function(_, callback)
callback(
require("lspconfig.util").root_pattern("Package.swift")(vim.fn.getcwd())
or require("lspconfig.util").find_git_ancestor(vim.fn.getcwd())
)
end,
cmd = { vim.trim(vim.fn.system("xcrun -f sourcekit-lsp")) }
})
vim.lsp.enable("sourcekit")
-- nice icons
local signs = { Error = " ", Warn = " ", Hint = " ", Info = " " }
for type, icon in pairs(signs) do
local hl = "DiagnosticSign" .. type
vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = "" })
end
end,
}👉 nvim-cmp configuration
return {
"hrsh7th/nvim-cmp",
event = "InsertEnter",
dependencies = {
"hrsh7th/cmp-buffer", -- source for text in buffer
"hrsh7th/cmp-path", -- source for file system paths
"L3MON4D3/LuaSnip", -- snippet engine
"saadparwaiz1/cmp_luasnip", -- for autocompletion
"rafamadriz/friendly-snippets", -- useful snippets
"onsails/lspkind.nvim", -- vs-code like pictograms
},
config = function()
local cmp = require("cmp")
local luasnip = require("luasnip")
local lspkind = require("lspkind")
-- loads vscode style snippets from installed plugins (e.g. friendly-snippets)
require("luasnip.loaders.from_vscode").lazy_load()
cmp.setup({
completion = {
completeopt = "menu,menuone,preview",
},
snippet = { -- configure how nvim-cmp interacts with snippet engine
expand = function(args)
luasnip.lsp_expand(args.body)
end,
},
mapping = cmp.mapping.preset.insert({
["<C-k>"] = cmp.mapping.select_prev_item(), -- previous suggestion
["<C-j>"] = cmp.mapping.select_next_item(), -- next suggestion
["<C-Space>"] = cmp.mapping.complete(), -- show completion suggestions
["<C-e>"] = cmp.mapping.abort(), -- close completion window
["<CR>"] = cmp.mapping.confirm({ select = false, behavior = cmp.ConfirmBehavior.Replace }),
["<C-b>"] = cmp.mapping(function(fallback)
if luasnip.jumpable(-1) then
luasnip.jump(-1)
else
fallback()
end
end, { "i", "s" }),
["<C-f>"] = cmp.mapping(function(fallback)
if luasnip.jumpable(1) then
luasnip.jump(1)
else
fallback()
end
end, { "i", "s" }),
}),
-- sources for autocompletion
sources = cmp.config.sources({
{ name = "nvim_lsp" },
{ name = "luasnip" }, -- snippets
{ name = "buffer" }, -- text within current buffer
{ name = "path" }, -- file system paths
}),
-- configure lspkind for vs-code like pictograms in completion menu
formatting = {
format = lspkind.cmp_format({
maxwidth = 50,
ellipsis_char = "...",
}),
},
})
end,
}👉 blink.cmp configuration
return {
"saghen/blink.cmp",
dependencies = {
"rafamadriz/friendly-snippets",
"echasnovski/mini.icons",
"onsails/lspkind-nvim",
},
event = "VeryLazy",
version = "*",
opts = {
enabled = function()
return not vim.tbl_contains({ "oil" }, vim.bo.filetype)
end,
keymap = {
preset = "enter",
["<C-h>"] = {
function(cmp)
cmp.show_documentation()
end,
},
["<tab>"] = {},
},
signature = { enabled = false },
appearance = {
-- use_nvim_cmp_as_default = true,
nerd_font_variant = "mono",
},
sources = {
default = { "lsp", "path", "snippets", "buffer" },
},
cmdline = {
keymap = {
["<Tab>"] = { "show", "select_next" },
["<S-Tab>"] = { "select_prev" },
["<cr>"] = { "select_and_accept", "fallback" },
["<space>"] = { "select_and_accept", "fallback" },
["<right>"] = { "select_and_accept", "fallback" },
["<down>"] = { "select_next", "fallback" },
["<up>"] = { "select_prev", "fallback" },
["<esc>"] = {
"cancel",
function()
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("<C-c>", true, false, true), "n", true)
end,
},
},
sources = function()
local type = vim.fn.getcmdtype()
-- Search forward and backward
if type == "/" or type == "?" then
return {}
end
-- Commands
if type == ":" or type == "@" then
return { "cmdline", "path" }
end
return {}
end,
completion = { ghost_text = { enabled = false } },
},
completion = {
trigger = {
show_on_trigger_character = true,
},
documentation = {
auto_show = true,
auto_show_delay_ms = 200,
window = {
border = "rounded",
winhighlight = "Normal:Normal,FloatBorder:FloatBorder,CursorLine:BlinkCmpDocCursorLine,Search:None",
},
},
list = {
selection = {
auto_insert = false,
},
},
menu = {
border = "rounded",
draw = {
gap = 2,
components = {
kind_icon = {
ellipsis = false,
highlight = function(ctx)
local _, hl, _ = require("mini.icons").get("lsp", ctx.kind)
return hl
end,
text = function(ctx)
local icon = require("lspkind").symbolic(ctx.kind, { mode = "symbol" })
return icon .. ctx.icon_gap
end,
},
},
},
winhighlight = "Normal:Normal,FloatBorder:FloatBorder,CursorLine:BlinkCmpMenuSelection,Search:None",
},
},
},
opts_extend = { "sources.default" },
}👉 xcode-build-server configuration
First, you need to install it:
brew install xcode-build-serverThen, you can configure it for your project:
xcode-build-server config -workspace <xcworkspace> -scheme <scheme>
# or
xcode-build-server config -project <xcodeproj> -scheme <scheme> Make sure to call it from your project root directory. It should create buildServer.json. Open it and make sure that all the information there is correct.
👉 Improved LSP diagnostic icons
local signs = { Error = " ", Warn = " ", Hint = " ", Info = " " }
for type, icon in pairs(signs) do
local hl = "DiagnosticSign" .. type
vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = "" })
end
vim.diagnostic.config({
float = { border = "rounded" },
virtual_text = true,
signs = {
text = {
[vim.diagnostic.severity.ERROR] = signs.Error,
[vim.diagnostic.severity.WARN] = signs.Warn,
[vim.diagnostic.severity.HINT] = signs.Hint,
[vim.diagnostic.severity.INFO] = signs.Info,
},
linehl = {
[vim.diagnostic.severity.ERROR] = "ErrorMsg",
},
numhl = {
[vim.diagnostic.severity.WARN] = "WarningMsg",
},
},
})Once all steps are finished, you should be able to open your project in Neovim and see working code completion. If something doesn't work, make sure to run a clean build from Xcode first. Also, you can run :LspInfo to see if the LSP is properly attached and the root directory is detected.
SwiftFormat is a very popular tool to keep formatting consistent across the project. You can easily integrate it with Neovim by using conform.nvim plugin.
Here is a sample config:
return {
"stevearc/conform.nvim",
event = { "BufReadPre", "BufNewFile" },
config = function()
local conform = require("conform")
conform.setup({
formatters_by_ft = {
swift = { "swiftformat" },
},
format_on_save = function(bufnr)
local ignore_filetypes = { "oil" }
if vim.tbl_contains(ignore_filetypes, vim.bo[bufnr].filetype) then
return
end
return { timeout_ms = 500, lsp_fallback = true }
end,
log_level = vim.log.levels.ERROR,
})
end,
}The code will be automatically formatted on save event 🔥.
Usually, you also need some linter to detect common issues. You can easily integrate SwiftLint using nvim-lint plugin.
Here is a sample config:
return {
"mfussenegger/nvim-lint",
event = { "BufReadPre", "BufNewFile" },
config = function()
local lint = require("lint")
lint.linters_by_ft = {
swift = { "swiftlint" },
}
local lint_augroup = vim.api.nvim_create_augroup("lint", { clear = true })
vim.api.nvim_create_autocmd({ "BufWritePost", "BufReadPost", "InsertLeave", "TextChanged" }, {
group = lint_augroup,
callback = function()
if not vim.endswith(vim.fn.bufname(), "swiftinterface") then
require("lint").try_lint()
end
end,
})
vim.keymap.set("n", "<leader>ml", function()
require("lint").try_lint()
end, { desc = "Lint file" })
end,
}