Skip to content

Commit

Permalink
Add TeX file database resolver
Browse files Browse the repository at this point in the history
  • Loading branch information
efoerster committed May 24, 2019
1 parent 3232833 commit e3be54b
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/event.rs
Expand Up @@ -2,6 +2,7 @@ use std::sync::Mutex;

#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Event {
Initialized,
WorkspaceChanged,
}

Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Expand Up @@ -16,6 +16,7 @@ pub mod hover;
pub mod link;
pub mod reference;
pub mod rename;
pub mod resolver;
pub mod server;
pub mod syntax;
pub mod workspace;
9 changes: 9 additions & 0 deletions src/resolver/miktex.rs
@@ -0,0 +1,9 @@
use super::{Error, Result};
use std::fs;
use std::path::{Path, PathBuf};

pub const DATABASE_PATH: &'static str = "miktex/data/le";

pub fn read_database(directory: &Path, root_directories: &[PathBuf]) -> Result<Vec<PathBuf>> {
unimplemented!()
}
96 changes: 96 additions & 0 deletions src/resolver/mod.rs
@@ -0,0 +1,96 @@
use std::collections::HashMap;
use std::env;
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command;

mod miktex;
mod texlive;

#[derive(Debug)]
pub enum Error {
KpsewhichNotFound,
UnsupportedTexDistribution,
CorruptFileDatabase,
}

pub type Result<T> = std::result::Result<T, Error>;

pub enum TexDistributionKind {
Texlive,
Miktex,
}

#[derive(Debug)]
pub struct TexResolver {
pub files_by_name: HashMap<OsString, PathBuf>,
}

impl TexResolver {
pub fn new() -> Self {
Self {
files_by_name: HashMap::new(),
}
}

pub fn load() -> Result<Self> {
let directories = Self::find_root_directories()?;
let kind = Self::detect_distribution(&directories)?;
let files_by_name = Self::read_database(&directories, kind)?;
Ok(Self { files_by_name })
}

fn find_root_directories() -> Result<Vec<PathBuf>> {
let texmf = Self::run_kpsewhich(&["-var-value", "TEXMF"])?;
let expanded = Self::run_kpsewhich(&[&format!("--expand-braces={}", texmf)])?;
let directories = env::split_paths(&expanded.replace("!", ""))
.filter(|x| x.exists())
.collect();
Ok(directories)
}

fn run_kpsewhich(args: &[&str]) -> Result<String> {
let output = Command::new("kpsewhich")
.args(args)
.output()
.map_err(|_| Error::KpsewhichNotFound)?;
Ok(String::from_utf8(output.stdout)
.expect("Could not decode output from kpsewhich")
.lines()
.nth(0)
.expect("Invalid output from kpsewhich")
.to_owned())
}

fn detect_distribution(directories: &[PathBuf]) -> Result<TexDistributionKind> {
for directory in directories {
if directory.join(texlive::DATABASE_PATH).exists() {
return Ok(TexDistributionKind::Texlive);
} else if directory.join(miktex::DATABASE_PATH).exists() {
return Ok(TexDistributionKind::Miktex);
}
}

Err(Error::UnsupportedTexDistribution)
}

fn read_database(
root_directories: &[PathBuf],
kind: TexDistributionKind,
) -> Result<HashMap<OsString, PathBuf>> {
let mut files_by_name = HashMap::new();
for directory in root_directories {
let database = match kind {
TexDistributionKind::Texlive => texlive::read_database(&directory),
TexDistributionKind::Miktex => miktex::read_database(&directory, root_directories),
}?;

for file in database {
let name = file.file_name().unwrap().to_owned();
files_by_name.insert(name, file);
}
}

Ok(files_by_name)
}
}
36 changes: 36 additions & 0 deletions src/resolver/texlive.rs
@@ -0,0 +1,36 @@
use super::{Error, Result};
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::str::Lines;

pub const DATABASE_PATH: &'static str = "ls-R";

