How can I get Vim Features inside GDScript code editor ?
Features like Motion, Jumps, Registers, Global Commands, Substitution
How can I get Vim Features inside GDScript code editor ?
Features like Motion, Jumps, Registers, Global Commands, Substitution
I'm just getting into nvim myself so I have wondered the same.
I think you have two options.
Option 1:
You could try plugins like this:
Option 2:
Look into resources like these to help you get vim setup as an external code editor for Godot.
Resource 1: https://github.com/habamax/vim-godot
Resource 2:
Thanks
External Editor seems the best.
Though it does not allow drag & drop to create $Node Path
I use this dap config, and lsp config. you don't need special plugins for any functionality, just a bit of trial and error.
return {
'mfussenegger/nvim-dap',
dependencies = {
'rcarriga/nvim-dap-ui',
'nvim-neotest/nvim-nio',
'theHamsta/nvim-dap-virtual-text',
'nvim-treesitter/nvim-treesitter',
},
lazy = false,
keys = {
{
'<F5>',
mode = { 'n' },
function()
require('dap').continue()
end,
desc = 'F5 dap ~ [c]ontinue',
},
{
'<F10>',
mode = { 'n' },
function()
require('dap').step_over()
end,
desc = 'dap ~ step [o]ver',
},
{
'<F11>',
mode = { 'n' },
function()
require('dap').step_into()
end,
desc = 'F11 dap ~ step into',
},
{
'<S-F11>',
mode = { 'n' },
function()
require('dap').step_out()
end,
desc = 'Shift F11 dap ~ Step [O]ut',
},
{
'<Leader>b',
mode = { 'n' },
function()
require('dap').toggle_breakpoint()
end,
desc = 'dap ~ toggle [b]reakpoint',
},
},
config = function()
local dap = require 'dap'
local dapui = require 'dapui'
-- Configure dapui
dapui.setup {
layouts = {
{
elements = {
{ id = 'scopes', size = 0.25 },
'breakpoints',
'stacks',
'watches',
},
size = 40,
position = 'left',
},
{
elements = {
'repl',
'console',
},
size = 0.25,
position = 'bottom',
},
},
}
-- Automatically open/close dapui
dap.listeners.before.attach.dapui_config = function()
dapui.open()
end
dap.listeners.before.launch.dapui_config = function()
dapui.open()
end
dap.listeners.before.event_terminated.dapui_config = function()
dapui.close()
end
dap.listeners.before.event_exited.dapui_config = function()
dapui.close()
end
-- Helper function to find Godot project file and directory
local function find_godot_project()
-- Create cache to avoid repeated lookups
if not _G.godot_project_cache then
_G.godot_project_cache = {}
end
-- Start with current working directory
local current_dir = vim.fn.getcwd()
-- Check cache first
if _G.godot_project_cache[current_dir] then
return _G.godot_project_cache[current_dir].file_path, _G.godot_project_cache[current_dir].dir_path
end
-- Function to check if a directory contains a project.godot file
local function has_project_file(dir)
local project_file = dir .. '/project.godot'
local stat = vim.uv.fs_stat(project_file)
if stat and stat.type == 'file' then
return project_file, dir
else
return nil, nil
end
end
-- Check current directory first
local project_file, project_dir = has_project_file(current_dir)
if project_file then
_G.godot_project_cache[current_dir] = { file_path = project_file, dir_path = project_dir }
vim.notify('Found Godot project at: ' .. project_file, vim.log.levels.INFO)
return project_file, project_dir
end
-- Search in parent directories up to a reasonable limit
local max_depth = 5
local dir = current_dir
for i = 1, max_depth do
-- Get parent directory
local parent = vim.fn.fnamemodify(dir, ':h')
-- Stop if we've reached the root
if parent == dir then
break
end
dir = parent
-- Check if this directory has a project.godot file
local project_file, project_dir = has_project_file(dir)
if project_file then
_G.godot_project_cache[current_dir] = { file_path = project_file, dir_path = project_dir }
vim.notify('Found Godot project in parent directory: ' .. project_file, vim.log.levels.INFO)
return project_file, project_dir
end
end
-- Search in immediate subdirectories (first level only)
local handle = vim.uv.fs_scandir(current_dir)
if handle then
while true do
local name, type = vim.uv.fs_scandir_next(handle)
if not name then
break
end
-- Only check directories
if type == 'directory' then
local subdir = current_dir .. '/' .. name
local project_file, project_dir = has_project_file(subdir)
if project_file then
_G.godot_project_cache[current_dir] = { file_path = project_file, dir_path = project_dir }
vim.notify('Found Godot project in subdirectory: ' .. project_file, vim.log.levels.INFO)
return project_file, project_dir
end
end
end
end
-- If still not found, ask the user
local input_dir = vim.fn.input('Godot project directory: ', current_dir, 'dir')
-- Validate the input path
if input_dir ~= '' then
local project_file, project_dir = has_project_file(input_dir)
if project_file then
_G.godot_project_cache[current_dir] = { file_path = project_file, dir_path = project_dir }
return project_file, project_dir
end
end
vim.notify('No valid Godot project found. Using current directory.', vim.log.levels.WARN)
return current_dir .. '/project.godot', current_dir
end
-- Function to debug and print the full command that will be executed
local function debug_command(executable, args)
local full_command = executable
for _, arg in ipairs(args) do
-- Properly quote arguments with spaces
if arg:find ' ' then
full_command = full_command .. ' "' .. arg .. '"'
else
full_command = full_command .. ' ' .. arg
end
end
local debug_msg = 'Executing: ' .. full_command
vim.notify(debug_msg, vim.log.levels.INFO)
vim.notify(debug_msg)
-- Debug environment info
vim.notify('Current working directory: ' .. vim.fn.getcwd())
vim.notify('HOME env: ' .. (os.getenv 'HOME' or 'not set'))
vim.notify('DISPLAY env: ' .. (os.getenv 'DISPLAY' or 'not set'))
vim.notify('XDG_SESSION_TYPE env: ' .. (os.getenv 'XDG_SESSION_TYPE' or 'not set'))
return args
end
-- Path to the Godot executable
local godot_executable = '/usr/lib/godot-mono/godot.linuxbsd.editor.x86_64.mono'
-- Get important environment variables
local function get_env_vars()
return {
-- Graphics-related variables (crucial for GUI apps)
DISPLAY = os.getenv 'DISPLAY' or ':0',
WAYLAND_DISPLAY = os.getenv 'WAYLAND_DISPLAY',
XDG_SESSION_TYPE = os.getenv 'XDG_SESSION_TYPE',
XAUTHORITY = os.getenv 'XAUTHORITY',
-- Audio-related variables
PULSE_SERVER = os.getenv 'PULSE_SERVER',
-- User-related variables
HOME = os.getenv 'HOME',
USER = os.getenv 'USER',
LOGNAME = os.getenv 'LOGNAME',
-- Path-related variables
PATH = os.getenv 'PATH',
LD_LIBRARY_PATH = os.getenv 'LD_LIBRARY_PATH',
-- Locale variables
LANG = os.getenv 'LANG' or 'en_US.UTF-8',
LC_ALL = os.getenv 'LC_ALL',
-- XDG variables
XDG_RUNTIME_DIR = os.getenv 'XDG_RUNTIME_DIR',
XDG_DATA_HOME = os.getenv 'XDG_DATA_HOME',
XDG_CONFIG_HOME = os.getenv 'XDG_CONFIG_HOME',
-- Other potentially relevant variables
SHELL = os.getenv 'SHELL',
TERM = os.getenv 'TERM',
DBUS_SESSION_BUS_ADDRESS = os.getenv 'DBUS_SESSION_BUS_ADDRESS',
}
end
-- Standard GDScript adapter for non-C# projects
dap.adapters.godot = {
type = 'server',
host = '127.0.0.1',
port = 6006,
}
dap.configurations.gdscript = {
{
type = 'godot',
request = 'launch',
name = 'Launch Scene',
project = '${workspaceFolder}',
launch_scene = true,
},
}
-- Direct launch approach using netcoredbg to start Godot-Mono
dap.adapters.coreclr = {
type = 'executable',
command = '/home/jamie/.local/share/nvim/mason/bin/netcoredbg',
args = {
'--interpreter=vscode',
'--',
godot_executable,
},
}
dap.configurations.cs = {
-- Launch Godot editor with project - simple approach
{
type = 'coreclr',
request = 'launch',
name = 'Simple Editor Launch',
cwd = function()
local project_file,project_dir = find_godot_project()
vim.notify("cwd " .. project_dir)
return project_dir
end,
env = get_env_vars(), -- Pass environment variables
args = function()
local project_file,project_dir = find_godot_project()
return {" --editor " .. project_file}
end,
},
}
-- Add visual indicators for breakpoints
vim.fn.sign_define('DapBreakpoint', { text = '●', texthl = 'DapBreakpoint', linehl = '', numhl = '' })
vim.fn.sign_define('DapBreakpointCondition', { text = '◆', texthl = 'DapBreakpointCondition', linehl = '', numhl = '' })
vim.fn.sign_define('DapLogPoint', { text = '◆', texthl = 'DapLogPoint', linehl = '', numhl = '' })
vim.fn.sign_define('DapStopped', { text = '→', texthl = 'DapStopped', linehl = 'DapStopped', numhl = 'DapStopped' })
vim.fn.sign_define('DapBreakpointRejected', { text = '●', texthl = 'DapBreakpointRejected', linehl = '', numhl = '' })
end,
}
local keymap = require 'keymaps'
return {
{
'williamboman/mason.nvim',
priority = 100, -- Make sure mason loads first
config = function()
local mason_ok, mason = pcall(require, 'mason')
if not mason_ok then
print 'Failed to load mason'
return
end
mason.setup {
check_outdated_packages_on_open = true,
border = 'none',
log_level = vim.log.levels.DEBUG,
ui = {
icons = {
package_installed = '✓',
package_pending = '➜',
package_uninstalled = '✗',
},
},
registries = {
'github:nvim-java/mason-registry',
'github:mason-org/mason-registry',
},
install = {
connection_timeout = 3600,
},
max_concurrent_installers = 2,
}
end,
},
{
'neovim/nvim-lspconfig',
dependencies = {
'williamboman/mason-lspconfig.nvim',
'WhoIsSethDaniel/mason-tool-installer.nvim',
'nvim-java/nvim-java',
'Hoffs/omnisharp-extended-lsp.nvim',
'kevinhwang91/nvim-ufo',
'kevinhwang91/promise-async',
'folke/lazydev.nvim',
'Bilal2453/luvit-meta',
'hrsh7th/nvim-cmp',
},
config = function()
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities.textDocument.foldingRange = {
dynamicRegistration = false,
lineFoldingOnly = true,
}
capabilities = vim.tbl_deep_extend('force', capabilities, require('cmp_nvim_lsp').default_capabilities())
-- Set up enhanced hover UI
local hover_config = {
border = 'rounded',
max_width = 80,
max_height = 30,
}
-- Configure the LSP handlers for better UI
vim.lsp.handlers['textDocument/hover'] = vim.lsp.with(vim.lsp.handlers.hover, hover_config)
vim.lsp.handlers['textDocument/signatureHelp'] = vim.lsp.with(vim.lsp.handlers.signature_help, hover_config)
-- Common on_attach function to use with all LSP servers
local on_attach = function(client, bufnr)
-- Set updatetime for faster hover/signature response
vim.opt.updatetime = 300
-- Keep existing functionality but prefer lspsaga if available
local has_saga, _ = pcall(require, 'lspsaga')
-- Regular LSP keymaps as fallback
if not has_saga then
keymap.buf_map(bufnr, 'n', '<leader>la', vim.lsp.buf.code_action, { desc = 'LSP code actions' })
keymap.buf_map(bufnr, 'n', '<leader>lh', vim.lsp.buf.hover, { desc = 'Show hover documentation' })
keymap.buf_map(bufnr, 'n', '<leader>ln', vim.lsp.buf.rename, { desc = 'Rename symbol' })
keymap.buf_map(bufnr, 'n', '<leader>ld', vim.lsp.buf.definition, { desc = 'Go to definition' })
keymap.buf_map(bufnr, 'n', '<leader>lD', vim.lsp.buf.declaration, { desc = 'Go to declaration' })
keymap.buf_map(bufnr, 'n', '<leader>li', vim.lsp.buf.implementation, { desc = 'Go to implementation' })
keymap.buf_map(bufnr, 'n', '<leader>lr', vim.lsp.buf.references, { desc = 'Find references' })
keymap.buf_map(bufnr, 'n', '<leader>lt', vim.lsp.buf.type_definition, { desc = 'Go to type definition' })
keymap.buf_map(bufnr, 'i', '<C-k>', vim.lsp.buf.signature_help, { desc = 'Show signature help' })
keymap.buf_map(bufnr, 'n', '<leader>lf', function()
vim.lsp.buf.format { timeout_ms = 2000 }
end, { desc = 'Format code' })
else
-- Keep format command using LSP directly since lspsaga doesn't provide this
keymap.buf_map(bufnr, 'n', '<leader>lf', function()
vim.lsp.buf.format { timeout_ms = 2000 }
end, { desc = 'Format code' })
end
end
-- GDScript setup
require('lspconfig').gdscript.setup {
cmd = { 'nc', 'localhost', '6005' },
filetypes = { 'gdscript', 'gd' },
root_dir = require('lspconfig').util.root_pattern('project.godot', '.git'),
capabilities = capabilities,
on_attach = function(client, bufnr)
client.server_capabilities.documentFormattingProvider = false
client.server_capabilities.documentRangeFormattingProvider = false
vim.bo[bufnr].expandtab = true
vim.bo[bufnr].shiftwidth = 4
vim.bo[bufnr].tabstop = 4
vim.bo[bufnr].softtabstop = 4
-- Call common on_attach for standard LSP features
on_attach(client, bufnr)
end,
}
-- Lua LSP setup
require('lspconfig').lua_ls.setup {
settings = {
Lua = {
completion = { callSnippet = 'Replace' },
},
},
capabilities = capabilities,
on_attach = on_attach,
}
-- OmniSharp setup with enhanced capabilities for C#
local pid = vim.fn.getpid()
require('lspconfig').omnisharp.setup {
cmd = {
'/home/jamie/.local/share/nvim/mason/bin/omnisharp',
'--languageserver',
'--hostPID',
tostring(pid),
},
handlers = {
['textDocument/definition'] = require('omnisharp_extended').handler,
['textDocument/implementation'] = require('omnisharp_extended').handler,
},
capabilities = capabilities,
on_attach = function(client, bufnr)
-- Call common LSP setup first
on_attach(client, bufnr)
-- OmniSharp specific settings
client.server_capabilities.documentFormattingProvider = true
client.server_capabilities.hoverProvider = true
client.server_capabilities.documentHighlightProvider = true
-- Use extended OmniSharp for improved functionality
keymap.buf_map(bufnr, 'n', 'gd', "<cmd>lua require('omnisharp_extended').lsp_definition()<cr>")
keymap.buf_map(bufnr, 'n', '<leader>D', "<cmd>lua require('omnisharp_extended').lsp_type_definition()<cr>")
keymap.buf_map(bufnr, 'n', 'gr', "<cmd>lua require('omnisharp_extended').lsp_references()<cr>")
keymap.buf_map(bufnr, 'n', 'gi', "<cmd>lua require('omnisharp_extended').lsp_implementation()<cr>")
-- C# specific settings
vim.bo[bufnr].expandtab = true
vim.bo[bufnr].shiftwidth = 4
vim.bo[bufnr].tabstop = 4
end,
}
end,
},
}