fix: C backend String functions, record type aliases, docs cleanup
- Add String.fromChar, chars, substring, toUpper, toLower, replace, startsWith, endsWith, join to C backend - Fix record type alias unification by adding expand_type_alias and unify_with_env functions - Update docs to reflect current implementation status - Clean up outdated roadmap items and fix inconsistencies - Add comprehensive language comparison document Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
433
docs/LSP.md
Normal file
433
docs/LSP.md
Normal file
@@ -0,0 +1,433 @@
|
||||
# Lux Language Server Protocol (LSP)
|
||||
|
||||
Lux includes a built-in language server that provides IDE features for any editor that supports the Language Server Protocol.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Starting the LSP Server
|
||||
|
||||
```bash
|
||||
lux lsp
|
||||
```
|
||||
|
||||
The server communicates via stdio (stdin/stdout), following the standard LSP protocol.
|
||||
|
||||
## Supported Features
|
||||
|
||||
| Feature | Status | Description |
|
||||
|---------|--------|-------------|
|
||||
| Diagnostics | Full | Real-time parse and type errors |
|
||||
| Hover | Full | Type information and documentation |
|
||||
| Completions | Full | Context-aware code completion |
|
||||
| Go to Definition | Full | Jump to function/type definitions |
|
||||
| Formatting | CLI only | Code formatting via `lux fmt` (not exposed via LSP) |
|
||||
| Rename | Planned | Rename symbols |
|
||||
| Find References | Planned | Find all usages |
|
||||
|
||||
## Editor Setup
|
||||
|
||||
### VS Code
|
||||
|
||||
1. Install the Lux extension from the VS Code marketplace (coming soon)
|
||||
|
||||
2. Or configure manually in `settings.json`:
|
||||
```json
|
||||
{
|
||||
"lux.lspPath": "/path/to/lux",
|
||||
"lux.enableLsp": true
|
||||
}
|
||||
```
|
||||
|
||||
3. Or use a generic LSP client extension like [vscode-languageclient](https://github.com/microsoft/vscode-languageserver-node):
|
||||
|
||||
```json
|
||||
{
|
||||
"languageServerExample.serverPath": "lux",
|
||||
"languageServerExample.serverArgs": ["lsp"]
|
||||
}
|
||||
```
|
||||
|
||||
### Neovim (with nvim-lspconfig)
|
||||
|
||||
Add to your Neovim config (`init.lua`):
|
||||
|
||||
```lua
|
||||
local lspconfig = require('lspconfig')
|
||||
local configs = require('lspconfig.configs')
|
||||
|
||||
-- Register Lux as a new LSP
|
||||
if not configs.lux then
|
||||
configs.lux = {
|
||||
default_config = {
|
||||
cmd = { 'lux', 'lsp' },
|
||||
filetypes = { 'lux' },
|
||||
root_dir = function(fname)
|
||||
return lspconfig.util.find_git_ancestor(fname) or vim.fn.getcwd()
|
||||
end,
|
||||
settings = {},
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
-- Enable the server
|
||||
lspconfig.lux.setup{}
|
||||
```
|
||||
|
||||
Also add filetype detection in `~/.config/nvim/ftdetect/lux.vim`:
|
||||
|
||||
```vim
|
||||
au BufRead,BufNewFile *.lux set filetype=lux
|
||||
```
|
||||
|
||||
### Neovim (with built-in LSP)
|
||||
|
||||
```lua
|
||||
vim.api.nvim_create_autocmd('FileType', {
|
||||
pattern = 'lux',
|
||||
callback = function()
|
||||
vim.lsp.start({
|
||||
name = 'lux',
|
||||
cmd = { 'lux', 'lsp' },
|
||||
root_dir = vim.fs.dirname(vim.fs.find({ '.git', 'lux.toml' }, { upward = true })[1]),
|
||||
})
|
||||
end,
|
||||
})
|
||||
```
|
||||
|
||||
### Emacs (with lsp-mode)
|
||||
|
||||
Add to your Emacs config:
|
||||
|
||||
```elisp
|
||||
(use-package lsp-mode
|
||||
:hook (lux-mode . lsp)
|
||||
:config
|
||||
(add-to-list 'lsp-language-id-configuration '(lux-mode . "lux"))
|
||||
(lsp-register-client
|
||||
(make-lsp-client
|
||||
:new-connection (lsp-stdio-connection '("lux" "lsp"))
|
||||
:major-modes '(lux-mode)
|
||||
:server-id 'lux-lsp)))
|
||||
```
|
||||
|
||||
### Emacs (with eglot)
|
||||
|
||||
```elisp
|
||||
(add-to-list 'eglot-server-programs '(lux-mode . ("lux" "lsp")))
|
||||
```
|
||||
|
||||
### Helix
|
||||
|
||||
Add to `~/.config/helix/languages.toml`:
|
||||
|
||||
```toml
|
||||
[[language]]
|
||||
name = "lux"
|
||||
scope = "source.lux"
|
||||
injection-regex = "lux"
|
||||
file-types = ["lux"]
|
||||
roots = ["lux.toml", ".git"]
|
||||
comment-token = "//"
|
||||
indent = { tab-width = 4, unit = " " }
|
||||
language-server = { command = "lux", args = ["lsp"] }
|
||||
```
|
||||
|
||||
### Sublime Text (with LSP package)
|
||||
|
||||
Add to `Preferences > Package Settings > LSP > Settings`:
|
||||
|
||||
```json
|
||||
{
|
||||
"clients": {
|
||||
"lux": {
|
||||
"enabled": true,
|
||||
"command": ["lux", "lsp"],
|
||||
"selector": "source.lux"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Zed
|
||||
|
||||
Add to your Zed settings:
|
||||
|
||||
```json
|
||||
{
|
||||
"lsp": {
|
||||
"lux": {
|
||||
"binary": {
|
||||
"path": "lux",
|
||||
"arguments": ["lsp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Feature Details
|
||||
|
||||
### Diagnostics
|
||||
|
||||
The LSP server provides real-time diagnostics for:
|
||||
|
||||
- **Parse errors**: Syntax issues, missing tokens, unexpected input
|
||||
- **Type errors**: Type mismatches, missing fields, unknown identifiers
|
||||
- **Effect errors**: Missing effect declarations, unhandled effects
|
||||
- **Behavioral type errors**: Violations of `is pure`, `is total`, etc.
|
||||
|
||||
Diagnostics appear as you type, typically within 100ms of changes.
|
||||
|
||||
Example diagnostic:
|
||||
```
|
||||
error[E0308]: mismatched types
|
||||
--> src/main.lux:10:5
|
||||
|
|
||||
10 | return "hello"
|
||||
| ^^^^^^^ expected Int, found String
|
||||
```
|
||||
|
||||
### Hover Information
|
||||
|
||||
Hover over any symbol to see:
|
||||
|
||||
- **Functions**: Signature and documentation
|
||||
- **Types**: Type definition
|
||||
- **Keywords**: Syntax explanation
|
||||
- **Effects**: Effect operations
|
||||
|
||||
Example hover for `List.map`:
|
||||
```markdown
|
||||
```lux
|
||||
List.map(list: List<A>, f: A -> B): List<B>
|
||||
```
|
||||
|
||||
Transform each element in a list by applying a function.
|
||||
```
|
||||
|
||||
### Completions
|
||||
|
||||
The LSP provides context-aware completions:
|
||||
|
||||
#### Module Member Completions
|
||||
|
||||
After typing a module name and `.`, you get relevant members:
|
||||
|
||||
```lux
|
||||
List. // Shows: map, filter, fold, reverse, etc.
|
||||
String. // Shows: length, split, join, trim, etc.
|
||||
Console. // Shows: print, readLine, readInt
|
||||
```
|
||||
|
||||
#### Keyword Completions
|
||||
|
||||
At the start of statements:
|
||||
|
||||
```lux
|
||||
fn // Function declaration
|
||||
let // Variable binding
|
||||
type // Type declaration
|
||||
effect // Effect declaration
|
||||
match // Pattern matching
|
||||
```
|
||||
|
||||
#### Type Completions
|
||||
|
||||
In type position:
|
||||
|
||||
```lux
|
||||
Int, Float, Bool, String, Unit
|
||||
List, Option, Result
|
||||
```
|
||||
|
||||
### Go to Definition
|
||||
|
||||
Jump to the definition of:
|
||||
|
||||
- **Functions**: Goes to the `fn` declaration
|
||||
- **Types**: Goes to the `type` declaration
|
||||
- **Effects**: Goes to the `effect` declaration
|
||||
- **Let bindings**: Goes to the `let` statement
|
||||
- **Imports**: Goes to the imported module
|
||||
|
||||
Works with:
|
||||
- `Ctrl+Click` / `Cmd+Click` in most editors
|
||||
- `gd` in Vim/Neovim
|
||||
- `M-.` in Emacs
|
||||
|
||||
## Module Completions Reference
|
||||
|
||||
### List Module
|
||||
|
||||
| Method | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `length` | `(list: List<A>): Int` | Get list length |
|
||||
| `head` | `(list: List<A>): Option<A>` | First element |
|
||||
| `tail` | `(list: List<A>): List<A>` | All but first |
|
||||
| `map` | `(list: List<A>, f: A -> B): List<B>` | Transform elements |
|
||||
| `filter` | `(list: List<A>, p: A -> Bool): List<A>` | Keep matching |
|
||||
| `fold` | `(list: List<A>, init: B, f: (B, A) -> B): B` | Reduce |
|
||||
| `reverse` | `(list: List<A>): List<A>` | Reverse order |
|
||||
| `concat` | `(a: List<A>, b: List<A>): List<A>` | Join lists |
|
||||
| `range` | `(start: Int, end: Int): List<Int>` | Create range |
|
||||
| `get` | `(list: List<A>, index: Int): Option<A>` | Get at index |
|
||||
| `find` | `(list: List<A>, p: A -> Bool): Option<A>` | Find first match |
|
||||
| `isEmpty` | `(list: List<A>): Bool` | Check empty |
|
||||
| `take` | `(list: List<A>, n: Int): List<A>` | Take n elements |
|
||||
| `drop` | `(list: List<A>, n: Int): List<A>` | Drop n elements |
|
||||
| `any` | `(list: List<A>, p: A -> Bool): Bool` | Any matches |
|
||||
| `all` | `(list: List<A>, p: A -> Bool): Bool` | All match |
|
||||
|
||||
### String Module
|
||||
|
||||
| Method | Signature | Description |
|
||||
|--------|-----------|-------------|
|
||||
| `length` | `(s: String): Int` | String length |
|
||||
| `split` | `(s: String, delim: String): List<String>` | Split by delimiter |
|
||||
| `join` | `(list: List<String>, delim: String): String` | Join with delimiter |
|
||||
| `trim` | `(s: String): String` | Remove whitespace |
|
||||
| `contains` | `(s: String, sub: String): Bool` | Check contains |
|
||||
| `replace` | `(s: String, from: String, to: String): String` | Replace |
|
||||
| `chars` | `(s: String): List<String>` | To char list |
|
||||
| `lines` | `(s: String): List<String>` | Split into lines |
|
||||
| `toUpper` | `(s: String): String` | Uppercase |
|
||||
| `toLower` | `(s: String): String` | Lowercase |
|
||||
|
||||
### Console Effect
|
||||
|
||||
| Operation | Signature | Description |
|
||||
|-----------|-----------|-------------|
|
||||
| `print` | `(msg: String): Unit` | Print message |
|
||||
| `readLine` | `(): String` | Read line |
|
||||
| `readInt` | `(): Int` | Read integer |
|
||||
|
||||
### Math Module
|
||||
|
||||
| Function | Signature | Description |
|
||||
|----------|-----------|-------------|
|
||||
| `abs` | `(x: Int): Int` | Absolute value |
|
||||
| `min` | `(a: Int, b: Int): Int` | Minimum |
|
||||
| `max` | `(a: Int, b: Int): Int` | Maximum |
|
||||
| `pow` | `(base: Float, exp: Float): Float` | Exponentiation |
|
||||
| `sqrt` | `(x: Float): Float` | Square root |
|
||||
| `floor` | `(x: Float): Int` | Floor |
|
||||
| `ceil` | `(x: Float): Int` | Ceiling |
|
||||
| `round` | `(x: Float): Int` | Round |
|
||||
|
||||
### File Effect
|
||||
|
||||
| Operation | Signature | Description |
|
||||
|-----------|-----------|-------------|
|
||||
| `read` | `(path: String): String` | Read file |
|
||||
| `write` | `(path: String, content: String): Unit` | Write file |
|
||||
| `exists` | `(path: String): Bool` | Check exists |
|
||||
| `delete` | `(path: String): Bool` | Delete file |
|
||||
| `listDir` | `(path: String): List<String>` | List directory |
|
||||
| `mkdir` | `(path: String): Bool` | Create directory |
|
||||
|
||||
### Http Effect
|
||||
|
||||
| Operation | Signature | Description |
|
||||
|-----------|-----------|-------------|
|
||||
| `get` | `(url: String): Result<String, String>` | HTTP GET |
|
||||
| `post` | `(url: String, body: String): Result<String, String>` | HTTP POST |
|
||||
| `put` | `(url: String, body: String): Result<String, String>` | HTTP PUT |
|
||||
| `delete` | `(url: String): Result<String, String>` | HTTP DELETE |
|
||||
|
||||
### Random Effect
|
||||
|
||||
| Operation | Signature | Description |
|
||||
|-----------|-----------|-------------|
|
||||
| `int` | `(min: Int, max: Int): Int` | Random integer |
|
||||
| `float` | `(): Float` | Random float 0-1 |
|
||||
| `bool` | `(): Bool` | Random boolean |
|
||||
|
||||
### Time Effect
|
||||
|
||||
| Operation | Signature | Description |
|
||||
|-----------|-----------|-------------|
|
||||
| `now` | `(): Int` | Current timestamp (ms) |
|
||||
| `sleep` | `(ms: Int): Unit` | Sleep for ms |
|
||||
|
||||
### Sql Effect
|
||||
|
||||
| Operation | Signature | Description |
|
||||
|-----------|-----------|-------------|
|
||||
| `connect` | `(url: String): Connection` | Connect to database |
|
||||
| `query` | `(conn: Connection, sql: String): List<Row>` | Execute query |
|
||||
| `execute` | `(conn: Connection, sql: String): Int` | Execute statement |
|
||||
| `transaction` | `(conn: Connection, f: () -> A): A` | Run in transaction |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### LSP Not Starting
|
||||
|
||||
1. Verify Lux is installed: `lux --version`
|
||||
2. Check the server starts: `lux lsp` (should wait for input)
|
||||
3. Check editor logs for connection errors
|
||||
|
||||
### No Completions
|
||||
|
||||
1. Ensure file has `.lux` extension
|
||||
2. Check file is valid (no parse errors)
|
||||
3. Verify LSP is connected (check status bar)
|
||||
|
||||
### Slow Diagnostics
|
||||
|
||||
The LSP re-parses and type-checks on every change. For large files:
|
||||
|
||||
1. Consider splitting into modules
|
||||
2. Check for complex recursive types
|
||||
3. Report performance issues on GitHub
|
||||
|
||||
### Debug Logging
|
||||
|
||||
Enable verbose logging:
|
||||
|
||||
```bash
|
||||
LUX_LSP_LOG=debug lux lsp
|
||||
```
|
||||
|
||||
Logs are written to stderr.
|
||||
|
||||
## Protocol Details
|
||||
|
||||
The Lux LSP server implements LSP version 3.17 with the following capabilities:
|
||||
|
||||
```json
|
||||
{
|
||||
"capabilities": {
|
||||
"textDocumentSync": "full",
|
||||
"hoverProvider": true,
|
||||
"completionProvider": {
|
||||
"triggerCharacters": ["."]
|
||||
},
|
||||
"definitionProvider": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Supported Methods
|
||||
|
||||
| Method | Support |
|
||||
|--------|---------|
|
||||
| `initialize` | Full |
|
||||
| `shutdown` | Full |
|
||||
| `textDocument/didOpen` | Full |
|
||||
| `textDocument/didChange` | Full |
|
||||
| `textDocument/didClose` | Full |
|
||||
| `textDocument/hover` | Full |
|
||||
| `textDocument/completion` | Full |
|
||||
| `textDocument/definition` | Full |
|
||||
| `textDocument/publishDiagnostics` | Full (server-initiated) |
|
||||
|
||||
## Contributing
|
||||
|
||||
The LSP implementation is in `src/lsp.rs`. To add new features:
|
||||
|
||||
1. Add capability to `ServerCapabilities`
|
||||
2. Implement handler in `handle_request`
|
||||
3. Add tests in `tests/lsp_tests.rs`
|
||||
4. Update this documentation
|
||||
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup.
|
||||
Reference in New Issue
Block a user