feat: implement LSP server for IDE integration

Add a Language Server Protocol (LSP) server to enable IDE integration.
The server provides:

- Real-time diagnostics (parse errors and type errors)
- Basic hover information
- Keyword completions (fn, let, if, match, type, effect, etc.)
- Go-to-definition stub (ready for implementation)

Usage: lux --lsp

The LSP server can be integrated with any editor that supports LSP,
including VS Code, Neovim, Emacs, and others.

Dependencies added:
- lsp-server 0.7
- lsp-types 0.94
- serde with derive feature
- serde_json

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 05:42:37 -05:00
parent 3206aad653
commit 20bf75a5f8
4 changed files with 722 additions and 7 deletions

345
Cargo.lock generated
View File

@@ -8,6 +8,12 @@ version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.10.0"
@@ -35,6 +41,32 @@ dependencies = [
"error-code",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "endian-type"
version = "0.1.2"
@@ -86,6 +118,15 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
"percent-encoding",
]
[[package]]
name = "getrandom"
version = "0.4.1"
@@ -129,12 +170,114 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "icu_collections"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
dependencies = [
"displaydoc",
"potential_utf",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_normalizer"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
dependencies = [
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
[[package]]
name = "icu_properties"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
dependencies = [
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
[[package]]
name = "icu_provider"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
dependencies = [
"displaydoc",
"icu_locale_core",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "idna"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
name = "indexmap"
version = "2.13.0"
@@ -171,17 +314,53 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "litemap"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "lsp-server"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d6ada348dbc2703cbe7637b2dda05cff84d3da2819c24abcb305dd613e0ba2e"
dependencies = [
"crossbeam-channel",
"log",
"serde",
"serde_derive",
"serde_json",
]
[[package]]
name = "lsp-types"
version = "0.94.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1"
dependencies = [
"bitflags 1.3.2",
"serde",
"serde_json",
"serde_repr",
"url",
]
[[package]]
name = "lux"
version = "0.1.0"
dependencies = [
"lsp-server",
"lsp-types",
"rustyline",
"serde",
"serde_json",
"tempfile",
"thiserror",
]
@@ -207,7 +386,7 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4"
dependencies = [
"bitflags",
"bitflags 2.10.0",
"cfg-if",
"cfg_aliases",
"libc",
@@ -219,6 +398,21 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "potential_utf"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
dependencies = [
"zerovec",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
@@ -269,7 +463,7 @@ version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags",
"bitflags 2.10.0",
"errno",
"libc",
"linux-raw-sys",
@@ -282,7 +476,7 @@ version = "14.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63"
dependencies = [
"bitflags",
"bitflags 2.10.0",
"cfg-if",
"clipboard-win",
"fd-lock",
@@ -311,6 +505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
@@ -346,12 +541,29 @@ dependencies = [
"zmij",
]
[[package]]
name = "serde_repr"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
version = "2.0.115"
@@ -363,6 +575,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tempfile"
version = "3.25.0"
@@ -396,6 +619,16 @@ dependencies = [
"syn",
]
[[package]]
name = "tinystr"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "unicode-ident"
version = "1.0.23"
@@ -420,6 +653,25 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "url"
version = "2.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
"serde_derive",
]
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
@@ -472,7 +724,7 @@ version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"bitflags 2.10.0",
"hashbrown 0.15.5",
"indexmap",
"semver",
@@ -633,7 +885,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"bitflags 2.10.0",
"indexmap",
"log",
"serde",
@@ -663,6 +915,89 @@ dependencies = [
"wasmparser",
]
[[package]]
name = "writeable"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "yoke"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
dependencies = [
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerotrie"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zmij"
version = "1.0.21"

View File

@@ -8,6 +8,10 @@ license = "MIT"
[dependencies]
rustyline = "14"
thiserror = "1"
lsp-server = "0.7"
lsp-types = "0.94"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
[dev-dependencies]
tempfile = "3"

352
src/lsp.rs Normal file
View File

@@ -0,0 +1,352 @@
//! LSP (Language Server Protocol) server for Lux
//!
//! Provides IDE features like:
//! - Diagnostics (errors and warnings)
//! - Hover information
//! - Go to definition
//! - Completions
use crate::parser::Parser;
use crate::typechecker::TypeChecker;
use lsp_server::{Connection, ExtractError, Message, Request, RequestId, Response};
use lsp_types::{
notification::{DidChangeTextDocument, DidOpenTextDocument, Notification},
request::{Completion, GotoDefinition, HoverRequest},
CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse,
Diagnostic, DiagnosticSeverity, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams,
HoverProviderCapability, InitializeParams, MarkupContent, MarkupKind, Position,
PublishDiagnosticsParams, Range, ServerCapabilities, TextDocumentSyncCapability,
TextDocumentSyncKind, Url,
};
use std::collections::HashMap;
use std::error::Error;
/// LSP Server for Lux
pub struct LspServer {
connection: Connection,
/// Document contents by URI
documents: HashMap<Url, String>,
}
impl LspServer {
/// Run the LSP server
pub fn run() -> Result<(), Box<dyn Error + Sync + Send>> {
eprintln!("Starting Lux LSP server...");
// Create the transport connection
let (connection, io_threads) = Connection::stdio();
// Run the server
let server = LspServer {
connection,
documents: HashMap::new(),
};
server.main_loop()?;
// Wait for the I/O threads to finish
io_threads.join()?;
eprintln!("Lux LSP server stopped.");
Ok(())
}
fn main_loop(mut self) -> Result<(), Box<dyn Error + Sync + Send>> {
// Initialize
let server_capabilities = serde_json::to_value(ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::FULL)),
hover_provider: Some(HoverProviderCapability::Simple(true)),
completion_provider: Some(CompletionOptions {
trigger_characters: Some(vec![".".to_string()]),
..Default::default()
}),
definition_provider: Some(lsp_types::OneOf::Left(true)),
..Default::default()
})?;
let init_params = self.connection.initialize(server_capabilities)?;
let _init_params: InitializeParams = serde_json::from_value(init_params)?;
eprintln!("LSP server initialized");
// Main message loop
loop {
let msg = match self.connection.receiver.recv() {
Ok(msg) => msg,
Err(_) => break, // Channel closed
};
match msg {
Message::Request(req) => {
if self.connection.handle_shutdown(&req)? {
return Ok(());
}
self.handle_request(req)?;
}
Message::Notification(not) => {
self.handle_notification(not)?;
}
Message::Response(_) => {}
}
}
Ok(())
}
fn handle_request(&self, req: Request) -> Result<(), Box<dyn Error + Sync + Send>> {
let req = match cast_request::<HoverRequest>(req) {
Ok((id, params)) => {
let result = self.handle_hover(params);
let resp = Response::new_ok(id, result);
self.connection.sender.send(Message::Response(resp))?;
return Ok(());
}
Err(req) => req,
};
let req = match cast_request::<Completion>(req) {
Ok((id, params)) => {
let result = self.handle_completion(params);
let resp = Response::new_ok(id, result);
self.connection.sender.send(Message::Response(resp))?;
return Ok(());
}
Err(req) => req,
};
let _req = match cast_request::<GotoDefinition>(req) {
Ok((id, params)) => {
let result = self.handle_goto_definition(params);
let resp = Response::new_ok(id, result);
self.connection.sender.send(Message::Response(resp))?;
return Ok(());
}
Err(req) => req,
};
Ok(())
}
fn handle_notification(
&mut self,
not: lsp_server::Notification,
) -> Result<(), Box<dyn Error + Sync + Send>> {
match not.method.as_str() {
DidOpenTextDocument::METHOD => {
let params: DidOpenTextDocumentParams = serde_json::from_value(not.params)?;
let uri = params.text_document.uri;
let text = params.text_document.text;
self.documents.insert(uri.clone(), text.clone());
self.publish_diagnostics(uri, &text)?;
}
DidChangeTextDocument::METHOD => {
let params: DidChangeTextDocumentParams = serde_json::from_value(not.params)?;
let uri = params.text_document.uri;
if let Some(change) = params.content_changes.into_iter().last() {
self.documents.insert(uri.clone(), change.text.clone());
self.publish_diagnostics(uri, &change.text)?;
}
}
_ => {}
}
Ok(())
}
fn publish_diagnostics(
&self,
uri: Url,
text: &str,
) -> Result<(), Box<dyn Error + Sync + Send>> {
let diagnostics = self.get_diagnostics(text);
let params = PublishDiagnosticsParams {
uri,
diagnostics,
version: None,
};
self.connection.sender.send(Message::Notification(
lsp_server::Notification::new(
"textDocument/publishDiagnostics".to_string(),
params,
),
))?;
Ok(())
}
fn get_diagnostics(&self, source: &str) -> Vec<Diagnostic> {
let mut diagnostics = Vec::new();
// Parse the source
let program = match Parser::parse_source(source) {
Ok(prog) => prog,
Err(err) => {
diagnostics.push(Diagnostic {
range: span_to_range(source, err.span.start, err.span.end),
severity: Some(DiagnosticSeverity::ERROR),
message: err.message,
..Default::default()
});
return diagnostics;
}
};
// Type check
let mut checker = TypeChecker::new();
if let Err(errors) = checker.check_program(&program) {
for error in errors {
diagnostics.push(Diagnostic {
range: span_to_range(source, error.span.start, error.span.end),
severity: Some(DiagnosticSeverity::ERROR),
message: error.message,
..Default::default()
});
}
}
diagnostics
}
fn handle_hover(&self, params: HoverParams) -> Option<Hover> {
let uri = params.text_document_position_params.text_document.uri;
let _position = params.text_document_position_params.position;
let _source = self.documents.get(&uri)?;
// For now, return basic hover info
// A full implementation would find the symbol at the position
// and return its type and documentation
Some(Hover {
contents: HoverContents::Markup(MarkupContent {
kind: MarkupKind::Markdown,
value: "Lux language element".to_string(),
}),
range: None,
})
}
fn handle_completion(&self, params: CompletionParams) -> Option<CompletionResponse> {
let _uri = params.text_document_position.text_document.uri;
// Return basic completions
// A full implementation would analyze context and provide
// relevant completions based on scope
let items = vec![
CompletionItem {
label: "fn".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Function declaration".to_string()),
..Default::default()
},
CompletionItem {
label: "let".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Variable binding".to_string()),
..Default::default()
},
CompletionItem {
label: "if".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Conditional expression".to_string()),
..Default::default()
},
CompletionItem {
label: "match".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Pattern matching".to_string()),
..Default::default()
},
CompletionItem {
label: "type".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Type declaration".to_string()),
..Default::default()
},
CompletionItem {
label: "effect".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Effect declaration".to_string()),
..Default::default()
},
CompletionItem {
label: "handler".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Effect handler".to_string()),
..Default::default()
},
CompletionItem {
label: "trait".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Trait declaration".to_string()),
..Default::default()
},
CompletionItem {
label: "impl".to_string(),
kind: Some(CompletionItemKind::KEYWORD),
detail: Some("Trait implementation".to_string()),
..Default::default()
},
];
Some(CompletionResponse::Array(items))
}
fn handle_goto_definition(
&self,
_params: GotoDefinitionParams,
) -> Option<GotoDefinitionResponse> {
// A full implementation would find the definition location
// of the symbol at the given position
None
}
}
/// Convert byte offsets to LSP Position
fn span_to_range(source: &str, start: usize, end: usize) -> Range {
let start_pos = offset_to_position(source, start);
let end_pos = offset_to_position(source, end);
Range {
start: start_pos,
end: end_pos,
}
}
fn offset_to_position(source: &str, offset: usize) -> Position {
let mut line = 0u32;
let mut col = 0u32;
for (i, c) in source.char_indices() {
if i >= offset {
break;
}
if c == '\n' {
line += 1;
col = 0;
} else {
col += 1;
}
}
Position {
line,
character: col,
}
}
fn cast_request<R>(req: Request) -> Result<(RequestId, R::Params), Request>
where
R: lsp_types::request::Request,
R::Params: serde::de::DeserializeOwned,
{
match req.extract(R::METHOD) {
Ok(params) => Ok(params),
Err(ExtractError::MethodMismatch(req)) => Err(req),
Err(ExtractError::JsonError { .. }) => {
// This shouldn't happen if the client is well-behaved
panic!("JSON deserialization error in LSP request")
}
}
}

View File

@@ -5,6 +5,7 @@ mod diagnostics;
mod exhaustiveness;
mod interpreter;
mod lexer;
mod lsp;
mod modules;
mod parser;
mod schema;
@@ -67,8 +68,31 @@ fn main() {
let args: Vec<String> = std::env::args().collect();
if args.len() > 1 {
// Run a file
run_file(&args[1]);
match args[1].as_str() {
"--lsp" => {
// Run LSP server
if let Err(e) = lsp::LspServer::run() {
eprintln!("LSP server error: {}", e);
std::process::exit(1);
}
}
"--help" | "-h" => {
println!("Lux {} - A functional language with first-class effects", VERSION);
println!();
println!("Usage:");
println!(" lux Start the REPL");
println!(" lux <file.lux> Run a file");
println!(" lux --lsp Start LSP server (for IDE integration)");
println!(" lux --help Show this help");
}
"--version" | "-v" => {
println!("Lux {}", VERSION);
}
path => {
// Run a file
run_file(path);
}
}
} else {
// Start REPL
run_repl();