use std::{
path::PathBuf,
sync::mpsc::{Receiver, Sender, channel},
thread::{self, JoinHandle},
time::Instant,
};
use kak_tree_sitter_config::Config;
use crate::{
error::OhNo,
kakoune::{selection::Sel, session::Session, text_objects::OperationMode},
protocol::{
request::Metadata,
response::{EnabledLang, Payload, Response},
},
tree_sitter::{languages::Languages, nav, state::Trees},
};
use super::triple_buffer::TripleBufferReader;
#[derive(Debug)]
pub enum Command {
SessionInit { metadata: Metadata },
SessionEnd { metadata: Metadata },
Shutdown,
BufferMetadata {
metadata: Metadata,
lang: String,
fifo_path: PathBuf,
sentinel: String,
},
BufferClose { metadata: Metadata },
BufferUpdate {
metadata: Metadata,
reader: TripleBufferReader,
},
TextObjects {
metadata: Metadata,
pattern: String,
selections: Vec<Sel>,
mode: OperationMode,
},
Nav {
metadata: Metadata,
selections: Vec<Sel>,
dir: nav::Dir,
},
Version { metadata: Metadata },
}
#[derive(Debug)]
pub struct CommandSender {
join_handle: Option<JoinHandle<()>>,
sender: Option<Sender<Command>>,
}
impl CommandSender {
pub fn send(&self, cmd: Command) -> Result<(), OhNo> {
self
.sender
.as_ref()
.unwrap()
.send(cmd)
.map_err(|_| OhNo::CannotSendCommand)
}
}
impl Drop for CommandSender {
fn drop(&mut self) {
self.sender = None;
if let Some(join_handle) = self.join_handle.take()
&& join_handle.join().is_err()
{
log::error!("handler not properly closed");
}
}
}
pub struct Handler {
config: Config,
trees: Trees,
langs: Languages,
with_highlighting: bool,
}
impl Handler {
pub fn create(config: &Config, with_highlighting: bool) -> CommandSender {
let (sender, cmds) = channel();
let config = config.clone();
let join_handle = thread::spawn(move || {
let langs = Languages::new(&config);
let handler = Self {
config,
trees: Trees::default(),
langs,
with_highlighting,
};
handler.start(cmds);
});
CommandSender {
join_handle: Some(join_handle),
sender: Some(sender),
}
}
fn start(mut self, cmds: Receiver<Command>) {
log::info!("handler started");
while let Ok(cmd) = cmds.recv() {
let resp = match cmd {
Command::SessionInit { metadata } => Ok(Some(self.handle_session_begin(metadata))),
Command::SessionEnd { metadata } => {
self.handle_session_end(metadata);
Ok(None)
}
Command::Shutdown => break,
Command::BufferMetadata {
metadata,
lang,
fifo_path,
sentinel,
} => self
.handle_buffer_metadata(metadata, lang, fifo_path, sentinel)
.map(Some),
Command::BufferClose { metadata } => self.handle_buffer_close(metadata).map(|_| None),
Command::BufferUpdate { metadata, reader } => {
self.handle_full_buffer_update(metadata, reader)
}
Command::TextObjects {
metadata,
pattern,
selections,
mode,
} => self
.handle_text_objects(metadata, pattern, selections, mode)
.map(Some),
Command::Nav {
metadata,
selections,
dir,
} => self.handle_nav(metadata, selections, dir).map(Some),
Command::Version { metadata } => Ok(Some(Response::from_req_metadata(
metadata,
Payload::Version(env!("VERSION").to_owned()),
))),
};
match resp {
Ok(Some(resp)) => {
self.respond_to_kak(resp);
}
Err(err) => {
log::error!("error in handler: {err}");
}
_ => (),
}
}
log::debug!("handler loop exiting");
}
fn respond_to_kak(&self, resp: Response) {
if let Err(err) = Session::send_response(resp) {
log::error!("sending response to Kakoune failed: {err}");
}
}
pub fn handle_session_begin(&mut self, metadata: Metadata) -> Response {
let enabled_langs = self
.config
.languages
.iter()
.map(|(name, lang)| EnabledLang {
name: name.to_owned(),
remove_default_highlighter: lang.remove_default_highlighter.into(),
filetype_hook: lang.filetype_hook.into(),
aliases: lang.aliases.clone(),
})
.collect();
Response::from_req_metadata(metadata, Payload::Init { enabled_langs })
}
pub fn handle_session_end(&mut self, metadata: Metadata) {
self.trees.clean_session(&metadata.session);
}
pub fn handle_buffer_metadata(
&mut self,
metadata: Metadata,
lang: String,
fifo_path: PathBuf,
sentinel: String,
) -> Result<Response, OhNo> {
let lang = self.langs.get(lang)?;
let id = metadata.to_buffer_id()?;
self.trees.compute(&self.langs, lang, &id)?;
let payload = Payload::BufferSetup {
fifo_path,
sentinel,
};
Ok(Response::from_req_metadata(metadata, payload))
}
pub fn handle_buffer_close(&mut self, metadata: Metadata) -> Result<(), OhNo> {
let id = metadata.to_buffer_id()?;
self.trees.delete_tree(&id);
Ok(())
}
pub fn handle_full_buffer_update(
&mut self,
metadata: Metadata,
reader: TripleBufferReader,
) -> Result<Option<Response>, OhNo> {
let id = metadata.to_buffer_id()?;
let tree = self.trees.get_tree_mut(&id)?;
let timer = Instant::now();
if !tree.update_buf(&self.langs, reader)? {
return Ok(None);
}
log::debug!(
"buffer tree {id:?} was recomputed in {}us",
timer.elapsed().as_micros()
);
if !self.with_highlighting {
return Ok(None);
}
let timer = Instant::now();
let ranges = tree.highlight(&self.langs)?;
log::debug!(
"highlights were recomputed in {}us",
timer.elapsed().as_micros()
);
if ranges.is_empty() {
log::debug!("no highlights to return");
return Ok(None);
}
let resp = Response::new(
id.session(),
None,
id.buffer().to_owned(),
Payload::Highlights {
faces: self.langs.faces().clone(),
ranges,
},
);
Ok(Some(resp))
}
pub fn handle_text_objects(
&mut self,
metadata: Metadata,
pattern: String,
selections: Vec<Sel>,
mode: OperationMode,
) -> Result<Response, OhNo> {
let id = metadata.to_buffer_id()?;
log::debug!("text-objects {pattern}, mode {mode:?} for buffer {id:?}");
let tree_state = self.trees.get_tree(&id)?;
let lang = self.langs.get(tree_state.lang_name())?;
let sels = tree_state.text_objects(lang, &pattern, &selections, &mode)?;
log::trace!("text-objects selections: {sels:?}");
Ok(Response::new(
metadata.session,
metadata.client,
None,
Payload::Selections { sels },
))
}
pub fn handle_nav(
&mut self,
metadata: Metadata,
selections: Vec<Sel>,
dir: nav::Dir,
) -> Result<Response, OhNo> {
let id = metadata.to_buffer_id()?;
log::debug!("nav {dir:?} for buffer {id:?}");
let tree_state = self.trees.get_tree(&id)?;
let sels = tree_state.tree_sitter_nav_tree(&selections, dir);
Ok(Response::new(
metadata.session,
metadata.client,
None,
Payload::Selections { sels },
))
}
}