Skip to content

Commit

Permalink
add STATUSMSG support
Browse files Browse the repository at this point in the history
  • Loading branch information
4e554c4c committed Oct 18, 2024
1 parent 4aec042 commit 9637d64
Show file tree
Hide file tree
Showing 13 changed files with 86 additions and 56 deletions.
2 changes: 1 addition & 1 deletion data/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ impl Buffer {
Self::Channel(_, channel) => message::Target::Channel {
channel,
source: message::Source::Server(source),
prefix: None,
prefix: Default::default(),
},
Self::Query(_, nick) => message::Target::Query {
nick,
Expand Down
15 changes: 15 additions & 0 deletions data/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1410,6 +1410,15 @@ impl Client {
}).unwrap_or(proto::DEFAULT_CHANNEL_PREFIXES)
}

pub fn statusmsg<'a>(&'a self) -> &'a [char] {
self.isupport.get(&isupport::Kind::STATUSMSG).map(|statusmsg| {
let isupport::Parameter::STATUSMSG(prefixes) = statusmsg else {
unreachable!("Corruption in isupport table.")
};
prefixes.as_ref()
}).unwrap_or(&[])
}

pub fn is_channel(&self, target: &str) -> bool {
proto::is_channel(target, self.chantypes())
}
Expand Down Expand Up @@ -1573,6 +1582,12 @@ impl Map {
.unwrap_or_default()
}

pub fn get_statusmsg<'a>(&'a self, server: &Server) -> &'a [char] {
self.client(server)
.map(|client| client.statusmsg())
.unwrap_or_default()
}

