wandres.dev
NINJA · IDE completo

Swift: sourcekit-lsp y proyectos Xcode

Swift en Neovim 0.12 (macOS): sourcekit-lsp de la toolchain declarado en la carpeta lsp, xcode-build-server para proyectos Xcode, parser de Treesitter, formateo con swift-format y gestión de toolchains con swiftly.

⏱ 13 min

Swift en Neovim es totalmente viable en macOS. El servidor de lenguaje, sourcekit-lsp, viene con la toolchain de Swift y de Xcode, así que no hay nada que instalar: solo hay que describirlo y activarlo. El trabajo de verdad está en que encuentre cómo se compila tu proyecto — trivial con SPM, un paso extra con Xcode.

🎯 Al terminar esta lección sabrás
  • Declarar sourcekit-lsp en lsp/sourcekit.lua y activarlo con vim.lsp.enable.
  • Evitar el choque de tipos de archivo con clangd.
  • Generar buildServer.json con xcode-build-server para proyectos Xcode.
  • Parser de Swift, formateo con swift-format y toolchains con swiftly.
⚠️
Esto es territorio macOS

sourcekit-lsp funciona mejor en macOS con Xcode instalado. En Linux funciona con la toolchain de Swift, pero sin las APIs de Apple. Como desarrollas Swift, asumimos macOS con las herramientas de línea de comandos de Xcode.

Declarar sourcekit-lsp

sourcekit-lsp no se instala aparte: viene con la toolchain, así que Mason no pinta nada aquí (su trabajo es conseguir binarios, y este ya lo tienes). Tampoco necesitas nvim-lspconfig: en Neovim 0.12 basta con una ficha en la carpeta lsp/ de tu runtimepath y una línea para activarla.

return {
  cmd = { "xcrun", "sourcekit-lsp" },
  filetypes = { "swift" },
  root_markers = {
    "buildServer.json",
    ".bsp",
    "Package.swift",
    "compile_commands.json",
    ".git",
  },
  capabilities = {
    workspace = {
      didChangeWatchedFiles = { dynamicRegistration = true },
    },
  },
}
vim.lsp.enable("sourcekit")

Dos decisiones de esa ficha merecen explicación.

La primera es el cmd. El binario se llama sourcekit-lsp a secas, y si tu toolchain está en el PATH puedes invocarlo así. Anteponer xcrun obliga a usar el de la toolchain activa de Xcode, que es justo lo que quieres cuando tienes varias instaladas: el propio proyecto de xcode-build-server avisa de que los fallos raros —del tipo “no se puede cargar la biblioteca estándar”— casi siempre son una versión de build y una de sourcekit-lsp que no coinciden. Con xcode-select cambias de toolchain y xcrun sourcekit-lsp te sigue.

La segunda es filetypes. sourcekit-lsp también sabe de C, C++ y Objective-C, y su ficha por defecto en el catálogo de nvim-lspconfig los declara todos. Si además usas clangd (lección 2.5), los dos servidores se adjuntarán al mismo buffer de C y verás diagnósticos duplicados. Deja { "swift" } a secas, o añade "objc" y "objcpp" si trabajas con código mixto de Apple, y que C y C++ sean territorio de clangd.

ℹ️
Comprueba la toolchain antes de culpar al editor

Verifica en el terminal que la tienes: xcrun sourcekit-lsp --help y swift --version. Si falla, instala las herramientas con xcode-select --install o abre Xcode una vez para que termine de configurarse. Ya dentro de Neovim, :checkhealth vim.lsp te dice si el cliente está adjunto y qué raíz resolvió, y el comando :lsp te da el estado en el buffer actual.

SPM: funciona directo

En un paquete de Swift Package Manager (con Package.swift en la raíz), sourcekit-lsp funciona sin más: abre cualquier .swift y tendrás completado, ir a definición y diagnósticos. Es el caso ideal, y también el motivo de que Package.swift esté en los root_markers.

Proyectos Xcode: xcode-build-server

Para proyectos .xcodeproj o .xcworkspace, sourcekit-lsp necesita un buildServer.json que le explique el grafo de compilación. Lo genera xcode-build-server, una implementación del Build Server Protocol que traduce entre Xcode y sourcekit-lsp:

brew install xcode-build-server

# En la raíz del proyecto (donde vive el .xcodeproj o el .xcworkspace):
xcode-build-server config -project MiApp.xcodeproj -scheme MiApp
# o, para un workspace:
xcode-build-server config -workspace MiApp.xcworkspace -scheme MiApp

Esto crea o actualiza buildServer.json en ese directorio, que debe ser la raíz que resuelve tu cliente LSP — por eso buildServer.json va el primero en root_markers. A partir de ahí, el servidor toma los flags de compilación del registro de la última build de Xcode. Si el proyecto cambia (archivos nuevos, otro SDK, macros condicionales) y algo deja de funcionar, no toques la configuración: compila en Xcode y los flags se refrescan solos.

