lsp.lua 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. local blink = require('blink.cmp')
  2. local lspconfig = require('lspconfig')
  3. -- 1. Setup blink.cmp
  4. blink.setup({
  5. keymap = {
  6. preset = 'default',
  7. ['<CR>'] = { 'accept', 'fallback' },
  8. ['<Tab>'] = { 'select_next', 'fallback' },
  9. ['<S-Tab>'] = { 'select_prev', 'fallback' },
  10. },
  11. snippets = { preset = 'luasnip' },
  12. appearance = {
  13. use_nvim_cmp_as_default = true,
  14. nerd_font_variant = 'mono'
  15. },
  16. sources = {
  17. default = { 'lsp', 'path', 'buffer', 'snippets' },
  18. },
  19. fuzzy = {
  20. implementation = "prefer_rust",
  21. },
  22. signature = { enabled = true }
  23. })
  24. -- Load custom snippets from ~/.config/nvim/snips
  25. require("luasnip.loaders.from_vscode").lazy_load({ paths = { "~/.config/nvim/snips" } })
  26. require("luasnip.loaders.from_snipmate").lazy_load({ paths = { "~/.config/nvim/snips" } })
  27. -- 2. Define on_attach
  28. local on_attach = function(client, bufnr)
  29. -- Keymaps are handled globally in keymaps.lua (formerly bindings.vim)
  30. -- Enable CodeLens if supported
  31. if client.supports_method("textDocument/codeLens") then
  32. vim.api.nvim_create_autocmd({ "BufEnter", "CursorHold", "InsertLeave" }, {
  33. buffer = bufnr,
  34. callback = function()
  35. vim.lsp.codelens.refresh({ bufnr = bufnr })
  36. end,
  37. })
  38. end
  39. -- Enable Document Highlight if supported
  40. if client.supports_method("textDocument/documentHighlight") then
  41. vim.api.nvim_create_autocmd({ "CursorHold", "CursorHoldI" }, {
  42. buffer = bufnr,
  43. callback = function()
  44. vim.lsp.buf.document_highlight()
  45. end,
  46. })
  47. vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, {
  48. buffer = bufnr,
  49. callback = function()
  50. vim.lsp.buf.clear_references()
  51. end,
  52. })
  53. end
  54. -- Format on Type (Commented out - prefer Format on Save in conform.nvim)
  55. -- if client.supports_method("textDocument/onTypeFormatting") then
  56. -- vim.api.nvim_create_autocmd("InsertLeave", {
  57. -- buffer = bufnr,
  58. -- callback = function()
  59. -- vim.lsp.buf.format({ bufnr = bufnr, async = true })
  60. -- end,
  61. -- })
  62. -- end
  63. end
  64. -- 3. Configure Servers using Neovim 0.11 API where possible
  65. local capabilities = blink.get_lsp_capabilities()
  66. -- Basic servers
  67. local servers = { 'pyright', 'ruff', 'bashls', 'html', 'cssls', 'jdtls', 'rust_analyzer', 'clangd' }
  68. for _, server in ipairs(servers) do
  69. vim.lsp.config(server, {
  70. capabilities = capabilities,
  71. on_attach = on_attach,
  72. })
  73. vim.lsp.enable(server)
  74. end
  75. -- cspell LSP integration (using the arch package cspell-lsp)
  76. vim.lsp.config('cspell', {
  77. cmd = { 'cspell-lsp', '--stdio' },
  78. filetypes = {
  79. "python", "sh", "rust", "kotlin", "java", "c", "cpp", "cmake",
  80. "markdown", "text", "gitcommit", "lua", "json", "yaml"
  81. },
  82. -- Explicitly tell Neovim where to look for the project root
  83. root_markers = { 'cspell.config.yaml', 'cspell.json', '.cspell.json', 'package.json', '.git' },
  84. capabilities = capabilities,
  85. -- initializationOptions often helps where 'settings' fails
  86. initializationOptions = {
  87. enabled = true,
  88. },
  89. -- Force absolute path in on_init to stop it from creating cspell.json
  90. on_init = function(client)
  91. local root = client.root_dir
  92. if root then
  93. local config_file = root .. "/cspell.config.yaml"
  94. client.config.settings.cSpell.configFile = config_file
  95. client.notify('workspace/didChangeConfiguration', { settings = client.config.settings })
  96. end
  97. return true
  98. end,
  99. -- Boost severity so we actually "see" the spell errors clearly (they default to Information)
  100. handlers = {
  101. ["textDocument/publishDiagnostics"] = function(err, result, ctx, config)
  102. if result and result.diagnostics then
  103. for _, diagnostic in ipairs(result.diagnostics) do
  104. if diagnostic.source == "cSpell" then
  105. -- Map to HINT so we can color it separately from code WARNings
  106. diagnostic.severity = vim.diagnostic.severity.HINT
  107. end
  108. end
  109. end
  110. vim.lsp.handlers["textDocument/publishDiagnostics"](err, result, ctx, config)
  111. end,
  112. },
  113. settings = {
  114. cSpell = {
  115. enabled = true,
  116. }
  117. },
  118. on_attach = function(client, bufnr)
  119. on_attach(client, bufnr)
  120. -- Visual confirmation that cspell is handling this buffer
  121. vim.notify("CSpell LSP Active (nospell)", vim.log.levels.INFO)
  122. vim.opt_local.spell = false
  123. end,
  124. })
  125. vim.lsp.enable('cspell')
  126. -- Custom Veridian setup (Verilog)
  127. vim.lsp.config('veridian', {
  128. cmd = { 'veridian' },
  129. filetypes = { 'systemverilog', 'verilog' },
  130. capabilities = capabilities,
  131. on_attach = on_attach,
  132. })
  133. vim.lsp.enable('veridian')
  134. -- Official JetBrains Kotlin LSP
  135. vim.lsp.config('kotlin_lsp', {
  136. cmd = { 'kotlin-lsp' },
  137. filetypes = { 'kotlin' },
  138. -- Tell the LSP to attach at the root of your Android Gradle project
  139. root_markers = { 'settings.gradle.kts', 'settings.gradle', 'build.gradle.kts', 'build.gradle' },
  140. capabilities = capabilities,
  141. on_attach = on_attach,
  142. -- Note: We drop the old fwcd/kotlin-language-server 'settings' block.
  143. -- The JetBrains server is much smarter and infers JVM targets, hints,
  144. -- and completions directly from IntelliJ's internal engine and your Gradle model.
  145. })
  146. vim.lsp.set_log_level("trace")
  147. vim.lsp.enable('kotlin_lsp')
  148. -- 4. Formatting
  149. local conform = require("conform")
  150. conform.setup({
  151. formatters_by_ft = {
  152. kotlin = { "ktlint" },
  153. python = { "black" },
  154. java = { "lsp" },
  155. sh = { "shfmt" },
  156. rust = { "rustfmt" },
  157. c = { "clang-format" },
  158. cpp = { "clang-format" },
  159. cmake = { "cmake_format" },
  160. },
  161. format_on_save = {
  162. timeout_ms = 2000,
  163. lsp_fallback = true,
  164. },
  165. })
  166. -- 5. Linting
  167. local lint = require("lint")
  168. lint.linters_by_ft = {
  169. python = { "pylint" },
  170. sh = { "shellcheck" },
  171. rust = { "clippy" },
  172. }
  173. vim.api.nvim_create_autocmd({ "BufWritePost", "BufEnter" }, {
  174. callback = function()
  175. lint.try_lint()
  176. end,
  177. })
  178. -- 6. Diagnostics config
  179. vim.diagnostic.config({
  180. virtual_text = true,
  181. signs = true,
  182. update_in_insert = false,
  183. underline = true,
  184. severity_sort = true,
  185. float = { border = 'rounded' },
  186. })
  187. -- 7. Diagnostic & Spell Highlighting
  188. vim.api.nvim_set_hl(0, 'DiagnosticUnderlineError', { undercurl = true, sp = '#ff0000' })
  189. vim.api.nvim_set_hl(0, 'DiagnosticUnderlineWarn', { undercurl = true, sp = '#ff8800' })
  190. vim.api.nvim_set_hl(0, 'DiagnosticUnderlineHint', { undercurl = true, sp = '#ffff00' })
  191. vim.api.nvim_set_hl(0, 'SpellBad', { undercurl = true, sp = '#ffff00' })
  192. vim.api.nvim_set_hl(0, 'SpellCap', { undercurl = true, sp = '#ffff00' })
  193. vim.api.nvim_set_hl(0, 'SpellLocal', { undercurl = true, sp = '#ffff00' })
  194. vim.api.nvim_set_hl(0, 'SpellRare', { undercurl = true, sp = '#ffff00' })