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
|
### Added
|
||||||
- Support for setting window title
|
- Support for setting window title
|
||||||
- More information to room list heading
|
- More information to room list heading
|
||||||
|
- Key bindings for live caesar cipher de- and encoding
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- Key binding to open present page
|
- Key binding to open present page
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,8 @@ default_bindings! {
|
||||||
pub fn mark_older_seen => ["ctrl+s"];
|
pub fn mark_older_seen => ["ctrl+s"];
|
||||||
pub fn info => ["i"];
|
pub fn info => ["i"];
|
||||||
pub fn links => ["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.
|
/// List links found in message.
|
||||||
#[serde(default = "default::tree_action::links")]
|
#[serde(default = "default::tree_action::links")]
|
||||||
pub links: KeyBinding,
|
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)]
|
#[derive(Debug, Default, Deserialize, Document)]
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use toss::widgets::{BoxedAsync, EditorState};
|
||||||
use toss::{Styled, WidgetExt};
|
use toss::{Styled, WidgetExt};
|
||||||
|
|
||||||
use crate::store::{Msg, MsgStore};
|
use crate::store::{Msg, MsgStore};
|
||||||
|
use crate::util;
|
||||||
|
|
||||||
use self::cursor::Cursor;
|
use self::cursor::Cursor;
|
||||||
use self::tree::TreeViewState;
|
use self::tree::TreeViewState;
|
||||||
|
|
@ -33,6 +34,7 @@ pub struct ChatState<M: Msg, S: MsgStore<M>> {
|
||||||
|
|
||||||
cursor: Cursor<M::Id>,
|
cursor: Cursor<M::Id>,
|
||||||
editor: EditorState,
|
editor: EditorState,
|
||||||
|
caesar: i8,
|
||||||
|
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
tree: TreeViewState<M, S>,
|
tree: TreeViewState<M, S>,
|
||||||
|
|
@ -43,6 +45,7 @@ impl<M: Msg, S: MsgStore<M> + Clone> ChatState<M, S> {
|
||||||
Self {
|
Self {
|
||||||
cursor: Cursor::Bottom,
|
cursor: Cursor::Bottom,
|
||||||
editor: EditorState::new(),
|
editor: EditorState::new(),
|
||||||
|
caesar: 0,
|
||||||
|
|
||||||
mode: Mode::Tree,
|
mode: Mode::Tree,
|
||||||
tree: TreeViewState::new(store.clone()),
|
tree: TreeViewState::new(store.clone()),
|
||||||
|
|
@ -68,7 +71,13 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::Tree => self
|
Mode::Tree => self
|
||||||
.tree
|
.tree
|
||||||
.widget(&mut self.cursor, &mut self.editor, nick, focused)
|
.widget(
|
||||||
|
&mut self.cursor,
|
||||||
|
&mut self.editor,
|
||||||
|
nick,
|
||||||
|
focused,
|
||||||
|
self.caesar,
|
||||||
|
)
|
||||||
.boxed_async(),
|
.boxed_async(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,7 +94,7 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
|
||||||
S: Send + Sync,
|
S: Send + Sync,
|
||||||
S::Error: Send,
|
S::Error: Send,
|
||||||
{
|
{
|
||||||
match self.mode {
|
let reaction = match self.mode {
|
||||||
Mode::Tree => {
|
Mode::Tree => {
|
||||||
self.tree
|
self.tree
|
||||||
.handle_input_event(
|
.handle_input_event(
|
||||||
|
|
@ -95,9 +104,28 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
|
||||||
&mut self.editor,
|
&mut self.editor,
|
||||||
can_compose,
|
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> {
|
pub fn cursor(&self) -> Option<&M::Id> {
|
||||||
|
|
|
||||||
|
|
@ -386,6 +386,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
|
||||||
editor: &'a mut EditorState,
|
editor: &'a mut EditorState,
|
||||||
nick: String,
|
nick: String,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
|
caesar: i8,
|
||||||
) -> TreeView<'a, M, S> {
|
) -> TreeView<'a, M, S> {
|
||||||
TreeView {
|
TreeView {
|
||||||
state: self,
|
state: self,
|
||||||
|
|
@ -393,6 +394,7 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
|
||||||
editor,
|
editor,
|
||||||
nick,
|
nick,
|
||||||
focused,
|
focused,
|
||||||
|
caesar,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -405,6 +407,7 @@ pub struct TreeView<'a, M: Msg, S: MsgStore<M>> {
|
||||||
|
|
||||||
nick: String,
|
nick: String,
|
||||||
focused: bool,
|
focused: bool,
|
||||||
|
caesar: i8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|
@ -432,6 +435,7 @@ where
|
||||||
size,
|
size,
|
||||||
nick: self.nick.clone(),
|
nick: self.nick.clone(),
|
||||||
focused: self.focused,
|
focused: self.focused,
|
||||||
|
caesar: self.caesar,
|
||||||
last_cursor: self.state.last_cursor.clone(),
|
last_cursor: self.state.last_cursor.clone(),
|
||||||
last_cursor_top: self.state.last_cursor_top,
|
last_cursor_top: self.state.last_cursor_top,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ pub struct TreeContext<Id> {
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
pub nick: String,
|
pub nick: String,
|
||||||
pub focused: bool,
|
pub focused: bool,
|
||||||
|
pub caesar: i8,
|
||||||
pub last_cursor: Cursor<Id>,
|
pub last_cursor: Cursor<Id>,
|
||||||
pub last_cursor_top: i32,
|
pub last_cursor_top: i32,
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +191,7 @@ where
|
||||||
};
|
};
|
||||||
let highlighted = highlighted && self.context.focused;
|
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);
|
let widget = Self::predraw(widget, self.context.size, self.widthdb);
|
||||||
Block::new(TreeBlockId::Msg(msg_id), widget, true)
|
Block::new(TreeBlockId::Msg(msg_id), widget, true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ where
|
||||||
size: self.last_size,
|
size: self.last_size,
|
||||||
nick: self.last_nick.clone(),
|
nick: self.last_nick.clone(),
|
||||||
focused: true,
|
focused: true,
|
||||||
|
caesar: 0,
|
||||||
last_cursor: self.last_cursor.clone(),
|
last_cursor: self.last_cursor.clone(),
|
||||||
last_cursor_top: self.last_cursor_top,
|
last_cursor_top: self.last_cursor_top,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use toss::{Style, Styled, WidgetExt};
|
||||||
use crate::store::Msg;
|
use crate::store::Msg;
|
||||||
use crate::ui::chat::widgets::{Indent, Seen, Time};
|
use crate::ui::chat::widgets::{Indent, Seen, Time};
|
||||||
use crate::ui::ChatMsg;
|
use crate::ui::ChatMsg;
|
||||||
|
use crate::util;
|
||||||
|
|
||||||
pub const PLACEHOLDER: &str = "[...]";
|
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 {
|
fn style_info() -> Style {
|
||||||
Style::new().italic().dark_grey()
|
Style::new().italic().dark_grey()
|
||||||
}
|
}
|
||||||
|
|
@ -46,10 +51,19 @@ pub fn msg<M: Msg + ChatMsg>(
|
||||||
highlighted: bool,
|
highlighted: bool,
|
||||||
indent: usize,
|
indent: usize,
|
||||||
msg: &M,
|
msg: &M,
|
||||||
|
caesar: i8,
|
||||||
folded_info: Option<usize>,
|
folded_info: Option<usize>,
|
||||||
) -> Boxed<'static, Infallible> {
|
) -> Boxed<'static, Infallible> {
|
||||||
let (nick, mut content) = msg.styled();
|
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 {
|
if let Some(amount) = folded_info {
|
||||||
content = content
|
content = content
|
||||||
.then_plain("\n")
|
.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))
|
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