Skip to content

Commit

Permalink
Merge pull request #570 from squidowl/feat/channel-fragments
Browse files Browse the repository at this point in the history
Add channel fragments
  • Loading branch information
tarkah authored Sep 16, 2024
2 parents 16acc69 + c7a7cee commit 44dd44a
Show file tree
Hide file tree
Showing 13 changed files with 330 additions and 141 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Added:

- Multi-window support: Pop out panes into separate windows.
- In-app theme editor with ability to with share it via a halloy:// URL.
- Clickable users and channels in buffers.
- New configuration options
- Ability to include or exclude channels for server messages (join, part, etc.). See [configuration](https://halloy.squidowl.org/configuration/buffer/server_messages/index.html).
- Ability to color nicknames within channel messages. See [configuration](https://halloy.squidowl.org/configuration/buffer/channel/message.html#nickname_color)
Expand Down
41 changes: 41 additions & 0 deletions data/src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,13 @@ pub fn parse_fragments(text: String, channel_users: &[User]) -> Content {

Either::Right(std::iter::once(fragment))
})
.flat_map(|fragment| {
if let Fragment::Text(text) = &fragment {
return Either::Left(parse_channel_fragments(text).into_iter());
}

Either::Right(std::iter::once(fragment))
})
.collect::<Vec<_>>();

if fragments.len() == 1 && matches!(&fragments[0], Fragment::Text(_)) {
Expand Down Expand Up @@ -403,6 +410,37 @@ fn parse_user_fragments(text: &str, channel_users: &[User]) -> Vec<Fragment> {
})
}

