lsp.lua 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  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 = { 'env', 'NODE_PATH=' .. vim.fn.expand('~/.local/share/npm/lib/node_modules'), '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' },
  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 not root then
  93. return false -- Prevent LSP from attaching if no config file is found (single-file mode)
  94. end
  95. local config_file
  96. if vim.fn.filereadable(root .. "/cspell.json") == 1 then
  97. config_file = root .. "/cspell.json"
  98. elseif vim.fn.filereadable(root .. "/cspell.config.yaml") == 1 then
  99. config_file = root .. "/cspell.config.yaml"
  100. elseif vim.fn.filereadable(root .. "/.cspell.json") == 1 then
  101. config_file = root .. "/.cspell.json"
  102. end
  103. if config_file then
  104. client.config.settings.cSpell.configFile = config_file
  105. client.notify('workspace/didChangeConfiguration', { settings = client.config.settings })
  106. end
  107. return true
  108. end,
  109. -- Boost severity so we actually "see" the spell errors clearly (they default to Information)
  110. handlers = {
  111. ["textDocument/publishDiagnostics"] = function(err, result, ctx, config)
  112. if result and result.diagnostics then
  113. for _, diagnostic in ipairs(result.diagnostics) do
  114. if diagnostic.source == "cSpell" then
  115. -- Map to HINT so we can color it separately from code WARNings
  116. diagnostic.severity = vim.diagnostic.severity.HINT
  117. end
  118. end
  119. end
  120. vim.lsp.handlers["textDocument/publishDiagnostics"](err, result, ctx, config)
  121. end,
  122. },
  123. settings = {
  124. cSpell = {
  125. enabled = true,
  126. }
  127. },
  128. on_attach = function(client, bufnr)
  129. if not client.root_dir then
  130. vim.schedule(function()
  131. if vim.lsp.buf_detach_client then
  132. vim.lsp.buf_detach_client(bufnr, client.id)
  133. end
  134. end)
  135. return
  136. end
  137. on_attach(client, bufnr)
  138. -- Visual confirmation that cspell is handling this buffer
  139. vim.notify("CSpell LSP Active (nospell)", vim.log.levels.INFO)
  140. vim.opt_local.spell = false
  141. end,
  142. })
  143. vim.lsp.enable('cspell')
  144. -- Custom Veridian setup (Verilog)
  145. vim.lsp.config('veridian', {
  146. cmd = { 'veridian' },
  147. filetypes = { 'systemverilog', 'verilog' },
  148. capabilities = capabilities,
  149. on_attach = on_attach,
  150. })
  151. vim.lsp.enable('veridian')
  152. -- Official JetBrains Kotlin LSP
  153. vim.lsp.config('kotlin_lsp', {
  154. cmd = { 'kotlin-lsp' },
  155. filetypes = { 'kotlin' },
  156. -- Tell the LSP to attach at the root of your Android Gradle project
  157. root_markers = { 'settings.gradle.kts', 'settings.gradle', 'build.gradle.kts', 'build.gradle' },
  158. capabilities = capabilities,
  159. on_attach = on_attach,
  160. -- Note: We drop the old fwcd/kotlin-language-server 'settings' block.
  161. -- The JetBrains server is much smarter and infers JVM targets, hints,
  162. -- and completions directly from IntelliJ's internal engine and your Gradle model.
  163. })
  164. vim.lsp.set_log_level("trace")
  165. vim.lsp.enable('kotlin_lsp')
  166. -- 4. Formatting
  167. local conform = require("conform")
  168. conform.setup({
  169. formatters_by_ft = {
  170. kotlin = { "ktlint" },
  171. python = { "black" },
  172. java = { "lsp" },
  173. sh = { "shfmt" },
  174. rust = { "rustfmt" },
  175. c = { "clang-format" },
  176. cpp = { "clang-format" },
  177. cmake = { "cmake_format" },
  178. },
  179. format_on_save = {
  180. timeout_ms = 2000,
  181. lsp_fallback = true,
  182. },
  183. })
  184. -- 5. Linting
  185. local lint = require("lint")
  186. lint.linters_by_ft = {
  187. python = { "pylint" },
  188. sh = { "shellcheck" },
  189. rust = { "clippy" },
  190. }
  191. vim.api.nvim_create_autocmd({ "BufWritePost", "BufEnter" }, {
  192. callback = function()
  193. lint.try_lint()
  194. end,
  195. })
  196. -- 6. Diagnostics config
  197. vim.diagnostic.config({
  198. virtual_text = true,
  199. signs = true,
  200. update_in_insert = false,
  201. underline = true,
  202. severity_sort = true,
  203. float = { border = 'rounded' },
  204. })
  205. -- 7. Diagnostic & Spell Highlighting
  206. vim.api.nvim_set_hl(0, 'DiagnosticUnderlineError', { undercurl = true, sp = '#ff0000' })
  207. vim.api.nvim_set_hl(0, 'DiagnosticUnderlineWarn', { undercurl = true, sp = '#ff8800' })
  208. vim.api.nvim_set_hl(0, 'DiagnosticUnderlineHint', { undercurl = true, sp = '#ffff00' })
  209. vim.api.nvim_set_hl(0, 'SpellBad', { undercurl = true, sp = '#ffff00' })
  210. vim.api.nvim_set_hl(0, 'SpellCap', { undercurl = true, sp = '#ffff00' })
  211. vim.api.nvim_set_hl(0, 'SpellLocal', { undercurl = true, sp = '#ffff00' })
  212. vim.api.nvim_set_hl(0, 'SpellRare', { undercurl = true, sp = '#ffff00' })