Move workspace into a separate crate

This commit is contained in:
Patrick Förster 2019-07-30 15:18:17 +02:00
parent 176d9d5335
commit 6ad870eccb
55 changed files with 92 additions and 77 deletions

View file

@ -1,13 +1,12 @@
#![feature(async_await)]
use texlab_syntax::*;
use futures::compat::*;
use lsp_types::*;
use std::process::{Command, Stdio};
use tempfile::tempdir;
use texlab_syntax::*;
use tokio_process::CommandExt;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum RenderCitationError {
InitializationFailed,

View file

@ -6,9 +6,9 @@ mod parser;
pub use self::ast::*;
use self::finder::LatexFinder;
pub use self::finder::LatexNode;
use super::language::*;
use self::lexer::LatexLexer;
use self::parser::LatexParser;
use super::language::*;
use super::text::SyntaxNode;
use lsp_types::{Position, Range, Uri};
use path_clean::PathClean;

View file

@ -1,13 +1,13 @@
mod bibtex;
mod language;
mod latex;
mod text;
mod language;
use lsp_types::Uri;
pub use self::text::*;
pub use self::bibtex::*;
pub use self::latex::*;
pub use self::language::*;
pub use self::latex::*;
pub use self::text::*;
use lsp_types::Uri;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Language {

View file

@ -0,0 +1,12 @@
[package]
name = "texlab-workspace"
version = "0.1.0"
authors = [
"Eric Förster <efoerster@users.noreply.github.com>",
"Patrick Förster <pfoerster@users.noreply.github.com>"]
edition = "2018"
[dependencies]
log = "0.4.6"
lsp-types = { git = "https://github.com/latex-lsp/lsp-types", rev = "9fcc5d9b9d3013ce84e20ef566267754d594b268", features = ["proposed"] }
texlab-syntax = { path = "../texlab_syntax" }

View file

@ -0,0 +1,327 @@
use log::*;
use lsp_types::{TextDocumentItem, Uri};
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use texlab_syntax::*;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Document {
pub uri: Uri,
pub text: String,
pub tree: SyntaxTree,
}
impl Document {
pub fn new(uri: Uri, text: String, tree: SyntaxTree) -> Self {
Document { uri, text, tree }
}
pub fn parse(uri: Uri, text: String, language: Language) -> Self {
let tree = SyntaxTree::parse(&uri, &text, language);
Document::new(uri, text, tree)
}
pub fn is_file(&self) -> bool {
self.uri.scheme() == "file"
}
}
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct Workspace {
pub documents: Vec<Arc<Document>>,
}
impl Workspace {
pub fn new() -> Self {
Workspace {
documents: Vec::new(),
}
}
pub fn find(&self, uri: &Uri) -> Option<Arc<Document>> {
self.documents
.iter()
.find(|document| document.uri == *uri)
.map(|document| Arc::clone(&document))
}
pub fn related_documents(&self, uri: &Uri) -> Vec<Arc<Document>> {
let mut edges: Vec<(Arc<Document>, Arc<Document>)> = Vec::new();
for parent in self.documents.iter().filter(|document| document.is_file()) {
if let SyntaxTree::Latex(tree) = &parent.tree {
for include in &tree.includes {
for targets in &include.all_targets {
for target in targets {
if let Some(ref child) = self.find(target) {
edges.push((Arc::clone(&parent), Arc::clone(&child)));
edges.push((Arc::clone(&child), Arc::clone(&parent)));
}
}
}
}
}
}
let mut results = Vec::new();
if let Some(start) = self.find(uri) {
let mut visited: Vec<Arc<Document>> = Vec::new();
let mut stack = Vec::new();
stack.push(start);
while let Some(current) = stack.pop() {
if visited.contains(&current) {
continue;
}
visited.push(Arc::clone(&current));
results.push(Arc::clone(&current));
for edge in &edges {
if edge.0 == current {
stack.push(Arc::clone(&edge.1));
}
}
}
}
results
}
pub fn find_parent(&self, uri: &Uri) -> Option<Arc<Document>> {
for document in self.related_documents(uri) {
if let SyntaxTree::Latex(tree) = &document.tree {
if tree.is_standalone {
return Some(document);
}
}
}
None
}
pub fn unresolved_includes(&self) -> Vec<PathBuf> {
let mut includes = Vec::new();
for document in &self.documents {
if let SyntaxTree::Latex(tree) = &document.tree {
for include in &tree.includes {
if include.kind != LatexIncludeKind::Latex
&& include.kind != LatexIncludeKind::Bibliography
{
continue;
}
for targets in &include.all_targets {
if targets.iter().any(|target| self.find(target).is_some()) {
continue;
}
for target in targets {
let path = target.to_file_path().unwrap();
if path.exists() {
includes.push(path);
}
}
}
}
}
}
includes
}
}
#[derive(Debug, Default)]
pub struct WorkspaceManager {
workspace: Mutex<Arc<Workspace>>,
}
impl WorkspaceManager {
pub fn get(&self) -> Arc<Workspace> {
let workspace = self.workspace.lock().unwrap();
Arc::clone(&workspace)
}
pub fn add(&self, document: TextDocumentItem) {
let language = match Language::by_language_id(&document.language_id) {
Some(language) => language,
None => {
error!("Invalid language id: {}", &document.language_id);
return;
}
};
let mut workspace = self.workspace.lock().unwrap();
*workspace = Self::add_or_update(&workspace, document.uri, document.text, language);
}
pub fn load(&self, path: &Path) {
let language = match path
.extension()
.and_then(OsStr::to_str)
.and_then(Language::by_extension)
{
Some(language) => language,
None => {
warn!("Could not determine language: {}", path.to_string_lossy());
return;
}
};
let uri = match Uri::from_file_path(path) {
Ok(uri) => uri,
Err(_) => {
error!("Invalid path: {}", path.to_string_lossy());
return;
}
};
let text = match fs::read_to_string(path) {
Ok(text) => text,
Err(_) => {
warn!("Could not open file: {}", path.to_string_lossy());
return;
}
};
let mut workspace = self.workspace.lock().unwrap();
*workspace = Self::add_or_update(&workspace, uri, text, language);
}
pub fn update(&self, uri: Uri, text: String) {
let mut workspace = self.workspace.lock().unwrap();
let old_document = match workspace.documents.iter().find(|x| x.uri == uri) {
Some(document) => document,
None => {
warn!("Document not found: {}", uri);
return;
}
};
let language = match old_document.tree {
SyntaxTree::Latex(_) => Language::Latex,
SyntaxTree::Bibtex(_) => Language::Bibtex,
};
*workspace = Self::add_or_update(&workspace, uri, text, language);
}
fn add_or_update(
workspace: &Workspace,
uri: Uri,
text: String,
language: Language,
) -> Arc<Workspace> {
let document = Document::parse(uri, text, language);
let mut documents: Vec<Arc<Document>> = workspace
.documents
.iter()
.filter(|x| x.uri != document.uri)
.cloned()
.collect();
documents.push(Arc::new(document));
Arc::new(Workspace { documents })
}
}
pub struct WorkspaceBuilder {
pub workspace: Workspace,
}
impl WorkspaceBuilder {
pub fn new() -> Self {
WorkspaceBuilder {
workspace: Workspace::default(),
}
}
pub fn document(&mut self, name: &str, text: &str) -> Uri {
let path = std::env::temp_dir().join(name);
let language = Language::by_extension(path.extension().unwrap().to_str().unwrap()).unwrap();
let uri = Uri::from_file_path(path).unwrap();
let document = Document::parse(uri.clone(), text.to_owned(), language);
self.workspace.documents.push(Arc::new(document));
uri
}
}
#[cfg(test)]
mod tests {
use super::*;
fn verify_documents(expected: Vec<Uri>, actual: Vec<Arc<Document>>) {
assert_eq!(expected.len(), actual.len());
for i in 0..expected.len() {
assert_eq!(expected[i], actual[i].uri);
}
}
#[test]
fn test_related_documents_append_extensions() {
let mut builder = WorkspaceBuilder::new();
let uri1 = builder.document("foo.tex", "\\include{bar/baz}");
let uri2 = builder.document("bar/baz.tex", "");
let documents = builder.workspace.related_documents(&uri1);
verify_documents(vec![uri1, uri2], documents);
}
#[test]
fn test_related_documents_relative_path() {
let mut builder = WorkspaceBuilder::new();
let uri1 = builder.document("foo.tex", "");
let uri2 = builder.document("bar/baz.tex", "\\input{../foo.tex}");
let documents = builder.workspace.related_documents(&uri1);
verify_documents(vec![uri1, uri2], documents);
}
#[test]
fn test_related_documents_invalid_includes() {
let mut builder = WorkspaceBuilder::new();
let uri = builder.document("foo.tex", "\\include{<foo>?|bar|:}\n\\include{}");
let documents = builder.workspace.related_documents(&uri);
verify_documents(vec![uri], documents);
}
#[test]
fn test_related_documents_bibliographies() {
let mut builder = WorkspaceBuilder::new();
let uri1 = builder.document("foo.tex", "\\addbibresource{bar.bib}");
let uri2 = builder.document("bar.bib", "");
let documents = builder.workspace.related_documents(&uri2);
verify_documents(vec![uri2, uri1], documents);
}
#[test]
fn test_related_documents_unresolvable_include() {
let mut builder = WorkspaceBuilder::new();
let uri = builder.document("foo.tex", "\\include{bar.tex}");
builder.document("baz.tex", "");
let documents = builder.workspace.related_documents(&uri);
verify_documents(vec![uri], documents);
}
#[test]
fn test_related_documents_include_cycles() {
let mut builder = WorkspaceBuilder::new();
let uri1 = builder.document("foo.tex", "\\input{bar.tex}");
let uri2 = builder.document("bar.tex", "\\input{foo.tex}");
let documents = builder.workspace.related_documents(&uri1);
verify_documents(vec![uri1, uri2], documents);
}
#[test]
fn test_find_parent() {
let mut builder = WorkspaceBuilder::new();
let uri1 = builder.document("foo.tex", "");
let uri2 = builder.document("bar.tex", "\\begin{document}\\include{foo}\\end{document}");
let document = builder.workspace.find_parent(&uri1).unwrap();
assert_eq!(uri2, document.uri);
}
#[test]
fn test_find_parent_no_parent() {
let mut builder = WorkspaceBuilder::new();
let uri = builder.document("foo.tex", "");
builder.document("bar.tex", "\\begin{document}\\end{document}");
let document = builder.workspace.find_parent(&uri);
assert_eq!(None, document);
}
}