use std::{env, fs, io, mem::ManuallyDrop, panic, path::Path, time::Duration};
use crate::{
client::ClientManager,
editor::{Editor, EditorContext, EditorControlFlow},
editor_utils::{load_config, MessageKind},
events::{ClientEvent, ClientEventReceiver, ServerEvent, TargetClient},
platform::{Key, Platform, PlatformEvent, PlatformRequest, ProcessTag},
plugin::{PluginCollection, PluginDefinition},
serialization::{DeserializeError, Serialize},
ui, Args, ResourceFile,
};
#[derive(Default, Clone, Copy)]
pub struct OnPanicConfig {
pub write_info_to_file: Option<&'static Path>,
pub try_attaching_debugger: bool,
}
pub struct ApplicationConfig {
pub args: Args,
pub plugin_definitions: Vec<PluginDefinition>,
pub static_configs: Vec<ResourceFile>,
pub on_panic_config: OnPanicConfig,
}
impl Default for ApplicationConfig {
fn default() -> Self {
Self {
args: Args::parse(),
static_configs: vec![
crate::DEFAULT_BINDINGS_CONFIG,
crate::DEFAULT_ALIASES_CONFIG,
crate::DEFAULT_SYNTAXES_CONFIG,
crate::DEFAULT_PLATFORM_CONFIG,
],
plugin_definitions: Vec::new(),
on_panic_config: OnPanicConfig::default(),
}
}
}
pub(crate) struct ServerApplication {
pub ctx: EditorContext,
client_event_receiver: ClientEventReceiver,
}
impl ServerApplication {
pub const fn connection_buffer_len() -> usize {
512
}
pub const fn idle_duration() -> Duration {
Duration::from_secs(1)
}
pub fn new(config: ApplicationConfig) -> Option<Self> {
let current_dir = env::current_dir().expect("could not retrieve the current directory");
let mut ctx = EditorContext {
editor: Editor::new(current_dir),
platform: Platform::default(),
clients: ClientManager::default(),
plugins: PluginCollection::default(),
};
for definition in config.plugin_definitions {
PluginCollection::add(&mut ctx, definition);
}
for config in &config.static_configs {
match load_config(&mut ctx, config.name, config.content) {
EditorControlFlow::Continue => (),
_ => return None,
};
}
for config in config.args.configs {
let path = Path::new(&config.path);
if config.suppress_file_not_found && !path.exists() {
continue;
}
match fs::read_to_string(path) {
Ok(source) => match load_config(&mut ctx, &config.path, &source) {
EditorControlFlow::Continue => (),
_ => return None,
},
Err(_) => ctx
.editor
.status_bar
.write(MessageKind::Error)
.fmt(format_args!("could not load config '{}'", config.path)),
}
}
Some(Self {
ctx,
client_event_receiver: ClientEventReceiver::default(),
})
}
pub fn update<I>(&mut self, events: I)
where
I: Iterator<Item = PlatformEvent>,
{
for event in events {
match event {
PlatformEvent::Idle => {
self.ctx.editor.on_idle();
self.ctx.trigger_event_handlers();
}
PlatformEvent::ConnectionOpen { handle } => {
self.ctx.clients.on_client_joined(handle)
}
PlatformEvent::ConnectionClose { handle } => {
self.ctx.clients.on_client_left(handle);
if self.ctx.clients.iter().next().is_none() {
self.ctx.platform.requests.enqueue(PlatformRequest::Quit);
break;
}
}
PlatformEvent::ConnectionOutput { handle, buf } => {
let mut events = self
.client_event_receiver
.receive_events(handle, buf.as_bytes());
self.ctx.platform.buf_pool.release(buf);
while let Some(event) = events.next(&self.client_event_receiver) {
match Editor::on_client_event(&mut self.ctx, handle, event) {
EditorControlFlow::Continue => (),
EditorControlFlow::Suspend => {
let mut buf = self.ctx.platform.buf_pool.acquire();
ServerEvent::Suspend.serialize(buf.write());
self.ctx
.platform
.requests
.enqueue(PlatformRequest::WriteToClient { handle, buf });
}
EditorControlFlow::Quit => {
self.ctx
.platform
.requests
.enqueue(PlatformRequest::CloseClient { handle });
break;
}
EditorControlFlow::QuitAll => {
self.ctx.platform.requests.enqueue(PlatformRequest::Quit);
break;
}
}
}
events.finish(&mut self.client_event_receiver);
}
PlatformEvent::ProcessSpawned { tag, handle } => {
match tag {
ProcessTag::Buffer(index) => self.ctx.editor.buffers.on_process_spawned(
&mut self.ctx.platform,
index,
handle,
),
ProcessTag::FindFiles => (),
ProcessTag::FindPattern => (),
ProcessTag::Plugin { plugin_handle, id } => {
PluginCollection::on_process_spawned(
&mut self.ctx,
plugin_handle,
id,
handle,
)
}
}
self.ctx.trigger_event_handlers();
}
PlatformEvent::ProcessOutput { tag, buf } => {
let bytes = buf.as_bytes();
match tag {
ProcessTag::Buffer(index) => self.ctx.editor.buffers.on_process_output(
&mut self.ctx.editor.word_database,
index,
bytes,
&mut self.ctx.editor.events,
),
ProcessTag::FindFiles => {
self.ctx.editor.mode.picker_state.on_process_output(
&mut self.ctx.editor.picker,
&self.ctx.editor.read_line,
bytes,
)
}
ProcessTag::FindPattern => {
self.ctx.editor.mode.read_line_state.on_process_output(
&mut self.ctx.editor.buffers,
&mut self.ctx.editor.word_database,
bytes,
&mut self.ctx.editor.events,
)
}
ProcessTag::Plugin { plugin_handle, id } => {
PluginCollection::on_process_output(
&mut self.ctx,
plugin_handle,
id,
bytes,
)
}
}
self.ctx.trigger_event_handlers();
self.ctx.platform.buf_pool.release(buf);
}
PlatformEvent::ProcessExit { tag } => {
match tag {
ProcessTag::Buffer(index) => self.ctx.editor.buffers.on_process_exit(
&mut self.ctx.editor.word_database,
index,
&mut self.ctx.editor.events,
),
ProcessTag::FindFiles => self.ctx.editor.mode.picker_state.on_process_exit(
&mut self.ctx.editor.picker,
&self.ctx.editor.read_line,
),
ProcessTag::FindPattern => {
self.ctx.editor.mode.read_line_state.on_process_exit(
&mut self.ctx.editor.buffers,
&mut self.ctx.editor.word_database,
&mut self.ctx.editor.events,
)
}
ProcessTag::Plugin { plugin_handle, id } => {
PluginCollection::on_process_exit(&mut self.ctx, plugin_handle, id)
}
}
self.ctx.trigger_event_handlers();
}
}
}
self.ctx.render();
}
}
pub(crate) struct ClientApplication {
target_client: TargetClient,
server_read_buf: Vec<u8>,
server_write_buf: Vec<u8>,
output: Option<ManuallyDrop<fs::File>>,
stdout_buf: Vec<u8>,
}
impl ClientApplication {
pub const fn stdin_buffer_len() -> usize {
4 * 1024
}
pub const fn connection_buffer_len() -> usize {
48 * 1024
}
pub fn new(output: Option<ManuallyDrop<fs::File>>) -> Self {
Self {
target_client: TargetClient::Sender,
server_read_buf: Vec::new(),
server_write_buf: Vec::new(),
output,
stdout_buf: Vec::new(),
}
}
pub fn init(&mut self, args: Args) -> &[u8] {
if args.as_focused_client {
self.target_client = TargetClient::Focused;
}
self.server_write_buf.clear();
self.reinit_screen();
if !args.quit && !args.as_focused_client {
ClientEvent::Key(self.target_client, Key::None).serialize(&mut self.server_write_buf);
}
let mut commands = String::new();
for path in &args.files {
commands.clear();
commands.push_str("open \"");
commands.push_str(path);
commands.push('"');
ClientEvent::Command(self.target_client, &commands)
.serialize(&mut self.server_write_buf);
}
if args.quit {
ClientEvent::Command(TargetClient::Sender, "quit")
.serialize(&mut self.server_write_buf);
}
self.server_write_buf.as_slice()
}
pub fn reinit_screen(&mut self) {
use io::Write;
if let Some(output) = &mut self.output {
let _ = output.write_all(ui::ENTER_ALTERNATE_BUFFER_CODE);
let _ = output.write_all(ui::HIDE_CURSOR_CODE);
let _ = output.write_all(ui::MODE_256_COLORS_CODE);
output.flush().unwrap();
}
}
pub fn restore_screen(&mut self) {
use io::Write;
if let Some(output) = &mut self.output {
let _ = output.write_all(ui::EXIT_ALTERNATE_BUFFER_CODE);
let _ = output.write_all(ui::SHOW_CURSOR_CODE);
let _ = output.write_all(ui::RESET_STYLE_CODE);
let _ = output.flush();
}
}
pub fn update<'a>(
&'a mut self,
resize: Option<(usize, usize)>,
keys: &[Key],
stdin_bytes: Option<&[u8]>,
server_bytes: &[u8],
) -> (bool, &'a [u8]) {
use io::Write;
self.server_write_buf.clear();
if let Some((width, height)) = resize {
ClientEvent::Resize(width as _, height as _).serialize(&mut self.server_write_buf);
}
for key in keys {
ClientEvent::Key(self.target_client, *key).serialize(&mut self.server_write_buf);
}
if let Some(bytes) = stdin_bytes {
ClientEvent::StdinInput(self.target_client, bytes)
.serialize(&mut self.server_write_buf);
}
let mut suspend = false;
if !server_bytes.is_empty() {
self.server_read_buf.extend_from_slice(server_bytes);
let mut read_slice = &self.server_read_buf[..];
loop {
let previous_slice = read_slice;
match ServerEvent::deserialize(&mut read_slice) {
Ok(ServerEvent::Display(display)) => {
if let Some(output) = &mut self.output {
output.write_all(display).unwrap();
}
}
Ok(ServerEvent::Suspend) => suspend = true,
Ok(ServerEvent::StdoutOutput(bytes)) => {
self.stdout_buf.clear();
self.stdout_buf.extend_from_slice(bytes);
}
Err(DeserializeError::InsufficientData) => {
let read_len = self.server_read_buf.len() - previous_slice.len();
self.server_read_buf.drain(..read_len);
break;
}
Err(DeserializeError::InvalidData) => {
panic!("client received invalid data from server")
}
}
}
if let Some(output) = &mut self.output {
output.flush().unwrap();
}
}
(suspend, self.server_write_buf.as_slice())
}
pub fn get_stdout_bytes(&self) -> &[u8] {
&self.stdout_buf
}
}
impl Drop for ClientApplication {
fn drop(&mut self) {
self.restore_screen();
}
}