Refactor textDocument/build request

This commit is contained in:
Patrick Förster 2023-04-16 10:12:58 +02:00
parent 27aba312a2
commit 669e7629d6
18 changed files with 454 additions and 438 deletions

View file

@ -7,10 +7,7 @@ edition.workspace = true
rust-version.workspace = true
[dependencies]
base-db = { path = "../base-db" }
rowan = "0.15.11"
rustc-hash = "1.1.0"
syntax = { path = "../syntax" }
url = "2.3.1"
[lib]

View file

@ -1,4 +1,4 @@
mod normalize;
mod normalize_uri;
mod placeholders;
pub use self::{normalize::normalize_uri, placeholders::*};
pub use self::{normalize_uri::normalize_uri, placeholders::*};

View file

@ -1,28 +0,0 @@
use url::Url;
pub fn normalize_uri(uri: &mut Url) {
if let Some(mut segments) = uri.path_segments() {
if let Some(mut path) = segments.next().and_then(fix_drive_letter) {
for segment in segments {
path.push('/');
path.push_str(segment);
}
uri.set_path(&path);
}
}
uri.set_fragment(None);
}
fn fix_drive_letter(text: &str) -> Option<String> {
if !text.is_ascii() {
return None;
}
match &text[1..] {
":" => Some(text.to_ascii_uppercase()),
"%3A" | "%3a" => Some(format!("{}:", text[0..1].to_ascii_uppercase())),
_ => None,
}
}

View file

@ -0,0 +1,56 @@
use url::Url;
pub fn normalize_uri(uri: &mut Url) {
if let Some(mut segments) = uri.path_segments() {
if let Some(mut path) = segments.next().and_then(fix_drive_letter) {
for segment in segments {
path.push('/');
path.push_str(segment);
}
uri.set_path(&path);
}
}
uri.set_fragment(None);
}
fn fix_drive_letter(text: &str) -> Option<String> {
if !text.is_ascii() {
return None;
}
match &text[1..] {
":" => Some(text.to_ascii_uppercase()),
"%3A" | "%3a" => Some(format!("{}:", text[0..1].to_ascii_uppercase())),
_ => None,
}
}
#[cfg(test)]
mod tests {
use url::Url;
use super::normalize_uri;
#[test]
fn test_lowercase_drive_letter() {
let mut uri = Url::parse("file://c:/foo/bar.txt").unwrap();
normalize_uri(&mut uri);
assert_eq!(uri.as_str(), "file:///C:/foo/bar.txt");
}
#[test]
fn test_uppercase_drive_letter() {
let mut uri = Url::parse("file://C:/foo/bar.txt").unwrap();
normalize_uri(&mut uri);
assert_eq!(uri.as_str(), "file:///C:/foo/bar.txt");
}
#[test]
fn test_fragment() {
let mut uri = Url::parse("foo:///bar/baz.txt#qux").unwrap();
normalize_uri(&mut uri);
assert_eq!(uri.as_str(), "foo:///bar/baz.txt");
}
}

View file