💡
Si no encuentra definiciones, compila

sourcekit-lsp usa indexado durante la compilación: el índice de símbolos se construye cuando construyes el proyecto, no cuando abres el archivo. En un proyecto recién clonado, ir a definición y buscar referencias funcionarán a medias hasta la primera build completa. No es un fallo de configuración, es cómo está diseñado.

El parser de Swift

⚠️
La API de Treesitter cambió de raíz

Nada de require("nvim-treesitter.configs").setup(...), ensure_installed ni highlight: ese módulo ya no existe en la rama main, que es la única viva. El plugin instala parsers y aporta queries; el resaltado y el plegado los pone Neovim.

require("nvim-treesitter").install({ "swift" })

-- Sin foldlevelstart = 99, cada archivo se abre ENTERO PLEGADO.
-- Ponlo una vez en tus opciones (leccion 2.3).
vim.o.foldlevelstart = 99

vim.api.nvim_create_autocmd("FileType", {
  pattern = { "swift" },
  callback = function()
    vim.treesitter.start()
    vim.wo[0][0].foldexpr = "v:lua.vim.treesitter.foldexpr()"
    vim.wo[0][0].foldmethod = "expr"
  end,
})

En SwiftUI, donde las vistas anidan mucho, el plegado por sintaxis cambia la vida: zc cierra el bloque bajo el cursor y zR lo abre todo. Y los objetos de texto de la lección 2.3 (vaf, cif, ]f) funcionan igual que en cualquier otro lenguaje con parser.

Formateo con swift-format

Ya declaraste swift = { "swift_format" } en conform (lección 2.4). Desde Swift 6, swift-format viene incluido en la toolchain y puedes invocarlo como swift format (con espacio) o localizar el binario con xcrun --find swift-format; si tu toolchain es más antigua, brew install swift-format.

Para el estilo, swift-format busca un fichero JSON llamado .swift-format en el directorio del archivo y va subiendo. La forma sensata de crearlo no es escribirlo a mano, sino volcar el que ya usa y editarlo:

swift-format dump-configuration > .swift-format

Con swift-format dump-configuration --effective compruebas qué configuración se aplicaría de verdad desde el directorio actual, teniendo en cuenta los .swift-format encontrados.

Gestionar toolchains con swiftly

swiftly es el gestor oficial de toolchains de Swift: instala varias versiones, cambia la activa y actualiza. Instálalo desde la página de instalación de swift.org y a partir de ahí:

swiftly list-available          # qué toolchains puedes instalar
swiftly install main-snapshot   # una nightly de la rama main
swiftly use main-snapshot       # cámbiate a ella
swiftly self-update             # actualizar el propio swiftly

Un detalle que ahorra discusiones en equipo: swiftly lee un fichero .swift-version en el proyecto, así que la versión recomendada puede viajar con el repositorio en lugar de vivir en la cabeza de cada uno.

💡
Compilar y correr sin salir de Neovim

Para builds, simuladores, tests y depuración de apps de Xcode desde el editor, mira xcodebuild.nvim (wojciech-kulik/xcodebuild.nvim): cubre iOS, iPadOS, watchOS, tvOS, visionOS y macOS, tiene explorador de tests, cobertura, gestión de simuladores e integración con nvim-dap. Es un plugin grande y con muchas piezas opcionales, así que su instalación y configuración están documentadas en su wiki; sigue esa, no un fragmento copiado de un blog.

Expectativas realistas, y por qué merece la pena igual

El ecosistema Swift en Neovim es más pequeño que el de Rust o el del frontend, y sourcekit-lsp puede tardar en indexar proyectos Xcode grandes. Aun así, para editar Swift —SwiftUI, paquetes SPM, lógica de servidor— tienes completado, navegación, diagnósticos, formateo y depuración. Fíjate en dónde ha estado el trabajo de esta lección: casi nada ha sido “configurar el editor”. Ha sido explicarle al servidor cómo se compila el proyecto, que es un problema del sistema de build, no del editor. Esa distinción se generaliza: cuando un LSP va mal, pregúntate primero si el problema está en el editor, en el servidor o en el proyecto. Nueve de cada diez veces está en el tercero, y ninguna configuración de Neovim lo va a arreglar.

⚔️ Swift funcionando
  1. Confirma xcrun sourcekit-lsp --help en el terminal.
  2. Escribe lsp/sourcekit.lua con filetypes limitado a Swift y actívalo con vim.lsp.enable.
  3. Abre un paquete SPM y verifica completado con K y gd.
  4. En un proyecto Xcode, genera buildServer.json con xcode-build-server, compila una vez en Xcode y vuelve a probar gd.
  5. Instala el parser de Swift y pliega una vista de SwiftUI con zc.
  6. Vuelca un .swift-format con dump-configuration, baja el ancho de línea y guarda un archivo mal formateado.