#![allow(clippy::unreachable)]
#![allow(clippy::pattern_type_mismatch)]
mod json_rpc;
mod lsp;
use {
core::{
cell::{Cell, RefCell},
convert::TryInto,
fmt::{self, Display},
},
fehler::{throw, throws},
log::{error, trace},
market::{Consumer as _, Failure as _, Producer as _},
std::{
process::{self, Command, ExitStatus},
rc::Rc,
thread::{self, JoinHandle},
},
};
#[derive(Clone, Copy, Debug, enum_map::Enum, parse_display::Display, PartialEq)]
#[display(style = "lowercase")]
pub enum Language {
Rust,
Plaintext,
}
#[derive(Debug)]
pub struct Tongue {
thread: JoinHandle<()>,
joiner: market::sync::Trigger,
transmission_channel: market::channel::Channel<market::channel::Crossbeam<ClientStatement>>,
reception_channel: market::channel::Channel<market::channel::Crossbeam<ServerStatement>>,
status_consumer: market::channel::CrossbeamConsumer<ExitStatus>,
}
impl Tongue {
#[inline]
#[throws(market::TakenParticipant)]
pub fn new(root_dir: &lsp_types::Url) -> Self {
let dir = root_dir.clone();
let mut lock = market::sync::Lock::new();
let mut transmission_channel =
market::channel::Channel::new(market::channel::Size::Infinite);
let mut reception_channel = market::channel::Channel::new(market::channel::Size::Infinite);
let mut status_channel =
market::channel::Channel::<market::channel::Crossbeam<ExitStatus>>::new(
market::channel::Size::Infinite,
);
let hammer = lock.hammer()?;
let transmission_consumer = transmission_channel.consumer()?;
let reception_producer = reception_channel.producer()?;
let status_producer = status_channel.producer()?;
Self {
joiner: lock.trigger()?,
thread: thread::spawn(move || {
if let Err(error) = Self::thread(
&dir,
&hammer,
&transmission_consumer,
&reception_producer,
&status_producer,
) {
error!("tongue thread error: {}", error);
}
}),
transmission_channel,
reception_channel,
status_consumer: status_channel.consumer()?,
}
}
#[inline]
#[throws(market::TakenParticipant)]
pub fn consumer(&mut self) -> market::channel::CrossbeamConsumer<ServerStatement> {
self.reception_channel.consumer()?
}
#[inline]
#[throws(market::TakenParticipant)]
pub fn producer(&mut self) -> market::channel::CrossbeamProducer<ClientStatement> {
self.transmission_channel.producer()?
}
#[throws(TranslationError)]
fn thread(
root_dir: &lsp_types::Url,
hammer: &market::sync::Hammer,
transmission_consumer: &market::channel::CrossbeamConsumer<ClientStatement>,
reception_producer: &market::channel::CrossbeamProducer<ServerStatement>,
status_producer: &market::channel::CrossbeamProducer<ExitStatus>,
) {
let rust_translator = Rc::new(RefCell::new(Translator::new(
Client::new(Command::new("rust-analyzer"))?,
root_dir.clone(),
)));
let plaintext_translator = Rc::new(RefCell::new(Translator::new(
Client::new(Command::new("echo"))?,
root_dir.clone(),
)));
plaintext_translator.borrow_mut().state = State::WaitingExit;
let translators = enum_map::enum_map! {
Language::Rust => Rc::clone(&rust_translator),
Language::Plaintext => Rc::clone(&plaintext_translator),
};
while !translators
.values()
.map(|t| t.borrow().state == State::WaitingExit)
.all(|x| x)
{
let will_shutdown = hammer.consume().is_ok();
for transmission in transmission_consumer.consume_all()? {
#[allow(clippy::indexing_slicing)]
translators[transmission.language()]
.borrow_mut()
.send_message(transmission.into())?;
}
for (_, translator) in &translators {
if let Some(input) = translator.borrow_mut().translate()? {
reception_producer.produce(input)?;
}
translator.borrow().log_errors();
if will_shutdown {
translator.borrow_mut().shutdown()?;
}
}
}
for (_, translator) in translators {
status_producer.produce(translator.borrow().waiter().demand()?)?;
}
}
#[inline]
#[throws(market::ProduceFailure<market::channel::DisconnectedFault>)]
pub fn join(&self) {
self.joiner.produce(())?;
}
}
#[derive(Clone, Copy, Debug, parse_display::Display)]
#[display(style = "CamelCase")]
pub enum ServerStatement {
Exit,
}
#[derive(Debug, parse_display::Display)]
#[display("")]
pub enum ClientStatement {
OpenDoc {
doc: lsp_types::TextDocumentItem,
},
CloseDoc {
doc: lsp_types::TextDocumentIdentifier,
},
}
impl ClientStatement {
#[inline]
#[must_use]
pub const fn open_doc(doc: lsp_types::TextDocumentItem) -> Self {
Self::OpenDoc { doc }
}
#[inline]
#[must_use]
pub const fn close_doc(doc: lsp_types::TextDocumentIdentifier) -> Self {
Self::CloseDoc { doc }
}
#[allow(clippy::unused_self)]
const fn language(&self) -> Language {
Language::Rust
}
}
impl From<ClientStatement> for ClientMessage {
#[inline]
fn from(value: ClientStatement) -> Self {
match value {
ClientStatement::OpenDoc { doc } => Self::Notification(ClientNotification::OpenDoc(
lsp_types::DidOpenTextDocumentParams { text_document: doc },
)),
ClientStatement::CloseDoc { doc } => Self::Notification(ClientNotification::CloseDoc(
lsp_types::DidCloseTextDocumentParams { text_document: doc },
)),
}
}
}
#[derive(Clone, Debug)]
pub struct ErrorMessage {
line: String,
}
#[derive(Clone, Copy, Debug, thiserror::Error)]
#[error("Error while composing error message")]
pub struct ErrorMessageCompositionError;
impl conventus::AssembleFrom<u8> for ErrorMessage {
type Error = ErrorMessageCompositionError;
#[inline]
#[throws(conventus::AssembleFailure<Self::Error>)]
fn assemble_from(parts: &mut Vec<u8>) -> Self {
if let Ok(s) = std::str::from_utf8_mut(parts) {
if let Some(index) = s.find('\n') {
let (l, remainder) = s.split_at_mut(index);
let (_, new_parts) = remainder.split_at_mut(1);
let line = (*l).to_string();
*parts = new_parts.as_bytes().to_vec();
Self { line }
} else {
throw!(conventus::AssembleFailure::Incomplete);
}
} else {
*parts = Vec::new();
throw!(ErrorMessageCompositionError);
}
}
}
impl Display for ErrorMessage {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.line)
}
}
#[derive(Debug, thiserror::Error)]
pub enum TranslationError {
#[error(transparent)]
Transmission(#[from] market::ProduceFailure<market::io::WriteError<lsp::Message>>),
#[error(transparent)]
Reception(#[from] ConsumeServerMessageError),
#[error(transparent)]
CreateClient(#[from] CreateClientError),
#[error(transparent)]
Wait(#[from] market::process::WaitFault),
#[error(transparent)]
CollectOutputs(#[from] market::channel::DisconnectedFault),
#[error("Invalid state: Cannot {0} while {1}")]
InvalidState(Box<Event>, State),
#[error(transparent)]
Storage(#[from] market::ProduceFailure<market::channel::DisconnectedFault>),
}
#[derive(Debug, parse_display::Display, PartialEq)]
pub enum Event {
#[display("send message")]
SendMessage(ClientMessage),
#[display("process initialization")]
Initialized(lsp_types::InitializeResult),
#[display("register capability")]
RegisterCapability(json_rpc::Id, lsp_types::RegistrationParams),
#[display("complete shutdown")]
CompletedShutdown,
#[display("exit")]
Exit,
}
#[derive(Clone, Debug, parse_display::Display, PartialEq)]
pub enum State {
#[display("uninitialized")]
Uninitialized {
root_dir: lsp_types::Url,
},
#[display("waiting initialization")]
WaitingInitialization {
messages: Vec<ClientMessage>,
},
#[display("running")]
Running {
server_state: Box<lsp_types::InitializeResult>,
registrations: Vec<lsp_types::Registration>,
},
#[display("waiting shutdown")]
WaitingShutdown,
#[display("waiting exit")]
WaitingExit,
}
pub(crate) struct Translator {
client: Client,
state: State,
}
impl Translator {
const fn new(client: Client, root_dir: lsp_types::Url) -> Self {
Self {
client,
state: State::Uninitialized { root_dir },
}
}
#[throws(TranslationError)]
fn send_message(&mut self, message: ClientMessage) {
self.process(Event::SendMessage(message))?
}
#[throws(TranslationError)]
fn translate(&mut self) -> Option<ServerStatement> {
match self.client.consume() {
Ok(message) => {
match message {
lsp::ServerMessage::Request { id, request } => match request {
lsp::ServerRequest::RegisterCapability(registration) => {
self.process(Event::RegisterCapability(id, registration))?;
}
},
lsp::ServerMessage::Response(response) => match response {
lsp::ServerResponse::Initialize(initialize) => {
self.process(Event::Initialized(initialize))?;
}
lsp::ServerResponse::Shutdown => {
self.process(Event::CompletedShutdown)?;
}
},
lsp::ServerMessage::Notification(notification) => match notification {
lsp::ServerNotification::PublishDiagnostics(_diagnostics) => {
}
},
}
}
Err(failure) => {
if let market::ConsumeFailure::Fault(fault) = failure {
throw!(TranslationError::from(fault));
}
}
}
None
}
#[throws(TranslationError)]
fn process(&mut self, event: Event) {
match self.state {
State::Uninitialized { ref root_dir } => match event {
Event::SendMessage(message) => {
self.client.initialize(root_dir)?;
self.state = State::WaitingInitialization {
messages: vec![message],
}
}
Event::Initialized(_)
| Event::CompletedShutdown
| Event::RegisterCapability(..) => {
throw!(TranslationError::InvalidState(
Box::new(event),
self.state.clone()
));
}
Event::Exit => {
self.client
.produce(ClientMessage::Notification(ClientNotification::Exit))?;
self.state = State::WaitingExit
}
},
State::WaitingInitialization { ref messages } => match event {
Event::SendMessage(message) => {
let mut new_messages = messages.clone();
new_messages.push(message);
self.state = State::WaitingInitialization {
messages: new_messages,
}
}
Event::Initialized(server_state) => {
self.client
.produce(ClientMessage::Notification(ClientNotification::Initialized))?;
for message in messages {
self.client.produce(message.clone())?;
}
self.state = State::Running {
server_state: Box::new(server_state),
registrations: Vec::new(),
}
}
Event::CompletedShutdown | Event::RegisterCapability(..) => {
throw!(TranslationError::InvalidState(
Box::new(event),
self.state.clone()
));
}
Event::Exit => {
}
},
State::Running {
ref server_state,
ref registrations,
} => match event {
Event::SendMessage(message) => {
self.client.produce(message)?;
}
Event::RegisterCapability(id, mut register) => {
let mut new_registrations = registrations.clone();
new_registrations.append(&mut register.registrations);
self.state = State::Running {
server_state: server_state.clone(),
registrations: new_registrations,
};
self.client.produce(ClientMessage::Response {
id,
response: ClientResponse::RegisterCapability,
})?;
}
Event::Initialized(_) | Event::CompletedShutdown => {
throw!(TranslationError::InvalidState(
Box::new(event),
self.state.clone()
));
}
Event::Exit => {
self.client
.produce(ClientMessage::Request(ClientRequest::Shutdown))?;
self.state = State::WaitingShutdown;
}
},
State::WaitingShutdown => match event {
Event::SendMessage(_)
| Event::Initialized(_)
| Event::Exit
| Event::RegisterCapability(..) => {
throw!(TranslationError::InvalidState(
Box::new(event),
self.state.clone()
));
}
Event::CompletedShutdown => {
self.client
.produce(ClientMessage::Notification(ClientNotification::Exit))?;
self.state = State::WaitingExit;
}
},
State::WaitingExit => self.while_waiting_exit(event)?,
}
}
#[throws(TranslationError)]
fn while_waiting_exit(&self, event: Event) {
if event != Event::Exit {
throw!(TranslationError::InvalidState(
Box::new(event),
self.state.clone(),
));
}
}
fn log_errors(&self) {
match self.client.stderr().consume_all() {
Ok(messages) => {
for message in messages {
error!("lsp stderr: {}", message);
}
}
Err(error) => {
error!("error logger: {}", error);
}
}
}
#[throws(TranslationError)]
fn shutdown(&mut self) {
self.process(Event::Exit)?;
}
const fn waiter(&self) -> &market::process::Waiter<lsp::Message, lsp::Message, ErrorMessage> {
self.client.waiter()
}
}
pub(crate) struct Client {
server: market::process::Process<lsp::Message, lsp::Message, ErrorMessage>,
next_id: Cell<u64>,
}
impl Client {
#[throws(CreateClientError)]
fn new(command: Command) -> Self {
Self {
server: market::process::Process::new(command)?,
next_id: Cell::new(1),
}
}
#[throws(TranslationError)]
fn initialize(&self, root_dir: &lsp_types::Url) {
#[allow(deprecated)]
self.produce(ClientMessage::Request(ClientRequest::Initialize(
lsp_types::InitializeParams {
process_id: Some(u64::from(process::id())),
root_path: None,
root_uri: Some(root_dir.clone()),
initialization_options: None,
capabilities: lsp_types::ClientCapabilities {
workspace: None,
text_document: Some(lsp_types::TextDocumentClientCapabilities {
synchronization: Some(lsp_types::SynchronizationCapability {
dynamic_registration: None,
will_save: None,
will_save_wait_until: None,
did_save: None,
}),
completion: None,
hover: None,
signature_help: None,
references: None,
document_highlight: None,
document_symbol: None,
formatting: None,
range_formatting: None,
on_type_formatting: None,
declaration: None,
definition: None,
type_definition: None,
implementation: None,
code_action: None,
code_lens: None,
document_link: None,
color_provider: None,
rename: None,
publish_diagnostics: None,
folding_range: None,
}),
window: None,
experimental: None,
},
trace: None,
workspace_folders: None,
client_info: None,
},
)))?;
}
const fn waiter(&self) -> &market::process::Waiter<lsp::Message, lsp::Message, ErrorMessage> {
self.server.waiter()
}
const fn stderr(&self) -> &Rc<market::io::Reader<ErrorMessage>> {
self.server.stderr()
}
fn next_id(&self) -> json_rpc::Id {
let id = self.next_id.get();
self.next_id.set(id.wrapping_add(1));
json_rpc::Id::Num(serde_json::Number::from(id))
}
}
impl market::Consumer for Client {
type Good = lsp::ServerMessage;
type Failure = market::ConsumeFailure<ConsumeServerMessageError>;
#[throws(Self::Failure)]
fn consume(&self) -> Self::Good {
let good = self
.server
.consume()
.map_err(market::ConsumeFailure::map_from)?
.try_into()
.map_err(|failure| {
market::ConsumeFailure::Fault(market::Fault::<Self::Failure>::from(failure))
})?;
trace!("LSP Rx: {}", good);
good
}
}
impl market::Producer for Client {
type Good = ClientMessage;
type Failure = market::ProduceFailure<market::io::WriteError<lsp::Message>>;
#[throws(Self::Failure)]
fn produce(&self, good: Self::Good) {
trace!("LSP Tx: {}", good);
let message = lsp::Message::from(match good {
ClientMessage::Response { id, response } => json_rpc::Object::response(id, &response),
ClientMessage::Request(request) => json_rpc::Object::request(self.next_id(), &request),
ClientMessage::Notification(notification) => {
json_rpc::Object::notification(¬ification)
}
});
self.server
.produce(message)
.map_err(market::ProduceFailure::map_into)?
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ClientMessage {
Request(ClientRequest),
Response {
id: json_rpc::Id,
response: ClientResponse,
},
Notification(ClientNotification),
}
impl Display for ClientMessage {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match *self {
Self::Request(ref request) => format!("{}: {}", "Request", request),
Self::Response { ref response, .. } => format!("{}: {}", "Response", response),
Self::Notification(ref notification) =>
format!("{}: {}", "Notification", notification),
}
)
}
}
#[derive(Clone, Debug, parse_display::Display, PartialEq)]
pub enum ClientRequest {
#[display("Initialize w/ {0:?}")]
Initialize(lsp_types::InitializeParams),
Shutdown,
}
impl json_rpc::Method for ClientRequest {
fn method(&self) -> String {
match *self {
Self::Initialize(_) => "initialize",
Self::Shutdown => "shutdown",
}
.to_string()
}
fn params(&self) -> json_rpc::Params {
match *self {
Self::Initialize(ref params) => {
#[allow(clippy::expect_used)]
json_rpc::Params::from(
serde_json::to_value(params)
.expect("Converting InitializeParams to JSON Value"),
)
}
Self::Shutdown => json_rpc::Params::None,
}
}
}
#[derive(Clone, Debug, parse_display::Display, PartialEq)]
pub enum ClientNotification {
Initialized,
Exit,
#[display("OpenDoc w/ {0:?}")]
OpenDoc(lsp_types::DidOpenTextDocumentParams),
#[display("CloseDoc w/ {0:?}")]
CloseDoc(lsp_types::DidCloseTextDocumentParams),
}
impl json_rpc::Method for ClientNotification {
fn method(&self) -> String {
match *self {
Self::Initialized => "initialized",
Self::Exit => "exit",
Self::OpenDoc(_) => "textDocument/didOpen",
Self::CloseDoc(_) => "textDocument/didClose",
}
.to_string()
}
fn params(&self) -> json_rpc::Params {
match *self {
Self::Initialized | Self::Exit => json_rpc::Params::None,
Self::OpenDoc(ref params) => {
#[allow(clippy::expect_used)]
json_rpc::Params::from(
serde_json::to_value(params)
.expect("Converting DidOpenTextDocumentParams to JSON Value"),
)
}
Self::CloseDoc(ref params) => {
#[allow(clippy::expect_used)]
json_rpc::Params::from(
serde_json::to_value(params)
.expect("Converting DidCloseTextDocumentParams to JSON Value"),
)
}
}
}
}
#[derive(Clone, Copy, Debug, parse_display::Display, PartialEq)]
pub enum ClientResponse {
RegisterCapability,
}
impl json_rpc::Success for ClientResponse {
fn result(&self) -> serde_json::Value {
match *self {
Self::RegisterCapability => serde_json::Value::Null,
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum CreateClientError {
#[error(transparent)]
CreateProcess(#[from] market::process::CreateProcessError),
}
#[derive(market::ConsumeFault, Debug, thiserror::Error)]
pub enum ConsumeServerMessageError {
#[error(transparent)]
Consume(#[from] market::io::ReadFault<lsp::Message>),
#[error(transparent)]
UnknownServerMessage(#[from] lsp::UnknownServerMessageFailure),
}