pub fn read_database(directory: &Path) -> Result<Vec<PathBuf>> {
let file = directory.join(DATABASE_PATH);
if !file.is_file() {
return Ok(Vec::new());
}

let text = fs::read_to_string(file).expect("Could not read ls-R file");
parse_database(&directory, text.lines()).map_err(|_| Error::CorruptFileDatabase)
}

fn parse_database(root_directory: &Path, lines: Lines) -> io::Result<Vec<PathBuf>> {
let mut files = Vec::new();
let mut directory = PathBuf::new();

for line in lines.filter(|x| !x.trim().is_empty() && !x.starts_with('%')) {
if line.ends_with(':') {
let path = &line[..line.len() - 1];
directory = root_directory.join(path);
} else {
let file = directory.join(line).canonicalize()?;
if file.is_file() {
files.push(file);
}
}
}

Ok(files)
}
36 changes: 36 additions & 0 deletions src/server.rs
Expand Up @@ -13,17 +13,21 @@ use crate::link::LinkProvider;
use crate::reference::ReferenceProvider;
use crate::rename::RenameProvider;
use crate::request;
use crate::resolver;
use crate::resolver::TexResolver;
use crate::syntax::bibtex::BibtexDeclaration;
use crate::syntax::text::SyntaxNode;
use crate::syntax::SyntaxTree;
use crate::workspace::WorkspaceManager;
use futures::future::BoxFuture;
use futures::lock::Mutex;
use futures::prelude::*;
use jsonrpc::server::Result;
use jsonrpc_derive::{jsonrpc_method, jsonrpc_server};
use log::*;
use lsp_types::*;
use serde::de::DeserializeOwned;
use serde_json::error::ErrorCode::Message;
use std::borrow::Cow;
use std::sync::Arc;
use walkdir::WalkDir;
Expand All @@ -32,6 +36,7 @@ pub struct LatexLspServer<C> {
client: Arc<C>,
workspace_manager: WorkspaceManager,
event_manager: EventManager,
resolver: Mutex<TexResolver>,
}

#[jsonrpc_server]
Expand All @@ -41,6 +46,7 @@ impl<C: LspClient + Send + Sync> LatexLspServer<C> {
client,
workspace_manager: WorkspaceManager::new(),
event_manager: EventManager::default(),
resolver: Mutex::new(TexResolver::new()),
}
}

Expand Down Expand Up @@ -101,6 +107,7 @@ impl<C: LspClient + Send + Sync> LatexLspServer<C> {

#[jsonrpc_method("initialized", kind = "notification")]
pub fn initialized(&self, _params: InitializedParams) {
self.event_manager.push(Event::Initialized);
self.event_manager.push(Event::WorkspaceChanged);
}

Expand Down Expand Up @@ -278,6 +285,35 @@ impl<C: LspClient + Send + Sync> jsonrpc::EventHandler for LatexLspServer<C> {
let handler = async move {
for event in self.event_manager.take() {
match event {
Event::Initialized => match TexResolver::load() {
Ok(res) => {
let mut resolver = await!(self.resolver.lock());
*resolver = res;
}
Err(why) => {
let message = match why {
resolver::Error::KpsewhichNotFound => {
"An error occurred while executing `kpsewhich`.\
Please make sure that your distribution is in your PATH \
environment variable and provides the `kpsewhich` tool."
}
resolver::Error::UnsupportedTexDistribution => {
"Your TeX distribution is not supported."
}
resolver::Error::CorruptFileDatabase => {
"The file database of your TeX distribution seems \
to be corrupt. Please rebuild it and try again."
}
};

let params = ShowMessageParams {
message: Cow::from(message),
typ: MessageType::Error,
};

self.client.show_message(params);
}
},
Event::WorkspaceChanged => {
let workspace = self.workspace_manager.get();
workspace
Expand Down

0 comments on commit e3be54b

Please sign in to comment.