Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 59 additions & 37 deletions crates/jett_lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,39 +26,7 @@ impl JettBackend {
.map(|p| p.display().to_string())
.unwrap_or_else(|_| uri.to_string());

let result = jett_driver::build_source(text, &file_path);

let diagnostics: Vec<Diagnostic> = result
.diagnostics
.iter()
.map(|d| {
let (start_line, start_col) =
jett_diagnostics::render::line_col(&result.source, d.span.start);
let (end_line, end_col) =
jett_diagnostics::render::line_col(&result.source, d.span.end);

let severity = match d.severity {
jett_diagnostics::Severity::Error => Some(DiagnosticSeverity::ERROR),
jett_diagnostics::Severity::Warning => Some(DiagnosticSeverity::WARNING),
jett_diagnostics::Severity::Info => Some(DiagnosticSeverity::INFORMATION),
};

// LSP positions are 0-based; line_col returns 1-based.
let range = Range::new(
Position::new(start_line as u32 - 1, start_col as u32 - 1),
Position::new(end_line as u32 - 1, end_col as u32 - 1),
);

Diagnostic {
range,
severity,
code: Some(NumberOrString::String(d.code.to_string())),
source: Some("jett".to_string()),
message: d.message.clone(),
..Diagnostic::default()
}
})
.collect();
let diagnostics = diagnostics_for_source(text, &file_path);

self.client
.publish_diagnostics(uri, diagnostics, None)
Expand Down Expand Up @@ -117,10 +85,12 @@ impl LanguageServer for JettBackend {
}

async fn did_close(&self, params: DidCloseTextDocumentParams) {
self.documents
.write()
.await
.remove(&params.text_document.uri);
let uri = params.text_document.uri;
self.documents.write().await.remove(&uri);

// A client keeps the last published diagnostics after a document is
// closed unless the server explicitly clears them.
self.client.publish_diagnostics(uri, Vec::new(), None).await;
}

async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
Expand Down Expand Up @@ -228,6 +198,42 @@ impl LanguageServer for JettBackend {
}
}

fn diagnostics_for_source(source: &str, file_path: &str) -> Vec<Diagnostic> {
let result = jett_driver::build_source(source, file_path);

result
.diagnostics
.iter()
.map(|d| {
let (start_line, start_col) =
jett_diagnostics::render::line_col(&result.source, d.span.start);
let (end_line, end_col) =
jett_diagnostics::render::line_col(&result.source, d.span.end);

let severity = match d.severity {
jett_diagnostics::Severity::Error => Some(DiagnosticSeverity::ERROR),
jett_diagnostics::Severity::Warning => Some(DiagnosticSeverity::WARNING),
jett_diagnostics::Severity::Info => Some(DiagnosticSeverity::INFORMATION),
};

// LSP positions are 0-based; line_col returns 1-based.
let range = Range::new(
Position::new(start_line as u32 - 1, start_col as u32 - 1),
Position::new(end_line as u32 - 1, end_col as u32 - 1),
);

Diagnostic {
range,
severity,
code: Some(NumberOrString::String(d.code.to_string())),
source: Some("jett".to_string()),
message: d.message.clone(),
..Diagnostic::default()
}
})
.collect()
}

/// Start the LSP server on stdin/stdout. This is the main entry point called
/// by `jett lsp`.
pub async fn run_server() {
Expand All @@ -242,6 +248,8 @@ pub async fn run_server() {

#[cfg(test)]
mod tests {
use super::*;

/// Verify that `build_source` produces diagnostics for invalid Jett code.
/// This exercises the same path the LSP uses to validate documents.
#[test]
Expand Down Expand Up @@ -273,6 +281,20 @@ mod tests {
);
}

#[test]
fn diagnostics_for_source_maps_compiler_diagnostic_to_lsp_fields() {
let diagnostics = diagnostics_for_source("this is not valid jett code !!!", "test.jett");
let diagnostic = diagnostics.first().expect("invalid source should diagnose");

assert_eq!(diagnostic.source.as_deref(), Some("jett"));
assert!(matches!(
diagnostic.code,
Some(NumberOrString::String(ref code)) if code.starts_with('E')
));
assert_eq!(diagnostic.range.start.line, 0);
assert_eq!(diagnostic.range.start.character, 0);
}

/// Verify that hover_type returns a type for a known expression.
#[test]
fn hover_type_returns_type_for_identifier() {
Expand Down