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:
@@ -6,8 +6,10 @@ Neovim support for the Lux programming language.
|
|||||||
|
|
||||||
- Syntax highlighting (vim regex and tree-sitter)
|
- Syntax highlighting (vim regex and tree-sitter)
|
||||||
- LSP integration (diagnostics, hover, completions, go-to-definition)
|
- LSP integration (diagnostics, hover, completions, go-to-definition)
|
||||||
|
- Tree-sitter text objects for code navigation
|
||||||
|
- Code folding
|
||||||
|
- REPL integration with send-to-REPL
|
||||||
- Commands for running, formatting, and testing
|
- Commands for running, formatting, and testing
|
||||||
- REPL integration
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -23,10 +25,19 @@ Neovim support for the Lux programming language.
|
|||||||
lsp = {
|
lsp = {
|
||||||
enabled = true,
|
enabled = true,
|
||||||
autostart = true,
|
autostart = true,
|
||||||
|
inlay_hints = false, -- requires neovim 0.10+
|
||||||
},
|
},
|
||||||
format = {
|
format = {
|
||||||
on_save = false,
|
on_save = false,
|
||||||
},
|
},
|
||||||
|
repl = {
|
||||||
|
position = "bottom", -- "bottom", "right", or "float"
|
||||||
|
size = 30, -- percentage of screen
|
||||||
|
},
|
||||||
|
diagnostics = {
|
||||||
|
virtual_text = true,
|
||||||
|
update_in_insert = false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
end,
|
end,
|
||||||
ft = "lux",
|
ft = "lux",
|
||||||
@@ -58,6 +69,7 @@ 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/ftplugin ~/.config/nvim/ftplugin
|
||||||
ln -s /path/to/lux/editors/nvim/syntax ~/.config/nvim/syntax
|
ln -s /path/to/lux/editors/nvim/syntax ~/.config/nvim/syntax
|
||||||
ln -s /path/to/lux/editors/nvim/lua/lux ~/.config/nvim/lua/lux
|
ln -s /path/to/lux/editors/nvim/lua/lux ~/.config/nvim/lua/lux
|
||||||
|
ln -s /path/to/lux/editors/nvim/after ~/.config/nvim/after
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tree-sitter Support
|
## Tree-sitter Support
|
||||||
@@ -69,6 +81,32 @@ For tree-sitter based highlighting, install the grammar:
|
|||||||
require("nvim-treesitter.configs").setup({
|
require("nvim-treesitter.configs").setup({
|
||||||
ensure_installed = { "lux" },
|
ensure_installed = { "lux" },
|
||||||
highlight = { enable = true },
|
highlight = { enable = true },
|
||||||
|
indent = { enable = true },
|
||||||
|
-- Enable text objects
|
||||||
|
textobjects = {
|
||||||
|
select = {
|
||||||
|
enable = true,
|
||||||
|
keymaps = {
|
||||||
|
["af"] = "@function.outer",
|
||||||
|
["if"] = "@function.inner",
|
||||||
|
["ac"] = "@class.outer",
|
||||||
|
["ic"] = "@class.inner",
|
||||||
|
["ab"] = "@block.outer",
|
||||||
|
["ib"] = "@block.inner",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
move = {
|
||||||
|
enable = true,
|
||||||
|
goto_next_start = {
|
||||||
|
["]f"] = "@function.outer",
|
||||||
|
["]c"] = "@class.outer",
|
||||||
|
},
|
||||||
|
goto_previous_start = {
|
||||||
|
["[f"] = "@function.outer",
|
||||||
|
["[c"] = "@class.outer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
-- Register the parser
|
-- Register the parser
|
||||||
@@ -89,7 +127,11 @@ parser_config.lux = {
|
|||||||
| `:LuxRun` | Run the current file |
|
| `:LuxRun` | Run the current file |
|
||||||
| `:LuxFormat` | Format the current file |
|
| `:LuxFormat` | Format the current file |
|
||||||
| `:LuxTest` | Run tests |
|
| `:LuxTest` | Run tests |
|
||||||
|
| `:LuxCheck` | Type check the current file |
|
||||||
| `:LuxRepl` | Start the REPL in a terminal |
|
| `:LuxRepl` | Start the REPL in a terminal |
|
||||||
|
| `:LuxReplToggle` | Toggle the REPL window |
|
||||||
|
| `:LuxSendLine` | Send current line to REPL |
|
||||||
|
| `:'<,'>LuxSend` | Send selection to REPL |
|
||||||
|
|
||||||
## Key Mappings
|
## Key Mappings
|
||||||
|
|
||||||
@@ -100,18 +142,56 @@ Default mappings in Lux files (using `<localleader>`):
|
|||||||
| `<localleader>r` | Run current file |
|
| `<localleader>r` | Run current file |
|
||||||
| `<localleader>f` | Format current file |
|
| `<localleader>f` | Format current file |
|
||||||
| `<localleader>t` | Run tests |
|
| `<localleader>t` | Run tests |
|
||||||
|
| `<localleader>c` | Type check file |
|
||||||
|
| `<localleader>R` | Toggle REPL |
|
||||||
|
| `<localleader>l` | Send line to REPL |
|
||||||
|
| `<localleader>s` (visual) | Send selection to REPL |
|
||||||
|
|
||||||
LSP mappings (when LSP is active):
|
LSP mappings (when LSP is active):
|
||||||
|
|
||||||
| Mapping | Action |
|
| Mapping | Action |
|
||||||
|---------|--------|
|
|---------|--------|
|
||||||
| `gd` | Go to definition |
|
| `gd` | Go to definition |
|
||||||
|
| `gD` | Go to declaration |
|
||||||
| `K` | Hover information |
|
| `K` | Hover information |
|
||||||
| `gr` | Find references |
|
| `gr` | Find references |
|
||||||
|
| `gi` | Go to implementation |
|
||||||
| `<leader>rn` | Rename symbol |
|
| `<leader>rn` | Rename symbol |
|
||||||
| `<leader>ca` | Code actions |
|
| `<leader>ca` | Code actions |
|
||||||
|
| `<leader>e` | Show diagnostics float |
|
||||||
|
| `<leader>q` | Diagnostics to location list |
|
||||||
| `[d` / `]d` | Previous/next diagnostic |
|
| `[d` / `]d` | Previous/next diagnostic |
|
||||||
|
|
||||||
|
## REPL Integration
|
||||||
|
|
||||||
|
The REPL can be positioned at the bottom, right, or as a floating window:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
require("lux").setup({
|
||||||
|
repl = {
|
||||||
|
position = "float", -- floating window
|
||||||
|
size = 30,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Send code to the running REPL:
|
||||||
|
- `<localleader>l` - Send current line
|
||||||
|
- Select code visually, then `<localleader>s` - Send selection
|
||||||
|
|
||||||
|
## Text Objects
|
||||||
|
|
||||||
|
With `nvim-treesitter-textobjects`, you can use:
|
||||||
|
|
||||||
|
| Object | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `af`/`if` | Function outer/inner |
|
||||||
|
| `ac`/`ic` | Type/class outer/inner |
|
||||||
|
| `ab`/`ib` | Block outer/inner |
|
||||||
|
| `aa`/`ia` | Parameter outer/inner |
|
||||||
|
|
||||||
|
Example: `dif` deletes the body of a function, `vaf` selects the entire function.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
@@ -124,11 +204,27 @@ require("lux").setup({
|
|||||||
enabled = true,
|
enabled = true,
|
||||||
-- Auto-start LSP when opening .lux files
|
-- Auto-start LSP when opening .lux files
|
||||||
autostart = true,
|
autostart = true,
|
||||||
|
-- Show inlay hints (requires neovim 0.10+)
|
||||||
|
inlay_hints = false,
|
||||||
},
|
},
|
||||||
|
|
||||||
format = {
|
format = {
|
||||||
-- Format on save
|
-- Format on save
|
||||||
on_save = false,
|
on_save = false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
repl = {
|
||||||
|
-- Position: "bottom", "right", or "float"
|
||||||
|
position = "bottom",
|
||||||
|
-- Size as percentage of screen
|
||||||
|
size = 30,
|
||||||
|
},
|
||||||
|
|
||||||
|
diagnostics = {
|
||||||
|
-- Show virtual text for diagnostics
|
||||||
|
virtual_text = true,
|
||||||
|
-- Update diagnostics in insert mode
|
||||||
|
update_in_insert = false,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|||||||
29
editors/nvim/after/queries/lux/folds.scm
Normal file
29
editors/nvim/after/queries/lux/folds.scm
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
; Folding queries for Lux
|
||||||
|
; Allows folding function bodies, type definitions, etc.
|
||||||
|
|
||||||
|
; Fold function bodies
|
||||||
|
(function_declaration) @fold
|
||||||
|
|
||||||
|
; Fold type definitions
|
||||||
|
(type_declaration) @fold
|
||||||
|
|
||||||
|
; Fold effect declarations
|
||||||
|
(effect_declaration) @fold
|
||||||
|
|
||||||
|
; Fold handler declarations
|
||||||
|
(handler_declaration) @fold
|
||||||
|
|
||||||
|
; Fold trait declarations
|
||||||
|
(trait_declaration) @fold
|
||||||
|
|
||||||
|
; Fold impl blocks
|
||||||
|
(impl_declaration) @fold
|
||||||
|
|
||||||
|
; Fold match expressions
|
||||||
|
(match_expression) @fold
|
||||||
|
|
||||||
|
; Fold block expressions
|
||||||
|
(block_expression) @fold
|
||||||
|
|
||||||
|
; Fold multi-line comments (doc comments)
|
||||||
|
(doc_comment)+ @fold
|
||||||
55
editors/nvim/after/queries/lux/textobjects.scm
Normal file
55
editors/nvim/after/queries/lux/textobjects.scm
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
; Tree-sitter text objects for Lux
|
||||||
|
; Use with nvim-treesitter-textobjects
|
||||||
|
|
||||||
|
; Function text objects
|
||||||
|
(function_declaration) @function.outer
|
||||||
|
(function_declaration body: (_) @function.inner)
|
||||||
|
|
||||||
|
; Class/type text objects (treat type declarations like classes)
|
||||||
|
(type_declaration) @class.outer
|
||||||
|
(type_declaration definition: (_) @class.inner)
|
||||||
|
|
||||||
|
; Parameter text objects
|
||||||
|
(parameters) @parameter.outer
|
||||||
|
(parameter) @parameter.inner
|
||||||
|
|
||||||
|
; Block text objects
|
||||||
|
(block_expression) @block.outer
|
||||||
|
(block_expression (_) @block.inner)
|
||||||
|
|
||||||
|
; Match arm text objects (like case statements)
|
||||||
|
(match_arm) @statement.outer
|
||||||
|
(match_arm consequence: (_) @statement.inner)
|
||||||
|
|
||||||
|
; Conditional text objects
|
||||||
|
(if_expression) @conditional.outer
|
||||||
|
(if_expression consequence: (_) @conditional.inner)
|
||||||
|
|
||||||
|
; Comment text objects
|
||||||
|
(line_comment) @comment.outer
|
||||||
|
(doc_comment) @comment.outer
|
||||||
|
|
||||||
|
; Call text objects
|
||||||
|
(call_expression) @call.outer
|
||||||
|
(call_expression arguments: (_) @call.inner)
|
||||||
|
|
||||||
|
; Effect/handler text objects (Lux-specific)
|
||||||
|
(effect_declaration) @effect.outer
|
||||||
|
(effect_declaration body: (_) @effect.inner)
|
||||||
|
|
||||||
|
(handler_declaration) @handler.outer
|
||||||
|
(handler_declaration body: (_) @handler.inner)
|
||||||
|
|
||||||
|
; Loop text objects (using recursion as loops in Lux)
|
||||||
|
(match_expression) @loop.outer
|
||||||
|
(match_expression body: (_) @loop.inner)
|
||||||
|
|
||||||
|
; Assignment text objects
|
||||||
|
(let_declaration) @assignment.outer
|
||||||
|
(let_declaration value: (_) @assignment.inner)
|
||||||
|
|
||||||
|
; Attribute/property text objects
|
||||||
|
(property_clause) @attribute.outer
|
||||||
|
|
||||||
|
; Return value (last expression in block)
|
||||||
|
(block_expression (_) @return.inner . "}")
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
-- Lux language support for Neovim
|
-- Lux language support for Neovim
|
||||||
-- Provides LSP configuration and utilities
|
-- Provides LSP configuration, REPL integration, and utilities
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
@@ -12,12 +12,35 @@ M.config = {
|
|||||||
enabled = true,
|
enabled = true,
|
||||||
-- Auto-start LSP when opening .lux files
|
-- Auto-start LSP when opening .lux files
|
||||||
autostart = true,
|
autostart = true,
|
||||||
|
-- Show inline hints (requires neovim 0.10+)
|
||||||
|
inlay_hints = false,
|
||||||
},
|
},
|
||||||
-- Formatting options
|
-- Formatting options
|
||||||
format = {
|
format = {
|
||||||
-- Format on save
|
-- Format on save
|
||||||
on_save = false,
|
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
|
-- Find lux binary
|
||||||
@@ -37,6 +60,8 @@ local function find_lux_binary()
|
|||||||
-- Try common locations
|
-- Try common locations
|
||||||
local common_paths = {
|
local common_paths = {
|
||||||
"./result/bin/lux",
|
"./result/bin/lux",
|
||||||
|
"./target/release/lux",
|
||||||
|
"./target/debug/lux",
|
||||||
"~/.local/bin/lux",
|
"~/.local/bin/lux",
|
||||||
"/usr/local/bin/lux",
|
"/usr/local/bin/lux",
|
||||||
}
|
}
|
||||||
@@ -70,7 +95,7 @@ function M.setup_lsp()
|
|||||||
filetypes = { "lux" },
|
filetypes = { "lux" },
|
||||||
root_dir = function(fname)
|
root_dir = function(fname)
|
||||||
return lspconfig.util.find_git_ancestor(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()
|
or vim.fn.getcwd()
|
||||||
end,
|
end,
|
||||||
settings = {},
|
settings = {},
|
||||||
@@ -87,6 +112,7 @@ function M.setup_lsp()
|
|||||||
-- Key mappings
|
-- Key mappings
|
||||||
local opts = { noremap = true, silent = true, buffer = bufnr }
|
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.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", "K", vim.lsp.buf.hover, opts)
|
||||||
vim.keymap.set("n", "gi", vim.lsp.buf.implementation, 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", "<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", "<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_prev, opts)
|
||||||
vim.keymap.set("n", "]d", vim.diagnostic.goto_next, 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
|
-- Format on save if enabled
|
||||||
if M.config.format.on_save then
|
if M.config.format.on_save then
|
||||||
@@ -110,31 +143,213 @@ function M.setup_lsp()
|
|||||||
})
|
})
|
||||||
end
|
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
|
-- Run current file
|
||||||
function M.run_file()
|
function M.run_file()
|
||||||
local file = vim.fn.expand("%:p")
|
local file = vim.fn.expand("%:p")
|
||||||
local lux_binary = find_lux_binary()
|
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
|
end
|
||||||
|
|
||||||
-- Format current file
|
-- Format current file
|
||||||
function M.format_file()
|
function M.format_file()
|
||||||
local file = vim.fn.expand("%:p")
|
local file = vim.fn.expand("%:p")
|
||||||
local lux_binary = find_lux_binary()
|
local lux_binary = find_lux_binary()
|
||||||
vim.cmd("!" .. lux_binary .. " fmt " .. vim.fn.shellescape(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.cmd("edit!") -- Reload the file
|
||||||
|
vim.notify("Formatted", vim.log.levels.INFO)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Run tests
|
-- Run tests
|
||||||
function M.run_tests()
|
function M.run_tests()
|
||||||
local lux_binary = find_lux_binary()
|
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
|
end
|
||||||
|
|
||||||
-- Start REPL in terminal
|
-- Start REPL in terminal
|
||||||
function M.start_repl()
|
function M.start_repl()
|
||||||
local lux_binary = find_lux_binary()
|
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
|
end
|
||||||
|
|
||||||
-- Setup function
|
-- Setup function
|
||||||
@@ -142,6 +357,9 @@ function M.setup(opts)
|
|||||||
-- Merge user config
|
-- Merge user config
|
||||||
M.config = vim.tbl_deep_extend("force", M.config, opts or {})
|
M.config = vim.tbl_deep_extend("force", M.config, opts or {})
|
||||||
|
|
||||||
|
-- Setup diagnostics
|
||||||
|
setup_diagnostics()
|
||||||
|
|
||||||
-- Setup LSP if enabled
|
-- Setup LSP if enabled
|
||||||
if M.config.lsp.enabled and M.config.lsp.autostart then
|
if M.config.lsp.enabled and M.config.lsp.autostart then
|
||||||
vim.api.nvim_create_autocmd("FileType", {
|
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("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("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("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
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
152
editors/vscode/README.md
Normal file
152
editors/vscode/README.md
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Lux Language Extension for VS Code
|
||||||
|
|
||||||
|
Visual Studio Code extension for the Lux programming language.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Syntax Highlighting**: Full syntax highlighting for Lux files
|
||||||
|
- **LSP Integration**: Real-time diagnostics, hover info, completions, go-to-definition
|
||||||
|
- **Snippets**: Common code patterns (functions, types, effects, handlers)
|
||||||
|
- **REPL Integration**: Start REPL and send code to it
|
||||||
|
- **Commands**: Run, format, type-check files
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### From VS Code Marketplace
|
||||||
|
|
||||||
|
Search for "Lux Language" in the VS Code extensions panel.
|
||||||
|
|
||||||
|
### From Source
|
||||||
|
|
||||||
|
1. Clone the repository
|
||||||
|
2. Navigate to `editors/vscode`
|
||||||
|
3. Run `npm install`
|
||||||
|
4. Run `npm run compile`
|
||||||
|
5. Press F5 to launch a development VS Code window
|
||||||
|
|
||||||
|
### Manual VSIX Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd editors/vscode
|
||||||
|
npm install
|
||||||
|
npm run compile
|
||||||
|
npx vsce package
|
||||||
|
code --install-extension lux-lang-0.1.0.vsix
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- **Lux binary**: Install Lux and ensure `lux` is in your PATH, or configure `lux.lspPath`
|
||||||
|
- **Node.js**: For development
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Setting | Default | Description |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| `lux.lspPath` | `""` | Path to Lux binary. Empty = search PATH |
|
||||||
|
| `lux.lsp.enabled` | `true` | Enable the language server |
|
||||||
|
| `lux.format.onSave` | `false` | Format files on save |
|
||||||
|
| `lux.diagnostics.enabled` | `true` | Show inline diagnostics |
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
| Command | Keybinding | Description |
|
||||||
|
|---------|------------|-------------|
|
||||||
|
| `Lux: Run Current File` | `Ctrl+Shift+R` | Run the active file |
|
||||||
|
| `Lux: Format Document` | - | Format the active file |
|
||||||
|
| `Lux: Type Check File` | - | Type check without running |
|
||||||
|
| `Lux: Start REPL` | - | Open Lux REPL in terminal |
|
||||||
|
| `Lux: Send Selection to REPL` | `Ctrl+Enter` | Send selected code to REPL |
|
||||||
|
| `Lux: Restart Language Server` | - | Restart the LSP |
|
||||||
|
|
||||||
|
## Snippets
|
||||||
|
|
||||||
|
| Prefix | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `fn` | Function definition |
|
||||||
|
| `fne` | Function with effects |
|
||||||
|
| `type` | Type definition |
|
||||||
|
| `effect` | Effect definition |
|
||||||
|
| `handler` | Effect handler |
|
||||||
|
| `match` | Match expression |
|
||||||
|
| `if` | If expression |
|
||||||
|
| `let` | Let binding |
|
||||||
|
| `run` | Run with handlers |
|
||||||
|
| `trait` | Trait definition |
|
||||||
|
| `impl` | Impl block |
|
||||||
|
| `matchopt` | Match on Option |
|
||||||
|
| `matchres` | Match on Result |
|
||||||
|
| `print` | Console.print |
|
||||||
|
| `main` | Main function template |
|
||||||
|
|
||||||
|
## Language Features
|
||||||
|
|
||||||
|
### Syntax Highlighting
|
||||||
|
|
||||||
|
- Keywords (`fn`, `let`, `type`, `effect`, `handler`, `match`, etc.)
|
||||||
|
- Built-in types (`Int`, `Float`, `Bool`, `String`, `Option`, `Result`)
|
||||||
|
- Operators (`=>`, `|>`, `==`, etc.)
|
||||||
|
- String interpolation
|
||||||
|
- Comments (line `//` and doc `///`)
|
||||||
|
|
||||||
|
### LSP Features
|
||||||
|
|
||||||
|
The Lux language server provides:
|
||||||
|
|
||||||
|
- **Diagnostics**: Real-time error and warning display
|
||||||
|
- **Hover**: Type information on hover
|
||||||
|
- **Completions**: Context-aware suggestions
|
||||||
|
- **Go to Definition**: Navigate to definitions
|
||||||
|
|
||||||
|
### Code Folding
|
||||||
|
|
||||||
|
Fold:
|
||||||
|
- Function bodies
|
||||||
|
- Type definitions
|
||||||
|
- Effect declarations
|
||||||
|
- Handler blocks
|
||||||
|
- Match expressions
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Compile
|
||||||
|
npm run compile
|
||||||
|
|
||||||
|
# Watch mode
|
||||||
|
npm run watch
|
||||||
|
|
||||||
|
# Lint
|
||||||
|
npm run lint
|
||||||
|
|
||||||
|
# Package
|
||||||
|
npx vsce package
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### LSP not starting
|
||||||
|
|
||||||
|
1. Check `lux.lspPath` is correct
|
||||||
|
2. Ensure `lux` binary is executable
|
||||||
|
3. Run `Lux: Restart Language Server`
|
||||||
|
4. Check Output panel for errors
|
||||||
|
|
||||||
|
### No syntax highlighting
|
||||||
|
|
||||||
|
1. Ensure file has `.lux` extension
|
||||||
|
2. Try reloading VS Code
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch
|
||||||
|
3. Make changes
|
||||||
|
4. Submit a pull request
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see the main Lux repository for details.
|
||||||
53
editors/vscode/language-configuration.json
Normal file
53
editors/vscode/language-configuration.json
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"comments": {
|
||||||
|
"lineComment": "//",
|
||||||
|
"blockComment": ["/*", "*/"]
|
||||||
|
},
|
||||||
|
"brackets": [
|
||||||
|
["{", "}"],
|
||||||
|
["[", "]"],
|
||||||
|
["(", ")"],
|
||||||
|
["<", ">"]
|
||||||
|
],
|
||||||
|
"autoClosingPairs": [
|
||||||
|
{ "open": "{", "close": "}" },
|
||||||
|
{ "open": "[", "close": "]" },
|
||||||
|
{ "open": "(", "close": ")" },
|
||||||
|
{ "open": "<", "close": ">", "notIn": ["string"] },
|
||||||
|
{ "open": "\"", "close": "\"", "notIn": ["string"] },
|
||||||
|
{ "open": "'", "close": "'", "notIn": ["string", "comment"] }
|
||||||
|
],
|
||||||
|
"surroundingPairs": [
|
||||||
|
{ "open": "{", "close": "}" },
|
||||||
|
{ "open": "[", "close": "]" },
|
||||||
|
{ "open": "(", "close": ")" },
|
||||||
|
{ "open": "<", "close": ">" },
|
||||||
|
{ "open": "\"", "close": "\"" },
|
||||||
|
{ "open": "'", "close": "'" }
|
||||||
|
],
|
||||||
|
"folding": {
|
||||||
|
"markers": {
|
||||||
|
"start": "^\\s*//\\s*#?region\\b",
|
||||||
|
"end": "^\\s*//\\s*#?endregion\\b"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wordPattern": "[a-zA-Z_][a-zA-Z0-9_]*",
|
||||||
|
"indentationRules": {
|
||||||
|
"increaseIndentPattern": "^.*\\{[^}]*$|^.*\\([^)]*$|^.*\\[[^\\]]*$|^\\s*(fn|type|effect|handler|trait|impl|match|if|else)\\b.*$",
|
||||||
|
"decreaseIndentPattern": "^\\s*[}\\])]"
|
||||||
|
},
|
||||||
|
"onEnterRules": [
|
||||||
|
{
|
||||||
|
"beforeText": "^\\s*///.*$",
|
||||||
|
"action": { "indent": "none", "appendText": "/// " }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"beforeText": ".*\\{\\s*$",
|
||||||
|
"action": { "indent": "indent" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"beforeText": "^\\s*\\}\\s*$",
|
||||||
|
"action": { "indent": "outdent" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
150
editors/vscode/package.json
Normal file
150
editors/vscode/package.json
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
{
|
||||||
|
"name": "lux-lang",
|
||||||
|
"displayName": "Lux Language",
|
||||||
|
"description": "Language support for Lux - a functional language with algebraic effects",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"publisher": "lux-lang",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/lux-lang/lux"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.75.0"
|
||||||
|
},
|
||||||
|
"categories": [
|
||||||
|
"Programming Languages"
|
||||||
|
],
|
||||||
|
"keywords": [
|
||||||
|
"lux",
|
||||||
|
"functional",
|
||||||
|
"effects",
|
||||||
|
"algebraic effects"
|
||||||
|
],
|
||||||
|
"activationEvents": [
|
||||||
|
"onLanguage:lux"
|
||||||
|
],
|
||||||
|
"main": "./out/extension.js",
|
||||||
|
"contributes": {
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"id": "lux",
|
||||||
|
"aliases": ["Lux", "lux"],
|
||||||
|
"extensions": [".lux"],
|
||||||
|
"configuration": "./language-configuration.json",
|
||||||
|
"icon": {
|
||||||
|
"light": "./icons/lux-light.png",
|
||||||
|
"dark": "./icons/lux-dark.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"grammars": [
|
||||||
|
{
|
||||||
|
"language": "lux",
|
||||||
|
"scopeName": "source.lux",
|
||||||
|
"path": "./syntaxes/lux.tmLanguage.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration": {
|
||||||
|
"title": "Lux",
|
||||||
|
"properties": {
|
||||||
|
"lux.lspPath": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Path to the Lux binary. If empty, searches PATH."
|
||||||
|
},
|
||||||
|
"lux.lsp.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable the Lux language server"
|
||||||
|
},
|
||||||
|
"lux.format.onSave": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Format files on save"
|
||||||
|
},
|
||||||
|
"lux.diagnostics.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable inline diagnostics"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "lux.run",
|
||||||
|
"title": "Lux: Run Current File"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "lux.format",
|
||||||
|
"title": "Lux: Format Document"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "lux.check",
|
||||||
|
"title": "Lux: Type Check File"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "lux.startRepl",
|
||||||
|
"title": "Lux: Start REPL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "lux.sendToRepl",
|
||||||
|
"title": "Lux: Send Selection to REPL"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "lux.restartLsp",
|
||||||
|
"title": "Lux: Restart Language Server"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"keybindings": [
|
||||||
|
{
|
||||||
|
"command": "lux.run",
|
||||||
|
"key": "ctrl+shift+r",
|
||||||
|
"mac": "cmd+shift+r",
|
||||||
|
"when": "editorLangId == lux"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "lux.sendToRepl",
|
||||||
|
"key": "ctrl+enter",
|
||||||
|
"mac": "cmd+enter",
|
||||||
|
"when": "editorLangId == lux && editorHasSelection"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"menus": {
|
||||||
|
"editor/context": [
|
||||||
|
{
|
||||||
|
"command": "lux.run",
|
||||||
|
"when": "editorLangId == lux",
|
||||||
|
"group": "lux"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "lux.sendToRepl",
|
||||||
|
"when": "editorLangId == lux && editorHasSelection",
|
||||||
|
"group": "lux"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"snippets": [
|
||||||
|
{
|
||||||
|
"language": "lux",
|
||||||
|
"path": "./snippets/lux.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"vscode:prepublish": "npm run compile",
|
||||||
|
"compile": "tsc -p ./",
|
||||||
|
"watch": "tsc -watch -p ./",
|
||||||
|
"lint": "eslint src --ext ts"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^18.x",
|
||||||
|
"@types/vscode": "^1.75.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||||
|
"@typescript-eslint/parser": "^6.0.0",
|
||||||
|
"eslint": "^8.0.0",
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-languageclient": "^9.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
182
editors/vscode/snippets/lux.json
Normal file
182
editors/vscode/snippets/lux.json
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
{
|
||||||
|
"Function": {
|
||||||
|
"prefix": "fn",
|
||||||
|
"body": [
|
||||||
|
"fn ${1:name}(${2:params}): ${3:ReturnType} =",
|
||||||
|
" ${0:body}"
|
||||||
|
],
|
||||||
|
"description": "Define a function"
|
||||||
|
},
|
||||||
|
"Function with Effects": {
|
||||||
|
"prefix": "fne",
|
||||||
|
"body": [
|
||||||
|
"fn ${1:name}(${2:params}): ${3:ReturnType} with {${4:Effects}} = {",
|
||||||
|
" ${0:body}",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Define a function with effects"
|
||||||
|
},
|
||||||
|
"Type Definition": {
|
||||||
|
"prefix": "type",
|
||||||
|
"body": [
|
||||||
|
"type ${1:Name} =",
|
||||||
|
" | ${2:Variant}(${3:fields})"
|
||||||
|
],
|
||||||
|
"description": "Define an algebraic data type"
|
||||||
|
},
|
||||||
|
"Effect Definition": {
|
||||||
|
"prefix": "effect",
|
||||||
|
"body": [
|
||||||
|
"effect ${1:Name} {",
|
||||||
|
" fn ${2:operation}(${3:params}): ${4:ReturnType}",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Define an effect"
|
||||||
|
},
|
||||||
|
"Handler": {
|
||||||
|
"prefix": "handler",
|
||||||
|
"body": [
|
||||||
|
"handler ${1:name}: ${2:Effect} {",
|
||||||
|
" fn ${3:operation}(${4:params}) = {",
|
||||||
|
" ${5:body}",
|
||||||
|
" resume(${0:value})",
|
||||||
|
" }",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Define an effect handler"
|
||||||
|
},
|
||||||
|
"Match Expression": {
|
||||||
|
"prefix": "match",
|
||||||
|
"body": [
|
||||||
|
"match ${1:value} {",
|
||||||
|
" ${2:Pattern} => ${3:result},",
|
||||||
|
" ${0:_ => default}",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Pattern matching expression"
|
||||||
|
},
|
||||||
|
"If Expression": {
|
||||||
|
"prefix": "if",
|
||||||
|
"body": [
|
||||||
|
"if ${1:condition} then ${2:trueCase}",
|
||||||
|
"else ${0:falseCase}"
|
||||||
|
],
|
||||||
|
"description": "Conditional expression"
|
||||||
|
},
|
||||||
|
"Let Binding": {
|
||||||
|
"prefix": "let",
|
||||||
|
"body": [
|
||||||
|
"let ${1:name} = ${0:value}"
|
||||||
|
],
|
||||||
|
"description": "Let binding"
|
||||||
|
},
|
||||||
|
"Run Expression": {
|
||||||
|
"prefix": "run",
|
||||||
|
"body": [
|
||||||
|
"run ${1:expr} with {",
|
||||||
|
" ${0:handlers}",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Run expression with handlers"
|
||||||
|
},
|
||||||
|
"Trait Definition": {
|
||||||
|
"prefix": "trait",
|
||||||
|
"body": [
|
||||||
|
"trait ${1:Name}<${2:T}> {",
|
||||||
|
" fn ${3:method}(${4:self}: ${2:T}): ${5:ReturnType}",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Define a trait"
|
||||||
|
},
|
||||||
|
"Impl Block": {
|
||||||
|
"prefix": "impl",
|
||||||
|
"body": [
|
||||||
|
"impl ${1:Trait} for ${2:Type} {",
|
||||||
|
" fn ${3:method}(${4:self}): ${5:ReturnType} =",
|
||||||
|
" ${0:body}",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Implement a trait"
|
||||||
|
},
|
||||||
|
"Option Match": {
|
||||||
|
"prefix": "matchopt",
|
||||||
|
"body": [
|
||||||
|
"match ${1:option} {",
|
||||||
|
" Some(${2:value}) => ${3:someCase},",
|
||||||
|
" None => ${0:noneCase}",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Match on Option"
|
||||||
|
},
|
||||||
|
"Result Match": {
|
||||||
|
"prefix": "matchres",
|
||||||
|
"body": [
|
||||||
|
"match ${1:result} {",
|
||||||
|
" Ok(${2:value}) => ${3:okCase},",
|
||||||
|
" Err(${4:error}) => ${0:errCase}",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Match on Result"
|
||||||
|
},
|
||||||
|
"Console Print": {
|
||||||
|
"prefix": "print",
|
||||||
|
"body": [
|
||||||
|
"Console.print(${0:\"message\"})"
|
||||||
|
],
|
||||||
|
"description": "Print to console"
|
||||||
|
},
|
||||||
|
"Main Function": {
|
||||||
|
"prefix": "main",
|
||||||
|
"body": [
|
||||||
|
"fn main(): Unit with {Console} = {",
|
||||||
|
" ${0:body}",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"let output = run main() with {}"
|
||||||
|
],
|
||||||
|
"description": "Main function template"
|
||||||
|
},
|
||||||
|
"Doc Comment": {
|
||||||
|
"prefix": "///",
|
||||||
|
"body": [
|
||||||
|
"/// ${1:Description}",
|
||||||
|
"///",
|
||||||
|
"/// # Examples",
|
||||||
|
"/// ```lux",
|
||||||
|
"/// ${0:example}",
|
||||||
|
"/// ```"
|
||||||
|
],
|
||||||
|
"description": "Documentation comment"
|
||||||
|
},
|
||||||
|
"Record Type": {
|
||||||
|
"prefix": "record",
|
||||||
|
"body": [
|
||||||
|
"type ${1:Name} = {",
|
||||||
|
" ${2:field}: ${3:Type},",
|
||||||
|
" ${0:...}",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Define a record type"
|
||||||
|
},
|
||||||
|
"List Map": {
|
||||||
|
"prefix": "lmap",
|
||||||
|
"body": [
|
||||||
|
"List.map(${1:list}, fn(${2:x}: ${3:T}): ${4:U} => ${0:transform})"
|
||||||
|
],
|
||||||
|
"description": "Map over a list"
|
||||||
|
},
|
||||||
|
"List Filter": {
|
||||||
|
"prefix": "lfilter",
|
||||||
|
"body": [
|
||||||
|
"List.filter(${1:list}, fn(${2:x}: ${3:T}): Bool => ${0:predicate})"
|
||||||
|
],
|
||||||
|
"description": "Filter a list"
|
||||||
|
},
|
||||||
|
"List Fold": {
|
||||||
|
"prefix": "lfold",
|
||||||
|
"body": [
|
||||||
|
"List.fold(${1:list}, ${2:initial}, fn(${3:acc}: ${4:A}, ${5:x}: ${6:T}): ${4:A} => ${0:combine})"
|
||||||
|
],
|
||||||
|
"description": "Fold over a list"
|
||||||
|
}
|
||||||
|
}
|
||||||
241
editors/vscode/src/extension.ts
Normal file
241
editors/vscode/src/extension.ts
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { spawn, ChildProcess } from 'child_process';
|
||||||
|
import {
|
||||||
|
LanguageClient,
|
||||||
|
LanguageClientOptions,
|
||||||
|
ServerOptions,
|
||||||
|
} from 'vscode-languageclient/node';
|
||||||
|
|
||||||
|
let client: LanguageClient | undefined;
|
||||||
|
let replTerminal: vscode.Terminal | undefined;
|
||||||
|
|
||||||
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
|
console.log('Lux extension activated');
|
||||||
|
|
||||||
|
// Start LSP if enabled
|
||||||
|
const config = vscode.workspace.getConfiguration('lux');
|
||||||
|
if (config.get('lsp.enabled', true)) {
|
||||||
|
startLspClient(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register commands
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.commands.registerCommand('lux.run', runCurrentFile),
|
||||||
|
vscode.commands.registerCommand('lux.format', formatDocument),
|
||||||
|
vscode.commands.registerCommand('lux.check', checkFile),
|
||||||
|
vscode.commands.registerCommand('lux.startRepl', startRepl),
|
||||||
|
vscode.commands.registerCommand('lux.sendToRepl', sendToRepl),
|
||||||
|
vscode.commands.registerCommand('lux.restartLsp', () => restartLspClient(context))
|
||||||
|
);
|
||||||
|
|
||||||
|
// Format on save if enabled
|
||||||
|
if (config.get('format.onSave', false)) {
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.workspace.onWillSaveTextDocument((event) => {
|
||||||
|
if (event.document.languageId === 'lux') {
|
||||||
|
event.waitUntil(formatDocumentAsync(event.document));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deactivate(): Thenable<void> | undefined {
|
||||||
|
if (replTerminal) {
|
||||||
|
replTerminal.dispose();
|
||||||
|
}
|
||||||
|
if (client) {
|
||||||
|
return client.stop();
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLuxBinary(): string {
|
||||||
|
const config = vscode.workspace.getConfiguration('lux');
|
||||||
|
const customPath = config.get<string>('lspPath');
|
||||||
|
|
||||||
|
if (customPath && customPath.trim() !== '') {
|
||||||
|
return customPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try common locations
|
||||||
|
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
|
||||||
|
const possiblePaths = [
|
||||||
|
workspaceFolder ? path.join(workspaceFolder, 'result', 'bin', 'lux') : null,
|
||||||
|
workspaceFolder ? path.join(workspaceFolder, 'target', 'release', 'lux') : null,
|
||||||
|
workspaceFolder ? path.join(workspaceFolder, 'target', 'debug', 'lux') : null,
|
||||||
|
].filter(Boolean) as string[];
|
||||||
|
|
||||||
|
// Check if any exist
|
||||||
|
for (const p of possiblePaths) {
|
||||||
|
try {
|
||||||
|
require('fs').accessSync(p, require('fs').constants.X_OK);
|
||||||
|
return p;
|
||||||
|
} catch {
|
||||||
|
// Continue to next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to PATH
|
||||||
|
return 'lux';
|
||||||
|
}
|
||||||
|
|
||||||
|
function startLspClient(context: vscode.ExtensionContext) {
|
||||||
|
const luxBinary = getLuxBinary();
|
||||||
|
|
||||||
|
const serverOptions: ServerOptions = {
|
||||||
|
command: luxBinary,
|
||||||
|
args: ['lsp'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const clientOptions: LanguageClientOptions = {
|
||||||
|
documentSelector: [{ scheme: 'file', language: 'lux' }],
|
||||||
|
synchronize: {
|
||||||
|
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.lux'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
client = new LanguageClient(
|
||||||
|
'luxLanguageServer',
|
||||||
|
'Lux Language Server',
|
||||||
|
serverOptions,
|
||||||
|
clientOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
client.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function restartLspClient(context: vscode.ExtensionContext) {
|
||||||
|
if (client) {
|
||||||
|
await client.stop();
|
||||||
|
}
|
||||||
|
startLspClient(context);
|
||||||
|
vscode.window.showInformationMessage('Lux language server restarted');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runCurrentFile() {
|
||||||
|
const editor = vscode.window.activeTextEditor;
|
||||||
|
if (!editor || editor.document.languageId !== 'lux') {
|
||||||
|
vscode.window.showErrorMessage('No Lux file is active');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the file first
|
||||||
|
await editor.document.save();
|
||||||
|
|
||||||
|
const filePath = editor.document.uri.fsPath;
|
||||||
|
const luxBinary = getLuxBinary();
|
||||||
|
|
||||||
|
// Create terminal and run
|
||||||
|
const terminal = vscode.window.createTerminal('Lux Run');
|
||||||
|
terminal.show();
|
||||||
|
terminal.sendText(`${luxBinary} "${filePath}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function formatDocument() {
|
||||||
|
const editor = vscode.window.activeTextEditor;
|
||||||
|
if (!editor || editor.document.languageId !== 'lux') {
|
||||||
|
vscode.window.showErrorMessage('No Lux file is active');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await editor.document.save();
|
||||||
|
const formatted = await formatDocumentAsync(editor.document);
|
||||||
|
|
||||||
|
if (formatted.length > 0) {
|
||||||
|
const edit = new vscode.WorkspaceEdit();
|
||||||
|
edit.set(editor.document.uri, formatted);
|
||||||
|
await vscode.workspace.applyEdit(edit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function formatDocumentAsync(document: vscode.TextDocument): Promise<vscode.TextEdit[]> {
|
||||||
|
const luxBinary = getLuxBinary();
|
||||||
|
const filePath = document.uri.fsPath;
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const process = spawn(luxBinary, ['fmt', filePath]);
|
||||||
|
let stdout = '';
|
||||||
|
let stderr = '';
|
||||||
|
|
||||||
|
process.stdout.on('data', (data) => { stdout += data; });
|
||||||
|
process.stderr.on('data', (data) => { stderr += data; });
|
||||||
|
|
||||||
|
process.on('close', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
// File was formatted, need to reload
|
||||||
|
resolve([]);
|
||||||
|
} else {
|
||||||
|
vscode.window.showErrorMessage(`Format error: ${stderr}`);
|
||||||
|
resolve([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkFile() {
|
||||||
|
const editor = vscode.window.activeTextEditor;
|
||||||
|
if (!editor || editor.document.languageId !== 'lux') {
|
||||||
|
vscode.window.showErrorMessage('No Lux file is active');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await editor.document.save();
|
||||||
|
const filePath = editor.document.uri.fsPath;
|
||||||
|
const luxBinary = getLuxBinary();
|
||||||
|
|
||||||
|
const process = spawn(luxBinary, ['check', filePath]);
|
||||||
|
let stderr = '';
|
||||||
|
|
||||||
|
process.stderr.on('data', (data) => { stderr += data; });
|
||||||
|
|
||||||
|
process.on('close', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
vscode.window.showInformationMessage('No type errors found');
|
||||||
|
} else {
|
||||||
|
vscode.window.showErrorMessage(`Type check failed:\n${stderr}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRepl() {
|
||||||
|
if (replTerminal) {
|
||||||
|
replTerminal.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
const luxBinary = getLuxBinary();
|
||||||
|
replTerminal = vscode.window.createTerminal({
|
||||||
|
name: 'Lux REPL',
|
||||||
|
shellPath: luxBinary,
|
||||||
|
});
|
||||||
|
replTerminal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendToRepl() {
|
||||||
|
const editor = vscode.window.activeTextEditor;
|
||||||
|
if (!editor) {
|
||||||
|
vscode.window.showErrorMessage('No active editor');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get selection or current line
|
||||||
|
let text: string;
|
||||||
|
if (editor.selection.isEmpty) {
|
||||||
|
text = editor.document.lineAt(editor.selection.active.line).text;
|
||||||
|
} else {
|
||||||
|
text = editor.document.getText(editor.selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start REPL if not running
|
||||||
|
if (!replTerminal) {
|
||||||
|
startRepl();
|
||||||
|
// Wait a bit for REPL to start
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replTerminal) {
|
||||||
|
replTerminal.sendText(text);
|
||||||
|
replTerminal.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
203
editors/vscode/syntaxes/lux.tmLanguage.json
Normal file
203
editors/vscode/syntaxes/lux.tmLanguage.json
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
|
||||||
|
"name": "Lux",
|
||||||
|
"scopeName": "source.lux",
|
||||||
|
"patterns": [
|
||||||
|
{ "include": "#comments" },
|
||||||
|
{ "include": "#strings" },
|
||||||
|
{ "include": "#numbers" },
|
||||||
|
{ "include": "#keywords" },
|
||||||
|
{ "include": "#operators" },
|
||||||
|
{ "include": "#types" },
|
||||||
|
{ "include": "#functions" },
|
||||||
|
{ "include": "#variables" }
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"comments": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "comment.line.documentation.lux",
|
||||||
|
"match": "///.*$"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "comment.line.double-slash.lux",
|
||||||
|
"match": "//.*$"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "comment.block.lux",
|
||||||
|
"begin": "/\\*",
|
||||||
|
"end": "\\*/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"strings": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "string.quoted.double.lux",
|
||||||
|
"begin": "\"",
|
||||||
|
"end": "\"",
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "constant.character.escape.lux",
|
||||||
|
"match": "\\\\[nrt\\\\\"'0{}]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "meta.embedded.interpolation.lux",
|
||||||
|
"begin": "\\{",
|
||||||
|
"end": "\\}",
|
||||||
|
"beginCaptures": {
|
||||||
|
"0": { "name": "punctuation.section.interpolation.begin.lux" }
|
||||||
|
},
|
||||||
|
"endCaptures": {
|
||||||
|
"0": { "name": "punctuation.section.interpolation.end.lux" }
|
||||||
|
},
|
||||||
|
"patterns": [
|
||||||
|
{ "include": "$self" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "string.quoted.single.lux",
|
||||||
|
"match": "'[^'\\\\]'"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "string.quoted.single.lux",
|
||||||
|
"match": "'\\\\[nrt\\\\\"'0]'"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"numbers": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "constant.numeric.float.lux",
|
||||||
|
"match": "\\b\\d+\\.\\d+\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.numeric.hex.lux",
|
||||||
|
"match": "\\b0x[0-9a-fA-F]+\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.numeric.binary.lux",
|
||||||
|
"match": "\\b0b[01]+\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.numeric.integer.lux",
|
||||||
|
"match": "\\b\\d+\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"keywords": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "keyword.control.lux",
|
||||||
|
"match": "\\b(if|then|else|match|with|run|resume)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.declaration.lux",
|
||||||
|
"match": "\\b(fn|let|type|effect|handler|trait|impl|for)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.other.lux",
|
||||||
|
"match": "\\b(import|export|is)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.language.boolean.lux",
|
||||||
|
"match": "\\b(true|false)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.language.unit.lux",
|
||||||
|
"match": "\\(\\)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.language.option.lux",
|
||||||
|
"match": "\\b(None|Some)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.language.result.lux",
|
||||||
|
"match": "\\b(Ok|Err)\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"operators": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.arrow.lux",
|
||||||
|
"match": "=>|->|<-"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.pipe.lux",
|
||||||
|
"match": "\\|>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.comparison.lux",
|
||||||
|
"match": "==|!=|<=|>=|<|>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.logical.lux",
|
||||||
|
"match": "&&|\\|\\||!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.arithmetic.lux",
|
||||||
|
"match": "[+\\-*/%]"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.assignment.lux",
|
||||||
|
"match": "="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.type.lux",
|
||||||
|
"match": ":"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "punctuation.separator.variant.lux",
|
||||||
|
"match": "\\|"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"types": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "support.type.primitive.lux",
|
||||||
|
"match": "\\b(Int|Float|Bool|String|Char|Unit)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "support.type.builtin.lux",
|
||||||
|
"match": "\\b(Option|Result|List)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "entity.name.type.lux",
|
||||||
|
"match": "\\b[A-Z][a-zA-Z0-9_]*\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"functions": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "entity.name.function.definition.lux",
|
||||||
|
"match": "(?<=fn\\s+)[a-z_][a-zA-Z0-9_]*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "entity.name.function.call.lux",
|
||||||
|
"match": "\\b[a-z_][a-zA-Z0-9_]*(?=\\s*\\()"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "entity.name.function.method.lux",
|
||||||
|
"match": "(?<=\\.)[a-z_][a-zA-Z0-9_]*(?=\\s*\\()"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"variables": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "variable.parameter.lux",
|
||||||
|
"match": "(?<=[(:,]\\s*)[a-z_][a-zA-Z0-9_]*(?=\\s*:)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "variable.other.lux",
|
||||||
|
"match": "\\b[a-z_][a-zA-Z0-9_]*\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
editors/vscode/tsconfig.json
Normal file
14
editors/vscode/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "ES2020",
|
||||||
|
"outDir": "out",
|
||||||
|
"lib": ["ES2020"],
|
||||||
|
"sourceMap": true,
|
||||||
|
"rootDir": "src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", ".vscode-test"]
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
//! This module compiles Lux programs to native machine code using Cranelift.
|
//! This module compiles Lux programs to native machine code using Cranelift.
|
||||||
//! Currently supports a subset of the language for performance-critical code.
|
//! Currently supports a subset of the language for performance-critical code.
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use crate::ast::{Expr, Program, Declaration, FunctionDecl, BinaryOp, UnaryOp, LiteralKind, Statement};
|
use crate::ast::{Expr, Program, Declaration, FunctionDecl, BinaryOp, UnaryOp, LiteralKind, Statement};
|
||||||
use cranelift_codegen::ir::{AbiParam, InstBuilder, Value, types};
|
use cranelift_codegen::ir::{AbiParam, InstBuilder, Value, types};
|
||||||
use cranelift_codegen::ir::condcodes::IntCC;
|
use cranelift_codegen::ir::condcodes::IntCC;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::parser::Parser;
|
|||||||
|
|
||||||
/// Formatter configuration
|
/// Formatter configuration
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
#[allow(dead_code)]
|
||||||
pub struct FormatConfig {
|
pub struct FormatConfig {
|
||||||
/// Number of spaces for indentation
|
/// Number of spaces for indentation
|
||||||
pub indent_size: usize,
|
pub indent_size: usize,
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ impl PackageManager {
|
|||||||
println!("Installing {} dependencies...", manifest.dependencies.len());
|
println!("Installing {} dependencies...", manifest.dependencies.len());
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
for (name, dep) in &manifest.dependencies {
|
for (_name, dep) in &manifest.dependencies {
|
||||||
self.install_dependency(dep)?;
|
self.install_dependency(dep)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,6 +351,7 @@ impl PackageManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the search paths for installed packages
|
/// Get the search paths for installed packages
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn get_package_paths(&self) -> Vec<PathBuf> {
|
pub fn get_package_paths(&self) -> Vec<PathBuf> {
|
||||||
let mut paths = vec![];
|
let mut paths = vec![];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user