Move workspace module to a separate crate

This commit is contained in:
Patrick Förster 2019-12-12 10:00:37 +01:00
parent 24abeaf8d5
commit 864a109ab8
79 changed files with 203 additions and 162 deletions

View file

@ -0,0 +1,19 @@
[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]
futures = "0.3"
futures-boxed = { path = "../futures_boxed" }
itertools = "0.8.2"
log = "0.4.6"
once_cell = "1.2.0"
serde = { version = "1.0.103", features = ["derive", "rc"] }
serde_json = "1.0.44"
texlab-distro = { path = "../texlab_distro" }
texlab-protocol = { path = "../texlab_protocol" }
texlab-syntax = { path = "../texlab_syntax" }

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,125 @@
use super::document::Document;
use itertools::Itertools;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use texlab_protocol::{MarkupContent, MarkupKind};
use texlab_syntax::*;
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Database {
pub components: Vec<Component>,
pub metadata: Vec<Metadata>,
}
impl Database {
pub fn find(&self, name: &str) -> Option<&Component> {
self.components.iter().find(|component| {
component
.file_names
.iter()
.any(|file_name| file_name == name)
})
}
pub fn contains(&self, short_name: &str) -> bool {
let sty = format!("{}.sty", short_name);
let cls = format!("{}.cls", short_name);
self.find(&sty).is_some() || self.find(&cls).is_some()
}
pub fn kernel(&self) -> &Component {
self.components
.iter()
.find(|component| component.file_names.is_empty())
.unwrap()
}
pub fn related_components(&self, documents: &[Arc<Document>]) -> Vec<&Component> {
let mut start_components = vec![self.kernel()];
for document in documents {
if let SyntaxTree::Latex(tree) = &document.tree {
tree.components
.iter()
.flat_map(|file| self.find(file))
.for_each(|component| start_components.push(component))
}
}
let mut all_components = Vec::new();
for component in start_components {
all_components.push(component);
component
.references
.iter()
.flat_map(|file| self.find(&file))
.for_each(|component| all_components.push(component))
}
all_components
.into_iter()
.unique_by(|component| &component.file_names)
.collect()
}
pub fn exists(&self, file_name: &str) -> bool {
self.components
.iter()
.any(|component| component.file_names.iter().any(|f| f == file_name))
}
pub fn documentation(&self, name: &str) -> Option<MarkupContent> {
let metadata = self
.metadata
.iter()
.find(|metadata| metadata.name == name)?;
let desc = metadata.description.to_owned()?;
Some(MarkupContent {
kind: MarkupKind::PlainText,
value: desc,
})
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Component {
pub file_names: Vec<String>,
pub references: Vec<String>,
pub commands: Vec<Command>,
pub environments: Vec<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Command {
pub name: String,
pub image: Option<String>,
pub glyph: Option<String>,
pub parameters: Vec<Parameter>,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Parameter(pub Vec<Argument>);
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Argument {
pub name: String,
pub image: Option<String>,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Metadata {
pub name: String,
pub caption: Option<String>,
pub description: Option<String>,
}
const JSON: &str = include_str!("completion.json");
pub static COMPLETION_DATABASE: Lazy<Database> = Lazy::new(|| serde_json::from_str(JSON).unwrap());

View file

@ -0,0 +1,36 @@
use std::time::SystemTime;
use texlab_distro::{Language, Resolver};
use texlab_protocol::*;
use texlab_syntax::{SyntaxTree, SyntaxTreeContext};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Document {
pub uri: Uri,
pub text: String,
pub tree: SyntaxTree,
pub modified: SystemTime,
}
impl Document {
pub fn new(uri: Uri, text: String, tree: SyntaxTree) -> Self {
Self {
uri,
text,
tree,
modified: SystemTime::now(),
}
}
pub fn parse(resolver: &Resolver, uri: Uri, text: String, language: Language) -> Self {
let context = SyntaxTreeContext {
resolver,
uri: &uri,
};
let tree = SyntaxTree::parse(context, &text, language);
Self::new(uri, text, tree)
}
pub fn is_file(&self) -> bool {
self.uri.scheme() == "file"
}
}

View file

@ -0,0 +1,266 @@
use super::document::Document;
use super::workspace::{Workspace, WorkspaceBuilder};
use futures_boxed::boxed;
use std::sync::Arc;
use texlab_distro::{Distribution, UnknownDistribution};
use texlab_protocol::*;
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DocumentView {
pub workspace: Arc<Workspace>,
pub document: Arc<Document>,
pub related_documents: Vec<Arc<Document>>,
}
impl DocumentView {
pub fn new(workspace: Arc<Workspace>, document: Arc<Document>) -> Self {
let related_documents = workspace.related_documents(&document.uri);
Self {
workspace,
document,
related_documents,
}
}
}
pub struct FeatureRequest<P> {
pub params: P,
pub view: DocumentView,
pub client_capabilities: Arc<ClientCapabilities>,
pub distribution: Arc<Box<dyn Distribution>>,
}
impl<P> FeatureRequest<P> {
pub fn workspace(&self) -> &Workspace {
&self.view.workspace
}
pub fn document(&self) -> &Document {
&self.view.document
}
pub fn related_documents(&self) -> &[Arc<Document>] {
&self.view.related_documents
}
}
pub trait FeatureProvider {
type Params;
type Output;
#[boxed]
async fn execute<'a>(&'a self, request: &'a FeatureRequest<Self::Params>) -> Self::Output;
}
type ListProvider<P, O> = Box<dyn FeatureProvider<Params = P, Output = Vec<O>> + Send + Sync>;
#[derive(Default)]
pub struct ConcatProvider<P, O> {
providers: Vec<ListProvider<P, O>>,
}
impl<P, O> ConcatProvider<P, O> {
pub fn new(providers: Vec<ListProvider<P, O>>) -> Self {
Self { providers }
}
}
impl<P, O> FeatureProvider for ConcatProvider<P, O>
where
P: Send + Sync,
O: Send + Sync,
{
type Params = P;
type Output = Vec<O>;
#[boxed]
async fn execute<'a>(&'a self, request: &'a FeatureRequest<P>) -> Vec<O> {
let mut items = Vec::new();
for provider in &self.providers {
items.append(&mut provider.execute(request).await);
}
items
}
}
type OptionProvider<P, O> = Box<dyn FeatureProvider<Params = P, Output = Option<O>> + Send + Sync>;
#[derive(Default)]
pub struct ChoiceProvider<P, O> {
providers: Vec<OptionProvider<P, O>>,
}
impl<P, O> ChoiceProvider<P, O> {
pub fn new(providers: Vec<OptionProvider<P, O>>) -> Self {
Self { providers }
}
}
impl<P, O> FeatureProvider for ChoiceProvider<P, O>
where
P: Send + Sync,
O: Send + Sync,
{
type Params = P;
type Output = Option<O>;
#[boxed]
async fn execute<'a>(&'a self, request: &'a FeatureRequest<P>) -> Option<O> {
for provider in &self.providers {
let item = provider.execute(request).await;
if item.is_some() {
return item;
}
}
None
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct FeatureSpecFile {
name: &'static str,
text: &'static str,
}
pub struct FeatureSpec {
pub files: Vec<FeatureSpecFile>,
pub main_file: &'static str,
pub position: Position,
pub new_name: &'static str,
pub include_declaration: bool,
pub client_capabilities: ClientCapabilities,
pub distribution: Box<dyn Distribution>,
}
impl Default for FeatureSpec {
fn default() -> Self {
Self {
files: Vec::new(),
main_file: "",
position: Position::new(0, 0),
new_name: "",
include_declaration: false,
client_capabilities: ClientCapabilities::default(),
distribution: Box::new(UnknownDistribution::default()),
}
}
}
impl FeatureSpec {
pub fn file(name: &'static str, text: &'static str) -> FeatureSpecFile {
FeatureSpecFile { name, text }
}
pub fn uri(name: &str) -> Url {
let path = std::env::temp_dir().join(name);
Url::from_file_path(path).unwrap()
}
fn identifier(&self) -> TextDocumentIdentifier {
let uri = Self::uri(self.main_file);
TextDocumentIdentifier::new(uri)
}
fn view(&self) -> DocumentView {
let mut builder = WorkspaceBuilder::new();
for file in &self.files {
builder.document(file.name, file.text);
}
let workspace = builder.workspace;
let main_uri = Self::uri(self.main_file);
let main_document = workspace.find(&main_uri.into()).unwrap();
DocumentView::new(Arc::new(workspace), main_document)
}
fn request<T>(self, params: T) -> FeatureRequest<T> {
FeatureRequest {
params,
view: self.view(),
client_capabilities: Arc::new(self.client_capabilities),
distribution: Arc::new(self.distribution),
}
}
}
impl Into<FeatureRequest<TextDocumentPositionParams>> for FeatureSpec {
fn into(self) -> FeatureRequest<TextDocumentPositionParams> {
let params = TextDocumentPositionParams::new(self.identifier(), self.position);
self.request(params)
}
}
impl Into<FeatureRequest<CompletionParams>> for FeatureSpec {
fn into(self) -> FeatureRequest<CompletionParams> {
let params = CompletionParams {
text_document_position: TextDocumentPositionParams::new(
self.identifier(),
self.position,
),
context: None,
};
self.request(params)
}
}
impl Into<FeatureRequest<FoldingRangeParams>> for FeatureSpec {
fn into(self) -> FeatureRequest<FoldingRangeParams> {
let params = FoldingRangeParams {
text_document: self.identifier(),
};
self.request(params)
}
}
impl Into<FeatureRequest<DocumentLinkParams>> for FeatureSpec {
fn into(self) -> FeatureRequest<DocumentLinkParams> {
let params = DocumentLinkParams {
text_document: self.identifier(),
};
self.request(params)
}
}
impl Into<FeatureRequest<ReferenceParams>> for FeatureSpec {
fn into(self) -> FeatureRequest<ReferenceParams> {
let params = ReferenceParams {
text_document_position: TextDocumentPositionParams::new(
self.identifier(),
self.position,
),
context: ReferenceContext {
include_declaration: self.include_declaration,
},
};
self.request(params)
}
}
impl Into<FeatureRequest<RenameParams>> for FeatureSpec {
fn into(self) -> FeatureRequest<RenameParams> {
let params = RenameParams {
text_document_position: TextDocumentPositionParams::new(
self.identifier(),
self.position,
),
new_name: self.new_name.to_owned(),
};
self.request(params)
}
}
impl Into<FeatureRequest<DocumentSymbolParams>> for FeatureSpec {
fn into(self) -> FeatureRequest<DocumentSymbolParams> {
let params = DocumentSymbolParams {
text_document: self.identifier(),
};
self.request(params)
}
}
pub fn test_feature<F, P, O, S>(provider: F, spec: S) -> O
where
F: FeatureProvider<Params = P, Output = O>,
S: Into<FeatureRequest<P>>,
{
futures::executor::block_on(provider.execute(&spec.into()))
}

View file

@ -0,0 +1,11 @@
mod completion;
mod document;
mod feature;
mod outline;
mod workspace;
pub use self::completion::*;
pub use self::document::Document;
pub use self::feature::*;
pub use self::outline::*;
pub use self::workspace::*;

View file

@ -0,0 +1,437 @@
use super::document::Document;
use super::feature::DocumentView;
use std::collections::HashSet;
use texlab_protocol::*;
use texlab_syntax::*;
#[derive(Debug, PartialEq, Eq, Clone, Default)]
pub struct Outline<'a> {
sections: Vec<OutlineSection<'a>>,
}
impl<'a> Outline<'a> {
fn new(sections: Vec<OutlineSection<'a>>) -> Self {
Self { sections }
}
pub fn find(&self, uri: &Uri, position: Position) -> Option<&'a LatexSection> {
self.sections
.iter()
.filter(|sec| sec.document.uri == *uri)
.rev()
.find(|sec| sec.item.end() <= position)
.map(|sec| sec.item)
}
}
impl<'a> From<&'a DocumentView> for Outline<'a> {
fn from(view: &'a DocumentView) -> Self {
let mut finder = OutlineSectionFinder::default();
let document = if let Some(parent) = view.workspace.find_parent(&view.document.uri) {
view.related_documents
.iter()
.find(|doc| doc.uri == parent.uri)
.unwrap()
} else {
&view.document
};
finder.analyze(view, &document);
Outline::new(finder.sections)
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
struct OutlineSection<'a> {
pub document: &'a Document,
pub item: &'a LatexSection,
}
impl<'a> OutlineSection<'a> {
fn new(document: &'a Document, item: &'a LatexSection) -> Self {
Self { document, item }
}
}
#[derive(Debug, Default)]
struct OutlineSectionFinder<'a> {
visited: HashSet<&'a Uri>,
sections: Vec<OutlineSection<'a>>,
}
impl<'a> OutlineSectionFinder<'a> {
fn analyze(&mut self, view: &'a DocumentView, document: &'a Document) {
if !self.visited.insert(&document.uri) {
return;
}
if let SyntaxTree::Latex(tree) = &document.tree {
let mut items = Vec::new();
for section in &tree.structure.sections {
items.push(OutlineItem::Section(section));
}
for include in &tree.includes {
items.push(OutlineItem::Include(include));
}
items.sort_by_key(SyntaxNode::start);
for item in items {
match item {
OutlineItem::Section(item) => {
let section = OutlineSection::new(document, item);
self.sections.push(section);
}
OutlineItem::Include(item) => {
for document in &view.related_documents {
for targets in &item.all_targets {
if targets.contains(&document.uri) {
self.analyze(view, document);
break;
}
}
}
}
}
}
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum OutlineItem<'a> {
Section(&'a LatexSection),
Include(&'a LatexInclude),
}
impl<'a> SyntaxNode for OutlineItem<'a> {
fn range(&self) -> Range {
match self {
OutlineItem::Section(section) => section.range(),
OutlineItem::Include(include) => include.range(),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum OutlineCaptionKind {
Figure,
Table,
Listing,
Algorithm,
}
impl OutlineCaptionKind {
pub fn as_str(self) -> &'static str {
match self {
Self::Figure => "Figure",
Self::Table => "Table",
Self::Listing => "Listing",
Self::Algorithm => "Algorithm",
}
}
pub fn parse(environment_name: &str) -> Option<Self> {
match environment_name {
"figure" | "subfigure" => Some(Self::Figure),
"table" | "subtable" => Some(Self::Table),
"listing" | "lstlisting" => Some(Self::Listing),
"algorithm" => Some(Self::Algorithm),
_ => None,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum OutlineContextItem {
Section {
prefix: &'static str,
text: String,
},
Caption {
kind: Option<OutlineCaptionKind>,
text: String,
},
Theorem {
kind: String,
description: Option<String>,
},
Equation,
Item,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct OutlineContext {
pub range: Range,
pub number: Option<String>,
pub item: OutlineContextItem,
}
impl OutlineContext {
pub fn reference(&self) -> String {
match (&self.number, &self.item) {
(Some(number), OutlineContextItem::Section { prefix, text }) => {
format!("{} {} ({})", prefix, number, text)
}
(Some(number), OutlineContextItem::Caption { kind: None, text }) => {
format!("{} {}", number, text)
}
(
Some(number),
OutlineContextItem::Caption {
kind: Some(kind),
text,
},
) => format!("{} {}: {}", kind.as_str(), number, text),
(
Some(number),
OutlineContextItem::Theorem {
kind,
description: None,
},
) => format!("{} {}", kind, number),
(
Some(number),
OutlineContextItem::Theorem {
kind,
description: Some(description),
},
) => format!("{} {} ({})", kind, number, description),
(Some(number), OutlineContextItem::Equation) => format!("Equation ({})", number),
(Some(number), OutlineContextItem::Item) => format!("Item {}", number),
(None, OutlineContextItem::Section { prefix, text }) => {
format!("{} ({})", prefix, text)
}
(None, OutlineContextItem::Caption { kind: None, text }) => text.clone(),
(
None,
OutlineContextItem::Caption {
kind: Some(kind),
text,
},
) => format!("{}: {}", kind.as_str(), text),
(
None,
OutlineContextItem::Theorem {
kind,
description: None,
},
) => kind.to_owned(),
(
None,
OutlineContextItem::Theorem {
kind,
description: Some(description),
},
) => format!("{} ({})", kind, description),
(None, OutlineContextItem::Equation) => "Equation".to_owned(),
(None, OutlineContextItem::Item) => "Item".to_owned(),
}
}
pub fn detail(&self) -> Option<String> {
match &self.item {
OutlineContextItem::Section { .. }
| OutlineContextItem::Theorem { .. }
| OutlineContextItem::Equation
| OutlineContextItem::Item => Some(self.reference()),
OutlineContextItem::Caption {
kind: Some(kind), ..
} => Some(match &self.number {
Some(number) => format!("{} {}", kind.as_str(), number),
None => kind.as_str().to_owned(),
}),
OutlineContextItem::Caption { .. } => None,
}
}
pub fn documentation(&self) -> MarkupContent {
MarkupContent {
kind: MarkupKind::PlainText,
value: self.reference(),
}
}
pub fn parse(view: &DocumentView, label: &LatexLabel, outline: &Outline) -> Option<Self> {
if let SyntaxTree::Latex(tree) = &view.document.tree {
Self::find_caption(view, label, tree)
.or_else(|| Self::find_theorem(view, label, tree))
.or_else(|| Self::find_equation(view, label, tree))
.or_else(|| Self::find_item(view, label, tree))
.or_else(|| Self::find_section(view, label, outline))
} else {
None
}
}
fn find_caption(
view: &DocumentView,
label: &LatexLabel,
tree: &LatexSyntaxTree,
) -> Option<Self> {
let caption_env = tree
.env
.environments
.iter()
.filter(|env| env.left.name().map(LatexToken::text) != Some("document"))
.find(|env| env.range().contains(label.start()))?;
let caption = tree
.structure
.captions
.iter()
.find(|cap| tree.is_direct_child(caption_env, cap.start()))?;
let caption_content = &caption.command.args[caption.index];
let caption_text = extract_group(caption_content);
let caption_kind = caption_env
.left
.name()
.map(LatexToken::text)
.and_then(OutlineCaptionKind::parse);
Some(Self {
range: caption_env.range(),
number: Self::find_number(view, label),
item: OutlineContextItem::Caption {
kind: caption_kind,
text: caption_text,
},
})
}
fn find_theorem(
view: &DocumentView,
label: &LatexLabel,
tree: &LatexSyntaxTree,
) -> Option<Self> {
let env = tree
.env
.environments
.iter()
.find(|env| env.range().contains(label.start()))?;
let env_name = env.left.name().map(LatexToken::text)?;
for document in &view.related_documents {
if let SyntaxTree::Latex(tree) = &document.tree {
for definition in &tree.math.theorem_definitions {
if env_name == definition.name().text() {
let kind = definition
.command
.args
.get(definition.index + 1)
.map(|content| extract_group(&content))
.unwrap_or_else(|| titlelize(env_name));
let description = env
.left
.command
.options
.get(0)
.map(|content| extract_group(&content));
return Some(Self {
range: env.range(),
number: Self::find_number(view, label),
item: OutlineContextItem::Theorem { kind, description },
});
}
}
}
}
None
}
fn find_equation(
view: &DocumentView,
label: &LatexLabel,
tree: &LatexSyntaxTree,
) -> Option<Self> {
tree.env
.environments
.iter()
.filter(|env| env.left.is_math())
.map(|env| env.range())
.find(|range| range.contains(label.start()))
.map(|range| Self {
range,
number: Self::find_number(view, label),
item: OutlineContextItem::Equation,
})
}
fn find_item(view: &DocumentView, label: &LatexLabel, tree: &LatexSyntaxTree) -> Option<Self> {
struct LatexItemNode<'a> {
item: &'a LatexItem,
range: Range,
}
let enumeration = tree
.env
.environments
.iter()
.find(|env| env.left.is_enum() && env.range().contains(label.start()))?;
let mut item_nodes: Vec<_> = tree
.structure
.items
.iter()
.filter(|item| tree.is_enumeration_item(enumeration, item))
.map(|item| LatexItemNode {
item,
range: Range::default(),
})
.collect();
for i in 0..item_nodes.len() {
let start = item_nodes[i].item.start();
let end = item_nodes
.get(i + 1)
.map(|node| node.item.start())
.unwrap_or_else(|| enumeration.right.start());
item_nodes[i].range = Range::new(start, end);
}
let node = item_nodes
.iter()
.find(|node| node.range.contains(label.start()))?;
let number = node.item.name().or_else(|| Self::find_number(view, label));
Some(Self {
range: enumeration.range(),
number,
item: OutlineContextItem::Item,
})
}
fn find_section(view: &DocumentView, label: &LatexLabel, outline: &Outline) -> Option<Self> {
let section = outline.find(&view.document.uri, label.start())?;
let content = &section.command.args[section.index];
Some(Self {
range: section.range(),
number: Self::find_number(view, label),
item: OutlineContextItem::Section {
prefix: section.prefix,
text: extract_group(content),
},
})
}
pub fn find_number(view: &DocumentView, label: &LatexLabel) -> Option<String> {
let label_names = label.names();
if label_names.len() != 1 {
return None;
}
for document in &view.related_documents {
if let SyntaxTree::Latex(tree) = &document.tree {
for numbering in &tree.structure.label_numberings {
if numbering.name().text() == label_names[0].text() {
return Some(numbering.number.clone());
}
}
}
}
None
}
}

View file

@ -0,0 +1,382 @@
use super::completion::COMPLETION_DATABASE;
use super::document::Document;
use futures::executor::block_on;
use log::*;
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use texlab_distro::{Distribution, Language, Resolver};
use texlab_protocol::*;
use texlab_syntax::*;
#[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 edges = self.build_dependency_graph();
let mut results = Vec::new();
if let Some(start) = self.find(uri) {
let mut visited: Vec<Arc<Document>> = Vec::new();
let mut stack = vec![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
}
fn build_dependency_graph(&self) -> Vec<(Arc<Document>, 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 tex_path = parent.uri.to_file_path().unwrap();
let aux_path = tex_path.with_extension("aux");
if let Some(child) = self.find(&Uri::from_file_path(aux_path).unwrap()) {
edges.push((Arc::clone(&parent), Arc::clone(&child)));
edges.push((Arc::clone(&child), Arc::clone(&parent)));
}
}
}
edges
}
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.env.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 {
match include.kind {
LatexIncludeKind::Bibliography | LatexIncludeKind::Latex => (),
LatexIncludeKind::Everything
| LatexIncludeKind::Image
| LatexIncludeKind::Pdf
| LatexIncludeKind::Svg => continue,
LatexIncludeKind::Package | LatexIncludeKind::Class => {
if include
.paths()
.iter()
.all(|name| COMPLETION_DATABASE.contains(name.text()))
{
continue;
}
}
}
for targets in &include.all_targets {
if targets.iter().any(|target| self.find(target).is_some()) {
continue;
}
for target in targets {
if let Ok(path) = target.to_file_path() {
if path.exists() {
includes.push(path);
}
}
}
}
}
if let Ok(aux_path) = document
.uri
.to_file_path()
.map(|path| path.with_extension("aux"))
{
if self
.find(&Uri::from_file_path(&aux_path).unwrap())
.is_none()
{
includes.push(aux_path);
}
}
}
}
includes
}
}
#[derive(Debug)]
pub enum LoadError {
UnknownLanguage,
InvalidPath,
IO(std::io::Error),
}
pub struct WorkspaceManager {
distribution: Arc<Box<dyn Distribution>>,
workspace: Mutex<Arc<Workspace>>,
}
impl WorkspaceManager {
pub fn new(distribution: Arc<Box<dyn Distribution>>) -> Self {
Self {
distribution,
workspace: Mutex::default(),
}
}
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.into(), document.text, language);
}
pub fn load(&self, path: &Path) -> Result<(), LoadError> {
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 Err(LoadError::UnknownLanguage);
}
};
let uri = match Uri::from_file_path(path) {
Ok(uri) => uri,
Err(_) => {
error!("Invalid path: {}", path.to_string_lossy());
return Err(LoadError::InvalidPath);
}
};
let text = match fs::read_to_string(path) {
Ok(text) => text,
Err(why) => {
warn!("Could not open file: {}", path.to_string_lossy());
return Err(LoadError::IO(why));
}
};
let mut workspace = self.workspace.lock().unwrap();
*workspace = self.add_or_update(&workspace, uri, text, language);
Ok(())
}
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(
&self,
workspace: &Workspace,
uri: Uri,
text: String,
language: Language,
) -> Arc<Workspace> {
let resolver = block_on(self.distribution.resolver());
let document = Document::parse(&resolver, 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 })
}
}
#[derive(Debug, Default)]
pub struct WorkspaceBuilder {
pub workspace: Workspace,
}
impl WorkspaceBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn document(&mut self, name: &str, text: &str) -> Uri {
let resolver = Resolver::default();
let path = 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(&resolver, 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_related_documents_same_parent() {
let mut builder = WorkspaceBuilder::new();
let uri1 = builder.document("test.tex", "\\include{test1}\\include{test2}");
let uri2 = builder.document("test1.tex", "\\label{foo}");
let uri3 = builder.document("test2.tex", "\\ref{foo}");
let documents = builder.workspace.related_documents(&uri3);
verify_documents(vec![uri3, uri1, uri2], documents);
}
#[test]
fn test_related_documents_aux_file() {
let mut builder = WorkspaceBuilder::new();
let uri1 = builder.document("foo.tex", "\\include{bar}");
let uri2 = builder.document("bar.tex", "");
let uri3 = builder.document("foo.aux", "");
let documents = builder.workspace.related_documents(&uri2);
verify_documents(vec![uri2, uri1, uri3], 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);
}
}