Skip to content

Commit 07216b3

Browse files
committed
refactor: simplify editor interfaces
1 parent 7bbec4b commit 07216b3

14 files changed

Lines changed: 274 additions & 179 deletions

File tree

src/app.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use ratatui::{
1010

1111
use crate::{
1212
exit::ExitHandler,
13-
screen::{Screen, ScreenCommand, home::MenuScreen},
13+
screen::{EventResult, Screen, ScreenCommand, home::MenuScreen},
1414
theme::Theme,
1515
widget::footer::Footer,
1616
};
@@ -86,9 +86,13 @@ impl App {
8686
| MouseEventKind::ScrollRight
8787
) =>
8888
{
89-
if let Some(cmd) = self.active_screen.handle_scroll(mouse) {
90-
self.exit_handler.reset();
91-
self.process_cmd(cmd);
89+
match self.active_screen.handle_scroll(mouse) {
90+
EventResult::Ignored => {}
91+
EventResult::Handled => self.exit_handler.reset(),
92+
EventResult::Command(cmd) => {
93+
self.exit_handler.reset();
94+
self.process_cmd(cmd);
95+
}
9296
}
9397
}
9498
_ => {}
@@ -101,15 +105,18 @@ impl App {
101105
self.exit_handler.press_ctrl_c();
102106
return;
103107
}
104-
if let Some(cmd) = self.active_screen.handle_key(key) {
105-
self.exit_handler.reset();
106-
self.process_cmd(cmd);
108+
match self.active_screen.handle_key(key) {
109+
EventResult::Ignored => {}
110+
EventResult::Handled => self.exit_handler.reset(),
111+
EventResult::Command(cmd) => {
112+
self.exit_handler.reset();
113+
self.process_cmd(cmd);
114+
}
107115
}
108116
}
109117

110118
fn process_cmd(&mut self, cmd: ScreenCommand) {
111119
match cmd {
112-
ScreenCommand::Stay => {}
113120
ScreenCommand::OpenEditor { path } => {
114121
self.active_screen =
115122
Box::new(super::screen::editor::TableScreen::new(self.theme, path));

src/model/workbook.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,26 @@ impl Workbook {
7272
self.cells.get(&addr)
7373
}
7474

75-
/// 写入指定地址的单元格
75+
/// 写入指定地址的文本
7676
///
77-
/// 该方法不检查地址是否越界,调用者负责传入合法地址。
78-
pub fn set_cell(&mut self, addr: CellAddress, raw: String, value: CellValue) {
79-
self.cells.insert(addr, Cell { raw, value });
77+
/// 该方法不检查地址是否越界,调用者负责传入合法地址。空文本会清除单元格。
78+
pub fn set_text(&mut self, addr: CellAddress, raw: String) {
79+
if raw.is_empty() {
80+
self.clear_cell(addr);
81+
} else {
82+
self.cells.insert(
83+
addr,
84+
Cell {
85+
value: CellValue::Text(raw.clone()),
86+
raw,
87+
},
88+
);
89+
}
90+
}
91+
92+
/// 清除指定地址的单元格内容。
93+
pub fn clear_cell(&mut self, addr: CellAddress) {
94+
self.cells.remove(&addr);
8095
}
8196

8297
/// 删除指定行,该行上方的行不动,下方行上移,总行数减一。

src/screen/editor/context.rs

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,70 @@ use super::{mode::Selection, viewport::Viewport};
88
/// 但涉及多步不变量的操作应优先通过方法完成,例如删除当前行列。
99
pub struct TableContext {
1010
pub theme: Theme,
11-
pub path: String,
12-
pub wb: Workbook,
1311
pub viewport: Viewport,
14-
pub selection: Option<Selection>,
12+
path: String,
13+
wb: Workbook,
14+
selection: Option<Selection>,
1515
/// 模式需要切换屏幕时写入,随后由 `ModeHost` 取走。
16-
pub pending_command: Option<ScreenCommand>,
16+
pending_command: Option<ScreenCommand>,
1717
}
1818

1919
impl TableContext {
20+
/// 创建编辑器共享状态。
21+
pub fn new(theme: Theme, path: String, wb: Workbook) -> Self {
22+
Self {
23+
theme,
24+
path,
25+
wb,
26+
viewport: Viewport::new(),
27+
selection: None,
28+
pending_command: None,
29+
}
30+
}
31+
32+
/// 当前工作簿。
33+
pub fn workbook(&self) -> &Workbook {
34+
&self.wb
35+
}
36+
37+
/// 当前工作簿名。
38+
pub fn workbook_name(&self) -> &str {
39+
&self.wb.name
40+
}
41+
42+
/// 当前工作簿行数。
43+
pub fn row_count(&self) -> usize {
44+
self.wb.rows
45+
}
46+
47+
/// 当前工作簿列数。
48+
pub fn column_count(&self) -> usize {
49+
self.wb.columns
50+
}
51+
52+
/// 当前选区。
53+
pub fn selection(&self) -> Option<&Selection> {
54+
self.selection.as_ref()
55+
}
56+
57+
/// 设置当前选区。
58+
pub fn set_selection(&mut self, selection: Selection) {
59+
self.selection = Some(selection);
60+
}
61+
62+
/// 清除当前选区。
63+
pub fn clear_selection(&mut self) {
64+
self.selection = None;
65+
}
66+
67+
/// 光标所在单元格的原始文本。
68+
pub fn current_cell_raw(&self) -> String {
69+
self.wb
70+
.get_cell(self.viewport.cursor())
71+
.map(|cell| cell.raw.clone())
72+
.unwrap_or_default()
73+
}
74+
2075
/// 保存当前工作簿。
2176
///
2277
/// 保存失败会被忽略;当前界面还没有错误提示位置。
@@ -34,6 +89,24 @@ impl TableContext {
3489
self.pending_command.take()
3590
}
3691

92+
/// 写入光标所在单元格的文本。
93+
pub fn set_current_cell_text(&mut self, raw: String) {
94+
self.wb.set_text(self.viewport.cursor(), raw);
95+
}
96+
97+
/// 清除光标所在单元格。
98+
pub fn clear_current_cell(&mut self) {
99+
self.wb.clear_cell(self.viewport.cursor());
100+
}
101+
102+
/// 清空当前选区,并退出选区状态。
103+
pub fn clear_selection_cells(&mut self) {
104+
if let Some(spec) = self.selection_clear_spec() {
105+
self.wb.clear_region(spec);
106+
self.selection = None;
107+
}
108+
}
109+
37110
/// 删除光标所在行,并同步裁剪光标和滚动位置。
38111
pub fn delete_current_row(&mut self) {
39112
self.wb.delete_row(self.viewport.cursor_row());
@@ -47,4 +120,19 @@ impl TableContext {
47120
self.viewport.clamp_cursor_col(self.wb.columns);
48121
self.viewport.scroll_into_view();
49122
}
123+
124+
fn selection_clear_spec(&self) -> Option<crate::model::workbook::ClearSpec> {
125+
use crate::model::workbook::ClearSpec;
126+
127+
self.selection.as_ref().map(|selection| match *selection {
128+
Selection::Row(r) => ClearSpec::Row(r),
129+
Selection::Column(c) => ClearSpec::Column(c),
130+
Selection::Range { anchor, cursor } => ClearSpec::Rect {
131+
c1: anchor.col.min(cursor.col),
132+
r1: anchor.row.min(cursor.row),
133+
c2: anchor.col.max(cursor.col),
134+
r2: anchor.row.max(cursor.row),
135+
},
136+
})
137+
}
50138
}

src/screen/editor/delete.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ use ratatui::{
77
widgets::Block,
88
};
99

10+
use crate::screen::EventResult;
1011
use crate::widget::selectable_list::{SelectableItem, SelectableList};
1112

1213
use super::{
1314
context::TableContext,
14-
mode::{FooterLine, Mode, ModeAction, ModeKind},
15+
mode::{FooterLine, Mode, ModeCommand, ModeKind, ModeResult},
1516
navigation::NavigationMode,
1617
};
1718

@@ -28,11 +29,11 @@ impl DeleteMode {
2829
let items = vec![
2930
SelectableItem::new("删除整行", |ctx: &mut TableContext| {
3031
ctx.delete_current_row();
31-
ModeAction::SwitchMode(Box::new(NavigationMode))
32+
EventResult::Command(ModeCommand::SwitchMode(Box::new(NavigationMode)))
3233
}),
3334
SelectableItem::new("删除整列", |ctx: &mut TableContext| {
3435
ctx.delete_current_column();
35-
ModeAction::SwitchMode(Box::new(NavigationMode))
36+
EventResult::Command(ModeCommand::SwitchMode(Box::new(NavigationMode)))
3637
}),
3738
];
3839
Self {
@@ -46,25 +47,25 @@ impl Mode for DeleteMode {
4647
ModeKind::Delete
4748
}
4849

49-
fn handle_key(&mut self, ctx: &mut TableContext, key: KeyEvent) -> ModeAction {
50+
fn handle_key(&mut self, ctx: &mut TableContext, key: KeyEvent) -> ModeResult {
5051
match key.code {
5152
KeyCode::Up => {
5253
self.list.handle_up();
53-
ModeAction::Handled
54+
EventResult::Handled
5455
}
5556
KeyCode::Down => {
5657
self.list.handle_down();
57-
ModeAction::Handled
58+
EventResult::Handled
5859
}
5960
KeyCode::Enter => {
6061
if let Some(action) = self.list.handle_enter(ctx) {
6162
action
6263
} else {
63-
ModeAction::Handled
64+
EventResult::Handled
6465
}
6566
}
66-
KeyCode::Esc => ModeAction::SwitchMode(Box::new(NavigationMode)),
67-
_ => ModeAction::Handled,
67+
KeyCode::Esc => EventResult::Command(ModeCommand::SwitchMode(Box::new(NavigationMode))),
68+
_ => EventResult::Handled,
6869
}
6970
}
7071

src/screen/editor/edit.rs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ use ratatui::{
77
widgets::Paragraph,
88
};
99

10-
use crate::{model::cell::CellValue, util::cursor_span};
10+
use crate::{screen::EventResult, util::cursor_span};
1111

1212
use super::{
1313
context::TableContext,
14-
mode::{FooterLine, Mode, ModeAction, ModeKind},
14+
mode::{FooterLine, Mode, ModeCommand, ModeKind, ModeResult},
1515
navigation::NavigationMode,
1616
};
1717

@@ -35,28 +35,22 @@ impl Mode for EditMode {
3535
ModeKind::Edit
3636
}
3737

38-
fn handle_key(&mut self, ctx: &mut TableContext, key: KeyEvent) -> ModeAction {
38+
fn handle_key(&mut self, ctx: &mut TableContext, key: KeyEvent) -> ModeResult {
3939
match key.code {
4040
KeyCode::Enter => {
41-
if !self.buffer.is_empty() {
42-
ctx.wb.set_cell(
43-
ctx.viewport.cursor(),
44-
self.buffer.clone(),
45-
CellValue::Text(self.buffer.clone()),
46-
);
47-
}
48-
ModeAction::SwitchMode(Box::new(NavigationMode))
41+
ctx.set_current_cell_text(self.buffer.clone());
42+
EventResult::Command(ModeCommand::SwitchMode(Box::new(NavigationMode)))
4943
}
50-
KeyCode::Esc => ModeAction::SwitchMode(Box::new(NavigationMode)),
44+
KeyCode::Esc => EventResult::Command(ModeCommand::SwitchMode(Box::new(NavigationMode))),
5145
KeyCode::Backspace => {
5246
self.buffer.pop();
53-
ModeAction::Handled
47+
EventResult::Handled
5448
}
5549
KeyCode::Char(c) => {
5650
self.buffer.push(c);
57-
ModeAction::Handled
51+
EventResult::Handled
5852
}
59-
_ => ModeAction::Handled,
53+
_ => EventResult::Handled,
6054
}
6155
}
6256

src/screen/editor/host.rs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
22
use ratatui::{Frame, layout::Rect};
33

4-
use crate::screen::ScreenCommand;
4+
use crate::screen::{EventResult, ScreenCommand};
55

66
use super::{
77
context::TableContext,
88
delete::DeleteMode,
99
menu::MenuMode,
10-
mode::{FooterLine, Mode, ModeAction, ModeKind},
10+
mode::{FooterLine, Mode, ModeCommand, ModeKind},
1111
navigation::NavigationMode,
1212
};
1313

1414
/// 编辑器模式宿主。
1515
///
1616
/// 宿主先处理跨模式快捷键,再把按键交给当前模式。
17-
/// 当前模式返回 `Handled` 时,宿主会向外层返回 `ScreenCommand::Stay`,
17+
/// 当前模式返回 `Handled` 时,宿主也会向外层返回 `Handled`,
1818
/// 让应用主循环知道该事件已经被消费。
1919
pub struct ModeHost {
2020
mode: Box<dyn Mode>,
@@ -33,16 +33,22 @@ impl ModeHost {
3333
}
3434

3535
/// 处理按键并转换为屏幕命令。
36-
pub fn handle_key(&mut self, ctx: &mut TableContext, key: KeyEvent) -> Option<ScreenCommand> {
37-
if let Some(cmd) = self.intercept_shortcut(ctx, key) {
38-
return Some(cmd);
36+
pub fn handle_key(
37+
&mut self,
38+
ctx: &mut TableContext,
39+
key: KeyEvent,
40+
) -> EventResult<ScreenCommand> {
41+
if let Some(result) = self.intercept_shortcut(ctx, key) {
42+
return result;
3943
}
4044
match self.mode.handle_key(ctx, key) {
41-
ModeAction::Handled => ctx.take_pending_command().or(Some(ScreenCommand::Stay)),
42-
ModeAction::Unhandled => None,
43-
ModeAction::SwitchMode(new_mode) => {
45+
EventResult::Handled => ctx
46+
.take_pending_command()
47+
.map_or(EventResult::Handled, EventResult::Command),
48+
EventResult::Ignored => EventResult::Ignored,
49+
EventResult::Command(ModeCommand::SwitchMode(new_mode)) => {
4450
self.mode = new_mode;
45-
Some(ScreenCommand::Stay)
51+
EventResult::Handled
4652
}
4753
}
4854
}
@@ -66,27 +72,27 @@ impl ModeHost {
6672
&mut self,
6773
ctx: &mut TableContext,
6874
key: KeyEvent,
69-
) -> Option<ScreenCommand> {
75+
) -> Option<EventResult<ScreenCommand>> {
7076
if Self::is_ctrl_s(key)
7177
&& self.mode.kind() != ModeKind::Menu
7278
&& self.mode.kind() != ModeKind::Delete
7379
{
7480
ctx.save();
75-
return Some(ScreenCommand::Stay);
81+
return Some(EventResult::Handled);
7682
}
7783
if Self::is_ctrl_p(key) {
7884
self.mode = match self.mode.kind() {
7985
ModeKind::Menu | ModeKind::Delete => Box::new(NavigationMode),
8086
_ => Box::new(MenuMode::new()),
8187
};
82-
return Some(ScreenCommand::Stay);
88+
return Some(EventResult::Handled);
8389
}
8490
if Self::is_ctrl_d(key)
8591
&& self.mode.kind() != ModeKind::Menu
8692
&& self.mode.kind() != ModeKind::Delete
8793
{
8894
self.mode = Box::new(DeleteMode::new());
89-
return Some(ScreenCommand::Stay);
95+
return Some(EventResult::Handled);
9096
}
9197
None
9298
}

0 commit comments

Comments
 (0)