Skip to content

Commit bd69e10

Browse files
committed
refactor!: use command-based api for modifying buffer
This ensures that the buffer is never directly modified.
1 parent 525709d commit bd69e10

File tree

14 files changed

+369
-258
lines changed

14 files changed

+369
-258
lines changed

crates/cmd_prompt/src/actions/actions/basic_input.rs

Lines changed: 32 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,108 +2,59 @@ use bevy::{asset::AsAssetId, input::keyboard::Key};
22

33
use crate::prelude::*;
44

5-
pub fn delete_char(
6-
input: In<ConsoleActionSystemInput>,
7-
mut console_q: Query<(&mut ConsoleInputText, &mut ConsoleBuffer)>,
8-
) {
9-
if let Ok((mut input, mut buffer)) = console_q.get_mut(input.console_id) {
10-
let len = buffer.pop_front().map(|c| c.len_utf8()).unwrap_or_default();
11-
input.move_cursor(-(len as isize));
12-
} else {
13-
error!(
14-
"Could not delete char from console with id {}",
15-
input.console_id
16-
);
17-
}
5+
pub fn delete_char(input: In<ConsoleActionSystemInput>, mut commands: Commands) {
6+
commands.write_message(ConsoleBufferMsg::new(
7+
input.console_id,
8+
ConsoleBufferMsgKind::RemoveCharacter,
9+
));
1810
}
19-
pub fn delete_word(
20-
input: In<ConsoleActionSystemInput>,
21-
mut console_q: Query<&mut ConsoleInputText>,
22-
) {
23-
if let Ok(mut input) = console_q.get_mut(input.console_id) {
24-
// TODO
25-
// let last_ws = input.text.rfind(char::is_whitespace).unwrap_or_default();
26-
// input.text.truncate(last_ws);
27-
// input.set_cursor(last_ws)
28-
} else {
29-
error!(
30-
"Could not delete word from console with id {}",
31-
input.console_id
32-
);
33-
}
11+
pub fn delete_word(input: In<ConsoleActionSystemInput>, mut commands: Commands) {
12+
commands.write_message(ConsoleBufferMsg::new(
13+
input.console_id,
14+
ConsoleBufferMsgKind::RemoveWord,
15+
));
3416
}
35-
pub fn write_char(
36-
input: In<ConsoleActionSystemInput>,
37-
mut console_q: Query<&mut ConsoleInputText>,
38-
mut commands: Commands,
39-
) {
40-
if let Ok(mut input_text) = console_q.get_mut(input.console_id) {
41-
for key in input.matched_logical_keys() {
42-
match key {
43-
Key::Character(c) => {
44-
commands.write_message(ConsoleWriteMsg {
45-
message: c.to_string(),
46-
console_id: input.console_id,
47-
pos: input_text.anchor + input_text.cursor(),
48-
});
49-
input_text.move_cursor(c.len() as isize);
50-
}
51-
Key::Space => {
52-
commands.write_message(ConsoleWriteMsg {
53-
message: " ".to_string(),
54-
console_id: input.console_id,
55-
pos: input_text.anchor + input_text.cursor(),
56-
});
57-
input_text.move_cursor(1);
58-
}
59-
Key::Enter => {
60-
commands.write_message(ConsoleWriteMsg {
61-
message: "\n".to_string(),
62-
console_id: input.console_id,
63-
pos: input_text.anchor + input_text.cursor(),
64-
});
65-
input_text.move_cursor(1);
66-
}
67-
_ => {}
17+
pub fn write_char(input: In<ConsoleActionSystemInput>, mut commands: Commands) {
18+
for key in input.matched_logical_keys() {
19+
match key {
20+
Key::Character(c) => {
21+
commands.write_message(ConsoleBufferMsg::write(input.console_id, c.to_string()));
22+
}
23+
Key::Space => {
24+
commands.write_message(ConsoleBufferMsg::write(input.console_id, " ".to_string()));
25+
}
26+
Key::Enter => {
27+
commands.write_message(ConsoleBufferMsg::write(input.console_id, "\n".to_string()));
6828
}
29+
_ => {}
6930
}
70-
commands.write_message(ConsoleViewMsg::jump_to_bottom(input.console_id));
71-
} else {
72-
error!(
73-
"Could not write char to console with id {}",
74-
input.console_id
75-
);
7631
}
32+
commands.write_message(ConsoleViewMsg::jump_to_bottom(input.console_id));
7733
}
7834

7935
pub fn submit(
8036
input: In<ConsoleActionSystemInput>,
81-
mut query: Query<(
82-
&mut ConsoleInputText,
83-
&ConsoleAssetHandle<ConsoleHistory>,
84-
&ConsoleBuffer,
85-
)>,
37+
mut query: Query<(&ConsoleBuffer, &ConsoleAssetHandle<ConsoleHistory>)>,
8638
mut assets: ResMut<Assets<ConsoleHistory>>,
8739
mut commands: Commands,
8840
) {
89-
if let Ok((mut input_text, history_handle, buffer)) = query.get_mut(input.console_id) {
41+
if let Ok((buffer, history_handle)) = query.get_mut(input.console_id) {
9042
let history = assets.get_mut(history_handle.as_asset_id());
9143
if history.is_none() {
9244
error!("Failed to get console history!");
9345
return;
9446
}
9547
let history = history.unwrap();
96-
let command = buffer.range(input_text.anchor, buffer.len() - input_text.anchor);
97-
if let Some(event) = SubmitEvent::new(input.console_id, command.clone()) {
48+
let command = buffer.user_input();
49+
history.push(command.clone());
50+
if let Some(event) = SubmitEvent::new(input.console_id, command) {
9851
commands.trigger(event);
9952
} else {
100-
commands.trigger(ConsolePrintln::new(
53+
commands.write_message(ConsoleBufferMsg::println(
10154
input.console_id,
10255
"Invalid shell expression".to_string(),
10356
));
10457
}
105-
input_text.set_cursor(0);
106-
history.push(command);
10758
} else {
10859
error!("Could not submit from console with id {}", input.console_id);
10960
}
@@ -115,19 +66,11 @@ fn on_scroll(input: In<ConsoleActionSystemInput>, mut commands: Commands) {
11566
}
11667

11768
fn clear(input: In<ConsoleActionSystemInput>, mut commands: Commands) {
118-
commands.run_system_cached_with(clear_buffer, input.console_id);
69+
commands.write_message(ConsoleBufferMsg::clear_buffer(input.console_id));
11970
}
12071

121-
fn clear_input(
122-
input: In<ConsoleActionSystemInput>,
123-
mut query: Query<(&mut ConsoleInputText, &mut ConsoleBuffer)>,
124-
) {
125-
// TODO: Don't edit buffer directly! Use a message
126-
let (mut input, mut buffer) = query.get_mut(input.console_id).unwrap();
127-
for _ in 0..(buffer.len() - input.anchor) {
128-
buffer.pop_front();
129-
}
130-
input.set_cursor(0);
72+
fn clear_input(input: In<ConsoleActionSystemInput>, mut commands: Commands) {
73+
commands.write_message(ConsoleBufferMsg::clear_input(input.console_id));
13174
}
13275

13376
pub fn scroll_line(input: In<ConsoleActionSystemInput>, mut commands: Commands) {

crates/cmd_prompt/src/actions/actions/history.rs

Lines changed: 38 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ use crate::prelude::*;
44

55
pub fn set_from_history(
66
input: In<ConsoleActionSystemInput>,
7-
mut q_console: Query<(&mut ConsoleInputText, &ConsoleAssetHandle<ConsoleHistory>)>,
7+
mut q_console: Query<(&ConsoleBuffer, &ConsoleAssetHandle<ConsoleHistory>)>,
88
mut assets: ResMut<Assets<ConsoleHistory>>,
9+
mut commands: Commands,
910
mut history_idx: Local<usize>,
1011
mut filtered_history: Local<Option<Vec<usize>>>,
1112
mut original_value: Local<Option<String>>,
@@ -27,31 +28,29 @@ pub fn set_from_history(
2728
_ => {}
2829
}
2930
if matches!(key, Key::ArrowUp | Key::ArrowDown) {
30-
let (mut input_text, handle) = q_console.get_mut(input.console_id).unwrap();
31+
let (buffer, handle) = q_console.get_mut(input.console_id).unwrap();
3132
let history = assets
3233
.get_mut(handle.id())
3334
.expect("History asset should exist");
3435
if filtered_history.is_none() {
35-
// *original_value = Some(std::mem::take(&mut input_text.text));
36+
let original_value = buffer.user_input();
3637
let f = history
3738
.iter()
3839
.enumerate()
39-
.filter_map(|(i, s)| s.starts_with(original_value.as_ref().unwrap()).then_some(i))
40+
.filter_map(|(i, s)| s.starts_with(&original_value).then_some(i))
4041
.collect::<Vec<_>>();
4142
*filtered_history = Some(f);
4243
}
4344
let fh = filtered_history.as_ref().unwrap();
4445
let ov = original_value.as_ref().unwrap();
4546
*history_idx = history_idx.saturating_add_signed(value).min(fh.len());
46-
if *history_idx == 0 {
47-
// TODO: ConsoleBuffer::replace / splice from text input anchor till end
48-
// input_text.text = ov.clone();
47+
let value = if *history_idx == 0 {
48+
ov.clone()
4949
} else {
5050
let idx = fh[fh.len().saturating_sub(*history_idx)];
51-
// input_text.text = history[idx].clone();
52-
}
53-
// let end = input_text.text.len();
54-
// input_text.set_cursor(end);
51+
history[idx].clone()
52+
};
53+
commands.write_message(ConsoleBufferMsg::set_input(input.console_id, value));
5554
}
5655
}
5756

@@ -65,8 +64,6 @@ pub fn plugin(app: &mut App) {
6564

6665
#[cfg(test)]
6766
mod test {
68-
use std::num::NonZeroU8;
69-
7067
use crate::prelude::*;
7168
use crate::test_harness;
7269
use bevy::ecs::relationship::RelationshipSourceCollection;
@@ -90,11 +87,8 @@ mod test {
9087
macro_rules! check_and_input {
9188
($app:ident, $step:expr, $value:expr, $key:ident) => {
9289
$app.add_step($step, |world: &mut World| {
93-
let input = world
94-
.query::<&ConsoleInputText>()
95-
.single_mut(world)
96-
.unwrap();
97-
// assert_eq!(input.text, $value);
90+
let input = world.query::<&ConsoleBuffer>().single_mut(world).unwrap();
91+
assert_eq!(input.user_input(), $value);
9892
world.write_message(key_input(KeyCode::$key, Key::$key, ButtonState::Pressed));
9993
world.write_message(key_input(KeyCode::$key, Key::$key, ButtonState::Released));
10094
world.resource_mut::<NextState<Step>>().set(Step($step + 1));
@@ -106,36 +100,27 @@ mod test {
106100
fn test_history() {
107101
let mut app = App::new();
108102
app.add_plugins(test_harness::plugin);
109-
app.add_systems(
110-
Startup,
111-
|mut commands: Commands, mut focus: ResMut<InputFocus>| {
112-
let id = commands.spawn(Console).id();
113-
focus.0 = Some(id);
114-
},
115-
);
103+
let console_id = app.world_mut().spawn(Console).id();
104+
app.add_systems(Startup, move |mut focus: ResMut<InputFocus>| {
105+
focus.0 = Some(console_id);
106+
});
116107
for step in 0..3 {
117108
app.add_step(
118109
step,
119-
move |mut q: Query<&mut ConsoleInputText>,
120-
mut commands: Commands,
121-
mut next_step: ResMut<NextState<Step>>| {
122-
if let Ok(mut input) = q.single_mut() {
123-
// input.text = step.to_string();
124-
commands.write_message(key_input(
125-
KeyCode::Enter,
126-
Key::Enter,
127-
ButtonState::Pressed,
128-
));
129-
commands.write_message(key_input(
130-
KeyCode::Enter,
131-
Key::Enter,
132-
ButtonState::Released,
133-
));
134-
next_step.set(Step(step + 1));
135-
} else {
136-
error!("Failed to get console");
137-
commands.write_message(AppExit::Error(NonZeroU8::new(1).unwrap()));
138-
}
110+
move |mut commands: Commands, mut next_step: ResMut<NextState<Step>>| {
111+
commands
112+
.write_message(ConsoleBufferMsg::set_input(console_id, step.to_string()));
113+
commands.write_message(key_input(
114+
KeyCode::Enter,
115+
Key::Enter,
116+
ButtonState::Pressed,
117+
));
118+
commands.write_message(key_input(
119+
KeyCode::Enter,
120+
Key::Enter,
121+
ButtonState::Released,
122+
));
123+
next_step.set(Step(step + 1));
139124
},
140125
);
141126
}
@@ -173,14 +158,14 @@ mod test {
173158
check_and_input!(app, 4, "2", ArrowUp);
174159
check_and_input!(app, 5, "1", ArrowUp);
175160
check_and_input!(app, 6, "0", ArrowDown);
176-
app.add_step(7, |world: &mut World| {
177-
let input = world
178-
.query::<&ConsoleInputText>()
179-
.single_mut(world)
180-
.unwrap();
181-
// assert_eq!(input.text, "1");
182-
world.write_message(AppExit::Success);
183-
});
161+
app.add_step(
162+
7,
163+
move |buffer: Query<&ConsoleBuffer>, mut commands: Commands| {
164+
let buffer = buffer.get(console_id).unwrap();
165+
assert_eq!(buffer.user_input(), "1");
166+
commands.write_message(AppExit::Success);
167+
},
168+
);
184169
app.run();
185170
}
186171
}

crates/cmd_prompt/src/commands/app_ext.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,6 @@ fn dispatch_cmd<T: ConsoleCommand>(
3535
Ok(())
3636
})();
3737
if let Err(e) = res {
38-
commands.trigger(ConsolePrintln::new(input.console_id(), e));
38+
commands.write_message(ConsoleBufferMsg::println(input.console_id, e));
3939
}
4040
}

crates/cmd_prompt/src/commands/commands/clear.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,9 @@ use crate::prelude::*;
66
#[command(name = "clear")]
77
struct ClearCmd;
88

9-
pub fn clear_buffer(
10-
id: In<Entity>,
11-
mut console_q: Query<(&mut ConsoleBuffer, &mut ConsoleInputText)>,
12-
mut commands: Commands,
13-
) {
14-
let (mut buffer, mut input_text) = console_q.get_mut(*id).unwrap();
15-
buffer.clear();
9+
pub fn clear_buffer(id: In<Entity>, mut commands: Commands) {
10+
commands.write_message(ConsoleBufferMsg::clear_buffer(id.0));
1611
commands.write_message(ConsoleViewMsg::jump_to_bottom(*id));
17-
input_text.anchor = 0;
1812
}
1913

2014
fn on_find_msg(mut reader: MessageReader<CommandMsg<ClearCmd>>, mut commands: Commands) {

crates/cmd_prompt/src/commands/commands/echo.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ fn on_find_msg(mut reader: MessageReader<CommandMsg<EchoCmd>>, mut commands: Com
7070
} else {
7171
text
7272
};
73-
commands.trigger(ConsolePrintln::new(msg.console_id, message));
73+
commands.write_message(ConsoleBufferMsg::println(msg.console_id, message));
7474
}
7575
}
7676

crates/cmd_prompt/src/commands/data.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,6 @@ pub struct CommandMsg<T: ConsoleCommand> {
5050
}
5151
impl<T: ConsoleCommand> CommandMsg<T> {
5252
pub fn println(&self, commands: &mut Commands, message: String) {
53-
commands.trigger(ConsolePrintln::new(self.console_id, message));
53+
commands.write_message(ConsoleBufferMsg::println(self.console_id, message));
5454
}
5555
}

crates/cmd_prompt/src/commands/events.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ fn on_submit(trigger: On<SubmitEvent>, cmds: Res<ConsoleCommands>, mut commands:
44
let name = r!(trigger.args().first());
55
if let Some(cmd) = cmds.get(name) {
66
commands.run_system_with(cmd.dispatch, trigger.event().clone());
7+
commands.write_message(ConsoleBufferMsg::clear_input(trigger.console_id));
8+
commands.write_message(ConsoleBufferMsg::reset_cursor(trigger.console_id));
79
} else {
8-
commands.trigger(ConsolePrintln::new(
10+
commands.write_message(ConsoleBufferMsg::println(
911
trigger.console_id,
1012
format!("Unknown command '{name}'"),
1113
));

0 commit comments

Comments
 (0)