@ -1,34 +1,50 @@
use std::borrow::Cow;
use rustc_hash::FxHashMap;
pub fn replace_placeholders<'a>(
input: &'a str,
placeholders: &FxHashMap<char, &str>,
) -> Cow<'a, str> {
match input
.strip_prefix('"')
.and_then(|input| input.strip_suffix('"'))
{
Some(input) => Cow::Borrowed(input),
None => {
let mut output = String::new();
let mut chars = input.chars();
while let Some(ch) = chars.next() {
if ch == '%' {
match chars.next() {
Some(key) => match placeholders.get(&key) {
Some(value) => output.push_str(&value),
None => output.push(key),
},
None => output.push('%'),
};
} else {
output.push(ch);
pub fn replace_placeholders(args: &[String], pairs: &[(char, &str)]) -> Vec<String> {
let map = FxHashMap::from_iter(pairs.iter().copied());
args.iter()
.map(|input| {
let quoted = input
.strip_prefix('"')
.and_then(|input| input.strip_suffix('"'));
match quoted {
Some(output) => String::from(output),
None => {
let mut output = String::new();
let mut chars = input.chars();
while let Some(ch) = chars.next() {
if ch == '%' {
match chars.next() {
Some(key) => match map.get(&key) {
Some(value) => output.push_str(&value),
None => output.push(key),
},
None => output.push('%'),
};
} else {
output.push(ch);
}
}
output
}
}
})
.collect()
}
Cow::Owned(output)
}
#[cfg(test)]
mod tests {
use super::replace_placeholders;
#[test]
fn test_quoted() {
let output = replace_placeholders(
&["foo".into(), "\"%f\"".into(), "%%f".into(), "%fbar".into()],
&[('f', "foo")],
);
assert_eq!(output, vec!["foo".into(), "%f", "%f".into(), "foobar"]);
}
}

View file

@ -10,6 +10,8 @@ rust-version.workspace = true
anyhow = "1.0.70"
base-db = { path = "../base-db" }
base-feature = { path = "../base-feature" }
bstr = "1.4.0"
crossbeam-channel = "0.5.8"
itertools = "0.10.5"
log = "0.4.17"
rowan = "0.15.11"

View file

@ -0,0 +1,102 @@
use std::{
io::{BufReader, Read},
path::{Path, PathBuf},
process::{ExitStatus, Stdio},
thread::{self, JoinHandle},
};
use anyhow::Result;
use base_db::Workspace;
use base_feature::replace_placeholders;
use bstr::io::BufReadExt;
use crossbeam_channel::Sender;
use thiserror::Error;
use url::Url;
#[derive(Debug, Error)]
pub enum BuildError {
#[error("Document \"{0}\" was not found")]
NotFound(Url),
#[error("Document \"{0}\" does not exist on the local file system")]
NotLocal(Url),
#[error("Unable to run compiler: {0}")]
Compile(#[from] std::io::Error),
}
#[derive(Debug)]
pub struct BuildCommand {
program: String,
args: Vec<String>,
working_dir: PathBuf,
}
impl BuildCommand {
pub fn new(workspace: &Workspace, uri: &Url) -> Result<Self, BuildError> {
let Some(document) = workspace.lookup(uri) else {
return Err(BuildError::NotFound(uri.clone()));
};
let document = workspace
.parents(document)
.into_iter()
.next()
.unwrap_or(document);
let Some(path) = document.path.as_deref().and_then(Path::to_str) else {
return Err(BuildError::NotLocal(document.uri.clone()));
};
let config = &workspace.config().build;
let program = config.program.clone();
let args = replace_placeholders(&config.args, &[('f', path)]);
let Ok(working_dir) = workspace.current_dir(&document.dir).to_file_path() else {
return Err(BuildError::NotLocal(document.uri.clone()));
};
Ok(Self {
program,
args,
working_dir,
})
}
pub fn run(self, sender: Sender<String>) -> Result<ExitStatus, BuildError> {
log::debug!(
"Spawning compiler {} {:#?} in directory {}",
self.program,
self.args,
self.working_dir.display()
);
let mut process = std::process::Command::new(&self.program)
.args(self.args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.current_dir(&self.working_dir)
.spawn()?;
track_output(process.stderr.take().unwrap(), sender.clone());
track_output(process.stdout.take().unwrap(), sender);
let status = process.wait();
Ok(status?)
}
}
fn track_output(
output: impl Read + Send + 'static,
sender: Sender<String>,
) -> JoinHandle<std::io::Result<()>> {
let mut reader = BufReader::new(output);
thread::spawn(move || {
reader.for_byte_line(|line| {
let text = String::from_utf8_lossy(line).into_owned();
let _ = sender.send(text);
Ok(true)
})
})
}

View file

@ -1,14 +1,12 @@
use std::{
borrow::Cow,
ffi::OsStr,
path::{Path, PathBuf},
process::Stdio,
};
use anyhow::Result;
use base_db::{Document, LineCol, Workspace};
use base_db::Workspace;
use base_feature::replace_placeholders;
use rustc_hash::FxHashMap;
use thiserror::Error;
use url::Url;
@ -18,7 +16,7 @@ pub enum ForwardSearchError {
Unconfigured,
#[error("Document \"{0}\" does not exist on the local file system")]
NonLocalFile(Url),
NotLocal(Url),
#[error("Document \"{0}\" has an invalid file path")]
InvalidPath(Url),
@ -41,17 +39,21 @@ pub struct ForwardSearch {
impl ForwardSearch {
pub fn new(
workspace: &Workspace,
child: &Document,
position: LineCol,
uri: &Url,
line: Option<u32>,
) -> Result<Self, ForwardSearchError> {
let Some(config) = &workspace.config().synctex else {
return Err(ForwardSearchError::Unconfigured);
};
let Some(child) = workspace.lookup(uri) else {
return Err(ForwardSearchError::TexNotFound(uri.clone()));
};
let parents = workspace.parents(child);
let parent = parents.into_iter().next().unwrap_or(child);
if parent.uri.scheme() != "file" {
return Err(ForwardSearchError::NonLocalFile(parent.uri.clone()));
return Err(ForwardSearchError::NotLocal(parent.uri.clone()));
}
let dir = workspace.current_dir(&parent.dir);
@ -76,20 +78,14 @@ impl ForwardSearch {
let tex_path = tex_path.to_string_lossy().into_owned();
let pdf_path = pdf_path.to_string_lossy().into_owned();
let line = (position.line + 1).to_string();
let mut placeholders = FxHashMap::default();
placeholders.insert('f', tex_path.as_str());
placeholders.insert('p', pdf_path.as_str());
placeholders.insert('l', line.as_str());
let line = line.unwrap_or_else(|| child.line_index.line_col(child.cursor).line);
let line = (line + 1).to_string();
let program = config.program.clone();
let args = config
.args
.iter()
.map(|arg| replace_placeholders(arg, &placeholders))
.map(Cow::into_owned)
.collect::<Vec<String>>();
let args = replace_placeholders(
&config.args,
&[('f', &tex_path), ('p', &pdf_path), ('l', &line)],
);
Ok(Self { program, args })
}

View file

@ -1,6 +1,13 @@
mod build;
mod change_env;
mod clean;
mod dep_graph;
mod fwd_search;
pub use self::{change_env::*, clean::*, dep_graph::*, fwd_search::*};
pub use self::{
build::{BuildCommand, BuildError},
change_env::{change_environment, ChangeEnvironmentResult},
clean::{CleanCommand, CleanTarget},
dep_graph::show_dependency_graph,
fwd_search::{ForwardSearch, ForwardSearchError},
};

View file

@ -46,7 +46,6 @@ encoding_rs_io = "0.1.7"
fern = "0.6.2"
flate2 = "1.0.25"
fuzzy-matcher = { version = "0.3.7", features = ["compact"] }
human_name = { version = "2.0.1", default-features = false }
itertools = "0.10.5"
log = "0.4.17"
lsp-server = "0.7.0"
@ -65,7 +64,6 @@ serde_repr = "0.1.12"
smol_str = { version = "0.1.24", features = ["serde"] }
syntax = { path = "../syntax" }
tempfile = "3.5.0"
thiserror = "1.0.40"
threadpool = "1.8.1"
titlecase = "2.2.1"

View file

@ -1,4 +1,3 @@
pub mod build;
pub mod completion;
pub mod definition;
pub mod folding;

View file

@ -1,171 +0,0 @@
mod progress;
use std::{
io::{BufRead, BufReader, Read},
path::{Path, PathBuf},
process::Stdio,
thread::{self, JoinHandle},
};
use base_db::Workspace;
use encoding_rs_io::DecodeReaderBytesBuilder;
use lsp_types::{
notification::LogMessage, ClientCapabilities, LogMessageParams, TextDocumentIdentifier, Url,
};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use crate::{client::LspClient, util::capabilities::ClientCapabilitiesExt};
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BuildParams {
pub text_document: TextDocumentIdentifier,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BuildResult {
pub status: BuildStatus,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum BuildStatus {
SUCCESS = 0,
ERROR = 1,
FAILURE = 2,
CANCELLED = 3,
}
#[derive(Debug)]
pub struct Command {
uri: Url,
progress: bool,
program: String,
args: Vec<String>,
working_dir: PathBuf,
client: LspClient,
}
impl Command {
pub fn new(
workspace: &Workspace,
uri: Url,
client: LspClient,
client_capabilities: &ClientCapabilities,
) -> Option<Self> {
let Some(document) = workspace
.lookup(&uri)
.map(|child| workspace.parents(child).into_iter().next().unwrap_or(child)) else { return None };
let Some(path) = document.path.as_deref() else {
log::warn!("Document {uri} cannot be compiled; skipping...");
return None;
};
let config = &workspace.config().build;
let program = config.program.clone();
let args = config
.args
.iter()
.map(|arg| replace_placeholder(arg, path))
.collect();
let working_dir = workspace.current_dir(&document.dir).to_file_path().ok()?;
Some(Self {
uri: document.uri.clone(),
progress: client_capabilities.has_work_done_progress_support(),
program,
args,
working_dir,
client,
})
}
pub fn run(self) -> BuildStatus {
let reporter = if self.progress {
let inner = progress::Reporter::new(&self.client);
inner.start(&self.uri).expect("report progress");
Some(inner)
} else {
None
};
let mut process = match std::process::Command::new(&self.program)
.args(self.args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.current_dir(&self.working_dir)
.spawn()
{
Ok(process) => process,
Err(why) => {
log::error!(
"Failed to spawn process {:?} in directory {}: {}",
self.program,
self.working_dir.display(),
why
);
return BuildStatus::FAILURE;
}
};
let (sender, receiver) = crossbeam_channel::unbounded();
track_output(process.stderr.take().unwrap(), sender.clone());
track_output(process.stdout.take().unwrap(), sender.clone());
let client = self.client.clone();
let handle = std::thread::spawn(move || {
let typ = lsp_types::MessageType::LOG;
while let Ok(Some(message)) = receiver.recv() {
let params = LogMessageParams { message, typ };
let _ = client.send_notification::<LogMessage>(params);
}
});
let status = process.wait().map_or(BuildStatus::FAILURE, |result| {
if result.success() {
BuildStatus::SUCCESS
} else {
BuildStatus::ERROR
}
});
let _ = sender.send(None);
handle.join().unwrap();
drop(reporter);
status
}
}
fn track_output(
output: impl Read + Send + 'static,
sender: crossbeam_channel::Sender<Option<String>>,
) -> JoinHandle<()> {
let reader = BufReader::new(
DecodeReaderBytesBuilder::new()
.encoding(Some(encoding_rs::UTF_8))
.utf8_passthru(true)
.strip_bom(true)
.build(output),
);
thread::spawn(move || {
let _ = reader
.lines()
.flatten()
.try_for_each(|line| sender.send(Some(line)));
})
}
fn replace_placeholder(arg: &str, file: &Path) -> String {
if arg.starts_with('"') || arg.ends_with('"') {
arg.to_string()
} else {
arg.replace("%f", &file.to_string_lossy())
}
}

View file

@ -1,54 +0,0 @@
use std::sync::atomic::{AtomicI32, Ordering};
use anyhow::Result;
use lsp_types::{
notification::Progress, request::WorkDoneProgressCreate, NumberOrString, ProgressParams,
ProgressParamsValue, Url, WorkDoneProgress, WorkDoneProgressBegin,
WorkDoneProgressCreateParams, WorkDoneProgressEnd,
};
use crate::client::LspClient;
static NEXT_TOKEN: AtomicI32 = AtomicI32::new(1);
pub struct Reporter<'a> {
client: &'a LspClient,
token: i32,
}
impl<'a> Reporter<'a> {
pub fn new(client: &'a LspClient) -> Self {
let token = NEXT_TOKEN.fetch_add(1, Ordering::SeqCst);
Self { client, token }
}
pub fn start(&self, uri: &Url) -> Result<()> {
self.client
.send_request::<WorkDoneProgressCreate>(WorkDoneProgressCreateParams {
token: NumberOrString::Number(self.token),
})?;
self.client.send_notification::<Progress>(ProgressParams {
token: NumberOrString::Number(self.token),
value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(WorkDoneProgressBegin {
title: "Building".to_string(),
message: Some(uri.as_str().to_string()),
cancellable: Some(false),
percentage: None,
})),
})?;
Ok(())
}
}
impl<'a> Drop for Reporter<'a> {
fn drop(&mut self) {
let _ = self.client.send_notification::<Progress>(ProgressParams {
token: NumberOrString::Number(self.token),
value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(WorkDoneProgressEnd {
message: None,
})),
});
}
}

View file

@ -1,33 +1,32 @@
mod dispatch;
mod extensions;
pub mod options;
mod progress;
use std::{
collections::HashMap,
path::PathBuf,
sync::{Arc, Mutex},
sync::{atomic::AtomicI32, Arc},
};
use anyhow::Result;
use base_db::{Config, LineColUtf16, Owner, Workspace};
use base_db::{Config, Owner, Workspace};
use base_feature::normalize_uri;
use commands::{CleanCommand, CleanTarget, ForwardSearch, ForwardSearchError};
use commands::{BuildCommand, CleanCommand, CleanTarget, ForwardSearch};
use crossbeam_channel::{Receiver, Sender};
use distro::{Distro, Language};
use lsp_server::{Connection, ErrorCode, Message, RequestId};
use lsp_types::{notification::*, request::*, *};
use once_cell::sync::Lazy;
use parking_lot::RwLock;
use parking_lot::{Mutex, RwLock};
use rowan::{ast::AstNode, TextSize};
use rustc_hash::{FxHashMap, FxHashSet};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use serde::{de::DeserializeOwned, Serialize};
use syntax::bibtex;
use threadpool::ThreadPool;
use crate::{
client::LspClient,
features::{
build::{self, BuildParams, BuildResult, BuildStatus},
completion::{self, builder::CompletionItemData},
definition, folding, formatting, highlight, hover, inlay_hint, link, reference, rename,
symbol,
@ -38,7 +37,14 @@ use crate::{
},
};
use self::options::{Options, StartupOptions};
use self::{
extensions::{
BuildParams, BuildRequest, BuildResult, BuildStatus, ForwardSearchRequest,
ForwardSearchResult, ForwardSearchStatus,
},
options::{Options, StartupOptions},
progress::ProgressReporter,
};
#[derive(Debug)]
enum InternalMessage {
@ -47,6 +53,7 @@ enum InternalMessage {
FileEvent(notify::Event),
Diagnostics,
ChktexResult(Url, Vec<lsp_types::Diagnostic>),
ForwardSearch(Url, Option<Position>),
}
pub struct Server {
@ -67,6 +74,7 @@ impl Server {
let client = LspClient::new(connection.sender.clone());
let (internal_tx, internal_rx) = crossbeam_channel::unbounded();
let watcher = FileWatcher::new(internal_tx.clone()).expect("init file watcher");
Self {
connection: Arc::new(connection),
internal_tx,
@ -407,7 +415,9 @@ impl Server {
normalize_uri(&mut uri);
if self.workspace.read().config().build.on_save {
self.build_internal(uri.clone(), |_| ())?;
let text_document = TextDocumentIdentifier::new(uri.clone());
let params = BuildParams { text_document };
self.build(None, params)?;
}
self.publish_diagnostics_with_delay();
@ -466,7 +476,7 @@ impl Server {
Ok(())
}
fn completion(&mut self, id: RequestId, params: CompletionParams) -> Result<()> {
fn completion(&self, id: RequestId, params: CompletionParams) -> Result<()> {
let mut uri = params.text_document_position.text_document.uri;
normalize_uri(&mut uri);
let position = params.text_document_position.position;
@ -612,7 +622,7 @@ impl Server {
Ok(())
}
fn execute_command(&mut self, id: RequestId, params: ExecuteCommandParams) -> Result<()> {
fn execute_command(&self, id: RequestId, params: ExecuteCommandParams) -> Result<()> {
match params.command.as_str() {
"texlab.cleanAuxiliary" => {
let command = self.prepare_clean_command(params, CleanTarget::Auxiliary);
@ -672,62 +682,87 @@ impl Server {
Ok(())
}
fn build(&mut self, id: RequestId, params: BuildParams) -> Result<()> {
fn build(&self, id: Option<RequestId>, params: BuildParams) -> Result<()> {
static LOCK: Mutex<()> = Mutex::new(());
static NEXT_TOKEN: AtomicI32 = AtomicI32::new(1);
let mut uri = params.text_document.uri;
normalize_uri(&mut uri);
let client = self.client.clone();
self.build_internal(uri, move |status| {
let result = BuildResult { status };
let _ = client.send_response(lsp_server::Response::new_ok(id, result));
})?;
Ok(())
}
fn build_internal(
&mut self,
uri: Url,
callback: impl FnOnce(BuildStatus) + Send + 'static,
) -> Result<()> {
static LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
let workspace = self.workspace.read();
let client = self.client.clone();
let Some(compiler) = build::Command::new(&workspace, uri.clone(), client, &self.client_capabilities) else {
callback(BuildStatus::FAILURE);
return Ok(());
};
let forward_search_after = workspace.config().build.forward_search_after;
let forward_search = if forward_search_after {
self.prepare_forward_search(&uri, None).ok()
} else {
None
};
let fwd_search_after = workspace.config().build.forward_search_after;
let (sender, receiver) = crossbeam_channel::unbounded();
self.redirect_build_log(receiver);
let command = BuildCommand::new(&workspace, &uri);
let internal = self.internal_tx.clone();
let progress = self.client_capabilities.has_work_done_progress_support();
self.pool.execute(move || {
let guard = LOCK.lock().unwrap();
let guard = LOCK.lock();
let status = compiler.run();
let progress_reporter = if progress {
let token = NEXT_TOKEN.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
Some(ProgressReporter::new(client.clone(), token, &uri))
} else {
None
};
if let Some(forward_search) = forward_search {
std::thread::spawn(move || forward_search.run().unwrap());
let status = match command.and_then(|command| command.run(sender)) {
Ok(status) if status.success() => BuildStatus::SUCCESS,
Ok(_) => BuildStatus::ERROR,
Err(why) => {
log::error!("Failed to compile document \"{uri}\": {why}");
BuildStatus::FAILURE
}
};
drop(progress_reporter);
drop(guard);
if let Some(id) = id {
let result = BuildResult { status };
let _ = client.send_response(lsp_server::Response::new_ok(id, result));
}
drop(guard);
callback(status);
if fwd_search_after {
let _ = internal.send(InternalMessage::ForwardSearch(uri, None));
}
});
Ok(())
}
fn forward_search(&mut self, id: RequestId, params: TextDocumentPositionParams) -> Result<()> {
let mut uri = params.text_document.uri;
fn redirect_build_log(&self, receiver: Receiver<String>) {
let client = self.client.clone();
self.pool.execute(move || {
let typ = MessageType::LOG;
for message in receiver {
client
.send_notification::<LogMessage>(LogMessageParams { message, typ })
.unwrap();
}
});
}
fn forward_search(
&self,
id: Option<RequestId>,
mut uri: Url,
position: Option<Position>,
) -> Result<()> {
normalize_uri(&mut uri);
let client = self.client.clone();
let command = self.prepare_forward_search(&uri, Some(params.position));
let command = ForwardSearch::new(
&self.workspace.read(),
&uri,
position.map(|position| position.line),
);
self.pool.execute(move || {
let status = match command.and_then(ForwardSearch::run) {
Ok(()) => ForwardSearchStatus::SUCCESS,
@ -737,22 +772,24 @@ impl Server {
}
};
let result = ForwardSearchResult { status };
client
.send_response(lsp_server::Response::new_ok(id, result))
.unwrap();
if let Some(id) = id {
let result = ForwardSearchResult { status };
client
.send_response(lsp_server::Response::new_ok(id, result))
.unwrap();
}
});
Ok(())
}
fn code_actions(&mut self, id: RequestId, _params: CodeActionParams) -> Result<()> {
fn code_actions(&self, id: RequestId, _params: CodeActionParams) -> Result<()> {
self.client
.send_response(lsp_server::Response::new_ok(id, Vec::<CodeAction>::new()))?;
Ok(())
}
fn code_action_resolve(&mut self, id: RequestId, action: CodeAction) -> Result<()> {
fn code_action_resolve(&self, id: RequestId, action: CodeAction) -> Result<()> {
self.client
.send_response(lsp_server::Response::new_ok(id, action))?;
Ok(())
@ -796,28 +833,6 @@ impl Server {
}
}
fn prepare_forward_search(
&self,
uri: &Url,
position: Option<Position>,
) -> Result<ForwardSearch, ForwardSearchError> {
let workspace = self.workspace.read();
let Some(document) = workspace.lookup(uri) else {
return Err(ForwardSearchError::TexNotFound(uri.clone()));
};
let position = match position {
Some(position) => {
let line = position.line;
let col = position.character;
document.line_index.to_utf8(LineColUtf16 { line, col })
}
None => document.line_index.line_col(document.cursor),
};
ForwardSearch::new(&workspace, document, position)
}
fn prepare_clean_command(
&self,
params: ExecuteCommandParams,
@ -925,9 +940,9 @@ impl Server {
self.document_highlight(id, params)
})?
.on::<Formatting, _>(|id, params| self.formatting(id, params))?
.on::<BuildRequest, _>(|id, params| self.build(id, params))?
.on::<BuildRequest, _>(|id, params| self.build(Some(id), params))?
.on::<ForwardSearchRequest, _>(|id, params| {
self.forward_search(id, params)
self.forward_search(Some(id), params.text_document.uri, Some(params.position))
})?
.on::<ExecuteCommand,_>(|id, params| self.execute_command(id, params))?
.on::<SemanticTokensRangeRequest, _>(|id, params| {
@ -988,6 +1003,9 @@ impl Server {
self.chktex_diagnostics.insert(uri, diagnostics);
self.publish_diagnostics()?;
}
InternalMessage::ForwardSearch(uri, position) => {
self.forward_search(None, uri, position)?;
}
};
}
};
@ -1025,50 +1043,3 @@ impl FileWatcher {
workspace.watch(&mut self.watcher, &mut self.watched_dirs);
}
}
struct BuildRequest;
impl lsp_types::request::Request for BuildRequest {
type Params = BuildParams;
type Result = BuildResult;
const METHOD: &'static str = "textDocument/build";
}
struct ForwardSearchRequest;
impl lsp_types::request::Request for ForwardSearchRequest {
type Params = TextDocumentPositionParams;
type Result = ForwardSearchResult;
const METHOD: &'static str = "textDocument/forwardSearch";
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum ForwardSearchStatus {
SUCCESS = 0,
ERROR = 1,
FAILURE = 2,
UNCONFIGURED = 3,
}
impl From<ForwardSearchError> for ForwardSearchStatus {
fn from(why: ForwardSearchError) -> Self {
match why {
ForwardSearchError::Unconfigured => ForwardSearchStatus::UNCONFIGURED,
ForwardSearchError::NonLocalFile(_) => ForwardSearchStatus::FAILURE,
ForwardSearchError::InvalidPath(_) => ForwardSearchStatus::ERROR,
ForwardSearchError::TexNotFound(_) => ForwardSearchStatus::FAILURE,
ForwardSearchError::PdfNotFound(_) => ForwardSearchStatus::ERROR,
ForwardSearchError::LaunchViewer(_) => ForwardSearchStatus::ERROR,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct ForwardSearchResult {
pub status: ForwardSearchStatus,
}

View file

@ -0,0 +1,72 @@
use commands::ForwardSearchError;
use lsp_types::{TextDocumentIdentifier, TextDocumentPositionParams};
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
pub struct BuildRequest;
impl lsp_types::request::Request for BuildRequest {
type Params = BuildParams;
type Result = BuildResult;
const METHOD: &'static str = "textDocument/build";
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BuildParams {
pub text_document: TextDocumentIdentifier,
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BuildResult {
pub status: BuildStatus,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum BuildStatus {
SUCCESS = 0,
ERROR = 1,
FAILURE = 2,
CANCELLED = 3,
}
pub struct ForwardSearchRequest;
impl lsp_types::request::Request for ForwardSearchRequest {
type Params = TextDocumentPositionParams;
type Result = ForwardSearchResult;
const METHOD: &'static str = "textDocument/forwardSearch";
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize_repr, Deserialize_repr)]
#[repr(i32)]
pub enum ForwardSearchStatus {
SUCCESS = 0,
ERROR = 1,
FAILURE = 2,
UNCONFIGURED = 3,
}
impl From<ForwardSearchError> for ForwardSearchStatus {
fn from(why: ForwardSearchError) -> Self {
match why {
ForwardSearchError::Unconfigured => ForwardSearchStatus::UNCONFIGURED,
ForwardSearchError::NotLocal(_) => ForwardSearchStatus::FAILURE,
ForwardSearchError::InvalidPath(_) => ForwardSearchStatus::ERROR,
ForwardSearchError::TexNotFound(_) => ForwardSearchStatus::FAILURE,
ForwardSearchError::PdfNotFound(_) => ForwardSearchStatus::ERROR,
ForwardSearchError::LaunchViewer(_) => ForwardSearchStatus::ERROR,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct ForwardSearchResult {
pub status: ForwardSearchStatus,
}

View file

@ -0,0 +1,44 @@
use lsp_types::{
notification::Progress, request::WorkDoneProgressCreate, NumberOrString, ProgressParams,
ProgressParamsValue, Url, WorkDoneProgress, WorkDoneProgressBegin,
WorkDoneProgressCreateParams, WorkDoneProgressEnd,
};
use crate::LspClient;
#[derive(Debug)]
pub struct ProgressReporter {
client: LspClient,
token: i32,
}
impl Drop for ProgressReporter {
fn drop(&mut self) {
let _ = self.client.send_notification::<Progress>(ProgressParams {
token: NumberOrString::Number(self.token),
value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(WorkDoneProgressEnd {
message: None,
})),
});
}
}
impl ProgressReporter {
pub fn new(client: LspClient, token: i32, uri: &Url) -> Self {
let _ = client.send_request::<WorkDoneProgressCreate>(WorkDoneProgressCreateParams {
token: NumberOrString::Number(token),
});
let _ = client.send_notification::<Progress>(ProgressParams {
token: NumberOrString::Number(token),
value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(WorkDoneProgressBegin {
title: "Building".into(),
message: Some(String::from(uri.as_str())),
cancellable: Some(false),
percentage: None,
})),
});
Self { client, token }
}
}