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:
345
Cargo.lock
generated
345
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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
352
src/lsp.rs
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/main.rs
26
src/main.rs
@@ -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 {
|
||||
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(&args[1]);
|
||||
run_file(path);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Start REPL
|
||||
run_repl();
|
||||
|
||||
Reference in New Issue
Block a user