use super::*;
impl Server {
pub fn document_symbols(&self, params: DocumentSymbolParams) -> Option<DocumentSymbolResponse> {
if let Some((bible, book)) = self.book_document(¶ms.text_document.uri) {
return book_symbols(bible, book);
}
let text = self.documents.get(¶ms.text_document.uri)?;
let index = LineIndex::new(text);
let symbols = self
.matcher
.search(text)
.into_iter()
.map(|m| {
let bytes = m.location.bytes;
let range = lsp_range(&index, bytes.start, bytes.end);
#[allow(deprecated)]
DocumentSymbol {
name: self.reference(&m),
detail: m.psg.to_osis(self.matcher.data().books()),
kind: SymbolKind::KEY,
tags: None,
deprecated: None,
range,
selection_range: range,
children: None,
}
})
.collect();
Some(DocumentSymbolResponse::Nested(symbols))
}
pub fn definition(&self, params: GotoDefinitionParams) -> Option<GotoDefinitionResponse> {
let position = params.text_document_position_params;
let (
TranslatedMatch {
reference: found,
translation,
},
_,
) = self.reference_at(&position.text_document.uri, position.position)?;
let bible = self.bibles.named(translation.as_ref())?;
let rendered = bible.render(found.psg.book)?;
let uri = Uri::from_str(&bible.uri(found.psg.book)?).ok()?;
let index = LineIndex::new(&rendered.text);
let mut locations: Vec<Location> = rendered
.passage_ranges(&found.psg)
.into_iter()
.map(|r| Location::new(uri.clone(), lsp_range(&index, r.start, r.end)))
.collect();
match locations.len() {
0 => None,
1 => locations.pop().map(GotoDefinitionResponse::Scalar),
_ => Some(GotoDefinitionResponse::Array(locations)),
}
}
pub fn book(&self, params: BookParams) -> Result<BookText, String> {
if self.bibles.is_empty() {
return Err("no Bible is set (the `bible` or `bibles` setting)".into());
}
let (bible, book) = self.bibles.book_of(¶ms.uri).ok_or_else(|| {
let names: Vec<&str> = self.bibles.iter().map(|b| b.name.as_str()).collect();
format!("{} is not a book of {}", params.uri, names.join(", "))
})?;
let rendered = bible
.render(book)
.ok_or_else(|| format!("{} can't be read", params.uri))?;
Ok(BookText {
title: bible.book_name(book).unwrap_or_default().to_string(),
text: rendered.text.clone(),
})
}
pub fn file_text(&self, params: BookParams) -> Result<BookText, String> {
let path = workspace::book_path(¶ms.uri)
.ok_or_else(|| format!("{} isn't a {} URI", params.uri, workspace::BOOK_SCHEME))?;
Ok(BookText {
title: (path.file_stem())
.map(|s| s.to_string_lossy().into_owned())
.unwrap_or_default(),
text: workspace::book_text(&path)?,
})
}
pub fn same_verse(&self, params: SameVerseParams) -> Result<Option<Location>, String> {
let (from, book) = self
.bibles
.book_of(¶ms.uri)
.ok_or_else(|| format!("{} is not a whole book", params.uri))?;
let target = self
.bibles
.get(¶ms.bible)
.ok_or_else(|| format!("no Bible is named `{}`", params.bible))?;
let (Some(rendered), Some(other)) = (from.render(book), target.render(book)) else {
return Ok(None);
};
let index = LineIndex::new(&rendered.text);
let position = params.position;
let cursor = index
.offset_of_utf16(position.line as usize, position.character as usize)
.unwrap_or(rendered.text.len());
let (chapter, verse) = match rendered.part_at(cursor) {
Some(Part::Verse { chapter, verse, .. }) => (chapter, Some(verse)),
Some(Part::Chapter(chapter)) => (chapter, None),
None => (0, None),
};
let chapter_of = |part: &Part| match *part {
Part::Verse { chapter, .. } | Part::Chapter(chapter) => chapter,
};
let parts = &other.parts;
let found = verse
.and_then(|verse| {
parts.iter().find(|(_, part)| {
matches!(*part, Part::Verse { chapter: c, verse: v, last }
if c == chapter && v <= verse && verse <= last)
})
})
.or_else(|| (parts.iter()).find(|(_, part)| *part == Part::Chapter(chapter)))
.or_else(|| (parts.iter()).find(|(_, part)| chapter_of(part) >= chapter))
.map_or(0..0, |(range, _)| range.clone());
let uri = Uri::from_str(&target.uri(book).ok_or("the book has no USFM code")?)
.map_err(|e| e.to_string())?;
let index = LineIndex::new(&other.text);
Ok(Some(Location::new(
uri,
lsp_range(&index, found.start, found.end),
)))
}
pub fn bibles(&self) -> BiblesList {
let bibles = self
.bibles
.iter()
.map(|bible| BibleInfo {
name: bible.name.clone(),
aliases: bible.aliases.clone(),
books: bible.book_codes(),
})
.collect();
BiblesList { bibles }
}
pub fn semantic_tokens(&self, params: SemanticTokensParams) -> SemanticTokens {
let Some(rendered) = self
.book_document(¶ms.text_document.uri)
.and_then(|(bible, book)| bible.render(book))
else {
return SemanticTokens::default();
};
let text = &rendered.text;
let index = LineIndex::new(text);
let mut data = vec![];
let (mut line, mut character) = (0, 0);
for (range, token) in &bible::tokens(&rendered.marks) {
let mut start = range.start;
for piece in text[range.clone()].split('\n') {
let end = start + piece.len();
if !piece.trim().is_empty() {
let position = lsp_position(&index, start);
let delta_line = position.line - line;
let delta_start = match delta_line {
0 => position.character - character,
_ => position.character,
};
data.push(SemanticToken {
delta_line,
delta_start,
length: piece.encode_utf16().count() as u32,
token_type: *token as u32,
token_modifiers_bitset: 0,
});
(line, character) = (position.line, position.character);
}
start = end + 1;
}
}
SemanticTokens {
result_id: None,
data,
}
}
}
fn book_symbols(bible: &Bible, book: BookId) -> Option<DocumentSymbolResponse> {
let rendered = bible.render(book)?;
let index = LineIndex::new(&rendered.text);
let name = bible.book_name(book).unwrap_or_default();
#[allow(deprecated)]
let symbol = |name: String, kind, range| DocumentSymbol {
name,
detail: None,
kind,
tags: None,
deprecated: None,
range,
selection_range: range,
children: None,
};
let mut chapters: Vec<(u8, DocumentSymbol)> = vec![];
for (range, part) in &rendered.parts {
let (chapter, verse) = match *part {
Part::Chapter(chapter) => (chapter, None),
Part::Verse {
chapter,
verse,
last,
} => (chapter, Some((verse, last))),
};
if chapter == 0 {
continue;
}
let range = lsp_range(&index, range.start, range.end);
if chapters.last().is_none_or(|(c, _)| *c != chapter) {
let label = symbol(format!("{name} {chapter}"), SymbolKind::NAMESPACE, range);
chapters.push((chapter, label));
}
let (_, current) = chapters.last_mut()?;
current.range.end = range.end;
if let Some((verse, last)) = verse {
let numbers = match last > verse {
true => format!("{verse}-{last}"),
false => verse.to_string(),
};
let verse = symbol(
format!("{name} {chapter}:{numbers}"),
SymbolKind::KEY,
range,
);
current.children.get_or_insert_default().push(verse);
}
}
let chapters = chapters.into_iter().map(|(_, symbol)| symbol).collect();
Some(DocumentSymbolResponse::Nested(chapters))
}