mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-19 21:29:52 -07:00
Support automatically toggling room focus (#337)
This commit is contained in:
parent
84eaadc09a
commit
9ed9400b67
4 changed files with 203 additions and 17 deletions
|
@ -1766,6 +1766,13 @@ impl RoomFocus {
|
|||
pub fn is_msgbar(&self) -> bool {
|
||||
matches!(self, RoomFocus::MessageBar)
|
||||
}
|
||||
|
||||
pub fn toggle(&mut self) {
|
||||
*self = match self {
|
||||
RoomFocus::MessageBar => RoomFocus::Scrollback,
|
||||
RoomFocus::Scrollback => RoomFocus::MessageBar,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Identifiers used to track where a mark was placed.
|
||||
|
|
|
@ -1141,18 +1141,18 @@ mod tests {
|
|||
let mut cmds = setup_commands();
|
||||
let ctx = EditContext::default();
|
||||
|
||||
let cmd = format!("room notify set mute");
|
||||
let res = cmds.input_cmd(&cmd, ctx.clone()).unwrap();
|
||||
let cmd = "room notify set mute";
|
||||
let res = cmds.input_cmd(cmd, ctx.clone()).unwrap();
|
||||
let act = RoomAction::Set(RoomField::NotificationMode, "mute".into());
|
||||
assert_eq!(res, vec![(act.into(), ctx.clone())]);
|
||||
|
||||
let cmd = format!("room notify unset");
|
||||
let res = cmds.input_cmd(&cmd, ctx.clone()).unwrap();
|
||||
let cmd = "room notify unset";
|
||||
let res = cmds.input_cmd(cmd, ctx.clone()).unwrap();
|
||||
let act = RoomAction::Unset(RoomField::NotificationMode);
|
||||
assert_eq!(res, vec![(act.into(), ctx.clone())]);
|
||||
|
||||
let cmd = format!("room notify show");
|
||||
let res = cmds.input_cmd(&cmd, ctx.clone()).unwrap();
|
||||
let cmd = "room notify show";
|
||||
let res = cmds.input_cmd(cmd, ctx.clone()).unwrap();
|
||||
let act = RoomAction::Show(RoomField::NotificationMode);
|
||||
assert_eq!(res, vec![(act.into(), ctx.clone())]);
|
||||
}
|
||||
|
|
|
@ -638,10 +638,7 @@ impl ChatState {
|
|||
}
|
||||
|
||||
pub fn focus_toggle(&mut self) {
|
||||
self.focus = match self.focus {
|
||||
RoomFocus::Scrollback => RoomFocus::MessageBar,
|
||||
RoomFocus::MessageBar => RoomFocus::Scrollback,
|
||||
};
|
||||
self.focus.toggle();
|
||||
}
|
||||
|
||||
pub fn room(&self) -> &MatrixRoom {
|
||||
|
@ -652,6 +649,14 @@ impl ChatState {
|
|||
&self.room_id
|
||||
}
|
||||
|
||||
pub fn auto_toggle_focus(
|
||||
&mut self,
|
||||
act: &EditorAction,
|
||||
ctx: &ProgramContext,
|
||||
) -> Option<EditorAction> {
|
||||
auto_toggle_focus(&mut self.focus, act, ctx, &self.scrollback, &mut self.tbox)
|
||||
}
|
||||
|
||||
pub fn typing_notice(
|
||||
&self,
|
||||
act: &EditorAction,
|
||||
|
@ -754,8 +759,15 @@ impl Editable<ProgramContext, ProgramStore, IambInfo> for ChatState {
|
|||
ctx: &ProgramContext,
|
||||
store: &mut ProgramStore,
|
||||
) -> EditResult<EditInfo, IambInfo> {
|
||||
// Check whether we should automatically switch between the message bar
|
||||
// or message scrollback, and use an adjusted action if we do so.
|
||||
let adjusted = self.auto_toggle_focus(act, ctx);
|
||||
let act = adjusted.as_ref().unwrap_or(act);
|
||||
|
||||
// Send typing notice if needed.
|
||||
self.typing_notice(act, ctx, store);
|
||||
|
||||
// And now we can finally run the editor command.
|
||||
match delegate!(self, w => w.editor_command(act, ctx, store)) {
|
||||
res @ Ok(_) => res,
|
||||
Err(EditError::WrongBuffer(IambBufferId::Room(room_id, thread, focus)))
|
||||
|
@ -993,3 +1005,158 @@ fn cmd(open_command: &Vec<String>) -> Option<Command> {
|
|||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn auto_toggle_focus(
|
||||
focus: &mut RoomFocus,
|
||||
act: &EditorAction,
|
||||
ctx: &ProgramContext,
|
||||
scrollback: &ScrollbackState,
|
||||
tbox: &mut TextBoxState<IambInfo>,
|
||||
) -> Option<EditorAction> {
|
||||
let is_insert = ctx.get_insert_style().is_some();
|
||||
|
||||
match (focus, act) {
|
||||
(f @ RoomFocus::Scrollback, _) if is_insert => {
|
||||
// Insert mode commands should switch focus.
|
||||
f.toggle();
|
||||
None
|
||||
},
|
||||
(f @ RoomFocus::Scrollback, EditorAction::InsertText(_)) => {
|
||||
// Pasting or otherwise inserting text should switch.
|
||||
f.toggle();
|
||||
None
|
||||
},
|
||||
(
|
||||
f @ RoomFocus::Scrollback,
|
||||
EditorAction::Edit(
|
||||
op,
|
||||
EditTarget::Motion(mov @ MoveType::Line(MoveDir1D::Next), count),
|
||||
),
|
||||
) if ctx.resolve(op).is_motion() => {
|
||||
let count = ctx.resolve(count);
|
||||
|
||||
if count > 0 && scrollback.is_latest() {
|
||||
// Trying to move down a line when already at the end of room history should
|
||||
// switch.
|
||||
f.toggle();
|
||||
|
||||
// And decrement the count for the action.
|
||||
let count = count.saturating_sub(1).into();
|
||||
let target = EditTarget::Motion(mov.clone(), count);
|
||||
let dec = EditorAction::Edit(op.clone(), target);
|
||||
|
||||
Some(dec)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
(
|
||||
f @ RoomFocus::MessageBar,
|
||||
EditorAction::Edit(
|
||||
op,
|
||||
EditTarget::Motion(mov @ MoveType::Line(MoveDir1D::Previous), count),
|
||||
),
|
||||
) if !is_insert && ctx.resolve(op).is_motion() => {
|
||||
let count = ctx.resolve(count);
|
||||
|
||||
if count > 0 && tbox.get_cursor().y == 0 {
|
||||
// Trying to move up a line when already at the top of the msgbar should
|
||||
// switch as long as we're not in Insert mode.
|
||||
f.toggle();
|
||||
|
||||
// And decrement the count for the action.
|
||||
let count = count.saturating_sub(1).into();
|
||||
let target = EditTarget::Motion(mov.clone(), count);
|
||||
let dec = EditorAction::Edit(op.clone(), target);
|
||||
|
||||
Some(dec)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
(RoomFocus::Scrollback, _) | (RoomFocus::MessageBar, _) => {
|
||||
// Do not switch.
|
||||
None
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use modalkit::actions::{EditAction, InsertTextAction};
|
||||
|
||||
use crate::tests::{mock_store, TEST_ROOM1_ID};
|
||||
|
||||
macro_rules! move_line {
|
||||
($dir: expr, $count: expr) => {
|
||||
EditorAction::Edit(
|
||||
EditAction::Motion.into(),
|
||||
EditTarget::Motion(MoveType::Line($dir), $count.into()),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_auto_focus() {
|
||||
let mut store = mock_store().await;
|
||||
let ctx = ProgramContext::default();
|
||||
|
||||
let room_id = TEST_ROOM1_ID.clone();
|
||||
let scrollback = ScrollbackState::new(room_id.clone(), None);
|
||||
|
||||
let id = IambBufferId::Room(room_id, None, RoomFocus::MessageBar);
|
||||
let ebuf = store.load_buffer(id);
|
||||
let mut tbox = TextBoxState::new(ebuf);
|
||||
|
||||
// Start out focused on the scrollback.
|
||||
let mut focused = RoomFocus::Scrollback;
|
||||
|
||||
// Inserting text toggles:
|
||||
let act = EditorAction::InsertText(InsertTextAction::Type(
|
||||
Char::from('a').into(),
|
||||
MoveDir1D::Next,
|
||||
1.into(),
|
||||
));
|
||||
let res = auto_toggle_focus(&mut focused, &act, &ctx, &scrollback, &mut tbox);
|
||||
assert_eq!(focused, RoomFocus::MessageBar);
|
||||
assert!(res.is_none());
|
||||
|
||||
// Going down in message bar doesn't toggle:
|
||||
let act = move_line!(MoveDir1D::Next, 1);
|
||||
let res = auto_toggle_focus(&mut focused, &act, &ctx, &scrollback, &mut tbox);
|
||||
assert_eq!(focused, RoomFocus::MessageBar);
|
||||
assert!(res.is_none());
|
||||
|
||||
// But going up will:
|
||||
let act = move_line!(MoveDir1D::Previous, 1);
|
||||
let res = auto_toggle_focus(&mut focused, &act, &ctx, &scrollback, &mut tbox);
|
||||
assert_eq!(focused, RoomFocus::Scrollback);
|
||||
assert_eq!(res, Some(move_line!(MoveDir1D::Previous, 0)));
|
||||
|
||||
// Going up in scrollback doesn't toggle:
|
||||
let act = move_line!(MoveDir1D::Previous, 1);
|
||||
let res = auto_toggle_focus(&mut focused, &act, &ctx, &scrollback, &mut tbox);
|
||||
assert_eq!(focused, RoomFocus::Scrollback);
|
||||
assert_eq!(res, None);
|
||||
|
||||
// And then go back down:
|
||||
let act = move_line!(MoveDir1D::Next, 1);
|
||||
let res = auto_toggle_focus(&mut focused, &act, &ctx, &scrollback, &mut tbox);
|
||||
assert_eq!(focused, RoomFocus::MessageBar);
|
||||
assert_eq!(res, Some(move_line!(MoveDir1D::Next, 0)));
|
||||
|
||||
// Go up 2 will go up 1 in scrollback:
|
||||
let act = move_line!(MoveDir1D::Previous, 2);
|
||||
let res = auto_toggle_focus(&mut focused, &act, &ctx, &scrollback, &mut tbox);
|
||||
assert_eq!(focused, RoomFocus::Scrollback);
|
||||
assert_eq!(res, Some(move_line!(MoveDir1D::Previous, 1)));
|
||||
|
||||
// Go down 3 will go down 2 in messagebar:
|
||||
let act = move_line!(MoveDir1D::Next, 3);
|
||||
let res = auto_toggle_focus(&mut focused, &act, &ctx, &scrollback, &mut tbox);
|
||||
assert_eq!(focused, RoomFocus::MessageBar);
|
||||
assert_eq!(res, Some(move_line!(MoveDir1D::Next, 2)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,14 +79,20 @@ fn nth_key_before(pos: MessageKey, n: usize, thread: &Messages) -> MessageKey {
|
|||
}
|
||||
|
||||
fn nth_before(pos: MessageKey, n: usize, thread: &Messages) -> MessageCursor {
|
||||
nth_key_before(pos, n, thread).into()
|
||||
let key = nth_key_before(pos, n, thread);
|
||||
|
||||
if matches!(thread.last_key_value(), Some((last, _)) if &key == last) {
|
||||
MessageCursor::latest()
|
||||
} else {
|
||||
MessageCursor::from(key)
|
||||
}
|
||||
}
|
||||
|
||||
fn nth_key_after(pos: MessageKey, n: usize, thread: &Messages) -> MessageKey {
|
||||
fn nth_key_after(pos: MessageKey, n: usize, thread: &Messages) -> Option<MessageKey> {
|
||||
let mut end = &pos;
|
||||
let iter = thread.range(&pos..).enumerate();
|
||||
let mut iter = thread.range(&pos..).enumerate();
|
||||
|
||||
for (i, (key, _)) in iter {
|
||||
for (i, (key, _)) in iter.by_ref() {
|
||||
end = key;
|
||||
|
||||
if i >= n {
|
||||
|
@ -94,11 +100,12 @@ fn nth_key_after(pos: MessageKey, n: usize, thread: &Messages) -> MessageKey {
|
|||
}
|
||||
}
|
||||
|
||||
end.clone()
|
||||
// Avoid returning the key if it's at the end.
|
||||
iter.next().map(|_| end.clone())
|
||||
}
|
||||
|
||||
fn nth_after(pos: MessageKey, n: usize, thread: &Messages) -> MessageCursor {
|
||||
nth_key_after(pos, n, thread).into()
|
||||
nth_key_after(pos, n, thread).map(MessageCursor::from).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn prevmsg<'a>(key: &MessageKey, thread: &'a Messages) -> Option<&'a Message> {
|
||||
|
@ -150,6 +157,10 @@ impl ScrollbackState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_latest(&self) -> bool {
|
||||
self.cursor.timestamp.is_none()
|
||||
}
|
||||
|
||||
pub fn goto_latest(&mut self) {
|
||||
self.cursor = MessageCursor::latest();
|
||||
}
|
||||
|
@ -1524,8 +1535,9 @@ mod tests {
|
|||
scrollback.edit(&EditAction::Motion, &next(1), &ctx, &mut store).unwrap();
|
||||
assert_eq!(scrollback.cursor, MSG5_KEY.clone().into());
|
||||
|
||||
// And one more becomes "latest" cursor:
|
||||
scrollback.edit(&EditAction::Motion, &next(1), &ctx, &mut store).unwrap();
|
||||
assert_eq!(scrollback.cursor, MSG1_KEY.clone().into());
|
||||
assert_eq!(scrollback.cursor, MessageCursor::latest());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue