diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0621f86..18fc5ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,3 +16,11 @@ jobs: - run: rustup update stable && rustup default stable - run: cargo build --verbose - run: cargo test --verbose + + wasm: + name: analysis lib - wasm32 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: rustup update stable && rustup default stable && rustup target add wasm32-unknown-unknown + - run: cargo build --no-default-features --target wasm32-unknown-unknown --verbose diff --git a/Cargo.toml b/Cargo.toml index cbf8636..b11f164 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,32 +4,39 @@ version = "0.1.0" edition = "2021" license = "GPL-3.0-only" +[lib] +name = "comline_language_server" +path = "src/lib.rs" + [[bin]] name = "comline-lsp" path = "src/main.rs" +required-features = ["server"] -[dependencies] -# LSP Infrastructure -tower-lsp = "0.20.0" -tokio = { version = "1.35", features = ["full"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" - -# Comline Core Integration — by git rev, like the rest of the tree (the LSP -# links `comline-core` and is part of the GPL toolchain). Local iteration: -# uncomment the `[patch]` below onto a sibling `core` checkout. -comline-core = { git = "https://github.com/ComlineProject/core", rev = "50c17cc740676416e945582dd6e2547e8535ac03" } -rust-sitter = "0.4.5" # For parse error types - -# Diagnostics & Error Reporting -ariadne = { version = "0.4.0", features = ["auto-color"] } -codespan-reporting = "0.11" +[features] +default = ["server"] +# The `tower-lsp` stdio server (the `comline-lsp` binary). With it off, only the +# analysis library builds — `parser` + `analysis` + `handlers`, over `lsp-types` +# and `comline-core` — and that compiles for `wasm32-unknown-unknown`. The +# Comline playground links the crate that way. +server = ["dep:tower-lsp", "dep:tokio", "dep:tracing-subscriber", "dep:dashmap"] -# Utilities +[dependencies] +# The analysis layer: pure `lsp-types` (serde only, wasm-safe), the parser, and +# `comline-core`. By git rev, like the rest of the tree — the LSP links +# `comline-core` and is part of the GPL toolchain. Local iteration: uncomment +# the `[patch]` below onto a sibling `core` checkout. +lsp-types = "0.94" +rust-sitter = "0.4.5" tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } anyhow = "1.0" -dashmap = "5.5" +comline-core = { git = "https://github.com/ComlineProject/core", rev = "50c17cc740676416e945582dd6e2547e8535ac03" } + +# `server`-only. +tower-lsp = { version = "0.20.0", optional = true } +tokio = { version = "1.35", features = ["full"], optional = true } +tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = true } +dashmap = { version = "5.5", optional = true } # Local iteration against a sibling `core` checkout — uncomment to build # `comline-core` from the working tree instead of the pinned git rev. diff --git a/README.md b/README.md index e992071..10d480d 100644 --- a/README.md +++ b/README.md @@ -11,19 +11,23 @@ A Language Server Protocol (LSP) implementation for [Comline](https://github.com - **Hover Information** - Rich tooltips showing full type definitions and signatures - **Go to Definition** - Jump from type references to their declarations - **Find References** - Locate all usages of a symbol (with include/exclude declaration option) -- **Auto-Completion** - Context-aware code suggestions: - - Keywords with snippets (`struct`, `enum`, `protocol`, `const`, `use`, `import`) - - Primitive types (i8-i64, u8-u64, f32/f64, string, bool) - - User-defined types (all structs, enums, protocols from current file) - - Context detection (type position, top-level, struct body) - -### 🚧 Planned - -- Find References - Locate all usages of a symbol -- Auto-Completion - Context-aware code completion -- Rename Symbol - Safe refactoring across files -- Code Formatting - Automatic code formatting -- Semantic Tokens - Enhanced syntax highlighting +- **Auto-Completion** - Context-aware code suggestions (keywords, primitives, user types) +- **Semantic Tokens** - Comline syntax highlighting from a shared lexer + +### 🚧 Rougher / next + +- Rename Symbol, Code Formatting, Signature Help, Code Actions — present but thin +- Cross-file `use` resolution (analysis is single-file today) +- AST-accurate spans (declaration ranges use a text-search heuristic) + +## Library + +The crate is also an **analysis library**. `--no-default-features` drops the +`server` feature (`tower-lsp`, `tokio`, the doc store, the `comline-lsp` bin), +leaving `parser` + `analysis` + `handlers` over `lsp-types` and `comline-core` — +which **builds for `wasm32-unknown-unknown`**. The Comline playground links it +that way so its browser editor runs the *same* diagnostics, hover, completion +and highlighting as the LSP. ## Installation diff --git a/src/analysis/diagnostics.rs b/src/analysis/diagnostics.rs index 275251e..c32af09 100644 --- a/src/analysis/diagnostics.rs +++ b/src/analysis/diagnostics.rs @@ -5,7 +5,7 @@ use comline_core::schema::idl::grammar::Document; use comline_core::schema::ir::compiler::interpreter::incremental::IncrementalInterpreter; use comline_core::schema::ir::compiler::Compile; use comline_core::schema::ir::validation; -use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity}; +use lsp_types::{Diagnostic, DiagnosticSeverity}; /// Semantic diagnostics from `comline-core`'s validation pass — undefined type /// references, duplicate declarations, and the like: the same checks diff --git a/src/analysis/symbols.rs b/src/analysis/symbols.rs index 107634b..f1e0f96 100644 --- a/src/analysis/symbols.rs +++ b/src/analysis/symbols.rs @@ -2,7 +2,7 @@ use comline_core::schema::idl::grammar::{Declaration, Document}; use std::collections::HashMap; -use tower_lsp::lsp_types::{Location, Position, Range, SymbolKind, Url}; +use lsp_types::{Location, Position, Range, SymbolKind, Url}; #[derive(Debug, Clone)] pub struct Symbol { diff --git a/src/backend.rs b/src/backend.rs index 127c909..ea61f9e 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -6,7 +6,7 @@ use crate::document::DocumentStore; // use tower_lsp::jsonrpc::Result; -use tower_lsp::lsp_types::*; +use lsp_types::*; use tower_lsp::{Client, LanguageServer}; pub struct Backend { @@ -51,17 +51,17 @@ impl LanguageServer for Backend { SemanticTokensServerCapabilities::SemanticTokensOptions( SemanticTokensOptions { legend: SemanticTokensLegend { + // Order must match the indices in + // `handlers::semantic_tokens` (`LEGEND_TYPES`). token_types: vec![ SemanticTokenType::KEYWORD, SemanticTokenType::TYPE, - SemanticTokenType::STRUCT, - SemanticTokenType::ENUM, - SemanticTokenType::INTERFACE, - SemanticTokenType::FUNCTION, - SemanticTokenType::VARIABLE, - SemanticTokenType::PROPERTY, + SemanticTokenType::STRING, + SemanticTokenType::COMMENT, + SemanticTokenType::NUMBER, + SemanticTokenType::DECORATOR, ], - token_modifiers: vec![SemanticTokenModifier::DECLARATION], + token_modifiers: vec![], }, range: Some(false), full: Some(SemanticTokensFullOptions::Bool(true)), diff --git a/src/document.rs b/src/document.rs index f03a516..f421953 100644 --- a/src/document.rs +++ b/src/document.rs @@ -1,6 +1,6 @@ use dashmap::DashMap; use std::sync::Arc; -use tower_lsp::lsp_types::Url; +use lsp_types::Url; /// Represents a document in the workspace #[derive(Debug, Clone)] diff --git a/src/handlers/code_actions.rs b/src/handlers/code_actions.rs index 8d76728..93f93af 100644 --- a/src/handlers/code_actions.rs +++ b/src/handlers/code_actions.rs @@ -1,6 +1,6 @@ // Code actions handler - provides quick fixes and refactorings -use tower_lsp::lsp_types::{CodeActionOrCommand, CodeActionParams, Url}; +use lsp_types::{CodeActionOrCommand, CodeActionParams, Url}; /// Get code actions for a given range pub fn get_code_actions( @@ -28,9 +28,9 @@ mod tests { let source = "struct User {}"; let uri = Url::parse("file:///test.ids").unwrap(); let params = CodeActionParams { - text_document: tower_lsp::lsp_types::TextDocumentIdentifier { uri: uri.clone() }, - range: tower_lsp::lsp_types::Range::default(), - context: tower_lsp::lsp_types::CodeActionContext { + text_document: lsp_types::TextDocumentIdentifier { uri: uri.clone() }, + range: lsp_types::Range::default(), + context: lsp_types::CodeActionContext { diagnostics: vec![], only: None, trigger_kind: None, diff --git a/src/handlers/completion.rs b/src/handlers/completion.rs index e855106..236c294 100644 --- a/src/handlers/completion.rs +++ b/src/handlers/completion.rs @@ -3,7 +3,7 @@ use crate::analysis::symbols; use crate::parser; use crate::util::position_to_offset; -use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, Position, Url}; +use lsp_types::{CompletionItem, CompletionItemKind, Position, Url}; /// Get completion suggestions at a position pub fn get_completions(source: &str, uri: &Url, position: Position) -> Vec { @@ -113,7 +113,7 @@ fn get_keyword_completions() -> Vec { kind: Some(CompletionItemKind::KEYWORD), detail: Some("Define a structure".to_string()), insert_text: Some("struct $1 {\n\t$0\n}".to_string()), - insert_text_format: Some(tower_lsp::lsp_types::InsertTextFormat::SNIPPET), + insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), ..Default::default() }, CompletionItem { @@ -121,7 +121,7 @@ fn get_keyword_completions() -> Vec { kind: Some(CompletionItemKind::KEYWORD), detail: Some("Define an enumeration".to_string()), insert_text: Some("enum $1 {\n\t$0\n}".to_string()), - insert_text_format: Some(tower_lsp::lsp_types::InsertTextFormat::SNIPPET), + insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), ..Default::default() }, CompletionItem { @@ -129,7 +129,7 @@ fn get_keyword_completions() -> Vec { kind: Some(CompletionItemKind::KEYWORD), detail: Some("Define a protocol".to_string()), insert_text: Some("protocol $1 {\n\t$0\n}".to_string()), - insert_text_format: Some(tower_lsp::lsp_types::InsertTextFormat::SNIPPET), + insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), ..Default::default() }, CompletionItem { @@ -137,7 +137,7 @@ fn get_keyword_completions() -> Vec { kind: Some(CompletionItemKind::KEYWORD), detail: Some("Define a constant".to_string()), insert_text: Some("const $1: $2 = $0".to_string()), - insert_text_format: Some(tower_lsp::lsp_types::InsertTextFormat::SNIPPET), + insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET), ..Default::default() }, CompletionItem { @@ -250,9 +250,9 @@ fn get_type_completions(symbol_table: &symbols::SymbolTable) -> Vec CompletionItemKind::STRUCT, - tower_lsp::lsp_types::SymbolKind::ENUM => CompletionItemKind::ENUM, - tower_lsp::lsp_types::SymbolKind::INTERFACE => CompletionItemKind::INTERFACE, + lsp_types::SymbolKind::STRUCT => CompletionItemKind::STRUCT, + lsp_types::SymbolKind::ENUM => CompletionItemKind::ENUM, + lsp_types::SymbolKind::INTERFACE => CompletionItemKind::INTERFACE, _ => CompletionItemKind::CLASS, }; diff --git a/src/handlers/definition.rs b/src/handlers/definition.rs index 4d7cb08..56ae591 100644 --- a/src/handlers/definition.rs +++ b/src/handlers/definition.rs @@ -3,7 +3,7 @@ use crate::analysis::symbols; use crate::parser; use crate::util::position_to_offset; -use tower_lsp::lsp_types::{GotoDefinitionResponse, Position, Url}; +use lsp_types::{GotoDefinitionResponse, Position, Url}; /// Find the definition of a symbol at a position pub fn find_definition(source: &str, uri: &Url, position: Position) -> Option { diff --git a/src/handlers/formatting.rs b/src/handlers/formatting.rs index 149326b..55715fa 100644 --- a/src/handlers/formatting.rs +++ b/src/handlers/formatting.rs @@ -1,6 +1,6 @@ // Formatting handler - formats Comline code -use tower_lsp::lsp_types::{Position, Range, TextEdit}; +use lsp_types::{Position, Range, TextEdit}; /// Format an entire document pub fn format_document(source: &str) -> Vec { diff --git a/src/handlers/hover.rs b/src/handlers/hover.rs index c126037..4baba5c 100644 --- a/src/handlers/hover.rs +++ b/src/handlers/hover.rs @@ -4,7 +4,7 @@ use crate::analysis::symbols; use crate::parser; use crate::util::position_to_offset; use comline_core::schema::idl::grammar::{Declaration, Type}; -use tower_lsp::lsp_types::{Hover, HoverContents, MarkedString, Position, Url}; +use lsp_types::{Hover, HoverContents, MarkedString, Position, Url}; /// Get hover information at a position pub fn get_hover_info(source: &str, uri: &Url, position: Position) -> Option { @@ -41,7 +41,7 @@ pub fn get_hover_info(source: &str, uri: &Url, position: Position) -> Option Hover { - use tower_lsp::lsp_types::SymbolKind; + use lsp_types::SymbolKind; let mut contents = vec![]; diff --git a/src/handlers/references.rs b/src/handlers/references.rs index 0a6eb12..a0c1bf0 100644 --- a/src/handlers/references.rs +++ b/src/handlers/references.rs @@ -4,7 +4,7 @@ use crate::analysis::symbols; use crate::parser; use crate::util::{byte_range_to_lsp_range, position_to_offset}; use comline_core::schema::idl::grammar::{Declaration, Type}; -use tower_lsp::lsp_types::{Location, Position, Url}; +use lsp_types::{Location, Position, Url}; /// Find all references to a symbol at a position pub fn find_references( diff --git a/src/handlers/rename.rs b/src/handlers/rename.rs index 41bdbe6..ebb6cc9 100644 --- a/src/handlers/rename.rs +++ b/src/handlers/rename.rs @@ -4,7 +4,7 @@ use crate::analysis::symbols; use crate::parser; use crate::util::position_to_offset; use std::collections::HashMap; -use tower_lsp::lsp_types::{Position, TextEdit, Url, WorkspaceEdit}; +use lsp_types::{Position, TextEdit, Url, WorkspaceEdit}; /// Rename a symbol at a position to a new name pub fn rename_symbol( diff --git a/src/handlers/semantic_tokens.rs b/src/handlers/semantic_tokens.rs index 92494eb..cffb8db 100644 --- a/src/handlers/semantic_tokens.rs +++ b/src/handlers/semantic_tokens.rs @@ -1,29 +1,186 @@ -// Semantic tokens handler - provides enhanced syntax highlighting +//! Semantic tokens — the single source of truth for Comline syntax +//! highlighting, consumed by the LSP (`comline-lsp`) and, via WASM, by the +//! playground's editor. +//! +//! A small line-oriented lexer: enough to colour keywords, primitive / user +//! types, strings, `//` comments, `@annotations` and numbers without a full +//! AST walk. Token positions are in characters (== UTF-16 units for ASCII +//! schemas, which is the common case). -use tower_lsp::lsp_types::{ - SemanticTokens, SemanticTokensResult, Url, -}; +use lsp_types::{SemanticToken, SemanticTokens, SemanticTokensResult, Url}; + +// Indices into the `SemanticTokensLegend` declared in `backend.rs` — keep in +// sync with `LEGEND_TYPES` there. +const KEYWORD: u32 = 0; +const TYPE: u32 = 1; +const STRING: u32 = 2; +const COMMENT: u32 = 3; +const NUMBER: u32 = 4; +const DECORATOR: u32 = 5; + +/// The token-type names, in legend order. `backend.rs` turns these into +/// `SemanticTokenType`s. +pub const LEGEND_TYPES: &[&str] = &["keyword", "type", "string", "comment", "number", "decorator"]; + +const KEYWORDS: &[&str] = &[ + "struct", "enum", "protocol", "error", "const", "use", "import", "validator", "settings", + "function", "optional", +]; +const PRIMITIVES: &[&str] = &[ + "s8", "s16", "s32", "s64", "u8", "u16", "u32", "u64", "f32", "f64", "bool", "str", "string", + "int", "float", +]; + +pub fn get_semantic_tokens(source: &str, _uri: &Url) -> Option { + let mut data: Vec = Vec::new(); + let mut prev_line = 0u32; + let mut prev_start = 0u32; + + for (line_idx, line) in source.split('\n').enumerate() { + let line_no = line_idx as u32; + for (start, length, token_type) in lex_line(line) { + let delta_line = line_no - prev_line; + let delta_start = if delta_line == 0 { start - prev_start } else { start }; + data.push(SemanticToken { + delta_line, + delta_start, + length, + token_type, + token_modifiers_bitset: 0, + }); + prev_line = line_no; + prev_start = start; + } + } -/// Generate semantic tokens for a document (basic stub implementation) -pub fn get_semantic_tokens(_source: &str, _uri: &Url) -> Option { - // Return empty tokens for now - this proves the module works - // Full implementation can be added later when we have proper AST walking Some(SemanticTokensResult::Tokens(SemanticTokens { result_id: None, - data: vec![], + data, })) } +/// `(start_char, length, token_type)` for each token on one line. +fn lex_line(line: &str) -> Vec<(u32, u32, u32)> { + let chars: Vec = line.chars().collect(); + let mut out = Vec::new(); + let mut i = 0usize; + + while i < chars.len() { + let c = chars[i]; + + if c == '/' && chars.get(i + 1) == Some(&'/') { + out.push((i as u32, (chars.len() - i) as u32, COMMENT)); + break; + } + + if c == '"' { + let mut j = i + 1; + while j < chars.len() && chars[j] != '"' { + if chars[j] == '\\' { + j += 1; + } + j += 1; + } + let end = (j + 1).min(chars.len()); + out.push((i as u32, (end - i) as u32, STRING)); + i = end; + continue; + } + + if c == '@' && chars.get(i + 1).is_some_and(|c| c.is_alphabetic() || *c == '_') { + let mut j = i + 1; + while j < chars.len() && (chars[j].is_alphanumeric() || chars[j] == '_') { + j += 1; + } + out.push((i as u32, (j - i) as u32, DECORATOR)); + i = j; + continue; + } + + if c.is_ascii_digit() { + let mut j = i; + while j < chars.len() + && (chars[j].is_alphanumeric() || chars[j] == '.' || chars[j] == '_') + { + j += 1; + } + out.push((i as u32, (j - i) as u32, NUMBER)); + i = j; + continue; + } + + if c.is_alphabetic() || c == '_' { + let mut j = i; + while j < chars.len() && (chars[j].is_alphanumeric() || chars[j] == '_') { + j += 1; + } + let word: String = chars[i..j].iter().collect(); + let kind = if KEYWORDS.contains(&word.as_str()) { + Some(KEYWORD) + } else if PRIMITIVES.contains(&word.as_str()) + || word.starts_with(|c: char| c.is_uppercase()) + { + Some(TYPE) + } else { + None + }; + if let Some(k) = kind { + out.push((i as u32, (j - i) as u32, k)); + } + i = j; + continue; + } + + i += 1; + } + + out +} + #[cfg(test)] mod tests { use super::*; - + + fn types_on(src: &str) -> Vec { + let r = get_semantic_tokens(src, &Url::parse("file:///t.ids").unwrap()).unwrap(); + let SemanticTokensResult::Tokens(t) = r else { + panic!() + }; + t.data.iter().map(|x| x.token_type).collect() + } + + #[test] + fn colours_keywords_types_strings_comments_annotations() { + let src = "@framing = \"jsonrpc\"\nstruct Msg {\n body: string // a note\n}\n"; + let ty = types_on(src); + assert!(ty.contains(&DECORATOR)); // @framing + assert!(ty.contains(&STRING)); // "jsonrpc" + assert!(ty.contains(&KEYWORD)); // struct + assert!(ty.contains(&TYPE)); // Msg, string + assert!(ty.contains(&COMMENT)); // // a note + } + + #[test] + fn empty_source_yields_no_tokens() { + let r = get_semantic_tokens("", &Url::parse("file:///t.ids").unwrap()).unwrap(); + let SemanticTokensResult::Tokens(t) = r else { + panic!() + }; + assert!(t.data.is_empty()); + } + #[test] - fn test_semantic_tokens_basic() { - let source = "struct User {}"; - let uri = Url::parse("file:///test.ids").unwrap(); - - let result = get_semantic_tokens(source, &uri); - assert!(result.is_some()); + fn deltas_are_relative() { + let r = get_semantic_tokens( + "struct A {}\nstruct B {}\n", + &Url::parse("file:///t.ids").unwrap(), + ) + .unwrap(); + let SemanticTokensResult::Tokens(t) = r else { + panic!() + }; + let second = t.data.iter().rev().find(|x| x.token_type == KEYWORD).unwrap(); + assert_eq!(second.delta_line, 1); + assert_eq!(second.delta_start, 0); } } diff --git a/src/handlers/signature_help.rs b/src/handlers/signature_help.rs index 9195e68..037a892 100644 --- a/src/handlers/signature_help.rs +++ b/src/handlers/signature_help.rs @@ -1,6 +1,6 @@ // Signature help handler - provides function signature information -use tower_lsp::lsp_types::{ +use lsp_types::{ Position, SignatureHelp, Url, }; diff --git a/src/handlers/symbols.rs b/src/handlers/symbols.rs index c85c073..d6c66cc 100644 --- a/src/handlers/symbols.rs +++ b/src/handlers/symbols.rs @@ -2,7 +2,7 @@ use crate::analysis::symbols; use crate::parser; -use tower_lsp::lsp_types::{DocumentSymbol, Range, SymbolInformation, SymbolKind, Url}; +use lsp_types::{DocumentSymbol, Range, SymbolInformation, SymbolKind, Url}; /// Get document symbols for outline view #[allow(deprecated)] // DocumentSymbol uses deprecated fields @@ -104,7 +104,7 @@ fn find_child_range(source: &str, child_name: &str, parent_name: &str) -> Range /// Convert byte offset to LSP Range fn byte_offset_to_range(source: &str, offset: usize, length: usize) -> Range { - use tower_lsp::lsp_types::Position; + use lsp_types::Position; let line_starts: Vec = std::iter::once(0) .chain(source.match_indices('\n').map(|(i, _)| i + 1)) diff --git a/src/lib.rs b/src/lib.rs index ed90b48..dc016c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,15 @@ -// Library exports for testing -pub mod backend; -pub mod document; +//! Comline language server. +//! +//! The **analysis** layer — `parser`, `analysis`, `handlers`, `util` — is +//! always built and depends only on `lsp-types` + `comline-core`, so it +//! compiles for `wasm32-unknown-unknown`. The Comline playground links the +//! crate with `default-features = false` and calls the same handlers the LSP +//! does. +//! +//! The `server` feature (on by default) adds `document` (the doc store) and +//! `backend` (the `tower-lsp` `LanguageServer` impl behind the `comline-lsp` +//! binary). + pub mod parser; pub mod util; @@ -23,3 +32,8 @@ pub mod handlers { pub mod signature_help; pub mod symbols; } + +#[cfg(feature = "server")] +pub mod backend; +#[cfg(feature = "server")] +pub mod document; diff --git a/src/main.rs b/src/main.rs index 95d1348..4182c72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,36 +1,12 @@ +//! The `comline-lsp` binary — a `tower-lsp` stdio server over the analysis +//! library. Built only with the `server` feature (see Cargo.toml). + +use comline_language_server::backend::Backend; use tower_lsp::{LspService, Server}; use tracing_subscriber::{fmt, prelude::*, EnvFilter}; -mod backend; -mod document; -mod parser; -mod util; - -mod analysis { - pub mod diagnostics; - pub mod imports; - pub mod symbols; - pub mod types; -} - -mod handlers { - pub mod code_actions; - pub mod completion; - pub mod definition; - pub mod formatting; - pub mod hover; - pub mod references; - pub mod rename; - pub mod semantic_tokens; - pub mod signature_help; - pub mod symbols; -} - -use backend::Backend; - #[tokio::main] async fn main() { - // Initialize tracing for logging tracing_subscriber::registry() .with(fmt::layer().with_writer(std::io::stderr)) .with(EnvFilter::from_default_env().add_directive(tracing::Level::INFO.into())) @@ -38,10 +14,7 @@ async fn main() { tracing::info!("Starting Comline Language Server"); - // Create the LSP service - let (service, socket) = LspService::new(|client| Backend::new(client)); - - // Start the server using stdio + let (service, socket) = LspService::new(Backend::new); Server::new(tokio::io::stdin(), tokio::io::stdout(), socket) .serve(service) .await; diff --git a/src/util.rs b/src/util.rs index 2b2fad3..3838294 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,4 @@ -use tower_lsp::lsp_types::{Position, Range}; +use lsp_types::{Position, Range}; /// Convert LSP Position to byte offset in text pub fn position_to_offset(text: &str, position: Position) -> Option { diff --git a/tests/e2e_tests.rs b/tests/e2e_tests.rs index acb407e..5fe958c 100644 --- a/tests/e2e_tests.rs +++ b/tests/e2e_tests.rs @@ -1,35 +1,28 @@ -// End-to-end test demonstrating the full LSP flow +// End-to-end test demonstrating the parse → diagnostics flow -use tower_lsp::lsp_types::*; +use comline_language_server::analysis::diagnostics; +use comline_language_server::parser; +use lsp_types::DiagnosticSeverity; -#[tokio::test] -async fn test_e2e_document_with_errors() { - use comline_language_server::{backend::Backend, document::DocumentStore}; - use tower_lsp::LspService; - - // Create test document with a syntax error +#[test] +fn test_e2e_document_with_errors() { let source = r#" struct User { name string // Missing colon - syntax error! age: i32 } "#; - - let uri = Url::parse("file:///test.ids").unwrap(); - - // The backend would parse this and should detect the error - // We'll just verify our parser catches it - let result = comline_language_server::parser::parse(source).unwrap(); + + let result = parser::parse(source).unwrap(); assert!(result.has_errors(), "Should detect syntax error"); - - // Generate diagnostics - let diagnostics = comline_language_server::analysis::diagnostics::generate_diagnostics(source, &result.errors); + + let diagnostics = diagnostics::generate_diagnostics(source, &result.errors); assert!(!diagnostics.is_empty(), "Should generate diagnostics"); assert_eq!(diagnostics[0].severity, Some(DiagnosticSeverity::ERROR)); } -#[tokio::test] -async fn test_e2e_valid_document() { +#[test] +fn test_e2e_valid_document() { let source = r#" struct User { name: string @@ -47,17 +40,14 @@ protocol UserService { function listUsers() -> User[]; } "#; - - // Parse should succeed - let result = comline_language_server::parser::parse(source).unwrap(); + + let result = parser::parse(source).unwrap(); assert!(result.is_ok(), "Should parse successfully"); assert!(!result.has_errors(), "Should have no errors"); - - // Should identify 3 declarations + let doc = result.document.unwrap(); - assert_eq!(comline_language_server::parser::get_declaration_count(&doc), 3); - - // Should generate no diagnostics - let diagnostics = comline_language_server::analysis::diagnostics::generate_diagnostics(source, &result.errors); + assert_eq!(parser::get_declaration_count(&doc), 3); + + let diagnostics = diagnostics::generate_diagnostics(source, &result.errors); assert!(diagnostics.is_empty(), "Should have no diagnostics for valid code"); } diff --git a/tests/fixtures.rs b/tests/fixtures.rs index 2dded2a..b051c7d 100644 --- a/tests/fixtures.rs +++ b/tests/fixtures.rs @@ -1,6 +1,6 @@ // Basic test fixtures for the LSP server -use tower_lsp::lsp_types::*; +use lsp_types::*; /// Create a simple test document pub fn create_test_document() -> (Url, String) { diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 3e6c119..8ad0342 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -3,7 +3,7 @@ mod fixtures; use comline_language_server::document::DocumentStore; -use tower_lsp::lsp_types::*; +use lsp_types::*; #[test] fn test_document_store() {