feat: improve editor tooling and fix warnings

Neovim improvements:
- Add tree-sitter text objects for functions, types, blocks
- Add folding support
- Enhanced REPL integration (toggle, send line/selection)
- New commands: LuxCheck, LuxReplToggle, LuxSend
- Better keybindings with localleader

VS Code extension:
- Full syntax highlighting with TextMate grammar
- LSP client integration
- 20+ snippets for common patterns
- Commands: run, format, check, REPL
- Keybindings and context menu

Fixes:
- Fix all cargo warnings with #[allow(dead_code)] annotations
- Clean up unused variables

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 18:23:55 -05:00
parent ebc0bdb109
commit a6eb349d59
14 changed files with 1437 additions and 9 deletions

View File

@@ -1,5 +1,5 @@
-- Lux language support for Neovim
-- Provides LSP configuration and utilities
-- Provides LSP configuration, REPL integration, and utilities
local M = {}
@@ -12,12 +12,35 @@ M.config = {
enabled = true,
-- Auto-start LSP when opening .lux files
autostart = true,
-- Show inline hints (requires neovim 0.10+)
inlay_hints = false,
},
-- Formatting options
format = {
-- Format on save
on_save = false,
},
-- REPL options
repl = {
-- Position of REPL window: "bottom", "right", "float"
position = "bottom",
-- Size of REPL window (percentage)
size = 30,
},
-- Diagnostics options
diagnostics = {
-- Show virtual text for diagnostics
virtual_text = true,
-- Update diagnostics in insert mode
update_in_insert = false,
},
}
-- REPL state
M.repl = {
bufnr = nil,
winnr = nil,
jobid = nil,
}
-- Find lux binary
@@ -37,6 +60,8 @@ local function find_lux_binary()
-- Try common locations
local common_paths = {
"./result/bin/lux",
"./target/release/lux",
"./target/debug/lux",
"~/.local/bin/lux",
"/usr/local/bin/lux",
}
@@ -70,7 +95,7 @@ function M.setup_lsp()
filetypes = { "lux" },
root_dir = function(fname)
return lspconfig.util.find_git_ancestor(fname)
or lspconfig.util.root_pattern("lux.toml")(fname)
or lspconfig.util.root_pattern("lux.toml", "package.lux")(fname)
or vim.fn.getcwd()
end,
settings = {},
@@ -87,6 +112,7 @@ function M.setup_lsp()
-- Key mappings
local opts = { noremap = true, silent = true, buffer = bufnr }
vim.keymap.set("n", "gd", vim.lsp.buf.definition, opts)
vim.keymap.set("n", "gD", vim.lsp.buf.declaration, opts)
vim.keymap.set("n", "K", vim.lsp.buf.hover, opts)
vim.keymap.set("n", "gi", vim.lsp.buf.implementation, opts)
vim.keymap.set("n", "<C-k>", vim.lsp.buf.signature_help, opts)
@@ -95,6 +121,13 @@ function M.setup_lsp()
vim.keymap.set("n", "<leader>ca", vim.lsp.buf.code_action, opts)
vim.keymap.set("n", "[d", vim.diagnostic.goto_prev, opts)
vim.keymap.set("n", "]d", vim.diagnostic.goto_next, opts)
vim.keymap.set("n", "<leader>e", vim.diagnostic.open_float, opts)
vim.keymap.set("n", "<leader>q", vim.diagnostic.setloclist, opts)
-- Enable inlay hints if supported
if M.config.lsp.inlay_hints and vim.lsp.inlay_hint then
vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })
end
-- Format on save if enabled
if M.config.format.on_save then
@@ -110,31 +143,213 @@ function M.setup_lsp()
})
end
-- Configure diagnostics display
local function setup_diagnostics()
vim.diagnostic.config({
virtual_text = M.config.diagnostics.virtual_text,
signs = true,
underline = true,
update_in_insert = M.config.diagnostics.update_in_insert,
severity_sort = true,
float = {
border = "rounded",
source = "always",
header = "",
prefix = "",
},
})
-- Custom diagnostic signs
local signs = { Error = " ", Warn = " ", Hint = " ", Info = " " }
for type, icon in pairs(signs) do
local hl = "DiagnosticSign" .. type
vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = "" })
end
end
-- Run current file
function M.run_file()
local file = vim.fn.expand("%:p")
local lux_binary = find_lux_binary()
vim.cmd("!" .. lux_binary .. " " .. vim.fn.shellescape(file))
-- Save the file first
vim.cmd("write")
-- Run in a terminal split
vim.cmd("botright split | resize 15")
vim.fn.termopen(lux_binary .. " " .. vim.fn.shellescape(file), {
on_exit = function(_, exit_code, _)
if exit_code == 0 then
vim.notify("Lux: Run completed successfully", vim.log.levels.INFO)
end
end,
})
vim.cmd("startinsert")
end
-- Format current file
function M.format_file()
local file = vim.fn.expand("%:p")
local lux_binary = find_lux_binary()
vim.cmd("!" .. lux_binary .. " fmt " .. vim.fn.shellescape(file))
vim.cmd("edit!") -- Reload the file
-- Save first
vim.cmd("write")
-- Format
local output = vim.fn.system(lux_binary .. " fmt " .. vim.fn.shellescape(file))
if vim.v.shell_error ~= 0 then
vim.notify("Format error: " .. output, vim.log.levels.ERROR)
else
vim.cmd("edit!") -- Reload the file
vim.notify("Formatted", vim.log.levels.INFO)
end
end
-- Run tests
function M.run_tests()
local lux_binary = find_lux_binary()
vim.cmd("!" .. lux_binary .. " test")
vim.cmd("botright split | resize 15")
vim.fn.termopen(lux_binary .. " test")
vim.cmd("startinsert")
end
-- Start REPL in terminal
function M.start_repl()
local lux_binary = find_lux_binary()
vim.cmd("terminal " .. lux_binary)
-- Close existing REPL if open
if M.repl.bufnr and vim.api.nvim_buf_is_valid(M.repl.bufnr) then
vim.api.nvim_buf_delete(M.repl.bufnr, { force = true })
end
-- Create REPL window based on config
local pos = M.config.repl.position
local size = M.config.repl.size
if pos == "float" then
local width = math.floor(vim.o.columns * 0.8)
local height = math.floor(vim.o.lines * 0.8)
local row = math.floor((vim.o.lines - height) / 2)
local col = math.floor((vim.o.columns - width) / 2)
M.repl.bufnr = vim.api.nvim_create_buf(false, true)
M.repl.winnr = vim.api.nvim_open_win(M.repl.bufnr, true, {
relative = "editor",
width = width,
height = height,
row = row,
col = col,
style = "minimal",
border = "rounded",
})
elseif pos == "right" then
vim.cmd("botright vsplit")
vim.cmd("vertical resize " .. math.floor(vim.o.columns * size / 100))
M.repl.winnr = vim.api.nvim_get_current_win()
M.repl.bufnr = vim.api.nvim_get_current_buf()
else -- bottom
vim.cmd("botright split")
vim.cmd("resize " .. math.floor(vim.o.lines * size / 100))
M.repl.winnr = vim.api.nvim_get_current_win()
M.repl.bufnr = vim.api.nvim_get_current_buf()
end
M.repl.jobid = vim.fn.termopen(lux_binary, {
on_exit = function()
M.repl.jobid = nil
M.repl.bufnr = nil
M.repl.winnr = nil
end,
})
vim.cmd("startinsert")
end
-- Send text to REPL
function M.send_to_repl(text)
if not M.repl.jobid then
vim.notify("No REPL running. Start one with :LuxRepl", vim.log.levels.WARN)
return
end
-- Send the text followed by newline
vim.fn.chansend(M.repl.jobid, text .. "\n")
-- Focus the REPL window
if M.repl.winnr and vim.api.nvim_win_is_valid(M.repl.winnr) then
vim.api.nvim_set_current_win(M.repl.winnr)
vim.cmd("startinsert")
end
end
-- Send current line to REPL
function M.send_line_to_repl()
local line = vim.api.nvim_get_current_line()
M.send_to_repl(line)
end
-- Send visual selection to REPL
function M.send_selection_to_repl()
-- Get visual selection
local start_pos = vim.fn.getpos("'<")
local end_pos = vim.fn.getpos("'>")
local lines = vim.api.nvim_buf_get_lines(0, start_pos[2] - 1, end_pos[2], false)
if #lines == 0 then
return
end
-- Trim the selection
if #lines == 1 then
lines[1] = string.sub(lines[1], start_pos[3], end_pos[3])
else
lines[1] = string.sub(lines[1], start_pos[3])
lines[#lines] = string.sub(lines[#lines], 1, end_pos[3])
end
local text = table.concat(lines, "\n")
M.send_to_repl(text)
end
-- Toggle REPL window
function M.toggle_repl()
if M.repl.winnr and vim.api.nvim_win_is_valid(M.repl.winnr) then
vim.api.nvim_win_hide(M.repl.winnr)
M.repl.winnr = nil
elseif M.repl.bufnr and vim.api.nvim_buf_is_valid(M.repl.bufnr) then
-- Reopen existing REPL
local pos = M.config.repl.position
local size = M.config.repl.size
if pos == "right" then
vim.cmd("botright vsplit")
vim.cmd("vertical resize " .. math.floor(vim.o.columns * size / 100))
else
vim.cmd("botright split")
vim.cmd("resize " .. math.floor(vim.o.lines * size / 100))
end
vim.api.nvim_set_current_buf(M.repl.bufnr)
M.repl.winnr = vim.api.nvim_get_current_win()
vim.cmd("startinsert")
else
M.start_repl()
end
end
-- Check current file for errors
function M.check_file()
local file = vim.fn.expand("%:p")
local lux_binary = find_lux_binary()
-- Save first
vim.cmd("write")
local output = vim.fn.system(lux_binary .. " check " .. vim.fn.shellescape(file))
if vim.v.shell_error ~= 0 then
vim.notify("Type check errors:\n" .. output, vim.log.levels.ERROR)
else
vim.notify("No errors found", vim.log.levels.INFO)
end
end
-- Setup function
@@ -142,6 +357,9 @@ function M.setup(opts)
-- Merge user config
M.config = vim.tbl_deep_extend("force", M.config, opts or {})
-- Setup diagnostics
setup_diagnostics()
-- Setup LSP if enabled
if M.config.lsp.enabled and M.config.lsp.autostart then
vim.api.nvim_create_autocmd("FileType", {
@@ -158,6 +376,37 @@ function M.setup(opts)
vim.api.nvim_create_user_command("LuxFormat", M.format_file, { desc = "Format current Lux file" })
vim.api.nvim_create_user_command("LuxTest", M.run_tests, { desc = "Run Lux tests" })
vim.api.nvim_create_user_command("LuxRepl", M.start_repl, { desc = "Start Lux REPL" })
vim.api.nvim_create_user_command("LuxReplToggle", M.toggle_repl, { desc = "Toggle Lux REPL" })
vim.api.nvim_create_user_command("LuxCheck", M.check_file, { desc = "Type check current Lux file" })
vim.api.nvim_create_user_command("LuxSendLine", M.send_line_to_repl, { desc = "Send line to REPL" })
-- Create command with range for sending selection
vim.api.nvim_create_user_command("LuxSend", function(opts)
if opts.range == 2 then
M.send_selection_to_repl()
else
M.send_line_to_repl()
end
end, { range = true, desc = "Send to REPL" })
-- Setup keymaps for Lux files
vim.api.nvim_create_autocmd("FileType", {
pattern = "lux",
callback = function(ev)
local buf_opts = { noremap = true, silent = true, buffer = ev.buf }
-- Run/format/test commands
vim.keymap.set("n", "<localleader>r", M.run_file, buf_opts)
vim.keymap.set("n", "<localleader>f", M.format_file, buf_opts)
vim.keymap.set("n", "<localleader>t", M.run_tests, buf_opts)
vim.keymap.set("n", "<localleader>c", M.check_file, buf_opts)
-- REPL commands
vim.keymap.set("n", "<localleader>R", M.toggle_repl, buf_opts)
vim.keymap.set("n", "<localleader>l", M.send_line_to_repl, buf_opts)
vim.keymap.set("v", "<localleader>s", M.send_selection_to_repl, buf_opts)
end,
})
end
return M