mirror of
https://github.com/latex-lsp/texlab.git
synced 2025-08-04 10:49:55 +00:00
Move workspace into a separate crate
This commit is contained in:
parent
176d9d5335
commit
6ad870eccb
55 changed files with 92 additions and 77 deletions
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
12
crates/texlab_workspace/Cargo.toml
Normal file
12
crates/texlab_workspace/Cargo.toml
Normal 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" }
|
327
crates/texlab_workspace/src/lib.rs
Normal file
327
crates/texlab_workspace/src/lib.rs
Normal 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(¤t) {
|
||||
continue;
|
||||
}
|
||||
visited.push(Arc::clone(¤t));
|
||||
|
||||
results.push(Arc::clone(¤t));
|
||||
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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue