
Команда на Rust
Команда — це поведінковий патерн, що дозволяє загортати запити або прості операції в окремі об’єкти.
Це дозволяє відкладати виконання команд, вибудовувати їх у чергу, а також зберігати історію і робити скасування.
In Rust, a command instance should NOT hold a permanent reference to global context, instead the latter should be passed from top to down as a mutable parameter of the "execute
" method:
fn execute(&mut self, app: &mut cursive::Cursive) -> bool;
Text Editor: Commands and Undo
Key points:
- Each button runs a separate command.
- Because a command is represented as an object, it can be pushed into a
history
array in order to be undone later. - TUI is created with
cursive
crate.
command.rs: Command Inteface
mod copy; mod cut; mod paste; pub use copy::CopyCommand; pub use cut::CutCommand; pub use paste::PasteCommand; /// Declares a method for executing (and undoing) a command. /// /// Each command receives an application context to access /// visual components (e.g. edit view) and a clipboard. pub trait Command { fn execute(&mut self, app: &mut cursive::Cursive) -> bool; fn undo(&mut self, app: &mut cursive::Cursive); }
command/copy.rs: Copy Command
use cursive::{views::EditView, Cursive}; use super::Command; use crate::AppContext; #[derive(Default)] pub struct CopyCommand; impl Command for CopyCommand { fn execute(&mut self, app: &mut Cursive) -> bool { let editor = app.find_name::<EditView>("Editor").unwrap(); let mut context = app.take_user_data::<AppContext>().unwrap(); context.clipboard = editor.get_content().to_string(); app.set_user_data(context); false } fn undo(&mut self, _: &mut Cursive) {} }
command/cut.rs: Cut Command
use cursive::{views::EditView, Cursive}; use super::Command; use crate::AppContext; #[derive(Default)] pub struct CutCommand { backup: String, } impl Command for CutCommand { fn execute(&mut self, app: &mut Cursive) -> bool { let mut editor = app.find_name::<EditView>("Editor").unwrap(); app.with_user_data(|context: &mut AppContext| { self.backup = editor.get_content().to_string(); context.clipboard = self.backup.clone(); editor.set_content("".to_string()); }); true } fn undo(&mut self, app: &mut Cursive) { let mut editor = app.find_name::<EditView>("Editor").unwrap(); editor.set_content(&self.backup); } }
command/paste.rs: Paste Command
use cursive::{views::EditView, Cursive}; use super::Command; use crate::AppContext; #[derive(Default)] pub struct PasteCommand { backup: String, } impl Command for PasteCommand { fn execute(&mut self, app: &mut Cursive) -> bool { let mut editor = app.find_name::<EditView>("Editor").unwrap(); app.with_user_data(|context: &mut AppContext| { self.backup = editor.get_content().to_string(); editor.set_content(context.clipboard.clone()); }); true } fn undo(&mut self, app: &mut Cursive) { let mut editor = app.find_name::<EditView>("Editor").unwrap(); editor.set_content(&self.backup); } }
main.rs: Client code
mod command; use cursive::{ traits::Nameable, views::{Dialog, EditView}, Cursive, }; use command::{Command, CopyCommand, CutCommand, PasteCommand}; /// An application context to be passed into visual component callbacks. /// It contains a clipboard and a history of commands to be undone. #[derive(Default)] struct AppContext { clipboard: String, history: Vec<Box<dyn Command>>, } fn main() { let mut app = cursive::default(); app.set_user_data(AppContext::default()); app.add_layer( Dialog::around(EditView::default().with_name("Editor")) .title("Type and use buttons") .button("Copy", |s| execute(s, CopyCommand)) .button("Cut", |s| execute(s, CutCommand::default())) .button("Paste", |s| execute(s, PasteCommand::default())) .button("Undo", undo) .button("Quit", |s| s.quit()), ); app.run(); } /// Executes a command and then pushes it to a history array. fn execute(app: &mut Cursive, mut command: impl Command + 'static) { if command.execute(app) { app.with_user_data(|context: &mut AppContext| { context.history.push(Box::new(command)); }); } } /// Pops the last command and executes an undo action. fn undo(app: &mut Cursive) { let mut context = app.take_user_data::<AppContext>().unwrap(); if let Some(mut command) = context.history.pop() { command.undo(app) } app.set_user_data(context); }