fn parse_channel_fragments(text: &str) -> Vec<Fragment> {
text.chars()
.group_by(|c| c.is_whitespace())
.into_iter()
.map(|(is_whitespace, chars)| {
let text = chars.collect::<String>();

if !is_whitespace
// Only parse on `#` since it's most common and
// using &!+ leads to more false positives than not
&& text.starts_with('#')
&& !text.contains(proto::CHANNEL_BLACKLIST_CHARS)
{
return Fragment::Channel(text);
}

Fragment::Text(text)
})
.fold(vec![], |mut acc, fragment| {
if let Some(Fragment::Text(text)) = acc.last_mut() {
if let Fragment::Text(next) = &fragment {
text.push_str(next);
return acc;
}
}

acc.push(fragment);
acc
})
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Content {
Plain(String),
Expand All @@ -421,6 +459,7 @@ impl Content {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum Fragment {
Text(String),
Channel(String),
User(User),
Url(Url),
Formatted {
Expand All @@ -433,6 +472,7 @@ impl Fragment {
pub fn as_str(&self) -> &str {
match self {
Fragment::Text(s) => s,
Fragment::Channel(s) => s,
Fragment::User(u) => u.as_str(),
Fragment::Url(u) => u.as_str(),
Fragment::Formatted { text, .. } => text,
Expand Down Expand Up @@ -1013,6 +1053,7 @@ pub fn reference_user_text(sender: NickRef, own_nick: NickRef, text: &str) -> bo

#[derive(Debug, Clone)]
pub enum Link {
Channel(String),
Url(String),
User(User),
}
Expand Down
11 changes: 9 additions & 2 deletions irc/proto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,18 @@ pub fn command(command: &str, parameters: Vec<String>) -> Message {
}
}

// Reference: https://defs.ircdocs.horse/defs/chantypes
/// Reference: https://defs.ircdocs.horse/defs/chantypes
pub const CHANNEL_PREFIXES: [char; 4] = ['#', '&', '+', '!'];
/// https://modern.ircdocs.horse/#channels
///
/// Channel names are strings (beginning with specified prefix characters). Apart from the requirement of
/// the first character being a valid channel type prefix character; the only restriction on a channel name
/// is that it may not contain any spaces (' ', 0x20), a control G / BELL ('^G', 0x07), or a comma (',', 0x2C)
/// (which is used as a list item separator by the protocol).
pub const CHANNEL_BLACKLIST_CHARS: [char; 3] = [',', '\u{07}', ','];

pub fn is_channel(target: &str) -> bool {
target.starts_with(CHANNEL_PREFIXES)
target.starts_with(CHANNEL_PREFIXES) && !target.contains(CHANNEL_BLACKLIST_CHARS)
}

// Reference: https://defs.ircdocs.horse/defs/chanmembers
Expand Down
12 changes: 10 additions & 2 deletions src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ pub enum Message {
#[derive(Debug, Clone)]
pub enum Event {
UserContext(user_context::Event),
OpenChannel(String),
}

impl Buffer {
Expand Down Expand Up @@ -71,20 +72,27 @@ impl Buffer {

let event = event.map(|event| match event {
channel::Event::UserContext(event) => Event::UserContext(event),
channel::Event::OpenChannel(channel) => Event::OpenChannel(channel),
});

(command.map(Message::Channel), event)
}
(Buffer::Server(state), Message::Server(message)) => {
let command = state.update(message, clients, history, config);
let (command, event) = state.update(message, clients, history, config);

let event = event.map(|event| match event {
server::Event::UserContext(event) => Event::UserContext(event),
server::Event::OpenChannel(channel) => Event::OpenChannel(channel),
});

(command.map(Message::Server), None)
(command.map(Message::Server), event)
}
(Buffer::Query(state), Message::Query(message)) => {
let (command, event) = state.update(message, clients, history, config);

let event = event.map(|event| match event {
query::Event::UserContext(event) => Event::UserContext(event),
query::Event::OpenChannel(channel) => Event::OpenChannel(channel),
});

(command.map(Message::Query), event)
Expand Down
16 changes: 13 additions & 3 deletions src/buffer/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ pub enum Message {
ScrollView(scroll_view::Message),
InputView(input_view::Message),
UserContext(user_context::Message),
Topic(topic::Message),
}

#[derive(Debug, Clone)]
pub enum Event {
UserContext(user_context::Event),
OpenChannel(String),
}

pub fn view<'a>(
Expand Down Expand Up @@ -120,16 +122,16 @@ pub fn view<'a>(
scroll_view::Message::Link,
theme::selectable_text::default,
move |link| match link {
message::Link::Url(_) => vec![],
message::Link::User(_) => {
user_context::Entry::list(buffer, our_user)
}
_ => vec![],
},
move |link, entry, length| match link {
message::Link::Url(_) => row![].into(),
message::Link::User(user) => entry
.view(user, current_user, length)
.map(scroll_view::Message::UserContext),
_ => row![].into(),
},
config,
);
Expand Down Expand Up @@ -327,6 +329,7 @@ impl Channel {

let event = event.map(|event| match event {
scroll_view::Event::UserContext(event) => Event::UserContext(event),
scroll_view::Event::OpenChannel(channel) => Event::OpenChannel(channel),
});

(command.map(Message::ScrollView), event)
Expand All @@ -353,6 +356,13 @@ impl Channel {
Task::none(),
user_context::update(message).map(Event::UserContext),
),
Message::Topic(message) => (
Task::none(),
topic::update(message).map(|event| match event {
topic::Event::UserContext(event) => Event::UserContext(event),
topic::Event::OpenChannel(channel) => Event::OpenChannel(channel),
}),
),
}
}

Expand Down Expand Up @@ -392,7 +402,7 @@ fn topic<'a>(
config,
theme,
)
.map(Message::UserContext),
.map(Message::Topic),
)
}

Expand Down
45 changes: 37 additions & 8 deletions src/buffer/channel/topic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,32 @@ use super::user_context;
use crate::widget::{double_pass, message_content, selectable_text, Element};
use crate::{theme, Theme};

#[derive(Debug, Clone)]
pub enum Event {
UserContext(user_context::Event),
OpenChannel(String),
}

#[derive(Debug, Clone)]
pub enum Message {
UserContext(user_context::Message),
Link(message::Link),
}

pub fn update(message: Message) -> Option<Event> {
match message {
Message::UserContext(message) => user_context::update(message).map(Event::UserContext),
Message::Link(message::Link::Channel(channel)) => Some(Event::OpenChannel(channel)),
Message::Link(message::Link::Url(url)) => {
let _ = open::that_detached(url);
None
}
Message::Link(message::Link::User(user)) => Some(Event::UserContext(
user_context::Event::SingleClick(user.nickname().to_owned()),
)),
}
}

pub fn view<'a>(
content: &'a message::Content,
who: Option<&'a str>,
Expand All @@ -18,7 +44,7 @@ pub fn view<'a>(
our_user: Option<&'a User>,
config: &'a Config,
theme: &'a Theme,
) -> Element<'a, user_context::Message> {
) -> Element<'a, Message> {
let set_by = who.and_then(|who| {
let nick = Nick::from(who.split('!').next()?);

Expand All @@ -42,18 +68,21 @@ pub fn view<'a>(
.into()
};

Some(row![
selectable_text("set by ").style(theme::selectable_text::topic),
user,
selectable_text(format!(" at {}", time?.to_rfc2822()))
.style(theme::selectable_text::topic),
])
Some(
Element::new(row![
selectable_text("set by ").style(theme::selectable_text::topic),
user,
selectable_text(format!(" at {}", time?.to_rfc2822()))
.style(theme::selectable_text::topic),
])
.map(Message::UserContext),
)
});

let content = column![message_content(
content,
theme,
user_context::Message::Link,
Message::Link,
theme::selectable_text::topic,
config,
)]
Expand Down
6 changes: 4 additions & 2 deletions src/buffer/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub enum Message {
#[derive(Debug, Clone)]
pub enum Event {
UserContext(user_context::Event),
OpenChannel(String),
}

pub fn view<'a>(
Expand Down Expand Up @@ -80,14 +81,14 @@ pub fn view<'a>(
scroll_view::Message::Link,
theme::selectable_text::default,
move |link| match link {
message::Link::Url(_) => vec![],
message::Link::User(_) => user_context::Entry::list(buffer, None),
_ => vec![],
},
move |link, entry, length| match link {
message::Link::Url(_) => row![].into(),
message::Link::User(user) => entry
.view(user, None, length)
.map(scroll_view::Message::UserContext),
_ => row![].into(),
},
config,
);
Expand Down Expand Up @@ -242,6 +243,7 @@ impl Query {

let event = event.map(|event| match event {
scroll_view::Event::UserContext(event) => Event::UserContext(event),
scroll_view::Event::OpenChannel(channel) => Event::OpenChannel(channel),
});

(command.map(Message::ScrollView), event)
Expand Down
4 changes: 4 additions & 0 deletions src/buffer/scroll_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pub enum Message {
#[derive(Debug, Clone)]
pub enum Event {
UserContext(user_context::Event),
OpenChannel(String),
}

#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -227,6 +228,9 @@ impl State {
user_context::update(message).map(Event::UserContext),
);
}
Message::Link(message::Link::Channel(channel)) => {
return (Task::none(), Some(Event::OpenChannel(channel)))
}
Message::Link(message::Link::Url(url)) => {
let _ = open::that_detached(url);
}
Expand Down
26 changes: 20 additions & 6 deletions src/buffer/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use data::{history, message, Config};
use iced::widget::{column, container, row, vertical_space};
use iced::{Length, Task};

use super::{input_view, scroll_view};
use super::{input_view, scroll_view, user_context};
use crate::widget::{message_content, selectable_text, Element};
use crate::{theme, Theme};

Expand All @@ -12,6 +12,12 @@ pub enum Message {
InputView(input_view::Message),
}

#[derive(Debug, Clone)]
pub enum Event {
UserContext(user_context::Event),
OpenChannel(String),
}

pub fn view<'a>(
state: &'a Server,
clients: &'a data::client::Map,
Expand Down Expand Up @@ -119,25 +125,33 @@ impl Server {
clients: &mut data::client::Map,
history: &mut history::Manager,
config: &Config,
) -> Task<Message> {
) -> (Task<Message>, Option<Event>) {
match message {
Message::ScrollView(message) => {
let (command, _) = self.scroll_view.update(message);
command.map(Message::ScrollView)
let (command, event) = self.scroll_view.update(message);

let event = event.map(|event| match event {
scroll_view::Event::UserContext(event) => Event::UserContext(event),
scroll_view::Event::OpenChannel(channel) => Event::OpenChannel(channel),
});

(command.map(Message::ScrollView), event)
}
Message::InputView(message) => {
let (command, event) =
self.input_view
.update(message, &self.buffer, clients, history, config);
let command = command.map(Message::InputView);

match event {
let task = match event {
Some(input_view::Event::InputSent) => Task::batch(vec![
command,
self.scroll_view.scroll_to_end().map(Message::ScrollView),
]),
None => command,
}
};

(task, None)
}
}
}
Expand Down
Loading

0 comments on commit 44dd44a

Please sign in to comment.