#![allow(dead_code)]
#[path = "../example_logging/mod.rs"]
mod example_logging;
use lspf::types::{Diagnostic, DiagnosticSeverity, Position, Range, Uri};
use lspf::{Document, LspError, PositionEncoding, Server, ServerContext};
pub(crate) async fn serve<S: Send + Sync + 'static>(server: Server<S>) -> lspf::Result<()> {
example_logging::init();
let outcome = lspf::stdio(server).serve().await?;
std::process::exit(outcome.code());
}
pub(crate) fn document(ctx: &ServerContext, uri: &Uri) -> Result<Document, LspError> {
ctx.documents()
.get(uri)
.ok_or_else(|| LspError::invalid_params(format!("document is not open: {}", uri.as_str())))
}
pub(crate) fn text(ctx: &ServerContext, uri: &Uri) -> Result<String, LspError> {
Ok(document(ctx, uri)?.text(None).into_owned())
}
pub(crate) fn line_range(line: u32, text: &str) -> Range {
Range::new(
Position::new(line, 0),
Position::new(line, text.trim_end_matches(['\r', '\n']).len() as u32),
)
}
pub(crate) fn parse_incomplete_sum(line: &str) -> Option<(u64, u64)> {
let expression = line.trim().strip_suffix('=')?.trim();
let (left, right) = expression.split_once('+')?;
Some((left.trim().parse().ok()?, right.trim().parse().ok()?))
}
pub(crate) fn parse_sum(line: &str) -> Option<(u64, u64, Option<u64>)> {
let (expression, answer) = line.trim().split_once('=')?;
let (left, right) = expression.split_once('+')?;
let answer = match answer.trim() {
"" => None,
value => Some(value.parse().ok()?),
};
Some((
left.trim().parse().ok()?,
right.trim().parse().ok()?,
answer,
))
}
pub(crate) fn sum_diagnostics(text: &str) -> Vec<Diagnostic> {
text.lines()
.enumerate()
.filter_map(|(line_number, line)| {
let (left, right, answer) = parse_sum(line)?;
if answer == Some(left + right) {
return None;
}
let (severity, message) = match answer {
None => (DiagnosticSeverity::Warning, "Missing answer".to_string()),
Some(actual) => (
DiagnosticSeverity::Error,
format!("Incorrect answer: {actual}"),
),
};
Some(Diagnostic {
range: line_range(line_number as u32, line),
severity: Some(severity),
source: Some("lspf-example".to_string()),
message: lspf::types::Message::String(message),
..Diagnostic::default()
})
})
.collect()
}
pub(crate) fn word_ranges(text: &str, needle: &str, encoding: PositionEncoding) -> Vec<Range> {
text.lines()
.enumerate()
.flat_map(|(line, text)| {
text.match_indices(needle).filter_map(move |(start, _)| {
let end = start + needle.len();
let boundary = |index: usize| {
text.as_bytes()
.get(index)
.is_none_or(|byte| !byte.is_ascii_alphanumeric() && *byte != b'_')
};
(boundary(start.wrapping_sub(1)) && boundary(end)).then(|| {
Range::new(
Position::new(line as u32, column(&text[..start], encoding)),
Position::new(line as u32, column(&text[..end], encoding)),
)
})
})
})
.collect()
}
#[cfg(all(test, feature = "testing"))]
pub(crate) mod text_tests;
fn column(prefix: &str, encoding: PositionEncoding) -> u32 {
match encoding {
PositionEncoding::Utf8 => prefix.len() as u32,
PositionEncoding::Utf16 => prefix.encode_utf16().count() as u32,
PositionEncoding::Utf32 => prefix.chars().count() as u32,
}
}