diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5798545 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*.lua] +indent_size = 2 +indent_style = space diff --git a/lua/godotdev/health.lua b/lua/godotdev/health.lua new file mode 100644 index 0000000..9f49ad0 --- /dev/null +++ b/lua/godotdev/health.lua @@ -0,0 +1,33 @@ +local M = {} + +function M.check() + vim.health.start("Godotdev.nvim") + + -- ncat + if vim.fn.executable("ncat") == 1 then + vim.health.ok("'ncat' is installed") + else + vim.health.error("'ncat' is missing! Required to attach to Godot editor LSP") + end + + -- godot-lsp + if vim.fn.executable("godot-lsp") == 1 then + vim.health.ok("'godot-lsp' executable found") + else + vim.health.warn("'godot-lsp' not found, standalone LSP mode will not work") + end + + -- Godot editor port + local handle = io.popen("nc -z 127.0.0.1 6005 >/dev/null 2>&1 && echo ok || echo fail") + if handle then + local result = handle:read("*a") + handle:close() + if result:match("ok") then + vim.health.ok("Godot editor LSP detected on port 6005") + else + vim.health.warn("Godot editor LSP not detected on port 6005") + end + end +end + +return M diff --git a/lua/init.lua b/lua/init.lua new file mode 100644 index 0000000..30a4bfe --- /dev/null +++ b/lua/init.lua @@ -0,0 +1,19 @@ +local M = {} + +M.config = { + lint_mode = false, +} + +M.setup = function(user_config) + if user_config then + M.config = vim.tbl_deep_extend("force", M.config, user_config) + end + + -- load submodules + require("godotdev.lsp").setup(M.config) + + package.loaded["godotdev.keymaps"] = nil + require("godotdev.keymaps").setup() +end + +return M diff --git a/lua/keymaps.lua b/lua/keymaps.lua new file mode 100644 index 0000000..7afb390 --- /dev/null +++ b/lua/keymaps.lua @@ -0,0 +1,103 @@ +local M = {} + +vim.api.nvim_create_autocmd("LspAttach", { + group = vim.api.nvim_create_augroup("MyLspKeymaps", { clear = true }), + callback = function(ev) + local client = vim.lsp.get_client_by_id(ev.data.client_id) + local opts = { buffer = ev.buf } + + -- Lazy-load Telescope when needed + local has_telescope, telescope_builtin = pcall(require, "telescope.builtin") + + local function map(mode, lhs, rhs, desc) + vim.keymap.set(mode, lhs, rhs, vim.tbl_extend("force", opts, { desc = desc })) + end + + -- Definitions / Declarations + if client.server_capabilities.definitionProvider then + map( + "n", + "gd", + has_telescope and telescope_builtin.lsp_definitions or vim.lsp.buf.definition, + "LSP: Go to definition" + ) + end + if client.server_capabilities.declarationProvider then + map("n", "gD", vim.lsp.buf.declaration, "LSP: Go to declaration") + end + if client.server_capabilities.typeDefinitionProvider then + map( + "n", + "gy", + has_telescope and telescope_builtin.lsp_type_definitions or vim.lsp.buf.type_definition, + "LSP: Type definition" + ) + end + if client.server_capabilities.implementationProvider then + map( + "n", + "gi", + has_telescope and telescope_builtin.lsp_implementations or vim.lsp.buf.implementation, + "LSP: Go to implementation" + ) + end + if client.server_capabilities.referencesProvider then + map( + "n", + "gr", + has_telescope and telescope_builtin.lsp_references or vim.lsp.buf.references, + "LSP: List references" + ) + end + + -- Info + if client.server_capabilities.hoverProvider then + map("n", "K", vim.lsp.buf.hover, "LSP: Hover documentation") + end + if client.server_capabilities.signatureHelpProvider then + map("n", "", vim.lsp.buf.signature_help, "LSP: Signature help") + end + + -- Symbols + if client.server_capabilities.documentSymbolProvider then + map( + "n", + "ds", + has_telescope and telescope_builtin.lsp_document_symbols or vim.lsp.buf.document_symbol, + "LSP: Document symbols" + ) + end + if client.server_capabilities.workspaceSymbolProvider then + map( + "n", + "ws", + has_telescope and telescope_builtin.lsp_dynamic_workspace_symbols or vim.lsp.buf.workspace_symbol, + "LSP: Workspace symbols" + ) + end + + -- Actions + if client.server_capabilities.renameProvider then + map("n", "rn", vim.lsp.buf.rename, "LSP: Rename symbol") + end + if client.server_capabilities.codeActionProvider then + map({ "n", "v" }, "ca", vim.lsp.buf.code_action, "LSP: Code action") + end + if client.server_capabilities.documentFormattingProvider then + map("n", "f", function() + vim.lsp.buf.format({ async = true }) + end, "LSP: Format buffer") + end + + -- Diagnostics (always available) + map("n", "gl", vim.diagnostic.open_float, "LSP: Show diagnostics") + map("n", "[d", vim.diagnostic.goto_prev, "LSP: Previous diagnostic") + map("n", "]d", vim.diagnostic.goto_next, "LSP: Next diagnostic") + end, +}) + +M.setup = function() + -- global setup if needed +end + +return M diff --git a/lua/lsp.lua b/lua/lsp.lua new file mode 100644 index 0000000..07418c6 --- /dev/null +++ b/lua/lsp.lua @@ -0,0 +1,36 @@ +local M = {} + +M.setup = function(config) + local lspconfig = require("lspconfig") + local utils = require("godotdev.utils") + local capabilities = vim.lsp.protocol.make_client_capabilities() + capabilities.textDocument.typeDefinition = nil + + -- GDscript standalone LSP + lspconfig.gdscript.setup({ + cmd = { "godot-lsp", "--stdio" }, + filetypes = { "gd", "gdscript" }, + root_dir = lspconfig.util.root_pattern("project.godot"), + capabilities = capabilities, + }) + + -- Godot editor LSP + lspconfig.godot_lsp = { + default_config = { + cmd = { "nc", "127.0.0.1", "6005" }, + root_dir = lspconfig.util.root_pattern("project.godot"), + filetypes = { "gd", "gdscript" }, + name = "godot", + }, + } + + lspconfig.godot_lsp.setup({ + capabilities = config.capabilities or vim.lsp.protocol.make_client_capabilities(), + on_attach = function(client, _) + -- Suppress known unsupported Godot messages + utils.suppress_lsp_messages(client, { "Method not found: godot/reloadScript" }) + end, + }) +end + +return M diff --git a/lua/utils.lua b/lua/utils.lua new file mode 100644 index 0000000..2fd1abe --- /dev/null +++ b/lua/utils.lua @@ -0,0 +1,24 @@ +local M = {} + +--- Suppress specific LSP messages +-- @param client vim.lsp.Client +-- @param patterns table List of string patterns to ignore in client messages +function M.suppress_lsp_messages(client, patterns) + local orig_handler = vim.lsp.handlers["window/showMessage"] + vim.lsp.handlers["window/showMessage"] = function(err, method, params, client_id) + local lsp_client = vim.lsp.get_client_by_id(client_id) + if lsp_client and lsp_client.name == client.name then + if params and params.message then + for _, pat in ipairs(patterns) do + if params.message:match(pat) then + return -- silently ignore this message + end + end + end + end + -- fallback to original handler for all other messages + orig_handler(err, method, params, client_id) + end +end + +return M diff --git a/plugin/health.lua b/plugin/health.lua new file mode 100644 index 0000000..a2dcb45 --- /dev/null +++ b/plugin/health.lua @@ -0,0 +1,5 @@ +if vim.fn.has("nvim-0.9") == 1 then + vim.api.nvim_create_user_command("CheckGodotdev", function() + require("godotdev.health").check() + end, {}) +end diff --git a/stylua.toml b/stylua.toml new file mode 100644 index 0000000..d0295e9 --- /dev/null +++ b/stylua.toml @@ -0,0 +1,5 @@ +indent_type = "Spaces" +indent_width = 2 +column_width = 120 +[sort_requires] +enabled = true