pub mod io_pool;
use crate::io_pool::IoPool;
use anyhow::Result;
use crossbeam_channel::select;
use eure::query::{Glob, TextFile};
use eure_ls::{CoreRequestId, Effect, LspCore, LspOutput};
use lsp_server::{Connection, Message, Notification, Request, Response};
use tracing::{error, info};
fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::from_default_env()
.add_directive("eure_ls=info".parse().unwrap()),
)
.with_writer(std::io::stderr)
.init();
info!("Starting Eure Language Server");
let (connection, io_threads) = Connection::stdio();
let server_capabilities = serde_json::to_value(eure_ls::server_capabilities())?;
let init_params = connection.initialize(server_capabilities)?;
let init_params: lsp_types::InitializeParams = serde_json::from_value(init_params)?;
info!("Eure Language Server initialized");
let io_pool = IoPool::new(4);
let mut core = LspCore::new();
eure_ls::register_workspaces_from_init(core.runtime_mut(), &init_params);
core.configure_client(&init_params.capabilities);
core.set_initialized();
loop {
select! {
recv(connection.receiver) -> msg => {
match msg {
Ok(Message::Request(req)) => {
if connection.handle_shutdown(&req)? {
info!("Shutdown requested");
break;
}
handle_request(req, &connection, &mut core, &io_pool);
}
Ok(Message::Notification(not)) => {
handle_notification(not, &connection, &mut core, &io_pool);
}
Ok(Message::Response(_)) => {
}
Err(e) => {
error!("Error receiving message: {}", e);
break;
}
}
}
recv(io_pool.receiver()) -> response => {
if let Ok(response) = response {
let result = match response.result {
Ok(content) => Ok(content.0),
Err(e) => Err(e.to_string()),
};
let (outputs, effects) = core.resolve_file(response.file, result);
send_outputs(&connection, outputs);
process_effects(&io_pool, &mut core, effects);
}
}
}
}
io_threads.join()?;
info!("Eure Language Server stopped");
Ok(())
}
fn handle_request(req: Request, connection: &Connection, core: &mut LspCore, io_pool: &IoPool) {
let id = CoreRequestId::from(req.id.clone());
let (outputs, effects) = core.handle_request(id, &req.method, req.params);
send_outputs(connection, outputs);
process_effects(io_pool, core, effects);
}
fn handle_notification(
not: Notification,
connection: &Connection,
core: &mut LspCore,
io_pool: &IoPool,
) {
let (outputs, effects) = core.handle_notification(¬.method, not.params);
send_outputs(connection, outputs);
process_effects(io_pool, core, effects);
}
fn send_outputs(connection: &Connection, outputs: Vec<LspOutput>) {
for output in outputs {
let msg = match output {
LspOutput::Response { id, result } => {
let response = match result {
Ok(value) => Response::new_ok(lsp_request_id(&id), value),
Err(err) => Response::new_err(lsp_request_id(&id), err.code, err.message),
};
Message::Response(response)
}
LspOutput::Notification { method, params } => {
Message::Notification(Notification::new(method, params))
}
};
if let Err(e) = connection.sender.send(msg) {
error!("Failed to send message: {}", e);
}
}
}
fn process_effects(io_pool: &IoPool, core: &mut LspCore, effects: Vec<Effect>) {
for effect in effects {
match effect {
Effect::FetchFile(file) => {
io_pool.request_file(file);
}
Effect::ExpandGlob { id, glob } => {
let files = expand_glob(&glob);
let (outputs, new_effects) = core.resolve_glob(&id, files);
drop(outputs);
if !new_effects.is_empty() {
process_effects(io_pool, core, new_effects);
}
}
}
}
}
fn expand_glob(glob: &Glob) -> Vec<TextFile> {
let pattern = glob.full_pattern();
let pattern_str = pattern.to_string_lossy();
glob::glob(&pattern_str)
.into_iter()
.flat_map(|paths| paths.flatten().map(TextFile::from_path))
.collect()
}
fn lsp_request_id(id: &CoreRequestId) -> lsp_server::RequestId {
if let Ok(n) = id.as_str().parse::<i32>() {
lsp_server::RequestId::from(n)
} else {
lsp_server::RequestId::from(id.as_str().to_string())
}
}