diff --git a/data/src/buffer.rs b/data/src/buffer.rs index db494ee4..4859a6b8 100644 --- a/data/src/buffer.rs +++ b/data/src/buffer.rs @@ -6,34 +6,64 @@ use crate::user::Nick; use crate::{channel, config, message, Server}; #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(untagged)] pub enum Buffer { + Upstream(Upstream), + Internal(Internal), +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Upstream { Server(Server), Channel(Server, String), Query(Server, Nick), } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, strum::Display)] +pub enum Internal { + #[strum(serialize = "File Transfers")] + FileTransfers, + Logs, + Highlights, +} + impl Buffer { + pub fn upstream(&self) -> Option<&Upstream> { + if let Self::Upstream(upstream) = self { + Some(upstream) + } else { + None + } + } + + pub fn internal(&self) -> Option<&Internal> { + if let Self::Internal(internal) = self { + Some(internal) + } else { + None + } + } +} + +impl Upstream { pub fn server(&self) -> &Server { match self { - Buffer::Server(server) | Buffer::Channel(server, _) | Buffer::Query(server, _) => { - server - } + Self::Server(server) | Self::Channel(server, _) | Self::Query(server, _) => server, } } pub fn channel(&self) -> Option<&str> { match self { - Buffer::Server(_) => None, - Buffer::Channel(_, channel) => Some(channel), - Buffer::Query(_, _) => None, + Self::Channel(_, channel) => Some(channel), + Self::Server(_) | Self::Query(_, _) => None, } } pub fn target(&self) -> Option { match self { - Buffer::Server(_) => None, - Buffer::Channel(_, channel) => Some(channel.clone()), - Buffer::Query(_, nick) => Some(nick.to_string()), + Self::Channel(_, channel) => Some(channel.clone()), + Self::Query(_, nick) => Some(nick.to_string()), + Self::Server(_) => None, } } @@ -55,6 +85,10 @@ impl Buffer { } } +impl Internal { + pub const ALL: &'static [Self] = &[Self::FileTransfers, Self::Logs, Self::Highlights]; +} + #[derive(Debug, Clone, Default, Deserialize, Serialize)] pub struct Settings { pub channel: channel::Settings, diff --git a/data/src/client.rs b/data/src/client.rs index eaf16a09..979a7a2f 100644 --- a/data/src/client.rs +++ b/data/src/client.rs @@ -10,7 +10,7 @@ use crate::history::ReadMarker; use crate::message::server_time; use crate::time::Posix; use crate::user::{Nick, NickRef}; -use crate::{config, ctcp, dcc, isupport, message, mode, Buffer, Server, User}; +use crate::{buffer, config, ctcp, dcc, isupport, message, mode, Server, User}; use crate::{file_transfer, server}; const HIGHLIGHT_BLACKOUT_INTERVAL: Duration = Duration::from_secs(5); @@ -99,7 +99,7 @@ pub struct Client { users: HashMap>, labels: HashMap, batches: HashMap, - reroute_responses_to: Option, + reroute_responses_to: Option, registration_step: RegistrationStep, listed_caps: Vec, supports_labels: bool, @@ -188,7 +188,7 @@ impl Client { } } - fn send(&mut self, buffer: &Buffer, mut message: message::Encoded) { + fn send(&mut self, buffer: &buffer::Upstream, mut message: message::Encoded) { if self.supports_labels { use proto::Tag; @@ -1471,7 +1471,7 @@ impl Map { } } - pub fn send(&mut self, buffer: &Buffer, message: message::Encoded) { + pub fn send(&mut self, buffer: &buffer::Upstream, message: message::Encoded) { if let Some(client) = self.client_mut(buffer.server()) { client.send(buffer, message); } @@ -1588,12 +1588,12 @@ impl Map { #[derive(Debug, Clone)] pub enum Context { - Buffer(Buffer), - Whois(Buffer), + Buffer(buffer::Upstream), + Whois(buffer::Upstream), } impl Context { - fn new(message: &message::Encoded, buffer: Buffer) -> Self { + fn new(message: &message::Encoded, buffer: buffer::Upstream) -> Self { if let Command::WHOIS(_, _) = message.command { Self::Whois(buffer) } else { @@ -1605,7 +1605,7 @@ impl Context { matches!(self, Self::Whois(_)) } - fn buffer(self) -> Buffer { + fn buffer(self) -> buffer::Upstream { match self { Context::Buffer(buffer) => buffer, Context::Whois(buffer) => buffer, diff --git a/data/src/command.rs b/data/src/command.rs index ec4eb3e0..3f165bc9 100644 --- a/data/src/command.rs +++ b/data/src/command.rs @@ -4,7 +4,7 @@ use irc::proto; use itertools::Itertools; use regex::Regex; -use crate::{ctcp, message::formatting, Buffer}; +use crate::{buffer, ctcp, message::formatting}; #[derive(Debug, Clone, Copy)] pub enum Kind { @@ -66,7 +66,7 @@ pub enum Command { Unknown(String, Vec), } -pub fn parse(s: &str, buffer: Option<&Buffer>) -> Result { +pub fn parse(s: &str, buffer: Option<&buffer::Upstream>) -> Result { let (head, rest) = s.split_once('/').ok_or(Error::MissingSlash)?; // Don't allow leading whitespace before slash if !head.is_empty() { diff --git a/data/src/history.rs b/data/src/history.rs index f09627a2..98d6737a 100644 --- a/data/src/history.rs +++ b/data/src/history.rs @@ -10,7 +10,7 @@ use tokio::fs; use tokio::time::Instant; use crate::user::Nick; -use crate::{compression, environment, message, server, Message}; +use crate::{buffer, compression, environment, message, Buffer, Message, Server}; pub use self::manager::{Manager, Resource}; pub use self::metadata::{Metadata, ReadMarker}; @@ -28,19 +28,59 @@ const FLUSH_AFTER_LAST_RECEIVED: Duration = Duration::from_secs(5); #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Kind { - Server, - Channel(String), - Query(Nick), + Server(Server), + Channel(Server, String), + Query(Server, Nick), Logs, Highlights, } impl Kind { + pub fn from_target(server: Server, target: String) -> Self { + if proto::is_channel(&target) { + Self::Channel(server, target) + } else { + Self::Query(server, Nick::from(target)) + } + } + + pub fn from_input_buffer(buffer: buffer::Upstream) -> Self { + match buffer { + buffer::Upstream::Server(server) => Self::Server(server), + buffer::Upstream::Channel(server, channel) => Self::Channel(server, channel), + buffer::Upstream::Query(server, nick) => Self::Query(server, nick), + } + } + + pub fn from_server_message(server: Server, message: &Message) -> Option { + match &message.target { + message::Target::Server { .. } => Some(Self::Server(server)), + message::Target::Channel { channel, .. } => { + Some(Self::Channel(server, channel.clone())) + } + message::Target::Query { nick, .. } => Some(Self::Query(server, nick.clone())), + message::Target::Logs => None, + message::Target::Highlights { .. } => None, + } + } +} + +impl Kind { + pub fn server(&self) -> Option<&Server> { + match self { + Kind::Server(server) => Some(server), + Kind::Channel(server, _) => Some(server), + Kind::Query(server, _) => Some(server), + Kind::Logs => None, + Kind::Highlights => None, + } + } + pub fn target(&self) -> Option<&str> { match self { - Kind::Server => None, - Kind::Channel(channel) => Some(channel), - Kind::Query(nick) => Some(nick.as_ref()), + Kind::Server(_) => None, + Kind::Channel(_, channel) => Some(channel), + Kind::Query(_, nick) => Some(nick.as_ref()), Kind::Logs => None, Kind::Highlights => None, } @@ -50,39 +90,25 @@ impl Kind { impl fmt::Display for Kind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Kind::Server => write!(f, "server"), - Kind::Channel(channel) => write!(f, "channel {channel}"), - Kind::Query(nick) => write!(f, "user {}", nick), + Kind::Server(server) => write!(f, "server on {server}"), + Kind::Channel(server, channel) => write!(f, "channel {channel} on {server}"), + Kind::Query(server, nick) => write!(f, "user {nick} on {server}"), Kind::Logs => write!(f, "logs"), Kind::Highlights => write!(f, "highlights"), } } } -impl From for Kind { - fn from(target: message::Target) -> Self { - match target { - message::Target::Server { .. } => Kind::Server, - message::Target::Channel { channel, .. } => Kind::Channel(channel), - message::Target::Query { nick, .. } => Kind::Query(nick), - message::Target::Logs => Kind::Logs, - message::Target::Highlights { .. } => Kind::Highlights, - } - } -} - -impl From for Kind { - fn from(target: String) -> Self { - Kind::from(target.as_ref()) - } -} - -impl From<&str> for Kind { - fn from(target: &str) -> Self { - if proto::is_channel(target) { - Kind::Channel(target.to_string()) - } else { - Kind::Query(target.to_string().into()) +impl From for Buffer { + fn from(kind: Kind) -> Self { + match kind { + Kind::Server(server) => Buffer::Upstream(buffer::Upstream::Server(server)), + Kind::Channel(server, channel) => { + Buffer::Upstream(buffer::Upstream::Channel(server, channel)) + } + Kind::Query(server, nick) => Buffer::Upstream(buffer::Upstream::Query(server, nick)), + Kind::Logs => Buffer::Internal(buffer::Internal::Logs), + Kind::Highlights => Buffer::Internal(buffer::Internal::Highlights), } } } @@ -93,49 +119,47 @@ pub struct Loaded { pub metadata: Metadata, } -pub async fn load(server: server::Server, kind: Kind) -> Result { - let path = path(&server, &kind).await?; +pub async fn load(kind: Kind) -> Result { + let path = path(&kind).await?; let messages = read_all(&path).await.unwrap_or_default(); - let metadata = metadata::load(server, kind).await.unwrap_or_default(); + let metadata = metadata::load(kind).await.unwrap_or_default(); Ok(Loaded { messages, metadata }) } pub async fn overwrite( - server: &server::Server, kind: &Kind, messages: &[Message], read_marker: Option, ) -> Result<(), Error> { if messages.is_empty() { - return metadata::save(server, kind, messages, read_marker).await; + return metadata::save(kind, messages, read_marker).await; } let latest = &messages[messages.len().saturating_sub(MAX_MESSAGES)..]; - let path = path(server, kind).await?; + let path = path(kind).await?; let compressed = compression::compress(&latest)?; fs::write(path, &compressed).await?; - metadata::save(server, kind, latest, read_marker).await?; + metadata::save(kind, latest, read_marker).await?; Ok(()) } pub async fn append( - server: &server::Server, kind: &Kind, messages: Vec, read_marker: Option, ) -> Result<(), Error> { - let loaded = load(server.clone(), kind.clone()).await?; + let loaded = load(kind.clone()).await?; let mut all_messages = loaded.messages; all_messages.extend(messages); - overwrite(server, kind, &all_messages, read_marker).await + overwrite(kind, &all_messages, read_marker).await } async fn read_all(path: &PathBuf) -> Result, Error> { @@ -155,13 +179,13 @@ pub async fn dir_path() -> Result { Ok(history_dir) } -async fn path(server: &server::Server, kind: &Kind) -> Result { +async fn path(kind: &Kind) -> Result { let dir = dir_path().await?; let name = match kind { - Kind::Server => format!("{server}"), - Kind::Channel(channel) => format!("{server}channel{channel}"), - Kind::Query(nick) => format!("{server}nickname{}", nick), + Kind::Server(server) => format!("{server}"), + Kind::Channel(server, channel) => format!("{server}channel{channel}"), + Kind::Query(server, nick) => format!("{server}nickname{}", nick), Kind::Logs => "logs".to_string(), Kind::Highlights => "highlights".to_string(), }; @@ -174,7 +198,6 @@ async fn path(server: &server::Server, kind: &Kind) -> Result { #[derive(Debug)] pub enum History { Partial { - server: server::Server, kind: Kind, messages: Vec, last_updated_at: Option, @@ -182,7 +205,6 @@ pub enum History { read_marker: Option, }, Full { - server: server::Server, kind: Kind, messages: Vec, last_updated_at: Option, @@ -191,9 +213,8 @@ pub enum History { } impl History { - fn partial(server: server::Server, kind: Kind) -> Self { + fn partial(kind: Kind) -> Self { Self::Partial { - server, kind, messages: vec![], last_updated_at: None, @@ -266,7 +287,6 @@ impl History { fn flush(&mut self, now: Instant) -> Option>> { match self { History::Partial { - server, kind, messages, last_updated_at, @@ -277,7 +297,6 @@ impl History { let since = now.duration_since(last_received); if since >= FLUSH_AFTER_LAST_RECEIVED { - let server = server.clone(); let kind = kind.clone(); let messages = std::mem::take(messages); let read_marker = *read_marker; @@ -285,8 +304,7 @@ impl History { *last_updated_at = None; return Some( - async move { append(&server, &kind, messages, read_marker).await } - .boxed(), + async move { append(&kind, messages, read_marker).await }.boxed(), ); } } @@ -294,7 +312,6 @@ impl History { None } History::Full { - server, kind, messages, last_updated_at, @@ -305,7 +322,6 @@ impl History { let since = now.duration_since(last_received); if since >= FLUSH_AFTER_LAST_RECEIVED && !messages.is_empty() { - let server = server.clone(); let kind = kind.clone(); let read_marker = *read_marker; *last_updated_at = None; @@ -317,8 +333,7 @@ impl History { let messages = messages.clone(); return Some( - async move { overwrite(&server, &kind, &messages, read_marker).await } - .boxed(), + async move { overwrite(&kind, &messages, read_marker).await }.boxed(), ); } } @@ -332,13 +347,11 @@ impl History { match self { History::Partial { .. } => None, History::Full { - server, kind, messages, read_marker, .. } => { - let server = server.clone(); let kind = kind.clone(); let messages = std::mem::take(messages); @@ -346,7 +359,6 @@ impl History { let max_triggers_unread = metadata::latest_triggers_unread(&messages); *self = Self::Partial { - server: server.clone(), kind: kind.clone(), messages: vec![], last_updated_at: None, @@ -355,7 +367,7 @@ impl History { }; Some(async move { - overwrite(&server, &kind, &messages, read_marker) + overwrite(&kind, &messages, read_marker) .await .map(|_| read_marker) }) @@ -366,18 +378,16 @@ impl History { async fn close(self) -> Result, Error> { match self { History::Partial { - server, kind, messages, read_marker, .. } => { - append(&server, &kind, messages, read_marker).await?; + append(&kind, messages, read_marker).await?; Ok(None) } History::Full { - server, kind, messages, read_marker, @@ -385,7 +395,7 @@ impl History { } => { let read_marker = ReadMarker::latest(&messages).max(read_marker); - overwrite(&server, &kind, &messages, read_marker).await?; + overwrite(&kind, &messages, read_marker).await?; Ok(read_marker) } diff --git a/data/src/history/manager.rs b/data/src/history/manager.rs index de02a6e4..b8fdb22c 100644 --- a/data/src/history/manager.rs +++ b/data/src/history/manager.rs @@ -3,32 +3,28 @@ use std::collections::{HashMap, HashSet}; use chrono::{DateTime, Utc}; use futures::future::BoxFuture; use futures::{future, Future, FutureExt}; -use itertools::Itertools; use tokio::time::Instant; use crate::history::{self, History}; use crate::message::{self, Limit}; use crate::user::Nick; -use crate::{config, input}; -use crate::{server, Buffer, Config, Input, Server, User}; +use crate::{buffer, config, input}; +use crate::{server, Config, Input, Server, User}; #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Resource { - pub server: server::Server, pub kind: history::Kind, } impl Resource { pub fn logs() -> Self { Self { - server: server::LOGS.clone(), kind: history::Kind::Logs, } } pub fn highlights() -> Self { Self { - server: server::HIGHLIGHTS.clone(), kind: history::Kind::Highlights, } } @@ -36,31 +32,20 @@ impl Resource { #[derive(Debug)] pub enum Message { - LoadFull( - server::Server, - history::Kind, - Result, - ), - UpdatePartial( - server::Server, - history::Kind, - Result, - ), + LoadFull(history::Kind, Result), + UpdatePartial(history::Kind, Result), UpdateReadMarker( - server::Server, history::Kind, history::ReadMarker, Result<(), history::Error>, ), Closed( - server::Server, history::Kind, Result, history::Error>, ), - Flushed(server::Server, history::Kind, Result<(), history::Error>), + Flushed(history::Kind, Result<(), history::Error>), Exited( Vec<( - Server, history::Kind, Result, history::Error>, )>, @@ -68,9 +53,9 @@ pub enum Message { } pub enum Event { - Loaded(server::Server, history::Kind), - Closed(server::Server, history::Kind, Option), - Exited(Vec<(Server, history::Kind, Option)>), + Loaded(history::Kind), + Closed(history::Kind, Option), + Exited(Vec<(history::Kind, Option)>), } #[derive(Debug, Default)] @@ -86,20 +71,18 @@ impl Manager { let added = added.into_iter().map(|resource| { async move { - history::load(resource.server.clone(), resource.kind.clone()) - .map(move |result| Message::LoadFull(resource.server, resource.kind, result)) + history::load(resource.kind.clone()) + .map(move |result| Message::LoadFull(resource.kind, result)) .await } .boxed() }); let removed = removed.into_iter().filter_map(|resource| { - self.data - .untrack(&resource.server, &resource.kind) - .map(|task| { - task.map(|result| Message::Closed(resource.server, resource.kind, result)) - .boxed() - }) + self.data.untrack(&resource.kind).map(|task| { + task.map(|result| Message::Closed(resource.kind, result)) + .boxed() + }) }); let tasks = added.chain(removed).collect(); @@ -111,60 +94,58 @@ impl Manager { pub fn update(&mut self, message: Message) -> Option { match message { - Message::LoadFull(server, kind, Ok(loaded)) => { + Message::LoadFull(kind, Ok(loaded)) => { log::debug!( - "loaded history for {kind} on {server}: {} messages", + "loaded history for {kind}: {} messages", loaded.messages.len() ); - self.data.load_full(server.clone(), kind.clone(), loaded); - return Some(Event::Loaded(server, kind)); + self.data.load_full(kind.clone(), loaded); + return Some(Event::Loaded(kind)); } - Message::LoadFull(server, kind, Err(error)) => { - log::warn!("failed to load history for {kind} on {server}: {error}"); + Message::LoadFull(kind, Err(error)) => { + log::warn!("failed to load history for {kind}: {error}"); } - Message::Closed(server, kind, Ok(read_marker)) => { - log::debug!("closed history for {kind} on {server}",); - return Some(Event::Closed(server, kind, read_marker)); + Message::Closed(kind, Ok(read_marker)) => { + log::debug!("closed history for {kind}",); + return Some(Event::Closed(kind, read_marker)); } - Message::Closed(server, kind, Err(error)) => { - log::warn!("failed to close history for {kind} on {server}: {error}") + Message::Closed(kind, Err(error)) => { + log::warn!("failed to close history for {kind}: {error}") } - Message::Flushed(server, kind, Ok(_)) => { + Message::Flushed(kind, Ok(_)) => { // Will cause flush loop if we emit a log every time we flush logs if !matches!(kind, history::Kind::Logs) { - log::debug!("flushed history for {kind} on {server}",); + log::debug!("flushed history for {kind}",); } } - Message::Flushed(server, kind, Err(error)) => { - log::warn!("failed to flush history for {kind} on {server}: {error}") + Message::Flushed(kind, Err(error)) => { + log::warn!("failed to flush history for {kind}: {error}") } - Message::UpdatePartial(server, kind, Ok(metadata)) => { - log::debug!("loaded metadata for {kind} on {server}"); - self.data.update_partial(server, kind, metadata); + Message::UpdatePartial(kind, Ok(metadata)) => { + log::debug!("loaded metadata for {kind}"); + self.data.update_partial(kind, metadata); } - Message::UpdatePartial(server, kind, Err(error)) => { - log::warn!("failed to load metadata for {kind} on {server}: {error}"); + Message::UpdatePartial(kind, Err(error)) => { + log::warn!("failed to load metadata for {kind}: {error}"); } - Message::UpdateReadMarker(server, kind, read_marker, Ok(_)) => { - log::debug!("updated read marker for {kind} on {server} to {read_marker}"); + Message::UpdateReadMarker(kind, read_marker, Ok(_)) => { + log::debug!("updated read marker for {kind} to {read_marker}"); } - Message::UpdateReadMarker(server, kind, read_marker, Err(error)) => { - log::warn!( - "failed to update read marker for {kind} on {server} to {read_marker}: {error}" - ); + Message::UpdateReadMarker(kind, read_marker, Err(error)) => { + log::warn!("failed to update read marker for {kind} to {read_marker}: {error}"); } Message::Exited(results) => { let mut output = vec![]; - for (server, kind, result) in results { + for (kind, result) in results { match result { Ok(marker) => { - log::debug!("closed history for {kind} on {server}",); - output.push((server, kind, marker)); + log::debug!("closed history for {kind}",); + output.push((kind, marker)); } Err(error) => { - log::warn!("failed to close history for {kind} on {server}: {error}"); - output.push((server, kind, None)); + log::warn!("failed to close history for {kind}: {error}"); + output.push((kind, None)); } } } @@ -180,30 +161,19 @@ impl Manager { self.data.flush_all(now) } - pub fn close( - &mut self, - server: Server, - kind: history::Kind, - ) -> Option> { - let history = self.data.map.get_mut(&server)?.remove(&kind)?; + pub fn close(&mut self, kind: history::Kind) -> Option> { + let history = self.data.map.remove(&kind)?; - Some( - history - .close() - .map(|result| Message::Closed(server, kind, result)), - ) + Some(history.close().map(|result| Message::Closed(kind, result))) } pub fn exit(&mut self) -> impl Future { let map = std::mem::take(&mut self.data).map; async move { - let tasks = map.into_iter().flat_map(|(server, map)| { - map.into_iter().map(move |(kind, state)| { - let server = server.clone(); - state.close().map(move |result| (server, kind, result)) - }) - }); + let tasks = map + .into_iter() + .map(|(kind, state)| state.close().map(move |result| (kind, result))); Message::Exited(future::join_all(tasks).await) } @@ -224,7 +194,7 @@ impl Manager { } if let Some(text) = input.raw() { - self.data.input.record(input.buffer(), text.to_string()); + self.data.input.record(&input.buffer, text.to_string()); } tasks @@ -239,42 +209,31 @@ impl Manager { server: &Server, message: crate::Message, ) -> Option> { - self.data.add_message( - server.clone(), - history::Kind::from(message.target.clone()), - message, - ) + history::Kind::from_server_message(server.clone(), &message) + .and_then(|kind| self.data.add_message(kind, message)) } pub fn record_log( &mut self, record: crate::log::Record, ) -> Option> { - self.data.add_message( - server::LOGS.clone(), - history::Kind::Logs, - crate::Message::log(record), - ) + self.data + .add_message(history::Kind::Logs, crate::Message::log(record)) } pub fn record_highlight( &mut self, message: crate::Message, ) -> Option> { - self.data.add_message( - server::HIGHLIGHTS.clone(), - history::Kind::Highlights, - message, - ) + self.data.add_message(history::Kind::Highlights, message) } pub fn update_read_marker( &mut self, - server: Server, kind: impl Into, read_marker: history::ReadMarker, ) -> Option> { - self.data.update_read_marker(server, kind, read_marker) + self.data.update_read_marker(kind, read_marker) } pub fn channel_joined( @@ -287,36 +246,31 @@ impl Manager { pub fn get_messages( &self, - server: &Server, kind: &history::Kind, limit: Option, buffer_config: &config::Buffer, ) -> Option> { - self.data.history_view(server, kind, limit, buffer_config) + self.data.history_view(kind, limit, buffer_config) } pub fn get_unique_queries(&self, server: &Server) -> Vec<&Nick> { - let Some(map) = self.data.map.get(server) else { - return vec![]; - }; - - let queries = map + let queries = self + .data + .map .keys() .filter_map(|kind| match kind { - history::Kind::Query(user) => Some(user), + history::Kind::Query(s, user) => (s == server).then_some(user), _ => None, }) - .unique() .collect::>(); queries } - pub fn has_unread(&self, server: &Server, kind: &history::Kind) -> bool { + pub fn has_unread(&self, kind: &history::Kind) -> bool { self.data .map - .get(server) - .and_then(|map| map.get(kind)) + .get(kind) .map(|history| history.has_unread()) .unwrap_or_default() } @@ -328,23 +282,25 @@ impl Manager { config: &Config, sent_time: DateTime, ) -> Vec> { - let map = self.data.map.entry(server.clone()).or_default(); - - let channels = map + let channels = self + .data + .map .keys() .filter_map(|kind| { - if let history::Kind::Channel(channel) = kind { - Some(channel) + if let history::Kind::Channel(s, channel) = kind { + (s == server).then_some(channel) } else { None } }) .cloned(); - let mut queries = map + let mut queries = self + .data + .map .keys() .filter_map(|kind| { - if let history::Kind::Query(nick) = kind { - Some(nick) + if let history::Kind::Query(s, nick) = kind { + (s == server).then_some(nick) } else { None } @@ -451,7 +407,7 @@ impl Manager { .collect() } - pub fn input<'a>(&'a self, buffer: &Buffer) -> input::Cache<'a> { + pub fn input<'a>(&'a self, buffer: &buffer::Upstream) -> input::Cache<'a> { self.data.input.get(buffer) } } @@ -476,12 +432,12 @@ fn with_limit<'a>( #[derive(Debug, Default)] struct Data { - map: HashMap>, + map: HashMap, input: input::Storage, } impl Data { - fn load_full(&mut self, server: server::Server, kind: history::Kind, data: history::Loaded) { + fn load_full(&mut self, kind: history::Kind, data: history::Loaded) { use std::collections::hash_map; let history::Loaded { @@ -489,12 +445,7 @@ impl Data { metadata, } = data; - match self - .map - .entry(server.clone()) - .or_default() - .entry(kind.clone()) - { + match self.map.entry(kind.clone()) { hash_map::Entry::Occupied(mut entry) => match entry.get_mut() { History::Partial { messages: new_messages, @@ -507,7 +458,6 @@ impl Data { let last_updated_at = *last_updated_at; messages.extend(std::mem::take(new_messages)); entry.insert(History::Full { - server, kind, messages, last_updated_at, @@ -516,7 +466,6 @@ impl Data { } _ => { entry.insert(History::Full { - server, kind, messages, last_updated_at: None, @@ -526,7 +475,6 @@ impl Data { }, hash_map::Entry::Vacant(entry) => { entry.insert(History::Full { - server, kind, messages, last_updated_at: None, @@ -536,20 +484,14 @@ impl Data { } } - fn update_partial( - &mut self, - server: server::Server, - kind: history::Kind, - data: history::Metadata, - ) { - if let Some(history) = self.map.get_mut(&server).and_then(|map| map.get_mut(&kind)) { + fn update_partial(&mut self, kind: history::Kind, data: history::Metadata) { + if let Some(history) = self.map.get_mut(&kind) { history.update_partial(data); } } fn history_view( &self, - server: &server::Server, kind: &history::Kind, limit: Option, buffer_config: &config::Buffer, @@ -558,7 +500,7 @@ impl Data { messages, read_marker, .. - } = self.map.get(server)?.get(kind)? + } = self.map.get(kind)? else { return None; }; @@ -649,7 +591,7 @@ impl Data { }); let max_prefix_chars = buffer_config.nickname.alignment.is_right().then(|| { - if matches!(kind, history::Kind::Channel(_)) { + if matches!(kind, history::Kind::Channel(..)) { filtered .iter() .filter_map(|message| { @@ -693,18 +635,12 @@ impl Data { fn add_message( &mut self, - server: server::Server, kind: history::Kind, message: crate::Message, ) -> Option> { use std::collections::hash_map; - match self - .map - .entry(server.clone()) - .or_default() - .entry(kind.clone()) - { + match self.map.entry(kind.clone()) { hash_map::Entry::Occupied(mut entry) => { entry.get_mut().add_message(message); @@ -712,14 +648,14 @@ impl Data { } hash_map::Entry::Vacant(entry) => { entry - .insert(History::partial(server.clone(), kind.clone())) + .insert(History::partial(kind.clone())) .add_message(message); Some( async move { - let loaded = history::metadata::load(server.clone(), kind.clone()).await; + let loaded = history::metadata::load(kind.clone()).await; - Message::UpdatePartial(server, kind, loaded) + Message::UpdatePartial(kind, loaded) } .boxed(), ) @@ -729,7 +665,6 @@ impl Data { fn update_read_marker( &mut self, - server: server::Server, kind: impl Into, read_marker: history::ReadMarker, ) -> Option> { @@ -737,12 +672,7 @@ impl Data { let kind = kind.into(); - match self - .map - .entry(server.clone()) - .or_default() - .entry(kind.clone()) - { + match self.map.entry(kind.clone()) { hash_map::Entry::Occupied(mut entry) => { entry.get_mut().update_read_marker(read_marker); @@ -750,9 +680,9 @@ impl Data { } hash_map::Entry::Vacant(_) => Some( async move { - let updated = history::metadata::update(&server, &kind, &read_marker).await; + let updated = history::metadata::update(&kind, &read_marker).await; - Message::UpdateReadMarker(server, kind, read_marker, updated) + Message::UpdateReadMarker(kind, read_marker, updated) } .boxed(), ), @@ -766,23 +696,18 @@ impl Data { ) -> Option> { use std::collections::hash_map; - let kind = history::Kind::Channel(channel); + let kind = history::Kind::Channel(server, channel); - match self - .map - .entry(server.clone()) - .or_default() - .entry(kind.clone()) - { + match self.map.entry(kind.clone()) { hash_map::Entry::Occupied(_) => None, hash_map::Entry::Vacant(entry) => { - entry.insert(History::partial(server.clone(), kind.clone())); + entry.insert(History::partial(kind.clone())); Some( async move { - let loaded = history::metadata::load(server.clone(), kind.clone()).await; + let loaded = history::metadata::load(kind.clone()).await; - Message::UpdatePartial(server, kind, loaded) + Message::UpdatePartial(kind, loaded) } .boxed(), ) @@ -792,26 +717,20 @@ impl Data { fn untrack( &mut self, - server: &server::Server, kind: &history::Kind, ) -> Option, history::Error>>> { - self.map - .get_mut(server) - .and_then(|map| map.get_mut(kind).and_then(History::make_partial)) + self.map.get_mut(kind).and_then(History::make_partial) } fn flush_all(&mut self, now: Instant) -> Vec> { self.map .iter_mut() - .flat_map(|(server, map)| { - map.iter_mut().filter_map(|(kind, state)| { - let server = server.clone(); - let kind = kind.clone(); - - state.flush(now).map(move |task| { - task.map(move |result| Message::Flushed(server, kind, result)) - .boxed() - }) + .filter_map(|(kind, state)| { + let kind = kind.clone(); + + state.flush(now).map(move |task| { + task.map(move |result| Message::Flushed(kind, result)) + .boxed() }) }) .collect() diff --git a/data/src/history/metadata.rs b/data/src/history/metadata.rs index e6ec0eca..30a5e513 100644 --- a/data/src/history/metadata.rs +++ b/data/src/history/metadata.rs @@ -8,7 +8,7 @@ use tokio::fs; use crate::history::{dir_path, Error, Kind}; use crate::message::source; -use crate::{server, Message}; +use crate::Message; #[derive(Debug, Clone, Copy, Default, Deserialize, Serialize)] pub struct Metadata { @@ -65,8 +65,8 @@ pub fn latest_triggers_unread(messages: &[Message]) -> Option> { .map(|message| message.server_time) } -pub async fn load(server: server::Server, kind: Kind) -> Result { - let path = path(&server, &kind).await?; +pub async fn load(kind: Kind) -> Result { + let path = path(&kind).await?; if let Ok(bytes) = fs::read(path).await { Ok(serde_json::from_slice(&bytes).unwrap_or_default()) @@ -76,7 +76,6 @@ pub async fn load(server: server::Server, kind: Kind) -> Result } pub async fn save( - server: &server::Server, kind: &Kind, messages: &[Message], read_marker: Option, @@ -86,19 +85,15 @@ pub async fn save( last_triggers_unread: latest_triggers_unread(messages), })?; - let path = path(server, kind).await?; + let path = path(kind).await?; fs::write(path, &bytes).await?; Ok(()) } -pub async fn update( - server: &server::Server, - kind: &Kind, - read_marker: &ReadMarker, -) -> Result<(), Error> { - let metadata = load(server.clone(), kind.clone()).await?; +pub async fn update(kind: &Kind, read_marker: &ReadMarker) -> Result<(), Error> { + let metadata = load(kind.clone()).await?; if metadata .read_marker @@ -112,20 +107,20 @@ pub async fn update( last_triggers_unread: metadata.last_triggers_unread, })?; - let path = path(server, kind).await?; + let path = path(kind).await?; fs::write(path, &bytes).await?; Ok(()) } -async fn path(server: &server::Server, kind: &Kind) -> Result { +async fn path(kind: &Kind) -> Result { let dir = dir_path().await?; let name = match kind { - Kind::Server => format!("{server}-metadata"), - Kind::Channel(channel) => format!("{server}channel{channel}-metadata"), - Kind::Query(nick) => format!("{server}nickname{}-metadata", nick), + Kind::Server(server) => format!("{server}-metadata"), + Kind::Channel(server, channel) => format!("{server}channel{channel}-metadata"), + Kind::Query(server, nick) => format!("{server}nickname{}-metadata", nick), Kind::Logs => "logs-metadata".to_string(), Kind::Highlights => "highlights-metadata".to_string(), }; diff --git a/data/src/input.rs b/data/src/input.rs index 560aa998..f4b344c3 100644 --- a/data/src/input.rs +++ b/data/src/input.rs @@ -3,13 +3,17 @@ use std::collections::HashMap; use irc::proto; use irc::proto::format; -use crate::buffer::AutoFormat; +use crate::buffer::{self, AutoFormat}; use crate::message::formatting; -use crate::{command, message, Buffer, Command, Message, Server, User}; +use crate::{command, message, Command, Message, Server, User}; const INPUT_HISTORY_LENGTH: usize = 100; -pub fn parse(buffer: Buffer, auto_format: AutoFormat, input: &str) -> Result { +pub fn parse( + buffer: buffer::Upstream, + auto_format: AutoFormat, + input: &str, +) -> Result { let content = match command::parse(input, Some(&buffer)) { Ok(command) => Content::Command(command), Err(command::Error::MissingSlash) => { @@ -41,13 +45,13 @@ pub fn parse(buffer: Buffer, auto_format: AutoFormat, input: &str) -> Result, } impl Input { - pub fn command(buffer: Buffer, command: Command) -> Self { + pub fn command(buffer: buffer::Upstream, command: Command) -> Self { Self { buffer, content: Content::Command(command), @@ -55,10 +59,6 @@ impl Input { } } - pub fn buffer(&self) -> &Buffer { - &self.buffer - } - pub fn server(&self) -> &Server { self.buffer.server() } @@ -120,7 +120,7 @@ enum Content { } impl Content { - fn command(&self, buffer: &Buffer) -> Option { + fn command(&self, buffer: &buffer::Upstream) -> Option { match self { Self::Text(text) => { let target = buffer.target()?; @@ -130,7 +130,7 @@ impl Content { } } - fn proto(&self, buffer: &Buffer) -> Option { + fn proto(&self, buffer: &buffer::Upstream) -> Option { self.command(buffer) .and_then(|command| proto::Command::try_from(command).ok()) .map(proto::Message::from) @@ -139,18 +139,18 @@ impl Content { #[derive(Debug, Clone)] pub struct Draft { - pub buffer: Buffer, + pub buffer: buffer::Upstream, pub text: String, } #[derive(Debug, Clone, Default)] pub struct Storage { - sent: HashMap>, - draft: HashMap, + sent: HashMap>, + draft: HashMap, } impl Storage { - pub fn get<'a>(&'a self, buffer: &Buffer) -> Cache<'a> { + pub fn get<'a>(&'a self, buffer: &buffer::Upstream) -> Cache<'a> { Cache { history: self.sent.get(buffer).map(Vec::as_slice).unwrap_or_default(), draft: self @@ -161,7 +161,7 @@ impl Storage { } } - pub fn record(&mut self, buffer: &Buffer, text: String) { + pub fn record(&mut self, buffer: &buffer::Upstream, text: String) { self.draft.remove(buffer); let history = self.sent.entry(buffer.clone()).or_default(); history.insert(0, text); diff --git a/data/src/pane.rs b/data/src/pane.rs index 17a9ef38..b826bbd8 100644 --- a/data/src/pane.rs +++ b/data/src/pane.rs @@ -15,9 +15,6 @@ pub enum Pane { settings: buffer::Settings, }, Empty, - FileTransfers, - Logs, - Highlights, } #[derive(Debug, Clone, Copy, Deserialize, Serialize)] diff --git a/data/src/server.rs b/data/src/server.rs index 79a02276..8bc529f9 100644 --- a/data/src/server.rs +++ b/data/src/server.rs @@ -1,4 +1,3 @@ -use once_cell::sync::Lazy; use std::collections::BTreeMap; use std::{fmt, str}; use tokio::fs; @@ -12,10 +11,6 @@ use crate::config; use crate::config::server::Sasl; use crate::config::Error; -// Hack since log messages are app wide and not scoped to any server -pub static LOGS: Lazy = Lazy::new(|| Server("".to_string())); -pub static HIGHLIGHTS: Lazy = Lazy::new(|| Server("".to_string())); - pub type Handle = Sender; #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] diff --git a/src/buffer.rs b/src/buffer.rs index fbd605e0..7a88fc65 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -1,4 +1,4 @@ -pub use data::buffer::Settings; +pub use data::buffer::{Internal, Settings, Upstream}; use data::user::Nick; use data::{buffer, file_transfer, history, message, Config}; use iced::Task; @@ -57,26 +57,37 @@ impl Buffer { Self::Empty } - pub fn server(&self) -> Option<&data::Server> { + pub fn upstream(&self) -> Option<&buffer::Upstream> { match self { - Buffer::Empty | Buffer::FileTransfers(_) => None, - Buffer::Channel(state) => Some(&state.server), - Buffer::Server(state) => Some(&state.server), - Buffer::Query(state) => Some(&state.server), - Buffer::Logs(_) => Some(&data::server::LOGS), - Buffer::Highlights(_) => Some(&data::server::HIGHLIGHTS), + Buffer::Channel(state) => Some(&state.buffer), + Buffer::Server(state) => Some(&state.buffer), + Buffer::Query(state) => Some(&state.buffer), + Buffer::Empty | Buffer::FileTransfers(_) | Buffer::Logs(_) | Buffer::Highlights(_) => { + None + } + } + } + + pub fn internal(&self) -> Option { + match self { + Buffer::Empty | Buffer::Channel(_) | Buffer::Server(_) | Buffer::Query(_) => None, + Buffer::FileTransfers(_) => Some(buffer::Internal::FileTransfers), + Buffer::Logs(_) => Some(buffer::Internal::Logs), + Buffer::Highlights(_) => Some(buffer::Internal::Highlights), } } - pub fn data(&self) -> Option<&data::Buffer> { + pub fn data(&self) -> Option { match self { Buffer::Empty => None, - Buffer::Channel(state) => Some(&state.buffer), - Buffer::Server(state) => Some(&state.buffer), - Buffer::Query(state) => Some(&state.buffer), - Buffer::FileTransfers(_) => None, - Buffer::Logs(_) => None, - Buffer::Highlights(_) => None, + Buffer::Channel(state) => Some(data::Buffer::Upstream(state.buffer.clone())), + Buffer::Server(state) => Some(data::Buffer::Upstream(state.buffer.clone())), + Buffer::Query(state) => Some(data::Buffer::Upstream(state.buffer.clone())), + Buffer::FileTransfers(_) => { + Some(data::Buffer::Internal(buffer::Internal::FileTransfers)) + } + Buffer::Logs(_) => Some(data::Buffer::Internal(buffer::Internal::Logs)), + Buffer::Highlights(_) => Some(data::Buffer::Internal(buffer::Internal::Highlights)), } } @@ -241,24 +252,20 @@ impl Buffer { nick: Nick, history: &mut history::Manager, ) -> Task { - if let Some(buffer) = self.data().cloned() { - match self { - Buffer::Empty - | Buffer::Server(_) - | Buffer::FileTransfers(_) - | Buffer::Logs(_) - | Buffer::Highlights(_) => Task::none(), - Buffer::Channel(channel) => channel - .input_view - .insert_user(nick, buffer, history) - .map(|message| Message::Channel(channel::Message::InputView(message))), - Buffer::Query(query) => query - .input_view - .insert_user(nick, buffer, history) - .map(|message| Message::Query(query::Message::InputView(message))), - } - } else { - Task::none() + match self { + Buffer::Empty + | Buffer::Server(_) + | Buffer::FileTransfers(_) + | Buffer::Logs(_) + | Buffer::Highlights(_) => Task::none(), + Buffer::Channel(state) => state + .input_view + .insert_user(nick, state.buffer.clone(), history) + .map(|message| Message::Channel(channel::Message::InputView(message))), + Buffer::Query(state) => state + .input_view + .insert_user(nick, state.buffer.clone(), history) + .map(|message| Message::Query(query::Message::InputView(message))), } } @@ -413,9 +420,18 @@ impl Buffer { impl From for Buffer { fn from(buffer: data::Buffer) -> Self { match buffer { - data::Buffer::Server(server) => Self::Server(Server::new(server)), - data::Buffer::Channel(server, channel) => Self::Channel(Channel::new(server, channel)), - data::Buffer::Query(server, user) => Self::Query(Query::new(server, user)), + data::Buffer::Upstream(upstream) => match upstream { + buffer::Upstream::Server(server) => Self::Server(Server::new(server)), + buffer::Upstream::Channel(server, channel) => { + Self::Channel(Channel::new(server, channel)) + } + buffer::Upstream::Query(server, user) => Self::Query(Query::new(server, user)), + }, + data::Buffer::Internal(internal) => match internal { + buffer::Internal::FileTransfers => Self::FileTransfers(FileTransfers::new()), + buffer::Internal::Logs => Self::Logs(Logs::new()), + buffer::Internal::Highlights => Self::Highlights(Highlights::new()), + }, } } } diff --git a/src/buffer/channel.rs b/src/buffer/channel.rs index fab4700b..c9375488 100644 --- a/src/buffer/channel.rs +++ b/src/buffer/channel.rs @@ -1,6 +1,6 @@ use data::server::Server; use data::user::Nick; -use data::User; +use data::{buffer, User}; use data::{channel, history, message, Config}; use iced::widget::{column, container, row}; use iced::{alignment, padding, Length, Task}; @@ -34,6 +34,8 @@ pub fn view<'a>( theme: &'a Theme, is_focused: bool, ) -> Element<'a, Message> { + let server = &state.server; + let channel = &state.channel; let buffer = &state.buffer; let input = history.input(buffer); let our_nick = clients.nickname(&state.server); @@ -113,9 +115,15 @@ pub fn view<'a>( .horizontal_alignment(alignment::Horizontal::Right); } - let nick = - user_context::view(text, user, current_user, Some(buffer), our_user) - .map(scroll_view::Message::UserContext); + let nick = user_context::view( + text, + server, + Some(channel), + user, + current_user, + our_user, + ) + .map(scroll_view::Message::UserContext); let text = message_content::with_context( &message.content, @@ -123,14 +131,12 @@ pub fn view<'a>( scroll_view::Message::Link, theme::selectable_text::default, move |link| match link { - message::Link::User(_) => { - user_context::Entry::list(Some(buffer), our_user) - } + message::Link::User(_) => user_context::Entry::list(true, our_user), _ => vec![], }, move |link, entry, length| match link { message::Link::User(user) => entry - .view(user, current_user, length) + .view(server, Some(channel), user, current_user, length) .map(scroll_view::Message::UserContext), _ => row![].into(), }, @@ -247,7 +253,8 @@ pub fn view<'a>( .width(Length::FillPortion(2)) .height(Length::Fill); - let nick_list = nick_list::view(users, buffer, our_user, config).map(Message::UserContext); + let nick_list = + nick_list::view(server, channel, users, our_user, config).map(Message::UserContext); // If topic toggles from None to Some then it messes with messages' scroll state, // so produce a zero-height placeholder when topic is None. @@ -299,7 +306,7 @@ pub fn view<'a>( #[derive(Debug, Clone)] pub struct Channel { - pub buffer: data::Buffer, + pub buffer: buffer::Upstream, pub server: Server, pub channel: String, @@ -310,7 +317,7 @@ pub struct Channel { impl Channel { pub fn new(server: Server, channel: String) -> Self { Self { - buffer: data::Buffer::Channel(server.clone(), channel.clone()), + buffer: buffer::Upstream::Channel(server.clone(), channel.clone()), server, channel, scroll_view: scroll_view::State::new(), @@ -395,12 +402,13 @@ fn topic<'a>( Some( topic::view( + &state.server, + &state.channel, topic.content.as_ref()?, topic.who.as_deref(), topic.time.as_ref(), config.buffer.channel.topic.max_lines, users, - &state.buffer, our_user, config, theme, @@ -410,7 +418,7 @@ fn topic<'a>( } mod nick_list { - use data::{config, Buffer, Config, User}; + use data::{config, Config, Server, User}; use iced::widget::{column, scrollable, Scrollable}; use iced::{alignment, Length}; use user_context::Message; @@ -420,8 +428,9 @@ mod nick_list { use crate::{font, theme}; pub fn view<'a>( + server: &'a Server, + channel: &'a str, users: &'a [User], - buffer: &'a Buffer, our_user: Option<&'a User>, config: &'a Config, ) -> Element<'a, Message> { @@ -459,7 +468,7 @@ mod nick_list { }) .width(Length::Fixed(width)); - user_context::view(content, user, Some(user), Some(buffer), our_user) + user_context::view(content, server, Some(channel), user, Some(user), our_user) })); Scrollable::new(content) diff --git a/src/buffer/channel/topic.rs b/src/buffer/channel/topic.rs index 2fb6757a..7b0f39ec 100644 --- a/src/buffer/channel/topic.rs +++ b/src/buffer/channel/topic.rs @@ -1,6 +1,6 @@ use chrono::{DateTime, Utc}; use data::user::Nick; -use data::{message, Buffer, Config, User}; +use data::{message, Config, Server, User}; use iced::widget::{column, container, horizontal_rule, row, scrollable, Scrollable}; use iced::Length; @@ -36,12 +36,13 @@ pub fn update(message: Message) -> Option { } pub fn view<'a>( + server: &'a Server, + channel: &'a String, content: &'a message::Content, who: Option<&'a str>, time: Option<&'a DateTime>, max_lines: u16, users: &'a [User], - buffer: &'a Buffer, our_user: Option<&'a User>, config: &'a Config, theme: &'a Theme, @@ -58,9 +59,10 @@ pub fn view<'a>( false, ) }), + server, + Some(channel), user, Some(user), - Some(buffer), our_user, ) } else { diff --git a/src/buffer/highlights.rs b/src/buffer/highlights.rs index 95ea83e1..d211cc8f 100644 --- a/src/buffer/highlights.rs +++ b/src/buffer/highlights.rs @@ -47,7 +47,7 @@ pub fn view<'a>( selectable_text(timestamp).style(theme::selectable_text::timestamp) }); - let channel = selectable_rich_text::<_, _, (), _, _>(vec![ + let channel_text = selectable_rich_text::<_, _, (), _, _>(vec![ span(channel).color(theme.colors().buffer.url).link( message::Link::GoToMessage( server.clone(), @@ -78,8 +78,9 @@ pub fn view<'a>( ) }); - let nick = user_context::view(text, user, current_user, None, None) - .map(scroll_view::Message::UserContext); + let nick = + user_context::view(text, server, Some(channel), user, current_user, None) + .map(scroll_view::Message::UserContext); let text = message_content::with_context( &message.content, @@ -87,12 +88,12 @@ pub fn view<'a>( scroll_view::Message::Link, theme::selectable_text::default, move |link| match link { - message::Link::User(_) => user_context::Entry::list(None, None), + message::Link::User(_) => user_context::Entry::list(true, None), _ => vec![], }, move |link, entry, length| match link { message::Link::User(user) => entry - .view(user, current_user, length) + .view(server, Some(channel), user, current_user, length) .map(scroll_view::Message::UserContext), _ => row![].into(), }, @@ -103,7 +104,7 @@ pub fn view<'a>( container( row![] .push_maybe(timestamp) - .push(channel) + .push(channel_text) .push(nick) .push(selectable_text(" ")) .push(text), diff --git a/src/buffer/input_view.rs b/src/buffer/input_view.rs index 201d1a0b..bfca0cd7 100644 --- a/src/buffer/input_view.rs +++ b/src/buffer/input_view.rs @@ -1,6 +1,6 @@ use data::input::{self, Cache, Draft}; use data::user::Nick; -use data::{client, history, Buffer, Config}; +use data::{buffer, client, history, Config}; use iced::widget::{container, row, text, text_input}; use iced::Task; @@ -119,7 +119,7 @@ impl State { pub fn update( &mut self, message: Message, - buffer: &Buffer, + buffer: &buffer::Upstream, clients: &mut client::Map, history: &mut history::Manager, config: &Config, @@ -186,7 +186,7 @@ impl State { let mut channel_users = &[][..]; // Resolve our attributes if sending this message in a channel - if let Buffer::Channel(server, channel) = &buffer { + if let buffer::Upstream::Channel(server, channel) = &buffer { channel_users = clients.get_channel_users(server, channel); if let Some(user_with_attributes) = @@ -288,7 +288,7 @@ impl State { fn on_completion( &self, - buffer: &Buffer, + buffer: &buffer::Upstream, history: &mut history::Manager, text: String, ) -> (Task, Option) { @@ -313,7 +313,7 @@ impl State { pub fn insert_user( &mut self, nick: Nick, - buffer: Buffer, + buffer: buffer::Upstream, history: &mut history::Manager, ) -> Task { let mut text = history.input(&buffer).draft.to_string(); diff --git a/src/buffer/query.rs b/src/buffer/query.rs index adad43e8..c6d8876e 100644 --- a/src/buffer/query.rs +++ b/src/buffer/query.rs @@ -1,5 +1,5 @@ use data::user::Nick; -use data::{history, message, Config, Server}; +use data::{buffer, history, message, Config, Server}; use iced::widget::{column, container, row, vertical_space}; use iced::{alignment, Length, Task}; @@ -27,14 +27,15 @@ pub fn view<'a>( theme: &'a Theme, is_focused: bool, ) -> Element<'a, Message> { - let status = clients.status(&state.server); + let server = &state.server; + let status = clients.status(server); let buffer = &state.buffer; let input = history.input(buffer); let messages = container( scroll_view::view( &state.scroll_view, - scroll_view::Kind::Query(&state.server, &state.nick), + scroll_view::Kind::Query(server, &state.nick), history, config, move |message, max_nick_width, _| { @@ -72,7 +73,7 @@ pub fn view<'a>( .horizontal_alignment(alignment::Horizontal::Right); } - let nick = user_context::view(text, user, None, Some(buffer), None) + let nick = user_context::view(text, server, None, user, None, None) .map(scroll_view::Message::UserContext); let message = message_content::with_context( @@ -81,14 +82,12 @@ pub fn view<'a>( scroll_view::Message::Link, theme::selectable_text::default, move |link| match link { - message::Link::User(_) => { - user_context::Entry::list(Some(buffer), None) - } + message::Link::User(_) => user_context::Entry::list(false, None), _ => vec![], }, move |link, entry, length| match link { message::Link::User(user) => entry - .view(user, None, length) + .view(server, None, user, None, length) .map(scroll_view::Message::UserContext), _ => row![].into(), }, @@ -215,7 +214,7 @@ pub fn view<'a>( #[derive(Debug, Clone)] pub struct Query { - pub buffer: data::Buffer, + pub buffer: buffer::Upstream, pub server: Server, pub nick: Nick, pub scroll_view: scroll_view::State, @@ -225,7 +224,7 @@ pub struct Query { impl Query { pub fn new(server: Server, nick: Nick) -> Self { Self { - buffer: data::Buffer::Query(server.clone(), nick.clone()), + buffer: buffer::Upstream::Query(server.clone(), nick.clone()), server, nick, scroll_view: scroll_view::State::new(), diff --git a/src/buffer/scroll_view.rs b/src/buffer/scroll_view.rs index 3ae2b869..34400673 100644 --- a/src/buffer/scroll_view.rs +++ b/src/buffer/scroll_view.rs @@ -1,5 +1,5 @@ use data::message::{self, Limit}; -use data::server::{self, Server}; +use data::server::Server; use data::user::Nick; use data::{history, time, Config}; use iced::widget::{column, container, horizontal_rule, row, scrollable, text, Scrollable}; @@ -40,24 +40,14 @@ pub enum Kind<'a> { Highlights, } -impl Kind<'_> { - fn server(&self) -> &Server { - match self { - Kind::Server(server) => server, - Kind::Channel(server, _) => server, - Kind::Query(server, _) => server, - Kind::Logs => &server::LOGS, - Kind::Highlights => &server::HIGHLIGHTS, - } - } -} - impl From> for history::Kind { fn from(value: Kind<'_>) -> Self { match value { - Kind::Server(_) => history::Kind::Server, - Kind::Channel(_, channel) => history::Kind::Channel(channel.to_string()), - Kind::Query(_, nick) => history::Kind::Query(nick.clone()), + Kind::Server(server) => history::Kind::Server(server.clone()), + Kind::Channel(server, channel) => { + history::Kind::Channel(server.clone(), channel.to_string()) + } + Kind::Query(server, nick) => history::Kind::Query(server.clone(), nick.clone()), Kind::Logs => history::Kind::Logs, Kind::Highlights => history::Kind::Highlights, } @@ -77,12 +67,7 @@ pub fn view<'a>( new_messages, max_nick_chars, max_prefix_chars, - }) = history.get_messages( - kind.server(), - &kind.into(), - Some(state.limit), - &config.buffer, - ) + }) = history.get_messages(&kind.into(), Some(state.limit), &config.buffer) else { return column![].into(); }; @@ -366,7 +351,7 @@ impl State { old_messages, new_messages, .. - }) = history.get_messages(kind.server(), &kind.into(), None, &config.buffer) + }) = history.get_messages(&kind.into(), None, &config.buffer) else { // We're still loading history, which will trigger // scroll_to_backlog after loading. If this is set, @@ -408,7 +393,7 @@ impl State { total, old_messages, .. - }) = history.get_messages(kind.server(), &kind.into(), None, &config.buffer) + }) = history.get_messages(&kind.into(), None, &config.buffer) else { return Task::none(); }; diff --git a/src/buffer/server.rs b/src/buffer/server.rs index f083767c..c75de987 100644 --- a/src/buffer/server.rs +++ b/src/buffer/server.rs @@ -1,4 +1,4 @@ -use data::{history, message, Config}; +use data::{buffer, history, message, Config}; use iced::widget::{column, container, row, vertical_space}; use iced::{Length, Task}; @@ -103,7 +103,7 @@ pub fn view<'a>( #[derive(Debug, Clone)] pub struct Server { - pub buffer: data::Buffer, + pub buffer: buffer::Upstream, pub server: data::server::Server, pub scroll_view: scroll_view::State, pub input_view: input_view::State, @@ -112,7 +112,7 @@ pub struct Server { impl Server { pub fn new(server: data::server::Server) -> Self { Self { - buffer: data::Buffer::Server(server.clone()), + buffer: buffer::Upstream::Server(server.clone()), server, scroll_view: scroll_view::State::new(), input_view: input_view::State::new(), diff --git a/src/buffer/user_context.rs b/src/buffer/user_context.rs index 459ba768..f74d8b9f 100644 --- a/src/buffer/user_context.rs +++ b/src/buffer/user_context.rs @@ -1,5 +1,5 @@ use data::user::Nick; -use data::{Buffer, User}; +use data::{Server, User}; use iced::widget::{button, container, horizontal_rule, row, text, Space}; use iced::{padding, Length, Padding}; @@ -18,35 +18,36 @@ pub enum Entry { } impl Entry { - pub fn list(buffer: Option<&Buffer>, our_user: Option<&User>) -> Vec { - match buffer { - Some(Buffer::Channel(_, _)) => { - if our_user.is_some_and(|u| u.has_access_level(data::user::AccessLevel::Oper)) { - vec![ - Entry::UserInfo, - Entry::HorizontalRule, - Entry::Whois, - Entry::Query, - Entry::ToggleAccessLevelOp, - Entry::ToggleAccessLevelVoice, - Entry::SendFile, - ] - } else { - vec![ - Entry::UserInfo, - Entry::HorizontalRule, - Entry::Whois, - Entry::Query, - Entry::SendFile, - ] - } + pub fn list(is_channel: bool, our_user: Option<&User>) -> Vec { + if is_channel { + if our_user.is_some_and(|u| u.has_access_level(data::user::AccessLevel::Oper)) { + vec![ + Entry::UserInfo, + Entry::HorizontalRule, + Entry::Whois, + Entry::Query, + Entry::ToggleAccessLevelOp, + Entry::ToggleAccessLevelVoice, + Entry::SendFile, + ] + } else { + vec![ + Entry::UserInfo, + Entry::HorizontalRule, + Entry::Whois, + Entry::Query, + Entry::SendFile, + ] } - _ => vec![Entry::Whois, Entry::SendFile], + } else { + vec![Entry::Whois, Entry::SendFile] } } pub fn view<'a>( self, + server: &Server, + channel: Option<&str>, user: &User, current_user: Option<&User>, length: Length, @@ -54,39 +55,73 @@ impl Entry { let nickname = user.nickname().to_owned(); match self { - Entry::Whois => menu_button("Whois", Message::Whois(nickname), length), - Entry::Query => menu_button("Message", Message::Query(nickname), length), + Entry::Whois => menu_button("Whois", Message::Whois(server.clone(), nickname), length), + Entry::Query => { + menu_button("Message", Message::Query(server.clone(), nickname), length) + } Entry::ToggleAccessLevelOp => { - if user.has_access_level(data::user::AccessLevel::Oper) { - menu_button( - "Take Op (-o)", - Message::ToggleAccessLevel(nickname, "-o".to_owned()), - length, - ) + if let Some(channel) = channel { + if user.has_access_level(data::user::AccessLevel::Oper) { + menu_button( + "Take Op (-o)", + Message::ToggleAccessLevel( + server.clone(), + channel.to_string(), + nickname, + "-o".to_owned(), + ), + length, + ) + } else { + menu_button( + "Give Op (+o)", + Message::ToggleAccessLevel( + server.clone(), + channel.to_string(), + nickname, + "+o".to_owned(), + ), + length, + ) + } } else { - menu_button( - "Give Op (+o)", - Message::ToggleAccessLevel(nickname, "+o".to_owned()), - length, - ) + row![].into() } } Entry::ToggleAccessLevelVoice => { - if user.has_access_level(data::user::AccessLevel::Voice) { - menu_button( - "Take Voice (-v)", - Message::ToggleAccessLevel(nickname, "-v".to_owned()), - length, - ) + if let Some(channel) = channel { + if user.has_access_level(data::user::AccessLevel::Voice) { + menu_button( + "Take Voice (-v)", + Message::ToggleAccessLevel( + server.clone(), + channel.to_string(), + nickname, + "-v".to_owned(), + ), + length, + ) + } else { + menu_button( + "Give Voice (+v)", + Message::ToggleAccessLevel( + server.clone(), + channel.to_string(), + nickname, + "+v".to_owned(), + ), + length, + ) + } } else { - menu_button( - "Give Voice (+v)", - Message::ToggleAccessLevel(nickname, "+v".to_owned()), - length, - ) + row![].into() } } - Entry::SendFile => menu_button("Send File", Message::SendFile(nickname), length), + Entry::SendFile => menu_button( + "Send File", + Message::SendFile(server.clone(), nickname), + length, + ), Entry::UserInfo => user_info(current_user, length), Entry::HorizontalRule => match length { Length::Fill => container(horizontal_rule(1)).padding([0, 6]).into(), @@ -98,40 +133,43 @@ impl Entry { #[derive(Clone, Debug)] pub enum Message { - Whois(Nick), - Query(Nick), + Whois(Server, Nick), + Query(Server, Nick), + ToggleAccessLevel(Server, String, Nick, String), + SendFile(Server, Nick), SingleClick(Nick), - ToggleAccessLevel(Nick, String), - SendFile(Nick), } #[derive(Debug, Clone)] pub enum Event { - SendWhois(Nick), - OpenQuery(Nick), + SendWhois(Server, Nick), + OpenQuery(Server, Nick), + ToggleAccessLevel(Server, String, Nick, String), + SendFile(Server, Nick), SingleClick(Nick), - ToggleAccessLevel(Nick, String), - SendFile(Nick), } pub fn update(message: Message) -> Option { match message { - Message::Whois(nick) => Some(Event::SendWhois(nick)), - Message::Query(nick) => Some(Event::OpenQuery(nick)), + Message::Whois(server, nick) => Some(Event::SendWhois(server, nick)), + Message::Query(server, nick) => Some(Event::OpenQuery(server, nick)), + Message::ToggleAccessLevel(server, target, nick, mode) => { + Some(Event::ToggleAccessLevel(server, target, nick, mode)) + } + Message::SendFile(server, nick) => Some(Event::SendFile(server, nick)), Message::SingleClick(nick) => Some(Event::SingleClick(nick)), - Message::ToggleAccessLevel(nick, mode) => Some(Event::ToggleAccessLevel(nick, mode)), - Message::SendFile(nick) => Some(Event::SendFile(nick)), } } pub fn view<'a>( content: impl Into>, + server: &'a Server, + channel: Option<&'a str>, user: &'a User, current_user: Option<&'a User>, - buffer: Option<&'a Buffer>, our_user: Option<&'a User>, ) -> Element<'a, Message> { - let entries = Entry::list(buffer, our_user); + let entries = Entry::list(channel.is_some(), our_user); let content = button(content) .padding(0) @@ -142,7 +180,7 @@ pub fn view<'a>( Default::default(), content, entries, - move |entry, length| entry.view(user, current_user, length), + move |entry, length| entry.view(server, channel, user, current_user, length), ) .into() } diff --git a/src/main.rs b/src/main.rs index 76a09735..25ff72dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -670,8 +670,8 @@ impl Halloy { // only send notification if query has unread // or if window is not focused if dashboard.history().has_unread( - &server, &history::Kind::Query( + server.clone(), user.nickname().to_owned(), ), ) || !self.main_window.focused @@ -732,8 +732,10 @@ impl Halloy { commands.push( dashboard .update_read_marker( - server.clone(), - target, + history::Kind::from_target( + server.clone(), + target, + ), read_marker, ) .map(Message::Dashboard), diff --git a/src/screen/dashboard.rs b/src/screen/dashboard.rs index 9bbcb74b..26de3873 100644 --- a/src/screen/dashboard.rs +++ b/src/screen/dashboard.rs @@ -167,106 +167,97 @@ impl Dashboard { buffer::Event::UserContext(event) => { match event { buffer::user_context::Event::ToggleAccessLevel( + server, + channel, nick, mode, ) => { - let Some(buffer) = pane.buffer.data() else { - return (task, None); - }; - - let Some(target) = buffer.target() else { - return (task, None); - }; + let buffer = buffer::Upstream::Channel( + server.clone(), + channel.clone(), + ); let command = data::Command::Mode( - target, + channel, Some(mode), Some(vec![nick.to_string()]), ); - let input = - data::Input::command(buffer.clone(), command); + let input = data::Input::command(buffer, command); if let Some(encoded) = input.encoded() { - clients.send(input.buffer(), encoded); + clients.send(&input.buffer, encoded); } } - buffer::user_context::Event::SendWhois(nick) => { - if let Some(buffer) = pane.buffer.data() { - let command = - data::Command::Whois(None, nick.to_string()); + buffer::user_context::Event::SendWhois(server, nick) => { + let buffer = + pane.buffer.upstream().cloned().unwrap_or_else( + || buffer::Upstream::Server(server.clone()), + ); - let input = - data::Input::command(buffer.clone(), command); + let command = + data::Command::Whois(None, nick.to_string()); - if let Some(encoded) = input.encoded() { - clients.send(input.buffer(), encoded); - } + let input = + data::Input::command(buffer.clone(), command); - if let Some(nick) = - clients.nickname(buffer.server()) + if let Some(encoded) = input.encoded() { + clients.send(&input.buffer, encoded); + } + + if let Some(nick) = clients.nickname(buffer.server()) { + let mut user = nick.to_owned().into(); + let mut channel_users = &[][..]; + + // Resolve our attributes if sending this message in a channel + if let buffer::Upstream::Channel(server, channel) = + &buffer { - let mut user = nick.to_owned().into(); - let mut channel_users = &[][..]; + channel_users = + clients.get_channel_users(server, channel); - // Resolve our attributes if sending this message in a channel - if let data::Buffer::Channel(server, channel) = - &buffer + if let Some(user_with_attributes) = clients + .resolve_user_attributes( + server, channel, &user, + ) { - channel_users = clients - .get_channel_users(server, channel); + user = user_with_attributes.clone(); + } + } - if let Some(user_with_attributes) = clients - .resolve_user_attributes( - server, channel, &user, - ) + if let Some(messages) = + input.messages(user, channel_users) + { + let mut tasks = vec![task]; + + for message in messages { + if let Some(task) = self + .history + .record_message(input.server(), message) { - user = user_with_attributes.clone(); + tasks.push(Task::perform( + task, + Message::History, + )); } } - if let Some(messages) = - input.messages(user, channel_users) - { - let mut tasks = vec![task]; - - for message in messages { - if let Some(task) = - self.history.record_message( - input.server(), - message, - ) - { - tasks.push(Task::perform( - task, - Message::History, - )); - } - } - - return (Task::batch(tasks), None); - } + return (Task::batch(tasks), None); } } } - buffer::user_context::Event::OpenQuery(nick) => { - if let Some(data) = pane.buffer.data() { - let buffer = data::Buffer::Query( - data.server().clone(), - nick, - ); - return ( - Task::batch(vec![ - task, - self.open_buffer( - main_window, - config.buffer.clone().into(), - |b| b.data() == Some(&buffer), - || Buffer::from(buffer.clone()), - ), - ]), - None, - ); - } + buffer::user_context::Event::OpenQuery(server, nick) => { + let buffer = buffer::Upstream::Query(server, nick); + return ( + Task::batch(vec![ + task, + self.open_buffer( + main_window, + data::Buffer::Upstream(buffer), + config.buffer.clone().into(), + ), + ]), + None, + ); } buffer::user_context::Event::SingleClick(nick) => { let Some((_, pane, history)) = @@ -290,45 +281,43 @@ impl Dashboard { None, ); } - buffer::user_context::Event::SendFile(nick) => { - if let Some(buffer) = pane.buffer.data() { - let server = buffer.server().clone(); - let starting_directory = - config.file_transfer.save_directory.clone(); - - return ( - Task::batch(vec![ - task, - Task::perform( - async move { - rfd::AsyncFileDialog::new() - .set_directory( - starting_directory, - ) - .pick_file() - .await - .map(|handle| { - handle.path().to_path_buf() - }) - }, - move |file| { - Message::SendFileSelected( - server.clone(), - nick.clone(), - file, - ) - }, - ), - ]), - None, - ); - } + buffer::user_context::Event::SendFile(server, nick) => { + let starting_directory = + config.file_transfer.save_directory.clone(); + + return ( + Task::batch(vec![ + task, + Task::perform( + async move { + rfd::AsyncFileDialog::new() + .set_directory(starting_directory) + .pick_file() + .await + .map(|handle| { + handle.path().to_path_buf() + }) + }, + move |file| { + Message::SendFileSelected( + server.clone(), + nick.clone(), + file, + ) + }, + ), + ]), + None, + ); } } } buffer::Event::OpenChannel(channel) => { - if let Some(server) = - pane.buffer.data().map(data::Buffer::server).cloned() + if let Some(server) = pane + .buffer + .upstream() + .map(buffer::Upstream::server) + .cloned() { return ( Task::batch(vec![ @@ -352,7 +341,9 @@ impl Dashboard { ) } buffer::Event::GoToMessage(server, channel, message) => { - let buffer = data::Buffer::Channel(server, channel); + let buffer = data::Buffer::Upstream(buffer::Upstream::Channel( + server, channel, + )); let mut tasks = vec![]; @@ -363,9 +354,8 @@ impl Dashboard { { tasks.push(self.open_buffer( main_window, + buffer.clone(), config.buffer.clone().into(), - |b| b.data() == Some(&buffer), - || Buffer::from(buffer.clone()), )); } @@ -450,28 +440,27 @@ impl Dashboard { }; let (event_task, event) = match event { - sidebar::Event::Open(kind) => ( + sidebar::Event::Open(buffer) => ( self.open_buffer( main_window, + data::Buffer::Upstream(buffer), config.buffer.clone().into(), - |b| b.data() == Some(&kind), - || Buffer::from(kind.clone()), ), None, ), sidebar::Event::Popout(buffer) => ( self.open_popout_window( main_window, - Pane::new(Buffer::from(buffer), config), + Pane::new(Buffer::from(data::Buffer::Upstream(buffer)), config), ), None, ), sidebar::Event::Focus(window, pane) => { (self.focus_pane(main_window, window, pane), None) } - sidebar::Event::Replace(window, kind, pane) => { + sidebar::Event::Replace(window, buffer, pane) => { if let Some(state) = self.panes.get_mut(main_window.id, window, pane) { - state.buffer = Buffer::from(kind); + state.buffer = Buffer::from(data::Buffer::Upstream(buffer)); self.last_changed = Some(Instant::now()); self.focus = None; ( @@ -522,13 +511,10 @@ impl Dashboard { sidebar::Event::Leave(buffer) => { self.leave_buffer(main_window, clients, buffer) } - sidebar::Event::ToggleFileTransfers => { - (self.toggle_file_transfers(config, main_window), None) - } - sidebar::Event::ToggleLogs => (self.toggle_logs(config, main_window), None), - sidebar::Event::ToggleHighlights => { - (self.toggle_highlights(config, main_window), None) - } + sidebar::Event::ToggleInternalBuffer(buffer) => ( + self.toggle_internal_buffer(config, main_window, buffer), + None, + ), sidebar::Event::ToggleCommandBar => ( self.toggle_command_bar( &closed_buffers(self, main_window.id, clients), @@ -584,14 +570,11 @@ impl Dashboard { Message::History(message) => { if let Some(event) = self.history.update(message) { match event { - history::manager::Event::Loaded(server, kind) => { + history::manager::Event::Loaded(kind) => { + let buffer = kind.into(); + if let Some((window, pane, state)) = - self.panes.iter_mut(main_window.id).find(|(_, _, state)| { - state.buffer.server() == Some(&server) - && state.buffer.data().map_or(true, |data| { - data.target().as_deref() == kind.target() - }) - }) + self.panes.get_mut_by_buffer(main_window.id, &buffer) { return ( state.buffer.scroll_to_backlog(&self.history, config).map( @@ -606,16 +589,19 @@ impl Dashboard { ); } } - history::manager::Event::Closed(server, kind, read_marker) => { - if let Some((target, read_marker)) = kind.target().zip(read_marker) { - clients.send_markread(&server, target, read_marker); + history::manager::Event::Closed(kind, read_marker) => { + if let Some(((server, target), read_marker)) = + kind.server().zip(kind.target()).zip(read_marker) + { + clients.send_markread(server, target, read_marker); } } history::manager::Event::Exited(results) => { - for (server, kind, read_marker) in results { - if let Some((target, read_marker)) = kind.target().zip(read_marker) + for (kind, read_marker) in results { + if let Some(((server, target), read_marker)) = + kind.server().zip(kind.target()).zip(read_marker) { - clients.send_markread(&server, target, read_marker); + clients.send_markread(server, target, read_marker); } } @@ -671,7 +657,8 @@ impl Dashboard { if let Some(state) = self.panes.get_mut(main_window.id, window, pane) { - state.buffer = Buffer::from(buffer); + state.buffer = + Buffer::from(data::Buffer::Upstream(buffer)); self.last_changed = Some(Instant::now()); commands.extend(vec![ @@ -689,12 +676,10 @@ impl Dashboard { command_bar::Buffer::Merge => { (self.merge_pane(config, main_window), None) } - command_bar::Buffer::ToggleFileTransfers => { - (self.toggle_file_transfers(config, main_window), None) - } - command_bar::Buffer::ToggleLogs => { - (self.toggle_logs(config, main_window), None) - } + command_bar::Buffer::ToggleInternal(buffer) => ( + self.toggle_internal_buffer(config, main_window, buffer), + None, + ), }, command_bar::Command::Configuration(command) => match command { command_bar::Configuration::OpenDirectory => { @@ -807,10 +792,12 @@ impl Dashboard { let open_buffers = open_buffers(self, main_window.id); if let Some((window, pane, state)) = self.get_focused_mut(main_window) { - if let Some(buffer) = - cycle_next_buffer(state.buffer.data(), all_buffers, &open_buffers) - { - state.buffer = Buffer::from(buffer); + if let Some(buffer) = cycle_next_buffer( + state.buffer.upstream(), + all_buffers, + &open_buffers, + ) { + state.buffer = Buffer::from(data::Buffer::Upstream(buffer)); self.focus = None; return (self.focus_pane(main_window, window, pane), None); } @@ -822,11 +809,11 @@ impl Dashboard { if let Some((window, pane, state)) = self.get_focused_mut(main_window) { if let Some(buffer) = cycle_previous_buffer( - state.buffer.data(), + state.buffer.upstream(), all_buffers, &open_buffers, ) { - state.buffer = Buffer::from(buffer); + state.buffer = Buffer::from(data::Buffer::Upstream(buffer)); self.focus = None; return (self.focus_pane(main_window, window, pane), None); } @@ -834,7 +821,7 @@ impl Dashboard { } LeaveBuffer => { if let Some((_, _, state)) = self.get_focused_mut(main_window) { - if let Some(buffer) = state.buffer.data().cloned() { + if let Some(buffer) = state.buffer.upstream().cloned() { return self.leave_buffer(main_window, clients, buffer); } } @@ -873,10 +860,24 @@ impl Dashboard { return (Task::perform(Config::load(), Message::ConfigReloaded), None); } FileTransfers => { - return (self.toggle_file_transfers(config, main_window), None); + return ( + self.toggle_internal_buffer( + config, + main_window, + buffer::Internal::FileTransfers, + ), + None, + ); } Logs => { - return (self.toggle_logs(config, main_window), None); + return ( + self.toggle_internal_buffer( + config, + main_window, + buffer::Internal::Logs, + ), + None, + ); } ThemeEditor => { return (self.toggle_theme_editor(theme, main_window), None); @@ -1157,15 +1158,6 @@ impl Dashboard { } } - fn toggle_file_transfers(&mut self, config: &Config, main_window: &Window) -> Task { - self.toggle_buffer( - config, - main_window, - |buffer| matches!(buffer, Buffer::FileTransfers(_)), - || Buffer::FileTransfers(buffer::FileTransfers::new()), - ) - } - fn toggle_theme_editor(&mut self, theme: &mut Theme, main_window: &Window) -> Task { if let Some(editor) = self.theme_editor.take() { *theme = theme.selected(); @@ -1179,50 +1171,34 @@ impl Dashboard { } } - fn toggle_logs(&mut self, config: &Config, main_window: &Window) -> Task { - self.toggle_buffer( - config, - main_window, - |buffer| matches!(buffer, Buffer::Logs(_)), - || Buffer::Logs(buffer::Logs::new()), - ) - } - - fn toggle_highlights(&mut self, config: &Config, main_window: &Window) -> Task { - self.toggle_buffer( - config, - main_window, - |buffer| matches!(buffer, Buffer::Highlights(_)), - || Buffer::Highlights(buffer::Highlights::new()), - ) - } - - fn toggle_buffer( + fn toggle_internal_buffer( &mut self, config: &Config, main_window: &Window, - matches: impl Fn(&Buffer) -> bool, - new: impl Fn() -> Buffer, + buffer: buffer::Internal, ) -> Task { let panes = self.panes.clone(); let open = panes .iter(main_window.id) .find_map(|(window_id, pane, state)| { - (matches)(&state.buffer).then_some((window_id, pane)) + (state.buffer.internal().as_ref() == Some(&buffer)).then_some((window_id, pane)) }); if let Some((window, pane)) = open { self.close_pane(main_window, window, pane) } else { match config.sidebar.buffer_action { - // Don't replace for file transfer / logs / highlights - BufferAction::NewPane | BufferAction::ReplacePane => { - self.open_buffer(main_window, config.buffer.clone().into(), matches, new) - } - BufferAction::NewWindow => { - self.open_popout_window(main_window, Pane::new(new(), config)) - } + // Don't replace for internal buffers + BufferAction::NewPane | BufferAction::ReplacePane => self.open_buffer( + main_window, + data::Buffer::Internal(buffer), + config.buffer.clone().into(), + ), + BufferAction::NewWindow => self.open_popout_window( + main_window, + Pane::new(Buffer::from(data::Buffer::Internal(buffer)), config), + ), } } } @@ -1230,15 +1206,14 @@ impl Dashboard { fn open_buffer( &mut self, main_window: &Window, + buffer: data::Buffer, settings: buffer::Settings, - matches: impl Fn(&Buffer) -> bool, - new: impl Fn() -> Buffer, ) -> Task { let panes = self.panes.clone(); - // If channel already is open, we focus it. + // If buffer already is open, we focus it. for (window, id, pane) in panes.iter(main_window.id) { - if matches(&pane.buffer) { + if pane.buffer.data().as_ref() == Some(&buffer) { self.focus = Some((window, id)); return self.focus_pane(main_window, window, id); @@ -1253,7 +1228,7 @@ impl Dashboard { .main .panes .entry(*id) - .and_modify(|p| *p = Pane::with_settings(new(), settings)); + .and_modify(|p| *p = Pane::with_settings(Buffer::from(buffer), settings)); self.last_changed = Some(Instant::now()); return self.focus_pane(main_window, main_window.id, *id); @@ -1274,10 +1249,11 @@ impl Dashboard { } }; - let result = - self.panes - .main - .split(axis, pane_to_split, Pane::with_settings(new(), settings)); + let result = self.panes.main.split( + axis, + pane_to_split, + Pane::with_settings(Buffer::from(buffer), settings), + ); self.last_changed = Some(Instant::now()); if let Some((pane, _)) = result { @@ -1291,13 +1267,13 @@ impl Dashboard { &mut self, main_window: &Window, clients: &mut data::client::Map, - buffer: data::Buffer, + buffer: buffer::Upstream, ) -> (Task, Option) { let open = self .panes .iter(main_window.id) .find_map(|(window, pane, state)| { - (state.buffer.data() == Some(&buffer)).then_some((window, pane)) + (state.buffer.upstream() == Some(&buffer)).then_some((window, pane)) }); let mut tasks = vec![]; @@ -1314,8 +1290,10 @@ impl Dashboard { } match buffer.clone() { - data::Buffer::Server(server) => (Task::batch(tasks), Some(Event::QuitServer(server))), - data::Buffer::Channel(server, channel) => { + buffer::Upstream::Server(server) => { + (Task::batch(tasks), Some(Event::QuitServer(server))) + } + buffer::Upstream::Channel(server, channel) => { // Send part & close history file let command = data::Command::Part(channel.clone(), None); let input = data::Input::command(buffer.clone(), command); @@ -1326,17 +1304,17 @@ impl Dashboard { tasks.push( self.history - .close(server, history::Kind::Channel(channel)) + .close(history::Kind::Channel(server, channel)) .map(|task| Task::perform(task, Message::History)) .unwrap_or_else(Task::none), ); (Task::batch(tasks), None) } - data::Buffer::Query(server, nick) => { + buffer::Upstream::Query(server, nick) => { tasks.push( self.history - .close(server, history::Kind::Query(nick)) + .close(history::Kind::Query(server, nick)) .map(|task| Task::perform(task, Message::History)) .unwrap_or_else(Task::none), ); @@ -1388,11 +1366,10 @@ impl Dashboard { pub fn update_read_marker( &mut self, - server: Server, kind: impl Into + 'static, read_marker: ReadMarker, ) -> Task { - if let Some(task) = self.history.update_read_marker(server, kind, read_marker) { + if let Some(task) = self.history.update_read_marker(kind, read_marker) { Task::perform(task, Message::History) } else { Task::none() @@ -1574,19 +1551,8 @@ impl Dashboard { .remove(&window) .and_then(|panes| panes.get(pane).cloned()) { - let task = match pane.buffer.data().cloned() { - Some(buffer) => self.open_buffer( - main_window, - pane.settings, - |b| b.data() == Some(&buffer), - || Buffer::from(buffer.clone()), - ), - None if matches!(pane.buffer, Buffer::FileTransfers(_)) => { - self.toggle_file_transfers(config, main_window) - } - None if matches!(pane.buffer, Buffer::Logs(_)) => { - self.toggle_logs(config, main_window) - } + let task = match pane.buffer.data() { + Some(buffer) => self.open_buffer(main_window, buffer, pane.settings), None => self.new_pane(pane_grid::Axis::Horizontal, config, main_window), }; @@ -1636,7 +1602,7 @@ impl Dashboard { pub fn toggle_command_bar( &mut self, - buffers: &[data::Buffer], + buffers: &[buffer::Upstream], version: &Version, config: &Config, theme: &mut Theme, @@ -1660,7 +1626,7 @@ impl Dashboard { fn open_command_bar( &mut self, - buffers: &[data::Buffer], + buffers: &[buffer::Upstream], version: &Version, config: &Config, main_window: &Window, @@ -1764,18 +1730,6 @@ impl Dashboard { Buffer::empty(), buffer::Settings::default(), )), - data::Pane::FileTransfers => Configuration::Pane(Pane::with_settings( - Buffer::FileTransfers(buffer::FileTransfers::new()), - buffer::Settings::default(), - )), - data::Pane::Logs => Configuration::Pane(Pane::with_settings( - Buffer::Logs(buffer::Logs::new()), - buffer::Settings::default(), - )), - data::Pane::Highlights => Configuration::Pane(Pane::with_settings( - Buffer::Highlights(buffer::Highlights::new()), - buffer::Settings::default(), - )), } } @@ -1926,7 +1880,7 @@ impl Dashboard { main_window: &Window, config: &Config, ) -> Task { - let buffer = data::Buffer::Channel(server.clone(), channel.clone()); + let buffer = buffer::Upstream::Channel(server.clone(), channel.clone()); // Need to join channel if !clients @@ -1942,7 +1896,7 @@ impl Dashboard { .panes .iter(main_window.id) .find_map(|(window, pane, state)| { - (state.buffer.data() == Some(&buffer)).then_some((window, pane)) + (state.buffer.upstream() == Some(&buffer)).then_some((window, pane)) }); if let Some((window, pane)) = matching_pane { @@ -1950,9 +1904,8 @@ impl Dashboard { } else { self.open_buffer( main_window, + data::Buffer::Upstream(buffer), config.buffer.clone().into(), - |b| b.data() == Some(&buffer), - || Buffer::from(buffer.clone()), ) } } @@ -2042,7 +1995,7 @@ impl Panes { buffer: &data::Buffer, ) -> Option<(window::Id, pane_grid::Pane, &mut Pane)> { self.iter_mut(main_window) - .find(|(_, _, state)| state.buffer.data().is_some_and(|b| b == buffer)) + .find(|(_, _, state)| state.buffer.data().is_some_and(|b| b == *buffer)) } fn iter( @@ -2080,32 +2033,32 @@ impl Panes { } } -fn all_buffers(clients: &client::Map, history: &history::Manager) -> Vec { +fn all_buffers(clients: &client::Map, history: &history::Manager) -> Vec { clients .connected_servers() .flat_map(|server| { - std::iter::once(data::Buffer::Server(server.clone())) + std::iter::once(buffer::Upstream::Server(server.clone())) .chain( clients .get_channels(server) .iter() - .map(|channel| data::Buffer::Channel(server.clone(), channel.clone())), + .map(|channel| buffer::Upstream::Channel(server.clone(), channel.clone())), ) .chain( history .get_unique_queries(server) .into_iter() - .map(|nick| data::Buffer::Query(server.clone(), nick.clone())), + .map(|nick| buffer::Upstream::Query(server.clone(), nick.clone())), ) }) .collect() } -fn open_buffers(dashboard: &Dashboard, main_window: window::Id) -> Vec { +fn open_buffers(dashboard: &Dashboard, main_window: window::Id) -> Vec { dashboard .panes .iter(main_window) - .filter_map(|(_, _, pane)| pane.buffer.data()) + .filter_map(|(_, _, pane)| pane.buffer.upstream()) .cloned() .collect() } @@ -2114,7 +2067,7 @@ fn closed_buffers( dashboard: &Dashboard, main_window: window::Id, clients: &client::Map, -) -> Vec { +) -> Vec { let open_buffers = open_buffers(dashboard, main_window); all_buffers(clients, &dashboard.history) @@ -2124,10 +2077,10 @@ fn closed_buffers( } fn cycle_next_buffer( - current: Option<&data::Buffer>, - mut all: Vec, - opened: &[data::Buffer], -) -> Option { + current: Option<&buffer::Upstream>, + mut all: Vec, + opened: &[buffer::Upstream], +) -> Option { all.retain(|buffer| Some(buffer) == current || !opened.contains(buffer)); let next = || { @@ -2140,10 +2093,10 @@ fn cycle_next_buffer( } fn cycle_previous_buffer( - current: Option<&data::Buffer>, - mut all: Vec, - opened: &[data::Buffer], -) -> Option { + current: Option<&buffer::Upstream>, + mut all: Vec, + opened: &[buffer::Upstream], +) -> Option { all.retain(|buffer| Some(buffer) == current || !opened.contains(buffer)); let previous = || { diff --git a/src/screen/dashboard/command_bar.rs b/src/screen/dashboard/command_bar.rs index b40aee83..ee34fc9f 100644 --- a/src/screen/dashboard/command_bar.rs +++ b/src/screen/dashboard/command_bar.rs @@ -1,4 +1,4 @@ -use data::Config; +use data::{buffer, Config}; use iced::widget::{column, container, pane_grid, text}; use iced::Length; @@ -20,7 +20,7 @@ pub enum Message { impl CommandBar { pub fn new( - buffers: &[data::Buffer], + buffers: &[buffer::Upstream], version: &data::Version, config: &Config, focus: Option<(window::Id, pane_grid::Pane)>, @@ -54,7 +54,7 @@ impl CommandBar { pub fn view<'a>( &'a self, - buffers: &[data::Buffer], + buffers: &[buffer::Upstream], focus: Option<(window::Id, pane_grid::Pane)>, resize_buffer: data::buffer::Resize, version: &data::Version, @@ -125,11 +125,10 @@ pub enum Buffer { Maximize(bool), New, Close, - Replace(data::Buffer), + Replace(buffer::Upstream), Popout, Merge, - ToggleFileTransfers, - ToggleLogs, + ToggleInternal(buffer::Internal), } #[derive(Debug, Clone)] @@ -152,7 +151,7 @@ pub enum Theme { impl Command { pub fn list( - buffers: &[data::Buffer], + buffers: &[buffer::Upstream], config: &Config, focus: Option<(window::Id, pane_grid::Pane)>, resize_buffer: data::buffer::Resize, @@ -196,12 +195,18 @@ impl std::fmt::Display for Command { impl Buffer { fn list( - buffers: &[data::Buffer], + buffers: &[buffer::Upstream], focus: Option<(window::Id, pane_grid::Pane)>, resize_buffer: data::buffer::Resize, main_window: window::Id, ) -> Vec { - let mut list = vec![Buffer::New, Buffer::ToggleFileTransfers, Buffer::ToggleLogs]; + let mut list = vec![Buffer::New]; + list.extend( + buffer::Internal::ALL + .iter() + .copied() + .map(Buffer::ToggleInternal), + ); if let Some((window, _)) = focus { list.push(Buffer::Close); @@ -290,16 +295,15 @@ impl std::fmt::Display for Buffer { Buffer::New => write!(f, "New buffer"), Buffer::Close => write!(f, "Close buffer"), Buffer::Replace(buffer) => match buffer { - data::Buffer::Server(server) => write!(f, "Change to {}", server), - data::Buffer::Channel(server, channel) => { + buffer::Upstream::Server(server) => write!(f, "Change to {}", server), + buffer::Upstream::Channel(server, channel) => { write!(f, "Change to {} ({})", channel, server) } - data::Buffer::Query(_, nick) => write!(f, "Change to {}", nick), + buffer::Upstream::Query(_, nick) => write!(f, "Change to {}", nick), }, Buffer::Popout => write!(f, "Pop out buffer"), Buffer::Merge => write!(f, "Merge buffer"), - Buffer::ToggleFileTransfers => write!(f, "Toggle File Transfers"), - Buffer::ToggleLogs => write!(f, "Toggle Logs"), + Buffer::ToggleInternal(internal) => write!(f, "Toggle {internal}"), } } } diff --git a/src/screen/dashboard/pane.rs b/src/screen/dashboard/pane.rs index 26426597..0515bb16 100644 --- a/src/screen/dashboard/pane.rs +++ b/src/screen/dashboard/pane.rs @@ -123,17 +123,14 @@ impl Pane { pub fn resource(&self) -> Option { match &self.buffer { Buffer::Empty => None, - Buffer::Channel(channel) => Some(history::Resource { - server: channel.server.clone(), - kind: history::Kind::Channel(channel.channel.clone()), + Buffer::Channel(state) => Some(history::Resource { + kind: history::Kind::Channel(state.server.clone(), state.channel.clone()), }), - Buffer::Server(server) => Some(history::Resource { - server: server.server.clone(), - kind: history::Kind::Server, + Buffer::Server(state) => Some(history::Resource { + kind: history::Kind::Server(state.server.clone()), }), - Buffer::Query(query) => Some(history::Resource { - server: query.server.clone(), - kind: history::Kind::Query(query.nick.clone()), + Buffer::Query(state) => Some(history::Resource { + kind: history::Kind::Query(state.server.clone(), state.nick.clone()), }), Buffer::FileTransfers(_) => None, Buffer::Logs(_) => Some(history::Resource::logs()), @@ -335,12 +332,16 @@ impl From for data::Pane { fn from(pane: Pane) -> Self { let buffer = match pane.buffer { Buffer::Empty => return data::Pane::Empty, - Buffer::Channel(state) => data::Buffer::Channel(state.server, state.channel), - Buffer::Server(state) => data::Buffer::Server(state.server), - Buffer::Query(state) => data::Buffer::Query(state.server, state.nick), - Buffer::FileTransfers(_) => return data::Pane::FileTransfers, - Buffer::Logs(_) => return data::Pane::Logs, - Buffer::Highlights(_) => return data::Pane::Highlights, + Buffer::Channel(state) => { + data::Buffer::Upstream(buffer::Upstream::Channel(state.server, state.channel)) + } + Buffer::Server(state) => data::Buffer::Upstream(buffer::Upstream::Server(state.server)), + Buffer::Query(state) => { + data::Buffer::Upstream(buffer::Upstream::Query(state.server, state.nick)) + } + Buffer::FileTransfers(_) => data::Buffer::Internal(buffer::Internal::FileTransfers), + Buffer::Logs(_) => data::Buffer::Internal(buffer::Internal::Logs), + Buffer::Highlights(_) => data::Buffer::Internal(buffer::Internal::Highlights), }; data::Pane::Buffer { diff --git a/src/screen/dashboard/sidebar.rs b/src/screen/dashboard/sidebar.rs index a508208a..ccd9b4f5 100644 --- a/src/screen/dashboard/sidebar.rs +++ b/src/screen/dashboard/sidebar.rs @@ -1,6 +1,6 @@ use data::config::{self, sidebar, Config}; use data::dashboard::{BufferAction, BufferFocusedAction}; -use data::{file_transfer, history, Buffer, Version}; +use data::{buffer, file_transfer, history, Version}; use iced::widget::{ button, column, container, horizontal_rule, horizontal_space, pane_grid, row, scrollable, text, vertical_rule, vertical_space, Column, Row, Scrollable, Space, @@ -18,16 +18,14 @@ const CONFIG_RELOAD_DELAY: Duration = Duration::from_secs(1); #[derive(Debug, Clone)] pub enum Message { - Open(Buffer), - Popout(Buffer), + Open(buffer::Upstream), + Popout(buffer::Upstream), Focus(window::Id, pane_grid::Pane), - Replace(window::Id, Buffer, pane_grid::Pane), + Replace(window::Id, buffer::Upstream, pane_grid::Pane), Close(window::Id, pane_grid::Pane), Swap(window::Id, pane_grid::Pane, window::Id, pane_grid::Pane), - Leave(Buffer), - ToggleFileTransfers, - ToggleLogs, - ToggleHighlights, + Leave(buffer::Upstream), + ToggleInternalBuffer(buffer::Internal), ToggleCommandBar, ToggleThemeEditor, ReloadingConfigFile, @@ -40,16 +38,14 @@ pub enum Message { #[derive(Debug, Clone)] pub enum Event { - Open(Buffer), - Popout(Buffer), + Open(buffer::Upstream), + Popout(buffer::Upstream), Focus(window::Id, pane_grid::Pane), - Replace(window::Id, Buffer, pane_grid::Pane), + Replace(window::Id, buffer::Upstream, pane_grid::Pane), Close(window::Id, pane_grid::Pane), Swap(window::Id, pane_grid::Pane, window::Id, pane_grid::Pane), - Leave(Buffer), - ToggleFileTransfers, - ToggleLogs, - ToggleHighlights, + Leave(buffer::Upstream), + ToggleInternalBuffer(buffer::Internal), ToggleCommandBar, ToggleThemeEditor, OpenReleaseWebsite, @@ -95,9 +91,9 @@ impl Sidebar { Some(Event::Swap(from_window, from_pane, to_window, to_pane)), ), Message::Leave(buffer) => (Task::none(), Some(Event::Leave(buffer))), - Message::ToggleFileTransfers => (Task::none(), Some(Event::ToggleFileTransfers)), - Message::ToggleLogs => (Task::none(), Some(Event::ToggleLogs)), - Message::ToggleHighlights => (Task::none(), Some(Event::ToggleHighlights)), + Message::ToggleInternalBuffer(buffer) => { + (Task::none(), Some(Event::ToggleInternalBuffer(buffer))) + } Message::ToggleCommandBar => (Task::none(), Some(Event::ToggleCommandBar)), Message::ToggleThemeEditor => (Task::none(), Some(Event::ToggleThemeEditor)), Message::ReloadingConfigFile => { @@ -188,20 +184,20 @@ impl Sidebar { } else { theme::text::tertiary }), - Message::ToggleFileTransfers, + Message::ToggleInternalBuffer(buffer::Internal::FileTransfers), ), Menu::Highlights => context_button( text("Highlights"), // TODO: Add keybind None, icon::highlights(), - Message::ToggleHighlights, + Message::ToggleInternalBuffer(buffer::Internal::Highlights), ), Menu::Logs => context_button( text("Logs"), Some(&keyboard.logs), icon::logs(), - Message::ToggleLogs, + Message::ToggleInternalBuffer(buffer::Internal::Logs), ), Menu::ThemeEditor => context_button( text("Theme Editor"), @@ -265,61 +261,64 @@ impl Sidebar { for (i, (server, state)) in clients.iter().enumerate() { match state { data::client::State::Disconnected => { - buffers.push(buffer_button( + buffers.push(upstream_buffer_button( main_window, panes, focus, - Buffer::Server(server.clone()), + buffer::Upstream::Server(server.clone()), false, config.buffer_action, config.buffer_focused_action, config.position, config.unread_indicator, - history.has_unread(server, &history::Kind::Server), + history.has_unread(&history::Kind::Server(server.clone())), )); } data::client::State::Ready(connection) => { - buffers.push(buffer_button( + buffers.push(upstream_buffer_button( main_window, panes, focus, - Buffer::Server(server.clone()), + buffer::Upstream::Server(server.clone()), true, config.buffer_action, config.buffer_focused_action, config.position, config.unread_indicator, - history.has_unread(server, &history::Kind::Server), + history.has_unread(&history::Kind::Server(server.clone())), )); for channel in connection.channels() { - buffers.push(buffer_button( + buffers.push(upstream_buffer_button( main_window, panes, focus, - Buffer::Channel(server.clone(), channel.clone()), + buffer::Upstream::Channel(server.clone(), channel.clone()), true, config.buffer_action, config.buffer_focused_action, config.position, config.unread_indicator, - history.has_unread(server, &history::Kind::Channel(channel.clone())), + history.has_unread(&history::Kind::Channel( + server.clone(), + channel.clone(), + )), )); } let queries = history.get_unique_queries(server); for user in queries { - buffers.push(buffer_button( + buffers.push(upstream_buffer_button( main_window, panes, focus, - Buffer::Query(server.clone(), user.clone()), + buffer::Upstream::Query(server.clone(), user.clone()), true, config.buffer_action, config.buffer_focused_action, config.position, config.unread_indicator, - history.has_unread(server, &history::Kind::Query(user.clone())), + history.has_unread(&history::Kind::Query(server.clone(), user.clone())), )); } @@ -466,11 +465,11 @@ impl Entry { } } -fn buffer_button( +fn upstream_buffer_button( main_window: window::Id, panes: &Panes, focus: Option<(window::Id, pane_grid::Pane)>, - buffer: Buffer, + buffer: buffer::Upstream, connected: bool, buffer_action: BufferAction, focused_buffer_action: Option, @@ -481,12 +480,12 @@ fn buffer_button( let open = panes .iter(main_window) .find_map(|(window_id, pane, state)| { - (state.buffer.data() == Some(&buffer)).then_some((window_id, pane)) + (state.buffer.upstream() == Some(&buffer)).then_some((window_id, pane)) }); let is_focused = panes .iter(main_window) .find_map(|(window_id, pane, state)| { - (Some((window_id, pane)) == focus && state.buffer.data() == Some(&buffer)) + (Some((window_id, pane)) == focus && state.buffer.upstream() == Some(&buffer)) .then_some((window_id, pane)) }); @@ -518,7 +517,7 @@ fn buffer_button( }; let row = match &buffer { - Buffer::Server(server) => row![ + buffer::Upstream::Server(server) => row![ icon::connected().style(if connected { if show_unread_indicator { theme::text::unread_indicator @@ -534,7 +533,7 @@ fn buffer_button( ] .spacing(8) .align_y(iced::Alignment::Center), - Buffer::Channel(_, channel) => row![] + buffer::Upstream::Channel(_, channel) => row![] .push(horizontal_space().width(3)) .push_maybe( show_unread_indicator @@ -548,7 +547,7 @@ fn buffer_button( ) .push(horizontal_space().width(3)) .align_y(iced::Alignment::Center), - Buffer::Query(_, nick) => row![] + buffer::Upstream::Query(_, nick) => row![] .push(horizontal_space().width(3)) .push_maybe( show_unread_indicator @@ -626,9 +625,9 @@ fn buffer_button( ), Entry::Leave => ( match &buffer { - Buffer::Server(_) => "Leave server", - Buffer::Channel(_, _) => "Leave channel", - Buffer::Query(_, _) => "Close query", + buffer::Upstream::Server(_) => "Leave server", + buffer::Upstream::Channel(_, _) => "Leave channel", + buffer::Upstream::Query(_, _) => "Close query", }, Message::Leave(buffer.clone()), ),