use std::collections::BTreeMap; use anyhow::Result; use lsp_types::{Position, Range, TextDocumentIdentifier, TextDocumentPositionParams}; use rustc_hash::FxHashMap; use crate::lsp::client::Client; #[derive(Debug, PartialEq, Eq, Clone, Hash)] enum Line<'a> { File(&'a str, &'a str), Plain(&'a str), Range(u32, u32, std::ops::Range), Cursor(usize), Empty, } fn parse_line(line: &str) -> Line { if let Some(name) = line.strip_prefix("%TEX ") { Line::File(name, "latex") } else if let Some(name) = line.strip_prefix("%BIB ") { Line::File(name, "bibtex") } else if let Some(name) = line.strip_prefix("%LOG ") { Line::File(name, "log") } else if let Some(text) = line.strip_prefix("%SRC ") { Line::Plain(text) } else if let Some(text) = line.strip_prefix("%CUR ") { let position = text.find('^').unwrap(); Line::Cursor(position) } else if line.is_empty() { Line::Empty } else { let key1 = line[1..2].parse().unwrap(); let key2 = line[3..4].parse().unwrap(); let line = &line[5..]; let range = line .find('^') .map_or(0..0, |start| start..(line.rfind('^').unwrap() + 1)); Line::Range(key1, key2, range) } } #[derive(Debug, Default)] pub struct FileRange<'a> { pub name: &'a str, pub range: Range, } #[derive(Debug, Default)] pub struct File<'a> { pub name: &'a str, pub lang: &'a str, pub text: String, } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] pub struct FileCursor<'a> { pub name: &'a str, pub position: Position, } impl<'a> FileCursor<'a> { pub fn into_params(self, server: &Client) -> Result { let text_document = TextDocumentIdentifier::new(server.uri(self.name)?); Ok(TextDocumentPositionParams { text_document, position: self.position, }) } } #[derive(Debug, Default)] pub struct Fixture<'a> { pub files: Vec>, pub cursor: Option>, pub ranges: BTreeMap>>, } pub fn parse(input: &str) -> Fixture { let mut fixture = Fixture::default(); let mut file = File::default(); let mut file_line_index = 0; for line in input.lines().map(parse_line) { match line { Line::File(name, lang) => { if !file.name.is_empty() { fixture.files.push(file); file = File::default(); } file.name = name; file.lang = lang; file_line_index = 0; } Line::Plain(line) => { file.text.push_str(line); file.text.push('\n'); file_line_index += 1; } Line::Range(key1, key2, range) => { let line = file_line_index - 1; let start = Position::new(line, range.start.try_into().unwrap()); let end = Position::new(line, range.end.try_into().unwrap()); let range = Range::new(start, end); fixture.ranges.entry(key1).or_default().insert( key2, FileRange { name: file.name, range, }, ); } Line::Cursor(position) => { fixture.cursor = Some(FileCursor { name: file.name, position: Position::new(file_line_index - 1, position.try_into().unwrap()), }); } Line::Empty => {} }; } fixture.files.push(file); fixture }