summaryrefslogtreecommitdiff
path: root/support/texlab/src
diff options
context:
space:
mode:
Diffstat (limited to 'support/texlab/src')
-rw-r--r--support/texlab/src/features.rs2
-rw-r--r--support/texlab/src/features/build.rs277
-rw-r--r--support/texlab/src/features/forward_search.rs14
-rw-r--r--support/texlab/src/features/hover.rs2
-rw-r--r--support/texlab/src/options.rs17
-rw-r--r--support/texlab/src/server.rs114
6 files changed, 314 insertions, 112 deletions
diff --git a/support/texlab/src/features.rs b/support/texlab/src/features.rs
index 09743c0035..36c406b6b9 100644
--- a/support/texlab/src/features.rs
+++ b/support/texlab/src/features.rs
@@ -25,7 +25,7 @@ pub use self::completion::{complete, CompletionItemData, COMPLETION_LIMIT};
#[cfg(feature = "semantic")]
pub use self::semantic::{find_semantic_tokens_range, legend};
pub use self::{
- build::{build_document, BuildParams, BuildResult, BuildStatus},
+ build::{BuildEngine, BuildParams, BuildResult, BuildStatus},
definition::goto_definition,
folding::find_foldings,
formatting::format_source_code,
diff --git a/support/texlab/src/features/build.rs b/support/texlab/src/features/build.rs
index c6a670528f..6953698a29 100644
--- a/support/texlab/src/features/build.rs
+++ b/support/texlab/src/features/build.rs
@@ -1,20 +1,29 @@
use std::{
- io::{self, BufRead, BufReader, Read},
+ io::{BufRead, BufReader, Read},
path::Path,
process::{Command, Stdio},
- thread,
+ sync::{Arc, Mutex},
+ thread::{self, JoinHandle},
};
+use anyhow::Result;
use cancellation::CancellationToken;
+use chashmap::CHashMap;
use crossbeam_channel::Sender;
use encoding_rs_io::DecodeReaderBytesBuilder;
-use lsp_types::TextDocumentIdentifier;
+use lsp_types::{
+ notification::{LogMessage, Progress},
+ LogMessageParams, NumberOrString, Position, ProgressParams, ProgressParamsValue,
+ TextDocumentIdentifier, TextDocumentPositionParams, WorkDoneProgress, WorkDoneProgressBegin,
+ WorkDoneProgressCreateParams, WorkDoneProgressEnd,
+};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
+use uuid::Uuid;
-use crate::Options;
+use crate::{client, req_queue::ReqQueue, ClientCapabilitiesExt, DocumentLanguage, Uri};
-use super::FeatureRequest;
+use super::{forward_search, FeatureRequest};
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -25,10 +34,10 @@ pub struct BuildParams {
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum BuildStatus {
- Success = 0,
- Error = 1,
- Failure = 2,
- Cancelled = 3,
+ SUCCESS = 0,
+ ERROR = 1,
+ FAILURE = 2,
+ CANCELLED = 3,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
@@ -37,78 +46,203 @@ pub struct BuildResult {
pub status: BuildStatus,
}
-pub fn build_document(
- request: FeatureRequest<BuildParams>,
- _cancellation_token: &CancellationToken,
- log_sender: Sender<String>,
-) -> BuildResult {
- let document = request
- .subset
- .documents
- .iter()
- .find(|document| {
- if let Some(data) = document.data.as_latex() {
- data.extras.has_document_environment
- } else {
- false
- }
- })
- .map(|document| document.as_ref())
- .unwrap_or_else(|| request.main_document());
+struct ProgressReporter<'a> {
+ supports_progress: bool,
+ req_queue: &'a Mutex<ReqQueue>,
+ lsp_sender: Sender<lsp_server::Message>,
+ token: &'a str,
+}
- if document.uri.scheme() != "file" {
- return BuildResult {
- status: BuildStatus::Failure,
+impl<'a> ProgressReporter<'a> {
+ pub fn start(&self, uri: &Uri) -> Result<()> {
+ if self.supports_progress {
+ client::send_request::<lsp_types::request::WorkDoneProgressCreate>(
+ self.req_queue,
+ &self.lsp_sender,
+ WorkDoneProgressCreateParams {
+ token: NumberOrString::String(self.token.to_string()),
+ },
+ )?;
+ client::send_notification::<Progress>(
+ &self.lsp_sender,
+ ProgressParams {
+ token: NumberOrString::String(self.token.to_string()),
+ value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(
+ WorkDoneProgressBegin {
+ title: "Building".to_string(),
+ message: Some(uri.as_str().to_string()),
+ cancellable: Some(false),
+ percentage: None,
+ },
+ )),
+ },
+ )?;
};
+ Ok(())
}
- log::info!("Building document {}", document.uri.as_str());
-
- let options = { request.context.options.read().unwrap().clone() };
+}
- let status = match build_internal(&document.uri.to_file_path().unwrap(), &options, log_sender) {
- Ok(true) => BuildStatus::Success,
- Ok(false) => BuildStatus::Error,
- Err(why) => {
- log::error!("Failed to execute textDocument/build: {}", why);
- BuildStatus::Failure
+impl<'a> Drop for ProgressReporter<'a> {
+ fn drop(&mut self) {
+ if self.supports_progress {
+ let _ = client::send_notification::<Progress>(
+ &self.lsp_sender,
+ ProgressParams {
+ token: NumberOrString::String(self.token.to_string()),
+ value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(
+ WorkDoneProgressEnd { message: None },
+ )),
+ },
+ );
}
- };
- BuildResult { status }
+ }
+}
+
+#[derive(Default)]
+pub struct BuildEngine {
+ lock: Mutex<()>,
+ pub positions_by_uri: CHashMap<Arc<Uri>, Position>,
}
-fn build_internal(path: &Path, options: &Options, log_sender: Sender<String>) -> io::Result<bool> {
- let build_dir = options
- .root_directory
- .as_ref()
- .map(AsRef::as_ref)
- .or_else(|| path.parent())
- .unwrap();
-
- let args: Vec<_> = options
- .build
- .args()
- .into_iter()
- .map(|arg| replace_placeholder(arg, path))
- .collect();
-
- let mut process = Command::new(options.build.executable())
- .args(args)
- .stdin(Stdio::null())
- .stdout(Stdio::piped())
- .stderr(Stdio::piped())
- .current_dir(build_dir)
- .spawn()?;
+impl BuildEngine {
+ pub fn build(
+ &self,
+ request: FeatureRequest<BuildParams>,
+ cancellation_token: &CancellationToken,
+ req_queue: &Mutex<ReqQueue>,
+ lsp_sender: &Sender<lsp_server::Message>,
+ ) -> Result<BuildResult> {
+ let lock = self.lock.lock().unwrap();
- track_output(process.stdout.take().unwrap(), log_sender.clone());
- track_output(process.stderr.take().unwrap(), log_sender.clone());
+ let document = request
+ .subset
+ .documents
+ .iter()
+ .find(|document| {
+ if let Some(data) = document.data.as_latex() {
+ data.extras.has_document_environment
+ } else {
+ false
+ }
+ })
+ .map(|document| document.as_ref())
+ .unwrap_or_else(|| request.main_document());
- if !options.build.is_continuous {
- process.wait().map(|status| status.success())
- } else {
- Ok(true)
+ if document.language() != DocumentLanguage::Latex {
+ return Ok(BuildResult {
+ status: BuildStatus::SUCCESS,
+ });
+ }
+
+ if document.uri.scheme() != "file" {
+ return Ok(BuildResult {
+ status: BuildStatus::FAILURE,
+ });
+ }
+ let path = document.uri.to_file_path().unwrap();
+
+ let supports_progress = {
+ request
+ .context
+ .client_capabilities
+ .lock()
+ .unwrap()
+ .has_work_done_progress_support()
+ };
+
+ let token = format!("texlab-build-{}", Uuid::new_v4());
+ let progress_reporter = ProgressReporter {
+ supports_progress,
+ req_queue,
+ lsp_sender: lsp_sender.clone(),
+ token: &token,
+ };
+ progress_reporter.start(&document.uri)?;
+
+ let options = { request.context.options.read().unwrap().clone() };
+
+ let build_dir = options
+ .root_directory
+ .as_ref()
+ .map(AsRef::as_ref)
+ .or_else(|| path.parent())
+ .unwrap();
+
+ let args: Vec<_> = options
+ .build
+ .args()
+ .into_iter()
+ .map(|arg| replace_placeholder(arg, &path))
+ .collect();
+
+ let mut process = Command::new(options.build.executable())
+ .args(args)
+ .stdin(Stdio::null())
+ .stdout(Stdio::piped())
+ .stderr(Stdio::piped())
+ .current_dir(build_dir)
+ .spawn()?;
+
+ let log_handle = capture_output(&mut process, lsp_sender);
+ let success = process.wait().map(|status| status.success())?;
+ log_handle.join().unwrap();
+ let status = if success {
+ BuildStatus::SUCCESS
+ } else {
+ BuildStatus::ERROR
+ };
+
+ drop(progress_reporter);
+ drop(lock);
+
+ if options.build.forward_search_after {
+ let request = FeatureRequest {
+ params: TextDocumentPositionParams {
+ position: self
+ .positions_by_uri
+ .get(&request.main_document().uri)
+ .map(|guard| guard.clone())
+ .unwrap_or_default(),
+ text_document: TextDocumentIdentifier::new(
+ request.main_document().uri.as_ref().clone().into(),
+ ),
+ },
+ context: request.context,
+ workspace: request.workspace,
+ subset: request.subset,
+ };
+ forward_search::execute_forward_search(request, cancellation_token);
+ }
+
+ Ok(BuildResult { status })
}
}
+fn capture_output(
+ process: &mut std::process::Child,
+ lsp_sender: &Sender<lsp_server::Message>,
+) -> JoinHandle<()> {
+ let (log_sender, log_receiver) = crossbeam_channel::unbounded();
+ track_output(process.stdout.take().unwrap(), log_sender.clone());
+ track_output(process.stderr.take().unwrap(), log_sender);
+ let log_handle = {
+ let lsp_sender = lsp_sender.clone();
+ thread::spawn(move || {
+ for message in &log_receiver {
+ client::send_notification::<LogMessage>(
+ &lsp_sender,
+ LogMessageParams {
+ message,
+ typ: lsp_types::MessageType::Log,
+ },
+ )
+ .unwrap();
+ }
+ })
+ };
+ log_handle
+}
+
fn replace_placeholder(arg: String, file: &Path) -> String {
if arg.starts_with('"') || arg.ends_with('"') {
arg
@@ -117,7 +251,7 @@ fn replace_placeholder(arg: String, file: &Path) -> String {
}
}
-fn track_output(output: impl Read + Send + 'static, sender: Sender<String>) {
+fn track_output(output: impl Read + Send + 'static, sender: Sender<String>) -> JoinHandle<()> {
let reader = BufReader::new(
DecodeReaderBytesBuilder::new()
.encoding(Some(encoding_rs::UTF_8))
@@ -125,9 +259,10 @@ fn track_output(output: impl Read + Send + 'static, sender: Sender<String>) {
.strip_bom(true)
.build(output),
);
+
thread::spawn(move || {
for line in reader.lines() {
sender.send(line.unwrap()).unwrap();
}
- });
+ })
}
diff --git a/support/texlab/src/features/forward_search.rs b/support/texlab/src/features/forward_search.rs
index 39470a64ec..4a54b8a52a 100644
--- a/support/texlab/src/features/forward_search.rs
+++ b/support/texlab/src/features/forward_search.rs
@@ -15,10 +15,10 @@ use super::FeatureRequest;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum ForwardSearchStatus {
- Success = 0,
- Error = 1,
- Failure = 2,
- Unconfigured = 3,
+ SUCCESS = 0,
+ ERROR = 1,
+ FAILURE = 2,
+ UNCONFIGURED = 3,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
@@ -43,7 +43,7 @@ pub fn execute_forward_search(
if options.executable.is_none() || options.args.is_none() {
return Some(ForwardSearchResult {
- status: ForwardSearchStatus::Unconfigured,
+ status: ForwardSearchStatus::UNCONFIGURED,
});
}
@@ -87,10 +87,10 @@ pub fn execute_forward_search(
.collect();
let status = match run_process(options.executable.unwrap(), args) {
- Ok(()) => ForwardSearchStatus::Success,
+ Ok(()) => ForwardSearchStatus::SUCCESS,
Err(why) => {
error!("Unable to execute forward search: {}", why);
- ForwardSearchStatus::Failure
+ ForwardSearchStatus::FAILURE
}
};
Some(ForwardSearchResult { status })
diff --git a/support/texlab/src/features/hover.rs b/support/texlab/src/features/hover.rs
index a78978bc33..efd7555a57 100644
--- a/support/texlab/src/features/hover.rs
+++ b/support/texlab/src/features/hover.rs
@@ -1,4 +1,4 @@
-#[cfg(feature = "citeproc")]
+#[cfg(feature = "citation")]
mod citation;
mod component;
mod entry_type;
diff --git a/support/texlab/src/options.rs b/support/texlab/src/options.rs
index 2626143e18..20f92a90f4 100644
--- a/support/texlab/src/options.rs
+++ b/support/texlab/src/options.rs
@@ -75,6 +75,12 @@ pub struct BuildOptions {
#[serde(default)]
pub is_continuous: bool,
+
+ #[serde(default)]
+ pub on_save: bool,
+
+ #[serde(default)]
+ pub forward_search_after: bool,
}
impl BuildOptions {
@@ -99,6 +105,17 @@ impl BuildOptions {
#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
+pub struct ViewerOptions {
+ #[serde(default)]
+ pub enabled: bool,
+
+ pub executable: Option<String>,
+
+ pub args: Option<String>,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
pub struct ChktexOptions {
#[serde(default)]
pub on_open_and_save: bool,
diff --git a/support/texlab/src/server.rs b/support/texlab/src/server.rs
index 5a78d5e2e2..6806e5f8e5 100644
--- a/support/texlab/src/server.rs
+++ b/support/texlab/src/server.rs
@@ -1,18 +1,17 @@
use std::{
path::PathBuf,
sync::{Arc, Mutex},
- thread,
};
use anyhow::Result;
use cancellation::{CancellationToken, CancellationTokenSource};
use crossbeam_channel::Sender;
-use log::{info, warn};
+use log::{error, info, warn};
use lsp_server::{Connection, ErrorCode, Message, RequestId};
use lsp_types::{
notification::{
Cancel, DidChangeConfiguration, DidChangeTextDocument, DidOpenTextDocument,
- DidSaveTextDocument, LogMessage, PublishDiagnostics,
+ DidSaveTextDocument, PublishDiagnostics,
},
request::{
DocumentLinkRequest, FoldingRangeRequest, Formatting, GotoDefinition, PrepareRenameRequest,
@@ -37,10 +36,10 @@ use crate::{
dispatch::{NotificationDispatcher, RequestDispatcher},
distro::Distribution,
features::{
- build_document, find_all_references, find_document_highlights, find_document_links,
- find_document_symbols, find_foldings, find_hover, find_workspace_symbols,
- format_source_code, goto_definition, prepare_rename_all, rename_all, BuildParams,
- BuildResult, FeatureRequest, ForwardSearchResult,
+ find_all_references, find_document_highlights, find_document_links, find_document_symbols,
+ find_foldings, find_hover, find_workspace_symbols, format_source_code, goto_definition,
+ prepare_rename_all, rename_all, BuildEngine, BuildParams, BuildResult, BuildStatus,
+ FeatureRequest, ForwardSearchResult,
},
req_queue::{IncomingData, ReqQueue},
DocumentLanguage, ServerContext, Uri, Workspace, WorkspaceSource,
@@ -55,6 +54,7 @@ pub struct Server {
chktex_debouncer: DiagnosticsDebouncer,
pool: ThreadPool,
load_resolver: bool,
+ build_engine: Arc<BuildEngine>,
}
impl Server {
@@ -83,6 +83,7 @@ impl Server {
chktex_debouncer,
pool: threadpool::Builder::new().build(),
load_resolver,
+ build_engine: Arc::default(),
})
}
@@ -257,7 +258,24 @@ impl Server {
fn did_change(&self, mut params: DidChangeTextDocumentParams) -> Result<()> {
let uri = params.text_document.uri.into();
assert_eq!(params.content_changes.len(), 1);
- let text = params.content_changes.pop().unwrap().text;
+ let old_document = self.workspace.get(&uri);
+ let old_text = old_document.as_ref().map(|document| document.text.as_str());
+ let new_text = params.content_changes.pop().unwrap().text;
+
+ let uri = Arc::new(uri);
+
+ let line = match old_text {
+ Some(old_text) => old_text
+ .lines()
+ .zip(new_text.lines())
+ .position(|(a, b)| a != b)
+ .unwrap_or_default() as u32,
+ None => 0,
+ };
+ self.build_engine
+ .positions_by_uri
+ .insert(Arc::clone(&uri), Position::new(line, 0));
+
let language = self
.workspace
.get(&uri)
@@ -266,7 +284,7 @@ impl Server {
let document = self
.workspace
- .open(Arc::new(uri), text, language, WorkspaceSource::Client);
+ .open(uri, new_text, language, WorkspaceSource::Client);
let should_lint = { self.context.options.read().unwrap().chktex.on_edit };
if let Some(document) = self
@@ -287,6 +305,36 @@ impl Server {
fn did_save(&self, params: DidSaveTextDocumentParams) -> Result<()> {
let uri = params.text_document.uri.into();
+
+ let should_build = { self.context.options.read().unwrap().build.on_save };
+ if let Some(request) =
+ self.workspace
+ .get(&uri)
+ .filter(|_| should_build)
+ .and_then(|document| {
+ self.feature_request(
+ Arc::clone(&document.uri),
+ BuildParams {
+ text_document: TextDocumentIdentifier::new(uri.clone().into()),
+ },
+ )
+ })
+ {
+ let lsp_sender = self.connection.sender.clone();
+ let req_queue = Arc::clone(&self.req_queue);
+ let build_engine = Arc::clone(&self.build_engine);
+ self.pool.execute(move || {
+ build_engine
+ .build(request, CancellationToken::none(), &req_queue, &lsp_sender)
+ .unwrap_or_else(|why| {
+ error!("Build failed: {}", why);
+ BuildResult {
+ status: BuildStatus::FAILURE,
+ }
+ });
+ });
+ }
+
let should_lint = { self.context.options.read().unwrap().chktex.on_open_and_save };
if let Some(document) = self.workspace.get(&uri).filter(|_| should_lint) {
self.chktex_debouncer
@@ -430,6 +478,11 @@ impl Server {
.clone()
.into(),
);
+
+ self.build_engine
+ .positions_by_uri
+ .insert(Arc::clone(&uri), params.text_document_position.position);
+
self.handle_feature_request(id, params, uri, token, crate::features::complete)?;
Ok(())
}
@@ -452,7 +505,7 @@ impl Server {
.documentation(&item.label)
.map(Documentation::MarkupContent);
}
- #[cfg(feature = "citeproc")]
+ #[cfg(feature = "citation")]
crate::features::CompletionItemData::Citation { uri, key } => {
if let Some(document) = workspace.get(&uri) {
if let Some(data) = document.data.as_bibtex() {
@@ -519,6 +572,11 @@ impl Server {
.clone()
.into(),
);
+ self.build_engine.positions_by_uri.insert(
+ Arc::clone(&uri),
+ params.text_document_position_params.position,
+ );
+
self.handle_feature_request(id, params, uri, token, find_hover)?;
Ok(())
}
@@ -635,23 +693,17 @@ impl Server {
) -> Result<()> {
let uri = Arc::new(params.text_document.uri.clone().into());
let lsp_sender = self.connection.sender.clone();
- self.handle_feature_request(id, params, uri, token, |request, token| {
- let (log_sender, log_receiver) = crossbeam_channel::unbounded();
-
- thread::spawn(move || {
- for message in &log_receiver {
- send_notification::<LogMessage>(
- &lsp_sender,
- LogMessageParams {
- message,
- typ: MessageType::Log,
- },
- )
- .unwrap();
- }
- });
-
- build_document(request, token, log_sender)
+ let req_queue = Arc::clone(&self.req_queue);
+ let build_engine = Arc::clone(&self.build_engine);
+ self.handle_feature_request(id, params, uri, token, move |request, token| {
+ build_engine
+ .build(request, token, &req_queue, &lsp_sender)
+ .unwrap_or_else(|why| {
+ error!("Build failed: {}", why);
+ BuildResult {
+ status: BuildStatus::FAILURE,
+ }
+ })
})?;
Ok(())
}
@@ -745,11 +797,9 @@ impl Server {
Message::Response(response) => {
let mut req_queue = self.req_queue.lock().unwrap();
let data = req_queue.outgoing.complete(response.id);
- let result = match response.result {
- Some(result) => Ok(result),
- None => Err(response
- .error
- .expect("response without result or error received")),
+ let result = match response.error {
+ Some(error) => Err(error),
+ None => Ok(response.result.unwrap_or_default()),
};
data.sender.send(result)?;
}