use tower_lsp::jsonrpc::Result;
use tower_lsp::lsp_types::*;
use tower_lsp::{Client, LanguageServer};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
use crate::parser::Parser;
use crate::semantic::SemanticAnalyzer;
pub struct ReluxScriptLanguageServer {
client: Client,
documents: Arc<Mutex<HashMap<Url, DocumentState>>>,
}
struct DocumentState {
content: String,
version: i32,
ast: Option<crate::parser::ast::Program>,
diagnostics: Vec<Diagnostic>,
}
impl ReluxScriptLanguageServer {
pub fn new(client: Client) -> Self {
Self {
client,
documents: Arc::new(Mutex::new(HashMap::new())),
}
}
async fn analyze_document(&self, uri: &Url, content: &str) -> Vec<Diagnostic> {
use crate::lsp::diagnostics::{parse_error_to_diagnostic, semantic_errors_to_diagnostics};
let mut diagnostics = Vec::new();
let tokens = match crate::lexer::Lexer::tokenize(content) {
Ok(tokens) => tokens,
Err(e) => {
diagnostics.push(Diagnostic {
range: Range {
start: Position { line: 0, character: 0 },
end: Position { line: 0, character: 0 },
},
severity: Some(DiagnosticSeverity::ERROR),
message: format!("Lexer error: {}", e),
source: Some("reluxscript".to_string()),
..Default::default()
});
return diagnostics;
}
};
match Parser::new(tokens).parse() {
Ok(ast) => {
match SemanticAnalyzer::new().analyze(&ast) {
Ok(_) => {
}
Err(errors) => {
diagnostics.extend(semantic_errors_to_diagnostics(errors));
}
}
}
Err(parse_error) => {
diagnostics.push(parse_error_to_diagnostic(parse_error));
}
}
diagnostics
}
}
#[tower_lsp::async_trait]
impl LanguageServer for ReluxScriptLanguageServer {
async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
Ok(InitializeResult {
capabilities: 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(), ":".to_string()]),
..Default::default()
}),
definition_provider: Some(OneOf::Left(true)),
document_formatting_provider: Some(OneOf::Left(true)),
..Default::default()
},
..Default::default()
})
}
async fn initialized(&self, _: InitializedParams) {
self.client
.log_message(MessageType::INFO, "ReluxScript language server initialized")
.await;
}
async fn shutdown(&self) -> Result<()> {
Ok(())
}
async fn did_open(&self, params: DidOpenTextDocumentParams) {
let uri = params.text_document.uri;
let content = params.text_document.text;
let version = params.text_document.version;
let diagnostics = self.analyze_document(&uri, &content).await;
let mut documents = self.documents.lock().await;
documents.insert(
uri.clone(),
DocumentState {
content: content.clone(),
version,
ast: None,
diagnostics: diagnostics.clone(),
},
);
self.client
.publish_diagnostics(uri, diagnostics, Some(version))
.await;
}
async fn did_change(&self, params: DidChangeTextDocumentParams) {
let uri = params.text_document.uri;
let version = params.text_document.version;
if let Some(change) = params.content_changes.into_iter().next() {
let content = change.text;
let diagnostics = self.analyze_document(&uri, &content).await;
let mut documents = self.documents.lock().await;
if let Some(doc) = documents.get_mut(&uri) {
doc.content = content;
doc.version = version;
doc.diagnostics = diagnostics.clone();
}
self.client
.publish_diagnostics(uri, diagnostics, Some(version))
.await;
}
}
async fn hover(&self, _params: HoverParams) -> Result<Option<Hover>> {
Ok(Some(Hover {
contents: HoverContents::Scalar(MarkedString::String(
"ReluxScript hover info".to_string(),
)),
range: None,
}))
}
async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
use crate::lsp::completions::*;
let uri = ¶ms.text_document_position.text_document.uri;
let position = params.text_document_position.position;
let documents = self.documents.lock().await;
let doc = match documents.get(uri) {
Some(doc) => doc,
None => return Ok(None),
};
let lines: Vec<&str> = doc.content.lines().collect();
let line_idx = position.line as usize;
let char_idx = position.character as usize;
if line_idx >= lines.len() {
return Ok(None);
}
let line = lines[line_idx];
let text_before_cursor = &line[..char_idx.min(line.len())];
let mut completions = Vec::new();
if text_before_cursor.ends_with('.') {
let before_dot = text_before_cursor.trim_end_matches('.');
if let Some(last_word) = before_dot.split_whitespace().last() {
if last_word == "node" || last_word.starts_with("node") {
completions.extend(get_field_completions_for_type("CallExpression"));
completions.extend(get_field_completions_for_type("MemberExpression"));
completions.extend(get_field_completions_for_type("Identifier"));
} else if last_word.ends_with("_str") || last_word.contains("name") {
completions.extend(get_method_completions_for_type("String"));
} else if last_word.ends_with("_vec") || last_word.contains("items") {
completions.extend(get_method_completions_for_type("Vec"));
}
}
}
else if text_before_cursor.ends_with(':') || text_before_cursor.ends_with('<') || text_before_cursor.contains(": ") {
completions.extend(get_ast_type_completions());
completions.extend(get_builtin_type_completions());
}
else if text_before_cursor.contains("fn visit_") || text_before_cursor.ends_with("visit_") {
completions.extend(get_snippet_completions());
}
else if text_before_cursor.contains("match ") || text_before_cursor.contains("if let ") || text_before_cursor.contains("=> ") {
completions.extend(get_common_pattern_completions());
completions.extend(get_keyword_completions());
}
else {
completions.extend(get_keyword_completions());
completions.extend(get_ast_type_completions());
completions.extend(get_builtin_type_completions());
completions.extend(get_macro_completions());
completions.extend(get_snippet_completions());
}
completions.dedup_by(|a, b| a.label == b.label);
Ok(Some(CompletionResponse::Array(completions)))
}
async fn formatting(&self, _params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
Ok(None)
}
}