summaryrefslogtreecommitdiff
path: root/support/texlab/tests/lsp/fixture.rs
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/tests/lsp/fixture.rs')
-rw-r--r--support/texlab/tests/lsp/fixture.rs249
1 files changed, 249 insertions, 0 deletions
diff --git a/support/texlab/tests/lsp/fixture.rs b/support/texlab/tests/lsp/fixture.rs
new file mode 100644
index 0000000000..182c5d40b8
--- /dev/null
+++ b/support/texlab/tests/lsp/fixture.rs
@@ -0,0 +1,249 @@
+use std::{
+ path::{Path, PathBuf},
+ thread::JoinHandle,
+};
+
+use anyhow::Result;
+use lsp_server::Connection;
+use lsp_types::{
+ notification::{DidOpenTextDocument, Exit, Initialized},
+ request::{Initialize, Shutdown},
+ ClientCapabilities, DidOpenTextDocumentParams, InitializeParams, InitializedParams, Location,
+ Position, Range, TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, Url,
+};
+use tempfile::{tempdir, TempDir};
+use texlab::{db::Language, LspClient, Server};
+
+#[derive(Debug)]
+pub struct Fixture {
+ pub documents: Vec<Document>,
+}
+
+impl Fixture {
+ pub fn parse(input: &str) -> Fixture {
+ let mut documents = Vec::new();
+
+ let mut start = 0;
+ for end in input
+ .match_indices("%!")
+ .skip(1)
+ .map(|(i, _)| i)
+ .chain(std::iter::once(input.len()))
+ {
+ documents.push(Document::parse(&input[start..end]));
+ start = end;
+ }
+
+ Self { documents }
+ }
+
+ pub fn setup(&self, client: &LspClient, dir: &Path) -> Result<()> {
+ for document in &self.documents {
+ let text = String::from(&document.text);
+ let path = dir.join(&document.path);
+ std::fs::create_dir_all(path.parent().unwrap())?;
+ std::fs::write(&path, &text)?;
+
+ let uri = Url::from_file_path(&path).unwrap();
+ let language = Language::from_path(&path).unwrap_or(Language::Tex);
+ let language_id = String::from(match language {
+ Language::Tex => "latex",
+ Language::Bib => "bibtex",
+ Language::Log | Language::TexlabRoot | Language::Tectonic => continue,
+ });
+
+ client.send_notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
+ text_document: TextDocumentItem::new(uri, language_id, 0, text),
+ })?;
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug)]
+pub struct Document {
+ pub path: PathBuf,
+ pub text: String,
+ pub cursor: Option<Position>,
+ pub ranges: Vec<Range>,
+}
+
+impl Document {
+ pub fn parse(input: &str) -> Self {
+ let mut lines = Vec::new();
+
+ let (path, input) = input
+ .trim()
+ .strip_prefix("%! ")
+ .map(|input| input.split_once('\n').unwrap_or((input, "")))
+ .unwrap();
+
+ let mut ranges = Vec::new();
+ let mut cursor = None;
+
+ for line in input.lines().map(|line| line.trim_end()) {
+ if line.chars().all(|c| matches!(c, ' ' | '^' | '|' | '!')) && !line.is_empty() {
+ let index = (lines.len() - 1) as u32;
+
+ cursor = cursor.or_else(|| {
+ let character = line.find('|')?;
+ Some(Position::new(index, character as u32))
+ });
+
+ if let Some(start) = line.find('!') {
+ let position = Position::new(index, start as u32);
+ ranges.push(Range::new(position, position));
+ }
+
+ if let Some(start) = line.find('^') {
+ let end = line.rfind('^').unwrap() + 1;
+ ranges.push(Range::new(
+ Position::new(index, start as u32),
+ Position::new(index, end as u32),
+ ));
+ }
+ } else {
+ lines.push(line);
+ }
+ }
+
+ Self {
+ path: PathBuf::from(path),
+ text: lines.join("\n"),
+ cursor,
+ ranges,
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct TestBed {
+ fixture: Fixture,
+ locations: Vec<Location>,
+ directory: TempDir,
+ client: LspClient,
+ client_thread: Option<JoinHandle<()>>,
+ server_thread: Option<JoinHandle<()>>,
+}
+
+impl Drop for TestBed {
+ fn drop(&mut self) {
+ let _ = self.client.send_request::<Shutdown>(());
+ let _ = self.client.send_notification::<Exit>(());
+ self.client_thread.take().unwrap().join().unwrap();
+ self.server_thread.take().unwrap().join().unwrap();
+ }
+}
+
+impl TestBed {
+ pub fn new(fixture: &str) -> Result<Self> {
+ let fixture = Fixture::parse(fixture);
+ let (server_conn, client_conn) = Connection::memory();
+
+ let client = LspClient::new(client_conn.sender);
+
+ let server_thread = std::thread::spawn(move || Server::new(server_conn).run().unwrap());
+ let client_thread = {
+ let client = client.clone();
+ std::thread::spawn(move || {
+ for message in &client_conn.receiver {
+ match message {
+ lsp_server::Message::Request(request) => {
+ client
+ .send_error(
+ request.id,
+ lsp_server::ErrorCode::MethodNotFound.into(),
+ "Method not found".into(),
+ )
+ .unwrap();
+ }
+ lsp_server::Message::Response(response) => {
+ client.recv_response(response).unwrap();
+ }
+ lsp_server::Message::Notification(_) => {}
+ }
+ }
+ })
+ };
+
+ let directory = tempdir()?;
+ let locations = fixture
+ .documents
+ .iter()
+ .flat_map(|document| {
+ let uri = Url::from_file_path(directory.path().join(&document.path)).unwrap();
+ document
+ .ranges
+ .iter()
+ .map(move |range| Location::new(uri.clone(), *range))
+ })
+ .collect();
+
+ Ok(TestBed {
+ fixture,
+ locations,
+ directory,
+ client,
+ client_thread: Some(client_thread),
+ server_thread: Some(server_thread),
+ })
+ }
+
+ pub fn initialize(&self, capabilities: ClientCapabilities) -> Result<()> {
+ self.client.send_request::<Initialize>(InitializeParams {
+ capabilities,
+ initialization_options: Some(serde_json::json!({ "skipDistro": true })),
+ ..Default::default()
+ })?;
+
+ self.client
+ .send_notification::<Initialized>(InitializedParams {})?;
+
+ self.fixture.setup(&self.client, &self.directory.path())?;
+ Ok(())
+ }
+
+ pub fn client(&self) -> &LspClient {
+ &self.client
+ }
+
+ pub fn cursor(&self) -> Option<TextDocumentPositionParams> {
+ let (document, cursor) = self
+ .fixture
+ .documents
+ .iter()
+ .find_map(|document| document.cursor.map(|cursor| (document, cursor)))?;
+
+ let uri = Url::from_file_path(self.directory.path().join(&document.path)).unwrap();
+ let id = TextDocumentIdentifier::new(uri);
+ Some(TextDocumentPositionParams::new(id, cursor))
+ }
+
+ pub fn locations(&self) -> &[Location] {
+ &self.locations
+ }
+
+ pub fn directory(&self) -> &Path {
+ self.directory.path()
+ }
+
+ pub fn documents(&self) -> &[Document] {
+ &self.fixture.documents
+ }
+
+ pub fn redact(&self, uri: &Url) -> Url {
+ let root = if cfg!(windows) {
+ PathBuf::from("C:/")
+ } else {
+ PathBuf::from("/")
+ };
+
+ let path = uri.to_file_path().unwrap();
+ let path = path.strip_prefix(self.directory()).unwrap_or(&path);
+ let path = root.join(path);
+
+ let uri = Url::from_file_path(path).unwrap();
+ Url::parse(&uri.as_str().replace("file:///C:/", "file:///")).unwrap()
+ }
+}