#![allow(clippy::unreachable)]
#![allow(clippy::pattern_type_mismatch)]
mod json_rpc;
mod lsp;
use {
conventus::{AssembleFailure, AssembleFrom},
core::{
cell::{Cell, RefCell},
convert::TryInto,
},
enum_map::{enum_map, Enum},
fehler::{throw, throws},
json_rpc::{Id, Method, Object, Params, Success},
log::{error, trace},
lsp::{
Message, ServerMessage, ServerNotification, ServerRequest, ServerResponse,
UnknownServerMessageFailure,
},
lsp_types::{
ClientCapabilities, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
InitializeParams, InitializeResult, Registration, RegistrationParams,
SynchronizationCapability, TextDocumentClientCapabilities, TextDocumentIdentifier,
TextDocumentItem, Url,
},
market::{
io::{ReadError, Reader, WriteError},
process::{CreateProcessError, Process, WaitProcessError, Waiter},
ConsumeFailure, Consumer, Never, PermanentQueue, ProduceFailure, Producer,
},
parse_display::Display as ParseDisplay,
serde_json::{Number, Value},
std::{
fmt::{self, Display},
process::{self, Command, ExitStatus},
rc::Rc,
sync::Arc,
thread::{self, JoinHandle},
},
thiserror::Error as ThisError,
};
#[derive(Clone, Copy, Debug, Enum, ParseDisplay, PartialEq)]
#[display(style = "lowercase")]
pub enum Language {
Rust,
Plaintext,
}
#[derive(Debug)]
pub struct Tongue {
thread: JoinHandle<()>,
joiner: Arc<market::sync::Trigger>,
outputs: Arc<PermanentQueue<ClientStatement>>,
inputs: Arc<PermanentQueue<ServerStatement>>,
statuses: Arc<PermanentQueue<ExitStatus>>,
}
impl Tongue {
#[inline]
#[must_use]
pub fn new(root_dir: &Url) -> Self {
let joiner = Arc::new(market::sync::Trigger::new());
let shared_joiner = Arc::clone(&joiner);
let dir = root_dir.clone();
let outputs = Arc::new(PermanentQueue::new());
let shared_outputs = Arc::clone(&outputs);
let inputs = Arc::new(PermanentQueue::new());
let shared_inputs = Arc::clone(&inputs);
let statuses = Arc::new(PermanentQueue::new());
let shared_statuses = Arc::clone(&statuses);
Self {
joiner,
thread: thread::spawn(move || {
if let Err(error) = Self::thread(
&dir,
&shared_joiner,
&shared_outputs,
&shared_inputs,
&shared_statuses,
) {
error!("tongue thread error: {}", error);
}
}),
outputs,
inputs,
statuses,
}
}
#[throws(TranslationError)]
fn thread(
root_dir: &Url,
joiner: &Arc<market::sync::Trigger>,
outputs: &Arc<PermanentQueue<ClientStatement>>,
inputs: &Arc<PermanentQueue<ServerStatement>>,
statuses: &Arc<PermanentQueue<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! {
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 = joiner.is_triggered();
for output in outputs.consume_all()? {
#[allow(clippy::indexing_slicing)]
translators[output.language()]
.borrow_mut()
.send_message(output.into())?;
}
for (_, translator) in &translators {
if let Some(input) = translator.borrow_mut().translate()? {
inputs.produce(input)?;
}
translator.borrow().log_errors();
if will_shutdown {
translator.borrow_mut().shutdown()?;
}
}
}
for (_, translator) in translators {
statuses.produce(translator.borrow().waiter().demand()?)?;
}
}
#[inline]
pub fn join(&self) {
self.joiner.trigger();
}
}
impl Consumer for Tongue {
type Good = ServerStatement;
type Fault = <PermanentQueue<ServerStatement> as Consumer>::Fault;
#[inline]
#[throws(ConsumeFailure<Self::Fault>)]
fn consume(&self) -> Self::Good {
self.inputs.consume()?
}
}
impl Producer for Tongue {
type Good = ClientStatement;
type Fault = <PermanentQueue<ClientStatement> as Producer>::Fault;
#[inline]
#[throws(ProduceFailure<Self::Fault>)]
fn produce(&self, good: Self::Good) {
self.outputs.produce(good)?
}
}
#[derive(Clone, Copy, Debug, ParseDisplay)]
#[display(style = "CamelCase")]
pub enum ServerStatement {
Exit,
}
#[derive(Debug, ParseDisplay)]
#[display("")]
pub enum ClientStatement {
OpenDoc {
doc: TextDocumentItem,
},
CloseDoc {
doc: TextDocumentIdentifier,
},
}
impl ClientStatement {
#[inline]
#[must_use]
pub const fn open_doc(doc: TextDocumentItem) -> Self {
Self::OpenDoc { doc }
}
#[inline]
#[must_use]
pub const fn close_doc(doc: 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(DidOpenTextDocumentParams {
text_document: doc,
}))
}
ClientStatement::CloseDoc { doc } => {
Self::Notification(ClientNotification::CloseDoc(DidCloseTextDocumentParams {
text_document: doc,
}))
}
}
}
}
#[derive(Clone, Debug)]
pub struct ErrorMessage {
line: String,
}
#[derive(Clone, Copy, Debug, ThisError)]
#[error("Error while composing error message")]
pub struct ErrorMessageCompositionError;
impl AssembleFrom<u8> for ErrorMessage {
type Error = ErrorMessageCompositionError;
#[inline]
#[throws(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!(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)]
pub enum TranslationError {
#[error(transparent)]
Transmission(#[from] ProduceFailure<WriteError<Message>>),
#[error(transparent)]
Reception(#[from] ConsumeServerMessageError),
#[error(transparent)]
CreateClient(#[from] CreateClientError),
#[error(transparent)]
Wait(#[from] WaitProcessError),
#[error(transparent)]
CollectOutputs(#[from] ConsumeFailure<<PermanentQueue<ClientMessage> as Consumer>::Fault>),
#[error("Invalid state: Cannot {0} while {1}")]
InvalidState(Box<Event>, State),
#[error(transparent)]
Storage(#[from] ProduceFailure<Never>),
#[error(transparent)]
Never(#[from] Never),
}
#[derive(Debug, ParseDisplay, PartialEq)]
pub enum Event {
#[display("send message")]
SendMessage(ClientMessage),
#[display("process initialization")]
Initialized(InitializeResult),
#[display("register capability")]
RegisterCapability(Id, RegistrationParams),
#[display("complete shutdown")]
CompletedShutdown,
#[display("exit")]
Exit,
}
#[derive(Clone, Debug, ParseDisplay, PartialEq)]
pub enum State {
#[display("uninitialized")]
Uninitialized {
root_dir: Url,
},
#[display("waiting initialization")]
WaitingInitialization {
messages: Vec<ClientMessage>,
},
#[display("running")]
Running {
server_state: Box<InitializeResult>,
registrations: Vec<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: 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 {
ServerMessage::Request { id, request } => match request {
ServerRequest::RegisterCapability(registration) => {
self.process(Event::RegisterCapability(id, registration))?;
}
},
ServerMessage::Response(response) => match response {
ServerResponse::Initialize(initialize) => {
self.process(Event::Initialized(initialize))?;
}
ServerResponse::Shutdown => {
self.process(Event::CompletedShutdown)?;
}
},
ServerMessage::Notification(notification) => match notification {
ServerNotification::PublishDiagnostics(_diagnostics) => {
}
},
}
}
Err(failure) => {
if let 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) -> &Waiter {
self.client.waiter()
}
}
pub(crate) struct Client {
server: Process<Message, Message, ErrorMessage>,
next_id: Cell<u64>,
}
impl Client {
#[throws(CreateClientError)]
fn new(command: Command) -> Self {
Self {
server: Process::new(command)?,
next_id: Cell::new(1),
}
}
#[throws(TranslationError)]
fn initialize(&self, root_dir: &Url) {
#[allow(deprecated)]
self.produce(ClientMessage::Request(ClientRequest::Initialize(
InitializeParams {
process_id: Some(u64::from(process::id())),
root_path: None,
root_uri: Some(root_dir.clone()),
initialization_options: None,
capabilities: ClientCapabilities {
workspace: None,
text_document: Some(TextDocumentClientCapabilities {
synchronization: Some(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) -> &Waiter {
self.server.waiter()
}
const fn stderr(&self) -> &Reader<ErrorMessage> {
self.server.stderr()
}
fn next_id(&self) -> Id {
let id = self.next_id.get();
self.next_id.set(id.wrapping_add(1));
Id::Num(Number::from(id))
}
}
impl Consumer for Client {
type Good = ServerMessage;
type Fault = ConsumeServerMessageError;
#[throws(ConsumeFailure<Self::Fault>)]
fn consume(&self) -> Self::Good {
let good = self
.server
.consume()
.map_err(ConsumeFailure::map_into)?
.try_into()
.map_err(|failure| ConsumeFailure::Fault(Self::Fault::from(failure)))?;
trace!("LSP Rx: {}", good);
good
}
}
impl Producer for Client {
type Good = ClientMessage;
type Fault = WriteError<Message>;
#[throws(ProduceFailure<Self::Fault>)]
fn produce(&self, good: Self::Good) {
trace!("LSP Tx: {}", good);
let message = Message::from(match good {
ClientMessage::Response { id, response } => Object::response(id, &response),
ClientMessage::Request(request) => Object::request(self.next_id(), &request),
ClientMessage::Notification(notification) => Object::notification(¬ification),
});
self.server
.produce(message)
.map_err(ProduceFailure::map_into)?
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ClientMessage {
Request(ClientRequest),
Response {
id: 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, ParseDisplay, PartialEq)]
pub enum ClientRequest {
#[display("Initialize w/ {0:?}")]
Initialize(InitializeParams),
Shutdown,
}
impl Method for ClientRequest {
fn method(&self) -> String {
match *self {
Self::Initialize(_) => "initialize",
Self::Shutdown => "shutdown",
}
.to_string()
}
fn params(&self) -> Params {
match *self {
Self::Initialize(ref params) => {
#[allow(clippy::expect_used)]
Params::from(
serde_json::to_value(params)
.expect("Converting InitializeParams to JSON Value"),
)
}
Self::Shutdown => Params::None,
}
}
}
#[derive(Clone, Debug, ParseDisplay, PartialEq)]
pub enum ClientNotification {
Initialized,
Exit,
#[display("OpenDoc w/ {0:?}")]
OpenDoc(DidOpenTextDocumentParams),
#[display("CloseDoc w/ {0:?}")]
CloseDoc(DidCloseTextDocumentParams),
}
impl 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) -> Params {
match *self {
Self::Initialized | Self::Exit => Params::None,
Self::OpenDoc(ref params) => {
#[allow(clippy::expect_used)]
Params::from(
serde_json::to_value(params)
.expect("Converting DidOpenTextDocumentParams to JSON Value"),
)
}
Self::CloseDoc(ref params) => {
#[allow(clippy::expect_used)]
Params::from(
serde_json::to_value(params)
.expect("Converting DidCloseTextDocumentParams to JSON Value"),
)
}
}
}
}
#[derive(Clone, Copy, Debug, ParseDisplay, PartialEq)]
pub enum ClientResponse {
RegisterCapability,
}
impl Success for ClientResponse {
fn result(&self) -> Value {
match *self {
Self::RegisterCapability => Value::Null,
}
}
}
#[derive(Debug, ThisError)]
pub enum CreateClientError {
#[error(transparent)]
CreateProcess(#[from] CreateProcessError),
}
#[derive(Debug, ThisError)]
pub enum ConsumeServerMessageError {
#[error(transparent)]
Consume(#[from] ReadError<Message>),
#[error(transparent)]
UnknownServerMessage(#[from] UnknownServerMessageFailure),
}