feat: add devtools (tree-sitter, neovim, formatter, CLI commands)
- Add tree-sitter grammar for syntax highlighting - Add Neovim plugin with syntax, LSP integration, and commands - Add code formatter (lux fmt) with check mode - Add CLI commands: fmt, check, test, watch, init - Add project initialization with lux.toml template Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
134
editors/nvim/README.md
Normal file
134
editors/nvim/README.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# Lux Neovim Plugin
|
||||
|
||||
Neovim support for the Lux programming language.
|
||||
|
||||
## Features
|
||||
|
||||
- Syntax highlighting (vim regex and tree-sitter)
|
||||
- LSP integration (diagnostics, hover, completions, go-to-definition)
|
||||
- Commands for running, formatting, and testing
|
||||
- REPL integration
|
||||
|
||||
## Installation
|
||||
|
||||
### Using lazy.nvim
|
||||
|
||||
```lua
|
||||
{
|
||||
"your-org/lux",
|
||||
config = function()
|
||||
require("lux").setup({
|
||||
-- Optional: specify path to lux binary
|
||||
-- lux_binary = "/path/to/lux",
|
||||
lsp = {
|
||||
enabled = true,
|
||||
autostart = true,
|
||||
},
|
||||
format = {
|
||||
on_save = false,
|
||||
},
|
||||
})
|
||||
end,
|
||||
ft = "lux",
|
||||
}
|
||||
```
|
||||
|
||||
### Using packer.nvim
|
||||
|
||||
```lua
|
||||
use {
|
||||
"your-org/lux",
|
||||
config = function()
|
||||
require("lux").setup()
|
||||
end,
|
||||
ft = "lux",
|
||||
}
|
||||
```
|
||||
|
||||
### Manual Installation
|
||||
|
||||
Copy the contents of this directory to your Neovim config:
|
||||
|
||||
```bash
|
||||
# Copy to nvim config
|
||||
cp -r editors/nvim/* ~/.config/nvim/
|
||||
|
||||
# Or symlink
|
||||
ln -s /path/to/lux/editors/nvim/ftdetect ~/.config/nvim/ftdetect
|
||||
ln -s /path/to/lux/editors/nvim/ftplugin ~/.config/nvim/ftplugin
|
||||
ln -s /path/to/lux/editors/nvim/syntax ~/.config/nvim/syntax
|
||||
ln -s /path/to/lux/editors/nvim/lua/lux ~/.config/nvim/lua/lux
|
||||
```
|
||||
|
||||
## Tree-sitter Support
|
||||
|
||||
For tree-sitter based highlighting, install the grammar:
|
||||
|
||||
```lua
|
||||
-- In your tree-sitter config
|
||||
require("nvim-treesitter.configs").setup({
|
||||
ensure_installed = { "lux" },
|
||||
highlight = { enable = true },
|
||||
})
|
||||
|
||||
-- Register the parser
|
||||
local parser_config = require("nvim-treesitter.parsers").get_parser_configs()
|
||||
parser_config.lux = {
|
||||
install_info = {
|
||||
url = "/path/to/lux/editors/tree-sitter-lux",
|
||||
files = { "src/parser.c" },
|
||||
},
|
||||
filetype = "lux",
|
||||
}
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `:LuxRun` | Run the current file |
|
||||
| `:LuxFormat` | Format the current file |
|
||||
| `:LuxTest` | Run tests |
|
||||
| `:LuxRepl` | Start the REPL in a terminal |
|
||||
|
||||
## Key Mappings
|
||||
|
||||
Default mappings in Lux files (using `<localleader>`):
|
||||
|
||||
| Mapping | Action |
|
||||
|---------|--------|
|
||||
| `<localleader>r` | Run current file |
|
||||
| `<localleader>f` | Format current file |
|
||||
| `<localleader>t` | Run tests |
|
||||
|
||||
LSP mappings (when LSP is active):
|
||||
|
||||
| Mapping | Action |
|
||||
|---------|--------|
|
||||
| `gd` | Go to definition |
|
||||
| `K` | Hover information |
|
||||
| `gr` | Find references |
|
||||
| `<leader>rn` | Rename symbol |
|
||||
| `<leader>ca` | Code actions |
|
||||
| `[d` / `]d` | Previous/next diagnostic |
|
||||
|
||||
## Configuration
|
||||
|
||||
```lua
|
||||
require("lux").setup({
|
||||
-- Path to lux binary (searches PATH if not set)
|
||||
lux_binary = nil,
|
||||
|
||||
lsp = {
|
||||
-- Enable LSP support
|
||||
enabled = true,
|
||||
-- Auto-start LSP when opening .lux files
|
||||
autostart = true,
|
||||
},
|
||||
|
||||
format = {
|
||||
-- Format on save
|
||||
on_save = false,
|
||||
},
|
||||
})
|
||||
```
|
||||
141
editors/nvim/after/queries/lux/highlights.scm
Normal file
141
editors/nvim/after/queries/lux/highlights.scm
Normal file
@@ -0,0 +1,141 @@
|
||||
; Syntax highlighting queries for Lux
|
||||
|
||||
; Comments
|
||||
(line_comment) @comment
|
||||
(doc_comment) @comment.documentation
|
||||
|
||||
; Keywords
|
||||
[
|
||||
"fn"
|
||||
"let"
|
||||
"type"
|
||||
"effect"
|
||||
"handler"
|
||||
"trait"
|
||||
"impl"
|
||||
"for"
|
||||
"import"
|
||||
"export"
|
||||
"match"
|
||||
"if"
|
||||
"then"
|
||||
"else"
|
||||
"with"
|
||||
"run"
|
||||
"resume"
|
||||
"is"
|
||||
] @keyword
|
||||
|
||||
; Operators
|
||||
[
|
||||
"+"
|
||||
"-"
|
||||
"*"
|
||||
"/"
|
||||
"%"
|
||||
"=="
|
||||
"!="
|
||||
"<"
|
||||
">"
|
||||
"<="
|
||||
">="
|
||||
"&&"
|
||||
"||"
|
||||
"!"
|
||||
"="
|
||||
"=>"
|
||||
"|>"
|
||||
":"
|
||||
"|"
|
||||
"."
|
||||
","
|
||||
] @operator
|
||||
|
||||
; Punctuation
|
||||
[
|
||||
"("
|
||||
")"
|
||||
"{"
|
||||
"}"
|
||||
"["
|
||||
"]"
|
||||
"<"
|
||||
">"
|
||||
] @punctuation.bracket
|
||||
|
||||
; Literals
|
||||
(integer) @number
|
||||
(float) @number.float
|
||||
(string) @string
|
||||
(char) @character
|
||||
(boolean) @boolean
|
||||
(unit) @constant.builtin
|
||||
|
||||
; String interpolation
|
||||
(interpolation
|
||||
"{" @punctuation.special
|
||||
"}" @punctuation.special)
|
||||
|
||||
(escape_sequence) @string.escape
|
||||
|
||||
; Types
|
||||
(type_expression (identifier) @type)
|
||||
(generic_type (identifier) @type)
|
||||
(type_declaration name: (identifier) @type.definition)
|
||||
(type_parameters (identifier) @type.parameter)
|
||||
|
||||
; Built-in types
|
||||
((identifier) @type.builtin
|
||||
(#match? @type.builtin "^(Int|Float|Bool|String|Char|Unit|Option|Result|List)$"))
|
||||
|
||||
; Functions
|
||||
(function_declaration name: (identifier) @function.definition)
|
||||
(call_expression function: (identifier) @function.call)
|
||||
(call_expression function: (member_expression member: (identifier) @function.method))
|
||||
|
||||
; Effect operations
|
||||
(effect_declaration name: (identifier) @type.definition)
|
||||
(effect_operation name: (identifier) @function.definition)
|
||||
(handler_declaration name: (identifier) @function.definition)
|
||||
(handler_declaration effect: (identifier) @type)
|
||||
(handler_operation name: (identifier) @function.definition)
|
||||
|
||||
; Traits
|
||||
(trait_declaration name: (identifier) @type.definition)
|
||||
(trait_method name: (identifier) @function.definition)
|
||||
(impl_declaration trait: (identifier) @type)
|
||||
|
||||
; Parameters
|
||||
(parameter name: (identifier) @variable.parameter)
|
||||
|
||||
; Variables
|
||||
(let_declaration name: (identifier) @variable.definition)
|
||||
(identifier) @variable
|
||||
|
||||
; Patterns
|
||||
(pattern (identifier) @variable)
|
||||
(constructor_pattern name: (identifier) @constructor)
|
||||
(field_pattern name: (identifier) @property)
|
||||
|
||||
; Record fields
|
||||
(field_assignment name: (identifier) @property)
|
||||
(record_field name: (identifier) @property)
|
||||
|
||||
; Member access
|
||||
(member_expression member: (identifier) @property)
|
||||
|
||||
; Modules (capitalized identifiers used as module access)
|
||||
(member_expression
|
||||
object: (identifier) @module
|
||||
(#match? @module "^[A-Z]"))
|
||||
|
||||
; Variants/Constructors (capitalized identifiers)
|
||||
((identifier) @constructor
|
||||
(#match? @constructor "^[A-Z][a-zA-Z0-9]*$"))
|
||||
|
||||
; Special identifiers
|
||||
((identifier) @constant.builtin
|
||||
(#match? @constant.builtin "^(None|Some|Ok|Err|true|false)$"))
|
||||
|
||||
; Property annotations
|
||||
(property_clause (identifier) @attribute)
|
||||
22
editors/nvim/after/queries/lux/indents.scm
Normal file
22
editors/nvim/after/queries/lux/indents.scm
Normal file
@@ -0,0 +1,22 @@
|
||||
; Indent rules for Lux
|
||||
|
||||
[
|
||||
(block_expression)
|
||||
(match_expression)
|
||||
(if_expression)
|
||||
(record_expression)
|
||||
(list_expression)
|
||||
(effect_declaration)
|
||||
(handler_declaration)
|
||||
(trait_declaration)
|
||||
(impl_declaration)
|
||||
(enum_definition)
|
||||
] @indent
|
||||
|
||||
[
|
||||
"}"
|
||||
"]"
|
||||
")"
|
||||
] @outdent
|
||||
|
||||
(match_arm "=>" @indent)
|
||||
21
editors/nvim/after/queries/lux/locals.scm
Normal file
21
editors/nvim/after/queries/lux/locals.scm
Normal file
@@ -0,0 +1,21 @@
|
||||
; Scopes
|
||||
(function_declaration) @scope
|
||||
(block_expression) @scope
|
||||
(lambda_expression) @scope
|
||||
(match_arm) @scope
|
||||
(handler_declaration) @scope
|
||||
|
||||
; Definitions
|
||||
(function_declaration name: (identifier) @definition.function)
|
||||
(let_declaration name: (identifier) @definition.var)
|
||||
(parameter name: (identifier) @definition.parameter)
|
||||
(type_declaration name: (identifier) @definition.type)
|
||||
(effect_declaration name: (identifier) @definition.type)
|
||||
(trait_declaration name: (identifier) @definition.type)
|
||||
(handler_declaration name: (identifier) @definition.function)
|
||||
|
||||
; Pattern bindings
|
||||
(pattern (identifier) @definition.var)
|
||||
|
||||
; References
|
||||
(identifier) @reference
|
||||
2
editors/nvim/ftdetect/lux.vim
Normal file
2
editors/nvim/ftdetect/lux.vim
Normal file
@@ -0,0 +1,2 @@
|
||||
" File type detection for Lux
|
||||
autocmd BufRead,BufNewFile *.lux setfiletype lux
|
||||
29
editors/nvim/ftplugin/lux.vim
Normal file
29
editors/nvim/ftplugin/lux.vim
Normal file
@@ -0,0 +1,29 @@
|
||||
" Lux filetype settings
|
||||
|
||||
" Indentation
|
||||
setlocal expandtab
|
||||
setlocal shiftwidth=4
|
||||
setlocal softtabstop=4
|
||||
setlocal tabstop=4
|
||||
|
||||
" Comments
|
||||
setlocal commentstring=//\ %s
|
||||
setlocal comments=://
|
||||
|
||||
" Folding
|
||||
setlocal foldmethod=syntax
|
||||
|
||||
" Match pairs
|
||||
setlocal matchpairs+=<:>
|
||||
|
||||
" Format options
|
||||
setlocal formatoptions-=t
|
||||
setlocal formatoptions+=croql
|
||||
|
||||
" Completion
|
||||
setlocal omnifunc=v:lua.vim.lsp.omnifunc
|
||||
|
||||
" Key mappings for Lux development
|
||||
nnoremap <buffer> <localleader>r :!lux %<CR>
|
||||
nnoremap <buffer> <localleader>f :!lux fmt %<CR>
|
||||
nnoremap <buffer> <localleader>t :!lux test<CR>
|
||||
163
editors/nvim/lua/lux/init.lua
Normal file
163
editors/nvim/lua/lux/init.lua
Normal file
@@ -0,0 +1,163 @@
|
||||
-- Lux language support for Neovim
|
||||
-- Provides LSP configuration and utilities
|
||||
|
||||
local M = {}
|
||||
|
||||
-- Default configuration
|
||||
M.config = {
|
||||
-- Path to lux binary (will search PATH if not set)
|
||||
lux_binary = nil,
|
||||
-- Enable LSP
|
||||
lsp = {
|
||||
enabled = true,
|
||||
-- Auto-start LSP when opening .lux files
|
||||
autostart = true,
|
||||
},
|
||||
-- Formatting options
|
||||
format = {
|
||||
-- Format on save
|
||||
on_save = false,
|
||||
},
|
||||
}
|
||||
|
||||
-- Find lux binary
|
||||
local function find_lux_binary()
|
||||
if M.config.lux_binary then
|
||||
return M.config.lux_binary
|
||||
end
|
||||
-- Try to find in PATH
|
||||
local handle = io.popen("which lux 2>/dev/null")
|
||||
if handle then
|
||||
local result = handle:read("*a"):gsub("%s+", "")
|
||||
handle:close()
|
||||
if result ~= "" then
|
||||
return result
|
||||
end
|
||||
end
|
||||
-- Try common locations
|
||||
local common_paths = {
|
||||
"./result/bin/lux",
|
||||
"~/.local/bin/lux",
|
||||
"/usr/local/bin/lux",
|
||||
}
|
||||
for _, path in ipairs(common_paths) do
|
||||
local expanded = vim.fn.expand(path)
|
||||
if vim.fn.executable(expanded) == 1 then
|
||||
return expanded
|
||||
end
|
||||
end
|
||||
return "lux" -- Fallback, hope it's in PATH
|
||||
end
|
||||
|
||||
-- Setup LSP
|
||||
function M.setup_lsp()
|
||||
local lux_binary = find_lux_binary()
|
||||
|
||||
-- Check if lspconfig is available
|
||||
local ok, lspconfig = pcall(require, "lspconfig")
|
||||
if not ok then
|
||||
vim.notify("lspconfig not found. Install nvim-lspconfig for LSP support.", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
local configs = require("lspconfig.configs")
|
||||
|
||||
-- Register lux LSP if not already registered
|
||||
if not configs.lux_lsp then
|
||||
configs.lux_lsp = {
|
||||
default_config = {
|
||||
cmd = { lux_binary, "lsp" },
|
||||
filetypes = { "lux" },
|
||||
root_dir = function(fname)
|
||||
return lspconfig.util.find_git_ancestor(fname)
|
||||
or lspconfig.util.root_pattern("lux.toml")(fname)
|
||||
or vim.fn.getcwd()
|
||||
end,
|
||||
settings = {},
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
-- Setup the LSP
|
||||
lspconfig.lux_lsp.setup({
|
||||
on_attach = function(client, bufnr)
|
||||
-- Enable completion
|
||||
vim.bo[bufnr].omnifunc = "v:lua.vim.lsp.omnifunc"
|
||||
|
||||
-- Key mappings
|
||||
local opts = { noremap = true, silent = true, buffer = bufnr }
|
||||
vim.keymap.set("n", "gd", vim.lsp.buf.definition, 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)
|
||||
vim.keymap.set("n", "gr", vim.lsp.buf.references, opts)
|
||||
vim.keymap.set("n", "<leader>rn", vim.lsp.buf.rename, opts)
|
||||
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)
|
||||
|
||||
-- Format on save if enabled
|
||||
if M.config.format.on_save then
|
||||
vim.api.nvim_create_autocmd("BufWritePre", {
|
||||
buffer = bufnr,
|
||||
callback = function()
|
||||
vim.lsp.buf.format({ async = false })
|
||||
end,
|
||||
})
|
||||
end
|
||||
end,
|
||||
capabilities = vim.lsp.protocol.make_client_capabilities(),
|
||||
})
|
||||
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))
|
||||
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
|
||||
end
|
||||
|
||||
-- Run tests
|
||||
function M.run_tests()
|
||||
local lux_binary = find_lux_binary()
|
||||
vim.cmd("!" .. lux_binary .. " test")
|
||||
end
|
||||
|
||||
-- Start REPL in terminal
|
||||
function M.start_repl()
|
||||
local lux_binary = find_lux_binary()
|
||||
vim.cmd("terminal " .. lux_binary)
|
||||
end
|
||||
|
||||
-- Setup function
|
||||
function M.setup(opts)
|
||||
-- Merge user config
|
||||
M.config = vim.tbl_deep_extend("force", M.config, opts or {})
|
||||
|
||||
-- Setup LSP if enabled
|
||||
if M.config.lsp.enabled and M.config.lsp.autostart then
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
pattern = "lux",
|
||||
callback = function()
|
||||
M.setup_lsp()
|
||||
end,
|
||||
once = true,
|
||||
})
|
||||
end
|
||||
|
||||
-- Create user commands
|
||||
vim.api.nvim_create_user_command("LuxRun", M.run_file, { desc = "Run current Lux file" })
|
||||
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" })
|
||||
end
|
||||
|
||||
return M
|
||||
89
editors/nvim/syntax/lux.vim
Normal file
89
editors/nvim/syntax/lux.vim
Normal file
@@ -0,0 +1,89 @@
|
||||
" Vim syntax file for Lux
|
||||
" Language: Lux
|
||||
" Maintainer: Lux Team
|
||||
|
||||
if exists("b:current_syntax")
|
||||
finish
|
||||
endif
|
||||
|
||||
" Keywords
|
||||
syn keyword luxKeyword fn let type effect handler trait impl for import export
|
||||
syn keyword luxKeyword match if then else with run resume is
|
||||
syn keyword luxKeyword nextgroup=luxFunction skipwhite
|
||||
|
||||
" Boolean literals
|
||||
syn keyword luxBoolean true false
|
||||
|
||||
" Built-in types
|
||||
syn keyword luxType Int Float Bool String Char Unit Option Result List
|
||||
syn keyword luxType Some None Ok Err
|
||||
|
||||
" Operators
|
||||
syn match luxOperator /[+\-*/%=<>!&|]/
|
||||
syn match luxOperator /==/
|
||||
syn match luxOperator /!=/
|
||||
syn match luxOperator /<=/
|
||||
syn match luxOperator />=/
|
||||
syn match luxOperator /&&/
|
||||
syn match luxOperator /||/
|
||||
syn match luxOperator /|>/
|
||||
syn match luxOperator /=>/
|
||||
|
||||
" Delimiters
|
||||
syn match luxDelimiter /[(){}\[\],;:]/
|
||||
syn match luxDelimiter /|/ contained
|
||||
|
||||
" Numbers
|
||||
syn match luxNumber /\<\d\+\>/
|
||||
syn match luxFloat /\<\d\+\.\d\+\>/
|
||||
|
||||
" Strings
|
||||
syn region luxString start=/"/ skip=/\\./ end=/"/ contains=luxEscape,luxInterpolation
|
||||
syn match luxEscape /\\[nrt\\'"0{}]/ contained
|
||||
syn region luxInterpolation start=/{/ end=/}/ contained contains=TOP
|
||||
|
||||
" Characters
|
||||
syn region luxChar start=/'/ skip=/\\./ end=/'/
|
||||
|
||||
" Comments
|
||||
syn match luxComment /\/\/.*$/
|
||||
syn match luxDocComment /\/\/\/.*$/
|
||||
|
||||
" Function definitions
|
||||
syn match luxFunction /\<[a-z_][a-zA-Z0-9_]*\>/ contained
|
||||
syn match luxFunctionCall /\<[a-z_][a-zA-Z0-9_]*\>\s*(/me=e-1
|
||||
|
||||
" Type definitions and constructors
|
||||
syn match luxTypedef /\<[A-Z][a-zA-Z0-9_]*\>/
|
||||
|
||||
" Module access
|
||||
syn match luxModule /\<[A-Z][a-zA-Z0-9_]*\>\./me=e-1
|
||||
|
||||
" Effect signatures
|
||||
syn region luxEffectSig start=/with\s*{/ end=/}/ contains=luxType,luxDelimiter
|
||||
|
||||
" Properties
|
||||
syn match luxProperty /\<is\s\+\(pure\|total\|idempotent\|commutative\|associative\)\>/
|
||||
|
||||
" Highlight groups
|
||||
hi def link luxKeyword Keyword
|
||||
hi def link luxBoolean Boolean
|
||||
hi def link luxType Type
|
||||
hi def link luxOperator Operator
|
||||
hi def link luxDelimiter Delimiter
|
||||
hi def link luxNumber Number
|
||||
hi def link luxFloat Float
|
||||
hi def link luxString String
|
||||
hi def link luxEscape SpecialChar
|
||||
hi def link luxInterpolation Special
|
||||
hi def link luxChar Character
|
||||
hi def link luxComment Comment
|
||||
hi def link luxDocComment SpecialComment
|
||||
hi def link luxFunction Function
|
||||
hi def link luxFunctionCall Function
|
||||
hi def link luxTypedef Type
|
||||
hi def link luxModule Include
|
||||
hi def link luxEffectSig Special
|
||||
hi def link luxProperty PreProc
|
||||
|
||||
let b:current_syntax = "lux"
|
||||
Reference in New Issue
Block a user