pub fn get_server_handle(&self, server: &Server) -> Option<&server::Handle> {
self.client(server).map(|client| &client.handle)
}
Expand Down
5 changes: 3 additions & 2 deletions data/src/history/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,11 @@ impl Manager {
user: User,
channel_users: &[User],
chantypes: &[char],
statusmsg: &[char],
) -> Vec<impl Future<Output = Message>> {
let mut tasks = vec![];

if let Some(messages) = input.messages(user, channel_users, chantypes) {
if let Some(messages) = input.messages(user, channel_users, chantypes, statusmsg) {
for message in messages {
tasks.extend(self.record_message(input.server(), message));
}
Expand Down Expand Up @@ -684,7 +685,7 @@ impl Data {
buffer_config
.status_message_prefix
.brackets
.format(prefix)
.format(prefix.iter().collect::<String>())
.chars()
.count()
+ 1
Expand Down
4 changes: 2 additions & 2 deletions data/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ impl Input {
self.buffer.server()
}

pub fn messages(&self, user: User, channel_users: &[User], chantypes: &[char]) -> Option<Vec<Message>> {
pub fn messages(&self, user: User, channel_users: &[User], chantypes: &[char], statusmsg: &[char]) -> Option<Vec<Message>> {
let to_target = |target: &str, source| {
if let Some((prefix, channel)) = proto::parse_channel_from_target(target, chantypes) {
if let Some((prefix, channel)) = proto::parse_channel_from_target(target, chantypes, statusmsg) {
Some(message::Target::Channel {
channel,
source,
Expand Down
8 changes: 4 additions & 4 deletions data/src/isupport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -350,11 +350,11 @@ impl FromStr for Operation {
parse_optional_positive_integer(value)?,
))),
"STATUSMSG" => {
if value
.chars()
let chars = value.chars().collect::<Vec<_>>();
if chars.iter()
.all(|c| proto::CHANNEL_MEMBERSHIP_PREFIXES.contains(&c))
{
Ok(Operation::Add(Parameter::STATUSMSG(value.to_string())))
Ok(Operation::Add(Parameter::STATUSMSG(chars)))
} else {
Err("unknown channel membership prefix(es)")
}
Expand Down Expand Up @@ -564,7 +564,7 @@ pub enum Parameter {
SAFELIST,
SECURELIST,
SILENCE(Option<u16>),
STATUSMSG(String),
STATUSMSG(Vec<char>),
TARGMAX(Vec<CommandTargetLimit>),
TOPICLEN(u16),
UHNAMES,
Expand Down
28 changes: 15 additions & 13 deletions data/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ pub enum Target {
Channel {
channel: Channel,
source: Source,
prefix: Option<char>,
prefix: Vec<char>,
},
Query {
nick: Nick,
Expand All @@ -120,10 +120,10 @@ pub enum Target {
}

impl Target {
pub fn prefix(&self) -> Option<&char> {
pub fn prefix(&self) -> Option<&[char]> {
match self {
Target::Server { .. } => None,
Target::Channel { prefix, .. } => prefix.as_ref(),
Target::Channel { prefix, .. } => Some(&prefix),
Target::Query { .. } => None,
Target::Logs => None,
}
Expand Down Expand Up @@ -178,7 +178,8 @@ impl Message {
config: &'a Config,
resolve_attributes: impl Fn(&User, &str) -> Option<User>,
channel_users: impl Fn(&str) -> &'a [User],
chantypes: &[char]
chantypes: &[char],
statusmsg: &[char],
) -> Option<Message> {
let server_time = server_time(&encoded);
let id = message_id(&encoded);
Expand All @@ -190,7 +191,7 @@ impl Message {
&channel_users,
chantypes,
)?;
let target = target(encoded, &our_nick, &resolve_attributes, chantypes)?;
let target = target(encoded, &our_nick, &resolve_attributes, chantypes, statusmsg)?;

Some(Message {
received_at: Posix::now(),
Expand Down Expand Up @@ -537,6 +538,7 @@ fn target(
our_nick: &Nick,
resolve_attributes: &dyn Fn(&User, &str) -> Option<User>,
chantypes: &[char],
statusmsg: &[char],
) -> Option<Target> {
use proto::command::Numeric::*;

Expand All @@ -547,28 +549,28 @@ fn target(
Command::MODE(target, ..) if proto::is_channel(&target, chantypes) => Some(Target::Channel {
channel: target,
source: source::Source::Server(None),
prefix: None,
prefix: Default::default(),
}),
Command::TOPIC(channel, _) | Command::KICK(channel, _, _) => Some(Target::Channel {
channel,
source: source::Source::Server(None),
prefix: None,
prefix: Default::default(),
}),
Command::PART(channel, _) => Some(Target::Channel {
channel,
source: source::Source::Server(Some(source::Server::new(
source::server::Kind::Part,
Some(user?.nickname().to_owned()),
))),
prefix: None,
prefix: Default::default(),
}),
Command::JOIN(channel, _) => Some(Target::Channel {
channel,
source: source::Source::Server(Some(source::Server::new(
source::server::Kind::Join,
Some(user?.nickname().to_owned()),
))),
prefix: None,
prefix: Default::default(),
}),
Command::Numeric(RPL_TOPIC | RPL_TOPICWHOTIME, params) => {
let channel = params.get(1)?.clone();
Expand All @@ -578,15 +580,15 @@ fn target(
source::server::Kind::ReplyTopic,
None,
))),
prefix: None,
prefix: Default::default(),
})
}
Command::Numeric(RPL_CHANNELMODEIS, params) => {
let channel = params.get(1)?.clone();
Some(Target::Channel {
channel,
source: source::Source::Server(None),
prefix: None,
prefix: Default::default(),
})
}
Command::Numeric(RPL_AWAY, params) => {
Expand All @@ -608,7 +610,7 @@ fn target(
}
};

match (proto::parse_channel_from_target(&target, chantypes), user) {
match (proto::parse_channel_from_target(&target, chantypes, statusmsg), user) {
(Some((prefix, channel)), Some(user)) => {
let source = source(resolve_attributes(&user, &channel).unwrap_or(user));
Some(Target::Channel {
Expand Down Expand Up @@ -642,7 +644,7 @@ fn target(
}
};

match (proto::parse_channel_from_target(&target, chantypes), user) {
match (proto::parse_channel_from_target(&target, chantypes, statusmsg), user) {
(Some((prefix, channel)), Some(user)) => {
let source = source(resolve_attributes(&user, &channel).unwrap_or(user));
Some(Target::Channel {
Expand Down
2 changes: 1 addition & 1 deletion data/src/message/broadcast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ fn expand(
Target::Channel {
channel,
source: source.clone(),
prefix: None,
prefix: Default::default(),
},
content.clone(),
)
Expand Down
31 changes: 20 additions & 11 deletions irc/proto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,26 @@ pub fn is_channel(target: &str, chantypes: &[char]) -> bool {
// Reference: https://defs.ircdocs.horse/defs/chanmembers
pub const CHANNEL_MEMBERSHIP_PREFIXES: &[char] = &['~', '&', '!', '@', '%', '+'];

pub fn parse_channel_from_target(target: &str, chantypes: &[char]) -> Option<(Option<char>, String)> {
if target.starts_with(CHANNEL_MEMBERSHIP_PREFIXES) {
let channel = target.strip_prefix(CHANNEL_MEMBERSHIP_PREFIXES)?;

if is_channel(channel, chantypes) {
return Some((target.chars().next(), channel.to_string()));
}
}

if is_channel(target, chantypes) {
Some((None, target.to_string()))
/// https://modern.ircdocs.horse/#channels
///
/// Given a target, split it into a channel name (beginning with a character in `chantypes`) and a
/// possible list of prefixes (given in `statusmsg_prefixes`). If these two lists overlap, the
/// behaviour is unspecified.
pub fn parse_channel_from_target(
target: &str,
chantypes: &[char],
statusmsg_prefixes: &[char],
) -> Option<(Vec<char>, String)> {
// We parse the target by finding the first character in chantypes, and returing (even if that
// character is in statusmsg_prefixes)
// If the characters before the first chantypes character are all valid prefixes, then we have
// a valid channel name with those prefixes. let chan_index = target.find(chantypes)?;
let chan_index = target.find(chantypes)?;

// will not panic, since `find` always returns a valid codepoint index
let (prefix, chan) = target.split_at(chan_index);
if prefix.chars().all(|ref c| statusmsg_prefixes.contains(c)) {
Some((prefix.chars().collect(), chan.to_owned()))
} else {
None
}
Expand Down
2 changes: 1 addition & 1 deletion src/buffer/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub fn view<'a>(
|prefix| {
let text = selectable_text(format!(
"{} ",
config.buffer.status_message_prefix.brackets.format(prefix)
config.buffer.status_message_prefix.brackets.format(String::from_iter(prefix))
))
.style(theme::selectable_text::tertiary);

Expand Down
3 changes: 2 additions & 1 deletion src/buffer/input_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ impl State {
let mut user = nick.to_owned().into();
let mut channel_users = &[][..];
let chantypes = clients.get_chantypes(buffer.server());
let statusmsg = clients.get_statusmsg(buffer.server());

// Resolve our attributes if sending this message in a channel
if let Buffer::Channel(server, channel) = &buffer {
Expand All @@ -199,7 +200,7 @@ impl State {

history_task = Task::batch(
history
.record_input(input, user, channel_users, chantypes)
.record_input(input, user, channel_users, chantypes, statusmsg)
.into_iter()
.map(Task::future),
);
Expand Down
35 changes: 16 additions & 19 deletions src/buffer/input_view/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,19 +185,20 @@ impl Commands {
}
}
"MSG" => {
let channel_membership_prefixes = if let Some(
let channel_membership_prefixes =
if let Some(
isupport::Parameter::STATUSMSG(channel_membership_prefixes),
) =
isupport.get(&isupport::Kind::STATUSMSG)
{
Some(channel_membership_prefixes)
channel_membership_prefixes.clone()
} else {
None
vec![]
};

let target_limit = find_target_limit(isupport, "PRIVMSG");

if channel_membership_prefixes.is_some() || target_limit.is_some() {
if !channel_membership_prefixes.is_empty() || target_limit.is_some() {
return msg_command(channel_membership_prefixes, target_limit);
}
}
Expand Down Expand Up @@ -1181,27 +1182,23 @@ static MONITOR_STATUS_COMMAND: Lazy<Command> = Lazy::new(|| Command {
});

fn msg_command(
channel_membership_prefixes: Option<&String>,
channel_membership_prefixes: Vec<char>,
target_limit: Option<&isupport::CommandTargetLimit>,
) -> Command {
let mut targets_tooltip = String::from(
"comma-separated\n {user}: user directly\n {channel}: all users in channel",
);

if let Some(channel_membership_prefixes) = channel_membership_prefixes {
channel_membership_prefixes
.chars()
.for_each(
|channel_membership_prefix| match channel_membership_prefix {
'~' => targets_tooltip.push_str("\n~{channel}: all founders in channel"),
'&' => targets_tooltip.push_str("\n&{channel}: all protected users in channel"),
'!' => targets_tooltip.push_str("\n!{channel}: all protected users in channel"),
'@' => targets_tooltip.push_str("\n@{channel}: all operators in channel"),
'%' => targets_tooltip.push_str("\n%{channel}: all half-operators in channel"),
'+' => targets_tooltip.push_str("\n+{channel}: all voiced users in channel"),
_ => (),
},
);
for channel_membership_prefix in channel_membership_prefixes {
match channel_membership_prefix {
'~' => targets_tooltip.push_str("\n~{channel}: all founders in channel"),
'&' => targets_tooltip.push_str("\n&{channel}: all protected users in channel"),
'!' => targets_tooltip.push_str("\n!{channel}: all protected users in channel"),
'@' => targets_tooltip.push_str("\n@{channel}: all operators in channel"),
'%' => targets_tooltip.push_str("\n%{channel}: all half-operators in channel"),
'+' => targets_tooltip.push_str("\n+{channel}: all voiced users in channel"),
_ => (),
}
}

if let Some(target_limit) = target_limit {
Expand Down
4 changes: 4 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ impl Halloy {
};

let chantypes = self.clients.get_chantypes(&server);
let statusmsg = self.clients.get_statusmsg(&server);

match event {
data::client::Event::Single(encoded, our_nick) => {
Expand All @@ -514,6 +515,7 @@ impl Halloy {
resolve_user_attributes,
channel_users,
chantypes,
statusmsg,
) {
commands.push(
dashboard
Expand All @@ -530,6 +532,7 @@ impl Halloy {
resolve_user_attributes,
channel_users,
chantypes,
statusmsg,
) {
commands.push(
dashboard
Expand Down Expand Up @@ -647,6 +650,7 @@ impl Halloy {
resolve_user_attributes,
channel_users,
chantypes,
statusmsg,
) {
commands.push(
dashboard
Expand Down
3 changes: 2 additions & 1 deletion src/screen/dashboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ impl Dashboard {
let mut user = nick.to_owned().into();
let mut channel_users = &[][..];
let chantypes = clients.get_chantypes(buffer.server());
let statusmsg = clients.get_statusmsg(buffer.server());

// Resolve our attributes if sending this message in a channel
if let data::Buffer::Channel(server, channel) =
Expand All @@ -225,7 +226,7 @@ impl Dashboard {
}

if let Some(messages) =
input.messages(user, channel_users, chantypes)
input.messages(user, channel_users, chantypes, statusmsg)
{
let mut tasks = vec![task];

Expand Down

0 comments on commit 9637d64

Please sign in to comment.