Implement live caesar cipher
This commit is contained in:
parent
ab81c89854
commit
ee7121b04e
8 changed files with 81 additions and 5 deletions
|
|
@ -18,6 +18,7 @@ Procedure when bumping the version number:
|
|||
### Added
|
||||
- Support for setting window title
|
||||
- More information to room list heading
|
||||
- Key bindings for live caesar cipher de- and encoding
|
||||
|
||||
### Removed
|
||||
- Key binding to open present page
|
||||
|
|
|
|||
|
|
@ -104,6 +104,8 @@ default_bindings! {
|
|||
pub fn mark_older_seen => ["ctrl+s"];
|
||||
pub fn info => ["i"];
|
||||
pub fn links => ["I"];
|
||||
pub fn increase_caesar => ["c"];
|
||||
pub fn decrease_caesar => ["C"];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -354,6 +356,12 @@ pub struct TreeAction {
|
|||
/// List links found in message.
|
||||
#[serde(default = "default::tree_action::links")]
|
||||
pub links: KeyBinding,
|
||||
/// Increase caesar cipher rotation.
|
||||
#[serde(default = "default::tree_action::increase_caesar")]
|
||||
pub increase_caesar: KeyBinding,
|
||||
/// Decrease caesar cipher rotation.
|
||||
#[serde(default = "default::tree_action::decrease_caesar")]
|
||||
pub decrease_caesar: KeyBinding,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Document)]
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use toss::widgets::{BoxedAsync, EditorState};
|
|||
use toss::{Styled, WidgetExt};
|
||||
|
||||
use crate::store::{Msg, MsgStore};
|
||||
use crate::util;
|
||||
|
||||
use self::cursor::Cursor;
|
||||
use self::tree::TreeViewState;
|
||||
|
|
@ -33,6 +34,7 @@ pub struct ChatState<M: Msg, S: MsgStore<M>> {
|
|||
|
||||
cursor: Cursor<M::Id>,
|
||||
editor: EditorState,
|
||||
caesar: i8,
|
||||
|
||||
mode: Mode,
|
||||
tree: TreeViewState<M, S>,
|
||||
|
|
@ -43,6 +45,7 @@ impl<M: Msg, S: MsgStore<M> + Clone> ChatState<M, S> {
|
|||
Self {
|
||||
cursor: Cursor::Bottom,
|
||||
editor: EditorState::new(),
|
||||
caesar: 0,
|
||||
|
||||
mode: Mode::Tree,
|
||||
tree: TreeViewState::new(store.clone()),
|
||||
|
|
@ -68,7 +71,13 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
|
|||
match self.mode {
|
||||
Mode::Tree => self
|
||||
.tree
|
||||
.widget(&mut self.cursor, &mut self.editor, nick, focused)
|
||||
.widget(
|
||||
&mut self.cursor,
|
||||
&mut self.editor,
|
||||
nick,
|
||||
focused,
|
||||
self.caesar,
|
||||
)
|
||||
.boxed_async(),
|
||||
}
|
||||
}
|
||||
|
|
@ -85,7 +94,7 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
|
|||
S: Send + Sync,
|
||||
S::Error: Send,
|
||||
{
|
||||
match self.mode {
|
||||
let reaction = match self.mode {
|
||||
Mode::Tree => {
|
||||
self.tree
|
||||
.handle_input_event(
|
||||
|
|
@ -95,9 +104,28 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
|
|||
&mut self.editor,
|
||||
can_compose,
|
||||
)
|
||||
.await
|
||||
.await?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(match reaction {
|
||||
Reaction::Composed { parent, content } if self.caesar != 0 => {
|
||||
let content = util::caesar(&content, self.caesar);
|
||||
Reaction::Composed { parent, content }
|
||||
}
|
||||
|
||||
Reaction::NotHandled if event.matches(&keys.tree.action.increase_caesar) => {
|
||||
self.caesar = (self.caesar + 1).rem_euclid(26);
|
||||
Reaction::Handled
|
||||
}
|
||||
|
||||
Reaction::NotHandled if event.matches(&keys.tree.action.decrease_caesar) => {
|
||||
self.caesar = (self.caesar - 1).rem_euclid(26);
|
||||
Reaction::Handled
|
||||
}
|
||||
|
||||
reaction => reaction,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cursor(&self) -> Option<&M::Id> {
|
||||
|
|
|
|||
|
|
@ -386,6 +386,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
|
|||
editor: &'a mut EditorState,
|
||||
nick: String,
|
||||
focused: bool,
|
||||
caesar: i8,
|
||||
) -> TreeView<'a, M, S> {
|
||||
TreeView {
|
||||
state: self,
|
||||
|
|
@ -393,6 +394,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
|
|||
editor,
|
||||
nick,
|
||||
focused,
|
||||
caesar,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -405,6 +407,7 @@ pub struct TreeView<'a, M: Msg, S: MsgStore<M>> {
|
|||
|
||||
nick: String,
|
||||
focused: bool,
|
||||
caesar: i8,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
|
@ -432,6 +435,7 @@ where
|
|||
size,
|
||||
nick: self.nick.clone(),
|
||||
focused: self.focused,
|
||||
caesar: self.caesar,
|
||||
last_cursor: self.state.last_cursor.clone(),
|
||||
last_cursor_top: self.state.last_cursor_top,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ pub struct TreeContext<Id> {
|
|||
pub size: Size,
|
||||
pub nick: String,
|
||||
pub focused: bool,
|
||||
pub caesar: i8,
|
||||
pub last_cursor: Cursor<Id>,
|
||||
pub last_cursor_top: i32,
|
||||
}
|
||||
|
|
@ -190,7 +191,7 @@ where
|
|||
};
|
||||
let highlighted = highlighted && self.context.focused;
|
||||
|
||||
let widget = widgets::msg(highlighted, indent, msg, folded_info);
|
||||
let widget = widgets::msg(highlighted, indent, msg, self.context.caesar, folded_info);
|
||||
let widget = Self::predraw(widget, self.context.size, self.widthdb);
|
||||
Block::new(TreeBlockId::Msg(msg_id), widget, true)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ where
|
|||
size: self.last_size,
|
||||
nick: self.last_nick.clone(),
|
||||
focused: true,
|
||||
caesar: 0,
|
||||
last_cursor: self.last_cursor.clone(),
|
||||
last_cursor_top: self.last_cursor_top,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use toss::{Style, Styled, WidgetExt};
|
|||
use crate::store::Msg;
|
||||
use crate::ui::chat::widgets::{Indent, Seen, Time};
|
||||
use crate::ui::ChatMsg;
|
||||
use crate::util;
|
||||
|
||||
pub const PLACEHOLDER: &str = "[...]";
|
||||
|
||||
|
|
@ -30,6 +31,10 @@ fn style_indent(highlighted: bool) -> Style {
|
|||
}
|
||||
}
|
||||
|
||||
fn style_caesar() -> Style {
|
||||
Style::new().green()
|
||||
}
|
||||
|
||||
fn style_info() -> Style {
|
||||
Style::new().italic().dark_grey()
|
||||
}
|
||||
|
|
@ -46,10 +51,19 @@ pub fn msg<M: Msg + ChatMsg>(
|
|||
highlighted: bool,
|
||||
indent: usize,
|
||||
msg: &M,
|
||||
caesar: i8,
|
||||
folded_info: Option<usize>,
|
||||
) -> Boxed<'static, Infallible> {
|
||||
let (nick, mut content) = msg.styled();
|
||||
|
||||
if caesar != 0 {
|
||||
// Apply caesar in inverse because we're decoding
|
||||
let rotated = util::caesar(content.text(), -caesar);
|
||||
content = content
|
||||
.then_plain("\n")
|
||||
.then(format!("{rotated} [rot{caesar}]"), style_caesar());
|
||||
}
|
||||
|
||||
if let Some(amount) = folded_info {
|
||||
content = content
|
||||
.then_plain("\n")
|
||||
|
|
|
|||
|
|
@ -48,3 +48,22 @@ pub fn convert_to_time_zone(tz: &TimeZone, time: OffsetDateTime) -> Option<Offse
|
|||
|
||||
Some(time.to_offset(utc_offset))
|
||||
}
|
||||
|
||||
pub fn caesar(text: &str, by: i8) -> String {
|
||||
let by = by.rem_euclid(26) as u8;
|
||||
text.chars()
|
||||
.map(|c| {
|
||||
if c.is_ascii_lowercase() {
|
||||
let c = c as u8 - b'a';
|
||||
let c = (c + by) % 26;
|
||||
(c + b'a') as char
|
||||
} else if c.is_ascii_uppercase() {
|
||||
let c = c as u8 - b'A';
|
||||
let c = (c + by) % 26;
|
||||
(c + b'A') as char
|
||||
} else {
|
||||
c
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue