Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions crates/libtiny_common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,26 @@ pub enum MsgTarget<'a> {
CurrentTab,
}

impl<'a> MsgTarget<'a> {
pub fn serv_name(&self) -> Option<&str> {
match self {
MsgTarget::Server { serv }
| MsgTarget::Chan { serv, .. }
| MsgTarget::User { serv, .. }
| MsgTarget::AllServTabs { serv } => Some(serv),
_ => None,
}
}

pub fn chan_or_user_name(&self) -> Option<&ChanNameRef> {
match self {
MsgTarget::Chan { chan, .. } => Some(chan),
MsgTarget::User { nick, .. } => Some(ChanNameRef::new(nick)),
_ => None,
}
}
}

/// Source of a message from the user.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MsgSource {
Expand All @@ -186,6 +206,13 @@ impl MsgSource {
}
}

pub fn chan_name(&self) -> Option<&ChanNameRef> {
match self {
MsgSource::Chan { chan, .. } => Some(chan),
_ => None,
}
}

pub fn to_target(&self) -> MsgTarget {
match self {
MsgSource::Serv { serv } => MsgTarget::Server { serv },
Expand Down
228 changes: 226 additions & 2 deletions crates/libtiny_tui/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
// To see how color numbers map to actual colors in your terminal run
// `cargo run --example colors`. Use tab to swap fg/bg colors.

use libtiny_common::{ChanName, ChanNameRef};
use serde::de::{self, Deserializer, MapAccess, Visitor};
use serde::Deserialize;
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::str::FromStr;

pub use termbox_simple::*;

use crate::key_map::KeyMap;
use crate::notifier::Notifier;

#[derive(Deserialize)]
#[derive(Debug, Default, Deserialize)]
pub(crate) struct Config {
pub(crate) servers: Vec<Server>,

pub(crate) defaults: Defaults,

#[serde(default)]
pub(crate) colors: Colors,

Expand All @@ -28,6 +36,222 @@ pub(crate) struct Config {
pub(crate) key_map: Option<KeyMap>,
}

#[derive(Debug, Deserialize, PartialEq, Eq)]
pub(crate) struct Server {
pub(crate) addr: String,
pub(crate) join: Vec<Chan>,
#[serde(flatten)]
pub(crate) config: TabConfig,
}

#[derive(Debug, Deserialize, PartialEq, Eq)]
pub(crate) struct Defaults {
#[serde(default, flatten)]
pub(crate) tab_config: TabConfig,
}

impl Default for Defaults {
fn default() -> Self {
Defaults {
tab_config: TabConfig {
ignore: Some(false),
notify: Some(Notifier::default()),
},
}
}
}

#[derive(Clone, Deserialize, Debug, PartialEq, Eq)]
#[serde(untagged)]
pub enum Chan {
#[serde(deserialize_with = "deser_chan_name")]
Name(ChanName),
WithConfig {
#[serde(deserialize_with = "deser_chan_name")]
name: ChanName,
#[serde(flatten)]
config: TabConfig,
},
}

impl Chan {
pub fn name(&self) -> &ChanNameRef {
match self {
Chan::Name(name) => name,
Chan::WithConfig { name, .. } => name,
}
.as_ref()
}
}

fn deser_chan_name<'de, D>(d: D) -> Result<ChanName, D::Error>
where
D: Deserializer<'de>,
{
let name: String = serde::de::Deserialize::deserialize(d)?;
Ok(ChanName::new(name))
}

impl FromStr for Chan {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
// Make sure channel starts with '#'
let s = if !s.starts_with('#') {
format!("#{}", s)
} else {
s.to_string()
};

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big deal probably, but I wonder if we should expect user to get this right instead of inserting a # for them. The problem is channel names can contain multiple #s, not just one, but we assume one here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something I noticed other clients do, so I added it if there is no #. It seems like a nicer UX, but we don't need to have it.

// Try to split chan name and args
match s.split_once(' ') {
// with args
Some((name, args)) => {
let config = TabConfig::from_str(args)?;
Ok(Chan::WithConfig {
name: ChanName::new(name.to_string()),
config,
})
}
// chan name only
None => Ok(Chan::Name(ChanName::new(s))),
}
}
}

/// Map of TabConfigs by tab names
#[derive(Debug, Default)]
pub(crate) struct TabConfigs(HashMap<String, TabConfig>);

impl TabConfigs {
pub(crate) fn get(
&self,
serv_name: &str,
chan_name: Option<&ChanNameRef>,
) -> Option<TabConfig> {
let key = if let Some(chan) = chan_name {
format!("{}_{}", serv_name, chan.display())
} else {
serv_name.to_string()
};
self.0.get(&key).cloned()
}

pub(crate) fn get_mut(
&mut self,
serv_name: &str,
chan_name: Option<&ChanNameRef>,
) -> Option<&mut TabConfig> {
let key = if let Some(chan) = chan_name {
format!("{}_{}", serv_name, chan.display())
} else {
serv_name.to_string()
};
self.0.get_mut(&key)
}

pub(crate) fn set(
&mut self,
serv_name: &str,
chan_name: Option<&ChanNameRef>,
config: TabConfig,
) {
let key = if let Some(chan) = chan_name {
format!("{}_{}", serv_name, chan.display())
} else {
serv_name.to_string()
};
self.0.insert(key, config);
}

pub(crate) fn set_by_server(&mut self, serv_name: &str, config: TabConfig) {
for c in self
.0
.iter_mut()
.filter(|entry| entry.0.starts_with(serv_name))
{
*c.1 = config;
}
}
}

impl From<&Config> for TabConfigs {
fn from(config: &Config) -> Self {
let mut tab_configs = HashMap::new();
for server in &config.servers {
let serv_tc = server.config.or_use(&config.defaults.tab_config);
tab_configs.insert(server.addr.clone(), serv_tc);
for chan in &server.join {
let (name, tc) = match chan {
Chan::Name(name) => (name, serv_tc),
Chan::WithConfig { name, config } => (name, config.or_use(&serv_tc)),
};
tab_configs.insert(format!("{}_{}", server.addr, name.display()), tc);
}
}
tab_configs.insert("_defaults".to_string(), config.defaults.tab_config);
debug!("new {tab_configs:?}");
Self(tab_configs)
}
}

#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Eq)]
pub struct TabConfig {
/// `true` if tab is ignoring join/part messages
#[serde(default)]
pub ignore: Option<bool>,
/// Notification setting for tab
#[serde(default)]
pub notify: Option<Notifier>,
}

impl TabConfig {
pub fn user_tab_defaults() -> TabConfig {
TabConfig {
ignore: Some(false),
notify: Some(Notifier::Messages),
}
}

pub(crate) fn or_use(&self, config: &TabConfig) -> TabConfig {
TabConfig {
ignore: self.ignore.or(config.ignore),
notify: self.notify.or(config.notify),
}
}

pub(crate) fn toggle_ignore(&mut self) -> bool {
let ignore = self.ignore.get_or_insert(false);
*ignore = !&*ignore;
*ignore
}
}

impl FromStr for TabConfig {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let config = s
.split('-')
.filter_map(|arg| (!arg.is_empty()).then(|| arg.trim()))
.try_fold(TabConfig::default(), |mut tc, arg| match arg {
// flag
"ignore" => {
tc.ignore = Some(true);
Ok(tc)
}
arg => match arg.split_once(' ') {
// arg with parameter
Some(("notify", val)) => {
tc.notify = Some(Notifier::from_str(val)?);
Ok(tc)
}
_ => Err(format!("Unexpected argument: {:?}", arg)),
},
})?;
Ok(config)
}
}

fn default_max_nick_length() -> usize {
12
}
Expand All @@ -41,7 +265,7 @@ pub struct Style {
pub bg: u16,
}

#[derive(Deserialize)]
#[derive(Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) enum Layout {
Compact,
Expand Down
23 changes: 22 additions & 1 deletion crates/libtiny_tui/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![allow(clippy::too_many_arguments)]
#![allow(clippy::cognitive_complexity)]

mod config;
pub mod config;
mod editor;
mod exit_dialogue;
mod input_area;
Expand All @@ -24,7 +24,9 @@ mod widget;
mod tests;

use crate::tui::{CmdResult, TUIRet};
use config::TabConfig;
use libtiny_common::{ChanNameRef, Event, MsgSource, MsgTarget, TabStyle};
pub use notifier::Notifier;
use term_input::Input;

use std::cell::RefCell;
Expand Down Expand Up @@ -256,6 +258,25 @@ impl TUI {
));
delegate!(set_tab_style(style: TabStyle, target: &MsgTarget,));

pub fn get_tab_config(&self, serv_name: &str, chan_name: Option<&ChanNameRef>) -> TabConfig {
self.inner
.upgrade()
.map(|tui| tui.borrow().get_tab_config(serv_name, chan_name))
.unwrap_or_default()
}

pub fn set_tab_config(
&self,
serv_name: &str,
chan_name: Option<&ChanNameRef>,
config: TabConfig,
) {
if let Some(tui) = self.inner.upgrade() {
tui.borrow_mut()
.set_tab_config(serv_name, chan_name, config);
}
}

pub fn user_tab_exists(&self, serv_name: &str, nick: &str) -> bool {
match self.inner.upgrade() {
Some(tui) => tui.borrow().user_tab_exists(serv_name, nick),
Expand Down
Loading