/ src / cmd.rs
cmd.rs
   1  use crate::autocomplete::{self, Autocomplete, FileCompleter, FileCompleterOpts};
   2  use crate::brush::BrushMode;
   3  use crate::history::History;
   4  use crate::parser::*;
   5  use crate::platform;
   6  use crate::session::{Direction, Input, Mode, PanState, Tool, VisualState};
   7  
   8  use memoir::traits::Parse;
   9  use memoir::*;
  10  
  11  use crate::gfx::Rect;
  12  use crate::gfx::Rgba8;
  13  
  14  use std::fmt;
  15  use std::path::Path;
  16  
  17  pub const COMMENT: char = '-';
  18  
  19  #[derive(Clone, PartialEq, Debug)]
  20  pub enum Op {
  21      Incr,
  22      Decr,
  23      Set(f32),
  24  }
  25  
  26  #[derive(Clone, Debug, PartialEq)]
  27  pub enum Axis {
  28      Horizontal,
  29      Vertical,
  30  }
  31  
  32  /// User command. Most of the interactions available to
  33  /// the user are modeled as commands that are processed
  34  /// by the session.
  35  #[derive(PartialEq, Debug, Clone)]
  36  pub enum Command {
  37      // Brush
  38      Brush,
  39      BrushSet(BrushMode),
  40      BrushToggle(BrushMode),
  41      BrushSize(Op),
  42      BrushUnset(BrushMode),
  43  
  44      #[allow(dead_code)]
  45      Crop(Rect<u32>),
  46      ChangeDir(Option<String>),
  47      Echo(Value),
  48  
  49      // Files
  50      Edit(Vec<String>),
  51      EditFrames(Vec<String>),
  52      Export(Option<u32>, String),
  53      Write(Option<String>),
  54      WriteFrames(Option<String>),
  55      WriteQuit,
  56      Quit,
  57      QuitAll,
  58      ForceQuit,
  59      ForceQuitAll,
  60      Source(Option<String>),
  61  
  62      // Frames
  63      FrameAdd,
  64      FrameClone(i32),
  65      FrameRemove,
  66      FramePrev,
  67      FrameNext,
  68      FrameResize(u32, u32),
  69  
  70      // Palette
  71      PaletteAdd(Rgba8),
  72      PaletteClear,
  73      PaletteGradient(Rgba8, Rgba8, usize),
  74      PaletteSample,
  75      PaletteSort,
  76      PaletteWrite(String),
  77  
  78      // Navigation
  79      Pan(i32, i32),
  80      Zoom(Op),
  81  
  82      PaintColor(Rgba8, i32, i32),
  83      PaintForeground(i32, i32),
  84      PaintBackground(i32, i32),
  85      PaintPalette(usize, i32, i32),
  86      PaintLine(Rgba8, i32, i32, i32, i32),
  87  
  88      // Selection
  89      SelectionMove(i32, i32),
  90      SelectionResize(i32, i32),
  91      SelectionOffset(i32, i32),
  92      SelectionExpand,
  93      SelectionPaste,
  94      SelectionYank,
  95      SelectionCut,
  96      SelectionFill(Option<Rgba8>),
  97      SelectionErase,
  98      SelectionJump(Direction),
  99      SelectionFlip(Axis),
 100  
 101      // Settings
 102      Set(String, Value),
 103      Toggle(String),
 104      Reset,
 105      Map(Box<KeyMapping>),
 106      MapClear,
 107  
 108      Slice(Option<usize>),
 109      Fill(Option<Rgba8>),
 110  
 111      SwapColors,
 112  
 113      Mode(Mode),
 114      Tool(Tool),
 115      ToolPrev,
 116  
 117      Undo,
 118      Redo,
 119  
 120      // View
 121      ViewCenter,
 122      ViewNext,
 123      ViewPrev,
 124  
 125      Noop,
 126  }
 127  
 128  impl Command {
 129      pub fn repeats(&self) -> bool {
 130          matches!(
 131              self,
 132              Self::Zoom(_)
 133                  | Self::BrushSize(_)
 134                  | Self::Pan(_, _)
 135                  | Self::Undo
 136                  | Self::Redo
 137                  | Self::ViewNext
 138                  | Self::ViewPrev
 139                  | Self::SelectionMove(_, _)
 140                  | Self::SelectionJump(_)
 141                  | Self::SelectionResize(_, _)
 142                  | Self::SelectionOffset(_, _)
 143          )
 144      }
 145  }
 146  
 147  impl fmt::Display for Command {
 148      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 149          match self {
 150              Self::Brush => write!(f, "Reset brush"),
 151              Self::BrushSet(m) => write!(f, "Set brush mode to `{}`", m),
 152              Self::BrushToggle(m) => write!(f, "Toggle `{}` brush mode", m),
 153              Self::BrushSize(Op::Incr) => write!(f, "Increase brush size"),
 154              Self::BrushSize(Op::Decr) => write!(f, "Decrease brush size"),
 155              Self::BrushSize(Op::Set(s)) => write!(f, "Set brush size to {}", s),
 156              Self::BrushUnset(m) => write!(f, "Unset brush `{}` mode", m),
 157              Self::Crop(_) => write!(f, "Crop view"),
 158              Self::ChangeDir(_) => write!(f, "Change the current working directory"),
 159              Self::Echo(_) => write!(f, "Echo a value"),
 160              Self::Edit(_) => write!(f, "Edit path(s)"),
 161              Self::EditFrames(_) => write!(f, "Edit path(s) as animation frames"),
 162              Self::Fill(Some(c)) => write!(f, "Fill view with {color}", color = c),
 163              Self::Fill(None) => write!(f, "Fill view with background color"),
 164              Self::ForceQuit => write!(f, "Quit view without saving"),
 165              Self::ForceQuitAll => write!(f, "Quit all views without saving"),
 166              Self::Map(_) => write!(f, "Map a key combination to a command"),
 167              Self::MapClear => write!(f, "Clear all key mappings"),
 168              Self::Mode(Mode::Help) => write!(f, "Toggle help"),
 169              Self::Mode(m) => write!(f, "Switch to {} mode", m),
 170              Self::FrameAdd => write!(f, "Add a blank frame to the view"),
 171              Self::FrameClone(i) => write!(f, "Clone frame {} and add it to the view", i),
 172              Self::FrameRemove => write!(f, "Remove the last frame of the view"),
 173              Self::FramePrev => write!(f, "Navigate to previous frame"),
 174              Self::FrameNext => write!(f, "Navigate to next frame"),
 175              Self::Noop => write!(f, "No-op"),
 176              Self::PaletteAdd(c) => write!(f, "Add {color} to palette", color = c),
 177              Self::PaletteClear => write!(f, "Clear palette"),
 178              Self::PaletteGradient(cs, ce, n) => {
 179                  write!(f, "Create {n} colors gradient from {cs} to {ce}")
 180              }
 181              Self::PaletteSample => write!(f, "Sample palette from view"),
 182              Self::PaletteSort => write!(f, "Sort palette colors"),
 183              Self::Pan(x, 0) if *x > 0 => write!(f, "Pan workspace right"),
 184              Self::Pan(x, 0) if *x < 0 => write!(f, "Pan workspace left"),
 185              Self::Pan(0, y) if *y > 0 => write!(f, "Pan workspace up"),
 186              Self::Pan(0, y) if *y < 0 => write!(f, "Pan workspace down"),
 187              Self::Pan(x, y) => write!(f, "Pan workspace by {},{}", x, y),
 188              Self::Quit => write!(f, "Quit active view"),
 189              Self::QuitAll => write!(f, "Quit all views"),
 190              Self::Redo => write!(f, "Redo view edit"),
 191              Self::FrameResize(_, _) => write!(f, "Resize active view frame"),
 192              Self::Tool(Tool::Pan(_)) => write!(f, "Pan tool"),
 193              Self::Tool(Tool::Brush) => write!(f, "Brush tool"),
 194              Self::Tool(Tool::Sampler) => write!(f, "Color sampler tool"),
 195              Self::Tool(Tool::FloodFill) => write!(f, "Flood fill tool"),
 196              Self::ToolPrev => write!(f, "Switch to previous tool"),
 197              Self::Set(s, v) => write!(f, "Set {setting} to {val}", setting = s, val = v),
 198              Self::Slice(Some(n)) => write!(f, "Slice view into {} frame(s)", n),
 199              Self::Slice(None) => write!(f, "Reset view slices"),
 200              Self::Source(_) => write!(f, "Source an rx script (eg. a palette)"),
 201              Self::SwapColors => write!(f, "Swap foreground & background colors"),
 202              Self::Toggle(s) => write!(f, "Toggle {setting} on/off", setting = s),
 203              Self::Undo => write!(f, "Undo view edit"),
 204              Self::ViewCenter => write!(f, "Center active view"),
 205              Self::ViewNext => write!(f, "Go to next view"),
 206              Self::ViewPrev => write!(f, "Go to previous view"),
 207              Self::Write(None) => write!(f, "Write view to disk"),
 208              Self::Write(Some(_)) => write!(f, "Write view to disk as..."),
 209              Self::WriteQuit => write!(f, "Write file to disk and quit"),
 210              Self::Zoom(Op::Incr) => write!(f, "Zoom in view"),
 211              Self::Zoom(Op::Decr) => write!(f, "Zoom out view"),
 212              Self::Zoom(Op::Set(z)) => write!(f, "Set view zoom to {:.1}", z),
 213              Self::Reset => write!(f, "Reset all settings to default"),
 214              Self::SelectionFill(None) => write!(f, "Fill selection with foreground color"),
 215              Self::SelectionYank => write!(f, "Yank (copy) selection"),
 216              Self::SelectionCut => write!(f, "Cut selection"),
 217              Self::SelectionPaste => write!(f, "Paste selection"),
 218              Self::SelectionExpand => write!(f, "Expand selection to frame"),
 219              Self::SelectionOffset(1, 1) => write!(f, "Outset selection"),
 220              Self::SelectionOffset(-1, -1) => write!(f, "Inset selection"),
 221              Self::SelectionOffset(x, y) => write!(f, "Offset selection by {:2},{:2}", x, y),
 222              Self::SelectionMove(x, 0) if *x > 0 => write!(f, "Move selection right"),
 223              Self::SelectionMove(x, 0) if *x < 0 => write!(f, "Move selection left"),
 224              Self::SelectionMove(0, y) if *y > 0 => write!(f, "Move selection up"),
 225              Self::SelectionMove(0, y) if *y < 0 => write!(f, "Move selection down"),
 226              Self::SelectionJump(Direction::Forward) => {
 227                  write!(f, "Move selection forward by one frame")
 228              }
 229              Self::SelectionJump(Direction::Backward) => {
 230                  write!(f, "Move selection backward by one frame")
 231              }
 232              Self::SelectionErase => write!(f, "Erase selection contents"),
 233              Self::SelectionFlip(Axis::Horizontal) => write!(f, "Flip selection horizontally"),
 234              Self::SelectionFlip(Axis::Vertical) => write!(f, "Flip selection vertically"),
 235              Self::PaintColor(_, x, y) => write!(f, "Paint {:2},{:2}", x, y),
 236              _ => write!(f, "..."),
 237          }
 238      }
 239  }
 240  
 241  impl From<Command> for String {
 242      fn from(cmd: Command) -> Self {
 243          match cmd {
 244              Command::Brush => format!("brush"),
 245              Command::BrushSet(m) => format!("brush/set {}", m),
 246              Command::BrushSize(Op::Incr) => format!("brush/size +"),
 247              Command::BrushSize(Op::Decr) => format!("brush/size -"),
 248              Command::BrushSize(Op::Set(s)) => format!("brush/size {}", s),
 249              Command::BrushUnset(m) => format!("brush/unset {}", m),
 250              Command::Echo(_) => unimplemented!(),
 251              Command::Edit(_) => unimplemented!(),
 252              Command::Fill(Some(c)) => format!("v/fill {}", c),
 253              Command::Fill(None) => format!("v/fill"),
 254              Command::ForceQuit => format!("q!"),
 255              Command::ForceQuitAll => format!("qa!"),
 256              Command::Map(_) => format!("map <key> <command> {{<command>}}"),
 257              Command::Mode(m) => format!("mode {}", m),
 258              Command::FrameAdd => format!("f/add"),
 259              Command::FrameClone(i) => format!("f/clone {}", i),
 260              Command::FrameRemove => format!("f/remove"),
 261              Command::Export(None, path) => format!("export {}", path),
 262              Command::Export(Some(s), path) => format!("export @{}x {}", s, path),
 263              Command::Noop => format!(""),
 264              Command::PaletteAdd(c) => format!("p/add {}", c),
 265              Command::PaletteClear => format!("p/clear"),
 266              Command::PaletteWrite(_) => format!("p/write"),
 267              Command::PaletteSample => format!("p/sample"),
 268              Command::PaletteGradient(cs, ce, n) => format!("p/gradient {} {} {}", cs, ce, n),
 269              Command::Pan(x, y) => format!("pan {} {}", x, y),
 270              Command::Quit => format!("q"),
 271              Command::Redo => format!("redo"),
 272              Command::FrameResize(w, h) => format!("f/resize {} {}", w, h),
 273              Command::Set(s, v) => format!("set {} = {}", s, v),
 274              Command::Slice(Some(n)) => format!("slice {}", n),
 275              Command::Slice(None) => format!("slice"),
 276              Command::Source(Some(path)) => format!("source {}", path),
 277              Command::SwapColors => format!("swap"),
 278              Command::Toggle(s) => format!("toggle {}", s),
 279              Command::Undo => format!("undo"),
 280              Command::ViewCenter => format!("v/center"),
 281              Command::ViewNext => format!("v/next"),
 282              Command::ViewPrev => format!("v/prev"),
 283              Command::Write(None) => format!("w"),
 284              Command::Write(Some(path)) => format!("w {}", path),
 285              Command::WriteQuit => format!("wq"),
 286              Command::Zoom(Op::Incr) => format!("v/zoom +"),
 287              Command::Zoom(Op::Decr) => format!("v/zoom -"),
 288              Command::Zoom(Op::Set(z)) => format!("v/zoom {}", z),
 289              _ => unimplemented!(),
 290          }
 291      }
 292  }
 293  
 294  ///////////////////////////////////////////////////////////////////////////////
 295  
 296  #[derive(PartialEq, Debug, Clone)]
 297  pub struct KeyMapping {
 298      pub input: Input,
 299      pub press: Command,
 300      pub release: Option<Command>,
 301      pub modes: Vec<Mode>,
 302  }
 303  
 304  impl KeyMapping {
 305      pub fn parser(modes: &[Mode]) -> Parser<KeyMapping> {
 306          let modes = modes.to_vec();
 307  
 308          // Prevent stack overflow.
 309          let press = Parser::new(
 310              move |input| Commands::default().parser().parse(input),
 311              "<cmd>",
 312          );
 313  
 314          // Prevent stack overflow.
 315          let release = Parser::new(
 316              move |input| {
 317                  if let Some(i) = input.bytes().position(|c| c == b'}') {
 318                      match Commands::default().parser().parse(&input[..i]) {
 319                          Ok((cmd, rest)) if rest.is_empty() => Ok((cmd, &input[i..])),
 320                          Ok((_, rest)) => {
 321                              Err((format!("expected {:?}, got {:?}", '}', rest).into(), rest))
 322                          }
 323                          Err(err) => Err(err),
 324                      }
 325                  } else {
 326                      Err(("unclosed '{' delimiter".into(), input))
 327                  }
 328              },
 329              "<cmd>",
 330          );
 331  
 332          let character = between('\'', '\'', character())
 333              .map(Input::Character)
 334              .skip(whitespace())
 335              .then(press.clone())
 336              .map(|(input, press)| ((input, press), None));
 337          let key = param::<platform::Key>()
 338              .map(Input::Key)
 339              .skip(whitespace())
 340              .then(press)
 341              .skip(optional(whitespace()))
 342              .then(optional(between('{', '}', release)));
 343  
 344          character
 345              .or(key)
 346              .map(move |((input, press), release)| KeyMapping {
 347                  input,
 348                  press,
 349                  release,
 350                  modes: modes.clone(),
 351              })
 352              .label("<key> <cmd>") // TODO: We should provide the full command somehow.
 353      }
 354  }
 355  
 356  ////////////////////////////////////////////////////////////////////////////////
 357  
 358  #[derive(Clone, PartialEq, Debug)]
 359  pub enum Value {
 360      Bool(bool),
 361      U32(u32),
 362      U32Tuple(u32, u32),
 363      F32Tuple(f32, f32),
 364      F64(f64),
 365      Str(String),
 366      Ident(String),
 367      Rgba8(Rgba8),
 368  }
 369  
 370  impl Value {
 371      pub fn is_set(&self) -> bool {
 372          if let Value::Bool(b) = self {
 373              return *b;
 374          }
 375          panic!("expected {:?} to be a `bool`", self);
 376      }
 377  
 378      pub fn to_f64(&self) -> f64 {
 379          if let Value::F64(n) = self {
 380              return *n;
 381          }
 382          panic!("expected {:?} to be a `float`", self);
 383      }
 384  
 385      pub fn to_u64(&self) -> u64 {
 386          if let Value::U32(n) = self {
 387              return *n as u64;
 388          }
 389          panic!("expected {:?} to be a `uint`", self);
 390      }
 391  
 392      pub fn to_rgba8(&self) -> Rgba8 {
 393          if let Value::Rgba8(rgba8) = self {
 394              return *rgba8;
 395          }
 396          panic!("expected {:?} to be a `Rgba8`", self);
 397      }
 398  
 399      pub fn description(&self) -> &'static str {
 400          match self {
 401              Self::Bool(_) => "on / off",
 402              Self::U32(_) => "positive integer, eg. 32",
 403              Self::F64(_) => "float, eg. 1.33",
 404              Self::U32Tuple(_, _) => "two positive integers, eg. 32, 48",
 405              Self::F32Tuple(_, _) => "two floats , eg. 32.17, 48.29",
 406              Self::Str(_) => "string, eg. \"fnord\"",
 407              Self::Rgba8(_) => "color, eg. #ffff00",
 408              Self::Ident(_) => "identifier, eg. fnord",
 409          }
 410      }
 411  }
 412  
 413  impl From<Value> for (u32, u32) {
 414      fn from(other: Value) -> (u32, u32) {
 415          if let Value::U32Tuple(x, y) = other {
 416              return (x, y);
 417          }
 418          panic!("expected {:?} to be a `(u32, u32)`", other);
 419      }
 420  }
 421  
 422  impl From<Value> for f32 {
 423      fn from(other: Value) -> f32 {
 424          if let Value::F64(x) = other {
 425              return x as f32;
 426          }
 427          panic!("expected {:?} to be a `f64`", other);
 428      }
 429  }
 430  
 431  impl From<Value> for f64 {
 432      fn from(other: Value) -> f64 {
 433          if let Value::F64(x) = other {
 434              return x;
 435          }
 436          panic!("expected {:?} to be a `f64`", other);
 437      }
 438  }
 439  
 440  impl fmt::Display for Value {
 441      fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 442          match self {
 443              Value::Bool(true) => "on".fmt(f),
 444              Value::Bool(false) => "off".fmt(f),
 445              Value::U32(u) => u.fmt(f),
 446              Value::F64(x) => x.fmt(f),
 447              Value::U32Tuple(x, y) => write!(f, "{},{}", x, y),
 448              Value::F32Tuple(x, y) => write!(f, "{},{}", x, y),
 449              Value::Str(s) => s.fmt(f),
 450              Value::Rgba8(c) => c.fmt(f),
 451              Value::Ident(i) => i.fmt(f),
 452          }
 453      }
 454  }
 455  
 456  impl Parse for Value {
 457      fn parser() -> Parser<Self> {
 458          let str_val = quoted().map(Value::Str).label("<string>");
 459          let rgba8_val = color().map(Value::Rgba8);
 460          let u32_tuple_val = tuple::<u32>(natural(), natural()).map(|(x, y)| Value::U32Tuple(x, y));
 461          let u32_val = natural::<u32>().map(Value::U32);
 462          let f64_tuple_val =
 463              tuple::<f32>(rational(), rational()).map(|(x, y)| Value::F32Tuple(x, y));
 464          let f64_val = rational::<f64>().map(Value::F64).label("0.0 .. 4096.0");
 465          let bool_val = string("on")
 466              .value(Value::Bool(true))
 467              .or(string("off").value(Value::Bool(false)))
 468              .label("on/off");
 469          let ident_val = identifier().map(Value::Ident);
 470  
 471          greediest(vec![
 472              rgba8_val,
 473              u32_tuple_val,
 474              f64_tuple_val,
 475              u32_val,
 476              f64_val,
 477              bool_val,
 478              ident_val,
 479              str_val,
 480          ])
 481          .label("<value>")
 482      }
 483  }
 484  
 485  ////////////////////////////////////////////////////////////////////////////////
 486  
 487  pub struct CommandLine {
 488      /// The history of commands entered.
 489      pub history: History,
 490      /// Command auto-complete.
 491      pub autocomplete: Autocomplete<CommandCompleter>,
 492      /// Input cursor position.
 493      pub cursor: usize,
 494      /// Parser.
 495      pub parser: Parser<Command>,
 496      /// Commands.
 497      pub commands: Commands,
 498      /// The current input string displayed to the user.
 499      input: String,
 500      /// File extensions supported.
 501      extensions: Vec<String>,
 502  }
 503  
 504  impl CommandLine {
 505      const MAX_INPUT: usize = 256;
 506  
 507      pub fn new<P: AsRef<Path>>(cwd: P, history_path: P, extensions: &[&str]) -> Self {
 508          let cmds = Commands::default();
 509  
 510          Self {
 511              input: String::with_capacity(Self::MAX_INPUT),
 512              cursor: 0,
 513              parser: cmds.line_parser(),
 514              commands: cmds,
 515              history: History::new(history_path, 1024),
 516              autocomplete: Autocomplete::new(CommandCompleter::new(cwd, extensions)),
 517              extensions: extensions.iter().map(|e| (*e).into()).collect(),
 518          }
 519      }
 520  
 521      pub fn set_cwd(&mut self, path: &Path) {
 522          let exts: Vec<_> = self.extensions.iter().map(|s| s.as_str()).collect();
 523          self.autocomplete = Autocomplete::new(CommandCompleter::new(path, exts.as_slice()));
 524      }
 525  
 526      pub fn parse(&self, input: &str) -> Result<Command, Error> {
 527          match self.parser.parse(input) {
 528              Ok((cmd, _)) => Ok(cmd),
 529              Err((err, _)) => Err(err),
 530          }
 531      }
 532  
 533      pub fn input(&self) -> String {
 534          self.input.clone()
 535      }
 536  
 537      pub fn is_empty(&self) -> bool {
 538          self.input.is_empty()
 539      }
 540  
 541      pub fn history_prev(&mut self) {
 542          let prefix = self.prefix();
 543  
 544          if let Some(entry) = self.history.prev(&prefix).map(str::to_owned) {
 545              self.replace(&entry);
 546          }
 547      }
 548  
 549      pub fn history_next(&mut self) {
 550          let prefix = self.prefix();
 551  
 552          if let Some(entry) = self.history.next(&prefix).map(str::to_owned) {
 553              self.replace(&entry);
 554          } else {
 555              self.reset();
 556          }
 557      }
 558  
 559      pub fn completion_next(&mut self) {
 560          let prefix = self.prefix();
 561  
 562          if let Some((completion, range)) = self.autocomplete.next(&prefix, self.cursor) {
 563              // Replace old completion with new one.
 564              self.cursor = range.start + completion.len();
 565              self.input.replace_range(range, &completion);
 566          }
 567      }
 568  
 569      pub fn cursor_backward(&mut self) -> Option<char> {
 570          if let Some(c) = self.peek_back() {
 571              let cursor = self.cursor - c.len_utf8();
 572  
 573              // Don't allow deleting the `:` prefix of the command.
 574              if c != ':' || cursor > 0 {
 575                  self.cursor = cursor;
 576                  self.autocomplete.invalidate();
 577                  return Some(c);
 578              }
 579          }
 580          None
 581      }
 582  
 583      pub fn cursor_forward(&mut self) -> Option<char> {
 584          if let Some(c) = self.input[self.cursor..].chars().next() {
 585              self.cursor += c.len_utf8();
 586              self.autocomplete.invalidate();
 587              Some(c)
 588          } else {
 589              None
 590          }
 591      }
 592  
 593      pub fn cursor_back(&mut self) {
 594          if self.cursor > 1 {
 595              self.cursor = 1;
 596              self.autocomplete.invalidate();
 597          }
 598      }
 599  
 600      pub fn cursor_front(&mut self) {
 601          self.cursor = self.input.len();
 602      }
 603  
 604      pub fn putc(&mut self, c: char) {
 605          if self.input.len() + c.len_utf8() > self.input.capacity() {
 606              return;
 607          }
 608          self.input.insert(self.cursor, c);
 609          self.cursor += c.len_utf8();
 610          self.autocomplete.invalidate();
 611      }
 612  
 613      pub fn puts(&mut self, s: &str) {
 614          // TODO: Check capacity.
 615          self.input.push_str(s);
 616          self.cursor += s.len();
 617          self.autocomplete.invalidate();
 618      }
 619  
 620      pub fn delc(&mut self) {
 621          match self.peek_back() {
 622              // Don't allow deleting the ':' unless it's the last remaining character.
 623              Some(c) if self.cursor > 1 || self.input.len() == 1 => {
 624                  self.cursor -= c.len_utf8();
 625                  self.input.remove(self.cursor);
 626                  self.autocomplete.invalidate();
 627              }
 628              _ => {}
 629          }
 630      }
 631  
 632      pub fn clear(&mut self) {
 633          self.cursor = 0;
 634          self.input.clear();
 635          self.history.reset();
 636          self.autocomplete.invalidate();
 637      }
 638  
 639      ////////////////////////////////////////////////////////////////////////////
 640  
 641      fn replace(&mut self, s: &str) {
 642          // We don't re-assign `input` here, because it
 643          // has a fixed capacity we want to preserve.
 644          self.input.clear();
 645          self.input.push_str(s);
 646          self.autocomplete.invalidate();
 647      }
 648  
 649      fn reset(&mut self) {
 650          self.clear();
 651          self.putc(':');
 652      }
 653  
 654      fn prefix(&self) -> String {
 655          self.input[..self.cursor].to_owned()
 656      }
 657  
 658      #[cfg(test)]
 659      fn peek(&self) -> Option<char> {
 660          self.input[self.cursor..].chars().next()
 661      }
 662  
 663      fn peek_back(&self) -> Option<char> {
 664          self.input[..self.cursor].chars().next_back()
 665      }
 666  }
 667  
 668  pub struct Commands {
 669      commands: Vec<(&'static str, &'static str, Parser<Command>)>,
 670  }
 671  
 672  impl Commands {
 673      pub fn new() -> Self {
 674          Self {
 675              commands: vec![(
 676                  "#",
 677                  "Add color to palette",
 678                  color().map(Command::PaletteAdd),
 679              )],
 680          }
 681      }
 682  
 683      pub fn parser(&self) -> Parser<Command> {
 684          use std::iter;
 685  
 686          let noop = expect(|s| s.is_empty(), "<empty>").value(Command::Noop);
 687          let commands = self.commands.iter().map(|(_, _, v)| v.clone());
 688          let choices = commands.chain(iter::once(noop)).collect();
 689  
 690          symbol(':')
 691              .then(
 692                  choice(choices).or(peek(
 693                      until(hush(whitespace()).or(end()))
 694                          .try_map(|cmd| Err(format!("unknown command: {}", cmd))),
 695                  )),
 696              )
 697              .map(|(_, cmd)| cmd)
 698      }
 699  
 700      pub fn line_parser(&self) -> Parser<Command> {
 701          self.parser()
 702              .skip(optional(whitespace()))
 703              .skip(optional(comment()))
 704              .end()
 705      }
 706  
 707      pub fn iter(&self) -> impl Iterator<Item = &(&'static str, &'static str, Parser<Command>)> {
 708          self.commands.iter()
 709      }
 710  
 711      ///////////////////////////////////////////////////////////////////////////
 712  
 713      fn command<F>(mut self, name: &'static str, help: &'static str, f: F) -> Self
 714      where
 715          F: Fn(Parser<String>) -> Parser<Command>,
 716      {
 717          let cmd = peek(
 718              string(name)
 719                  .followed_by(hush(whitespace()) / end())
 720                  .skip(optional(whitespace())),
 721          )
 722          .label(name);
 723  
 724          self.commands.push((name, help, f(cmd)));
 725          self
 726      }
 727  }
 728  
 729  impl Default for Commands {
 730      fn default() -> Self {
 731          Self::new()
 732              .command("q", "Quit view", |p| p.value(Command::Quit))
 733              .command("qa", "Quit all views", |p| p.value(Command::QuitAll))
 734              .command("q!", "Force quit view", |p| p.value(Command::ForceQuit))
 735              .command("qa!", "Force quit all views", |p| {
 736                  p.value(Command::ForceQuitAll)
 737              })
 738              .command("export", "Export view", |p| {
 739                  p.then(optional(scale().skip(whitespace())).then(path()))
 740                      .map(|(_, (scale, path))| Command::Export(scale, path))
 741              })
 742              .command("wq", "Write & quit view", |p| p.value(Command::WriteQuit))
 743              .command("x", "Write & quit view", |p| p.value(Command::WriteQuit))
 744              .command("w", "Write view", |p| {
 745                  p.then(optional(path()))
 746                      .map(|(_, path)| Command::Write(path))
 747              })
 748              .command("w/frames", "Write view as individual frames", |p| {
 749                  p.then(optional(path()))
 750                      .map(|(_, dir)| Command::WriteFrames(dir))
 751              })
 752              .command("e", "Edit path(s)", |p| {
 753                  p.then(paths()).map(|(_, paths)| Command::Edit(paths))
 754              })
 755              .command("e/frames", "Edit frames as view", |p| {
 756                  p.then(paths()).map(|(_, paths)| Command::EditFrames(paths))
 757              })
 758              .command("help", "Display help", |p| {
 759                  p.value(Command::Mode(Mode::Help))
 760              })
 761              .command("set", "Set setting to value", |p| {
 762                  p.then(setting())
 763                      .skip(optional(whitespace()))
 764                      .then(optional(
 765                          symbol('=')
 766                              .skip(optional(whitespace()))
 767                              .then(Value::parser())
 768                              .map(|(_, v)| v),
 769                      ))
 770                      .map(|((_, k), v)| Command::Set(k, v.unwrap_or(Value::Bool(true))))
 771              })
 772              .command("unset", "Set setting to `off`", |p| {
 773                  p.then(setting())
 774                      .map(|(_, k)| Command::Set(k, Value::Bool(false)))
 775              })
 776              .command("toggle", "Toggle setting", |p| {
 777                  p.then(setting()).map(|(_, k)| Command::Toggle(k))
 778              })
 779              .command("echo", "Echo setting or value", |p| {
 780                  p.then(Value::parser()).map(|(_, v)| Command::Echo(v))
 781              })
 782              .command("slice", "Slice view into <n> frames", |p| {
 783                  p.then(optional(natural::<usize>().label("<n>")))
 784                      .map(|(_, n)| Command::Slice(n))
 785              })
 786              .command(
 787                  "source",
 788                  "Source an rx script (eg. palette or config)",
 789                  |p| p.then(optional(path())).map(|(_, p)| Command::Source(p)),
 790              )
 791              .command("cd", "Change current directory", |p| {
 792                  p.then(optional(path())).map(|(_, p)| Command::ChangeDir(p))
 793              })
 794              .command("zoom", "Zoom view", |p| {
 795                  p.then(
 796                      peek(rational::<f32>().label("<level>"))
 797                          .try_map(|z| {
 798                              if z >= 1.0 {
 799                                  Ok(Command::Zoom(Op::Set(z)))
 800                              } else {
 801                                  Err("zoom level must be >= 1.0")
 802                              }
 803                          })
 804                          .or(symbol('+')
 805                              .value(Command::Zoom(Op::Incr))
 806                              .or(symbol('-').value(Command::Zoom(Op::Decr)))
 807                              .or(fail("couldn't parse zoom parameter")))
 808                          .label("+/-"),
 809                  )
 810                  .map(|(_, cmd)| cmd)
 811              })
 812              .command("brush/size", "Set brush size", |p| {
 813                  p.then(
 814                      natural::<usize>()
 815                          .label("<size>")
 816                          .map(|z| Command::BrushSize(Op::Set(z as f32)))
 817                          .or(symbol('+')
 818                              .value(Command::BrushSize(Op::Incr))
 819                              .or(symbol('-').value(Command::BrushSize(Op::Decr)))
 820                              .or(fail("couldn't parse brush size parameter")))
 821                          .label("+/-"),
 822                  )
 823                  .map(|(_, cmd)| cmd)
 824              })
 825              .command(
 826                  "brush/set",
 827                  "Set brush mode, eg. `xsym` for x-symmetry",
 828                  |p| {
 829                      p.then(param::<BrushMode>())
 830                          .map(|(_, m)| Command::BrushSet(m))
 831                  },
 832              )
 833              .command("brush/unset", "Unset brush mode", |p| {
 834                  p.then(param::<BrushMode>())
 835                      .map(|(_, m)| Command::BrushUnset(m))
 836              })
 837              .command("brush/toggle", "Toggle brush mode", |p| {
 838                  p.then(param::<BrushMode>())
 839                      .map(|(_, m)| Command::BrushToggle(m))
 840              })
 841              .command("brush", "Switch to brush", |p| {
 842                  p.value(Command::Tool(Tool::Brush))
 843              })
 844              .command("flood", "Switch to flood fill tool", |p| {
 845                  p.value(Command::Tool(Tool::FloodFill))
 846              })
 847              .command("mode", "Set session mode, eg. `visual` or `normal`", |p| {
 848                  p.then(param::<Mode>()).map(|(_, m)| Command::Mode(m))
 849              })
 850              .command("visual", "Set session mode to visual", |p| {
 851                  p.map(|_| Command::Mode(Mode::Visual(VisualState::default())))
 852              })
 853              .command("sampler/off", "Switch the sampler tool off", |p| {
 854                  p.value(Command::ToolPrev)
 855              })
 856              .command("sampler", "Switch to the sampler tool", |p| {
 857                  p.value(Command::Tool(Tool::Sampler))
 858              })
 859              .command("v/next", "Activate the next view", |p| {
 860                  p.value(Command::ViewNext)
 861              })
 862              .command("v/prev", "Activate the previous view", |p| {
 863                  p.value(Command::ViewPrev)
 864              })
 865              .command("v/center", "Center the active view", |p| {
 866                  p.value(Command::ViewCenter)
 867              })
 868              .command("v/clear", "Clear the active view", |p| {
 869                  p.value(Command::Fill(Some(Rgba8::TRANSPARENT)))
 870              })
 871              .command("v/fill", "Fill the active view", |p| {
 872                  p.then(optional(color())).map(|(_, c)| Command::Fill(c))
 873              })
 874              .command("pan", "Switch to the pan tool", |p| {
 875                  p.then(tuple::<i32>(integer().label("<x>"), integer().label("<y>")))
 876                      .map(|(_, (x, y))| Command::Pan(x, y))
 877              })
 878              .command("map", "Map keys to a command in all modes", |p| {
 879                  p.then(KeyMapping::parser(&[
 880                      Mode::Normal,
 881                      Mode::Visual(VisualState::selecting()),
 882                      Mode::Visual(VisualState::Pasting),
 883                  ]))
 884                  .map(|(_, km)| Command::Map(Box::new(km)))
 885              })
 886              .command("map/visual", "Map keys to a command in visual mode", |p| {
 887                  p.then(KeyMapping::parser(&[
 888                      Mode::Visual(VisualState::selecting()),
 889                      Mode::Visual(VisualState::Pasting),
 890                  ]))
 891                  .map(|(_, km)| Command::Map(Box::new(km)))
 892              })
 893              .command("map/normal", "Map keys to a command in normal mode", |p| {
 894                  p.then(KeyMapping::parser(&[Mode::Normal]))
 895                      .map(|(_, km)| Command::Map(Box::new(km)))
 896              })
 897              .command("map/help", "Map keys to a command in help mode", |p| {
 898                  p.then(KeyMapping::parser(&[Mode::Help]))
 899                      .map(|(_, km)| Command::Map(Box::new(km)))
 900              })
 901              .command("map/clear!", "Clear all key mappings", |p| {
 902                  p.value(Command::MapClear)
 903              })
 904              .command("p/add", "Add a color to the palette", |p| {
 905                  p.then(color()).map(|(_, rgba)| Command::PaletteAdd(rgba))
 906              })
 907              .command("p/clear", "Clear the color palette", |p| {
 908                  p.value(Command::PaletteClear)
 909              })
 910              .command("p/gradient", "Add a gradient to the palette", |p| {
 911                  p.then(tuple::<Rgba8>(
 912                      color().label("<from>"),
 913                      color().label("<to>"),
 914                  ))
 915                  .skip(whitespace())
 916                  .then(natural::<usize>().label("<count>"))
 917                  .map(|((_, (cs, ce)), n)| Command::PaletteGradient(cs, ce, n))
 918              })
 919              .command(
 920                  "p/sample",
 921                  "Sample palette colors from the active view",
 922                  |p| p.value(Command::PaletteSample),
 923              )
 924              .command("p/sort", "Sort the palette colors", |p| {
 925                  p.value(Command::PaletteSort)
 926              })
 927              .command("p/write", "Write the color palette to a file", |p| {
 928                  p.then(path()).map(|(_, path)| Command::PaletteWrite(path))
 929              })
 930              .command("undo", "Undo the last edit", |p| p.value(Command::Undo))
 931              .command("redo", "Redo the last edit", |p| p.value(Command::Redo))
 932              .command("f/add", "Add a blank frame to the active view", |p| {
 933                  p.value(Command::FrameAdd)
 934              })
 935              .command("f/clone", "Clone a frame and add it to the view", |p| {
 936                  p.then(optional(integer::<i32>().label("<index>")))
 937                      .map(|(_, index)| Command::FrameClone(index.unwrap_or(-1)))
 938              })
 939              .command(
 940                  "f/remove",
 941                  "Remove the last frame from the active view",
 942                  |p| p.value(Command::FrameRemove),
 943              )
 944              .command("f/prev", "Navigate to previous frame", |p| {
 945                  p.value(Command::FramePrev)
 946              })
 947              .command("f/next", "Navigate to next frame", |p| {
 948                  p.value(Command::FrameNext)
 949              })
 950              .command("f/resize", "Resize the active view frame(s)", |p| {
 951                  p.then(tuple::<u32>(
 952                      natural().label("<width>"),
 953                      natural().label("<height>"),
 954                  ))
 955                  .map(|(_, (w, h))| Command::FrameResize(w, h))
 956              })
 957              .command("tool", "Switch tool", |p| {
 958                  p.then(word().label("pan/brush/sampler/.."))
 959                      .try_map(|(_, t)| match t.as_str() {
 960                          "pan" => Ok(Command::Tool(Tool::Pan(PanState::default()))),
 961                          "brush" => Ok(Command::Tool(Tool::Brush)),
 962                          "sampler" => Ok(Command::Tool(Tool::Sampler)),
 963                          _ => Err(format!("unknown tool {:?}", t)),
 964                      })
 965              })
 966              .command("tool/prev", "Switch to previous tool", |p| {
 967                  p.value(Command::ToolPrev)
 968              })
 969              .command("swap", "Swap foreground and background colors", |p| {
 970                  p.value(Command::SwapColors)
 971              })
 972              .command("reset!", "Reset all settings to defaults", |p| {
 973                  p.value(Command::Reset)
 974              })
 975              .command("selection/move", "Move selection", |p| {
 976                  p.then(tuple::<i32>(integer().label("<x>"), integer().label("<y>")))
 977                      .map(|(_, (x, y))| Command::SelectionMove(x, y))
 978              })
 979              .command("selection/resize", "Resize selection", |p| {
 980                  p.then(tuple::<i32>(integer().label("<x>"), integer().label("<y>")))
 981                      .map(|(_, (x, y))| Command::SelectionResize(x, y))
 982              })
 983              .command("selection/yank", "Yank/copy selection content", |p| {
 984                  p.value(Command::SelectionYank)
 985              })
 986              .command("selection/cut", "Cut selection content", |p| {
 987                  p.value(Command::SelectionCut)
 988              })
 989              .command("selection/paste", "Paste into selection", |p| {
 990                  p.value(Command::SelectionPaste)
 991              })
 992              .command("selection/expand", "Expand selection", |p| {
 993                  p.value(Command::SelectionExpand)
 994              })
 995              .command("selection/erase", "Erase selection contents", |p| {
 996                  p.value(Command::SelectionErase)
 997              })
 998              .command("selection/offset", "Offset selection bounds", |p| {
 999                  p.then(tuple::<i32>(integer().label("<x>"), integer().label("<y>")))
1000                      .map(|(_, (x, y))| Command::SelectionOffset(x, y))
1001              })
1002              .command("selection/jump", "Translate selection by one frame", |p| {
1003                  p.then(param::<Direction>())
1004                      .map(|(_, dir)| Command::SelectionJump(dir))
1005              })
1006              .command("selection/fill", "Fill selection with color", |p| {
1007                  p.then(optional(color()))
1008                      .map(|(_, rgba)| Command::SelectionFill(rgba))
1009              })
1010              .command("selection/flip", "Flip selection", |p| {
1011                  p.then(word().label("x/y"))
1012                      .try_map(|(_, t)| match t.as_str() {
1013                          "x" => Ok(Command::SelectionFlip(Axis::Horizontal)),
1014                          "y" => Ok(Command::SelectionFlip(Axis::Vertical)),
1015                          _ => Err(format!("unknown axis {:?}, must be 'x' or 'y'", t)),
1016                      })
1017              })
1018              .command("paint/color", "Paint color", |p| {
1019                  p.then(color())
1020                      .skip(whitespace())
1021                      .then(tuple::<i32>(integer().label("<x>"), integer().label("<y>")))
1022                      .map(|((_, rgba), (x, y))| Command::PaintColor(rgba, x, y))
1023              })
1024              .command("paint/line", "Draw a line between two points", |p| {
1025                  p.then(color())
1026                      .skip(whitespace())
1027                      .then(tuple::<i32>(
1028                          integer().label("<x1>"),
1029                          integer().label("<y1>"),
1030                      ))
1031                      .skip(whitespace())
1032                      .then(tuple::<i32>(
1033                          integer().label("<x2>"),
1034                          integer().label("<y2>"),
1035                      ))
1036                      .map(|(((_, color), (x1, y1)), (x2, y2))| {
1037                          Command::PaintLine(color, x1, y1, x2, y2)
1038                      })
1039              })
1040              .command("paint/fg", "Paint foreground color", |p| {
1041                  p.then(tuple::<i32>(integer().label("<x>"), integer().label("<y>")))
1042                      .map(|(_, (x, y))| Command::PaintForeground(x, y))
1043              })
1044              .command("paint/bg", "Paint background color", |p| {
1045                  p.then(tuple::<i32>(integer().label("<x>"), integer().label("<y>")))
1046                      .map(|(_, (x, y))| Command::PaintBackground(x, y))
1047              })
1048              .command("paint/p", "Paint palette color", |p| {
1049                  p.then(natural::<usize>())
1050                      .skip(whitespace())
1051                      .then(tuple::<i32>(integer().label("<x>"), integer().label("<y>")))
1052                      .map(|((_, i), (x, y))| Command::PaintPalette(i, x, y))
1053              })
1054      }
1055  }
1056  
1057  ///////////////////////////////////////////////////////////////////////////////
1058  
1059  #[derive(Debug)]
1060  pub struct CommandCompleter {
1061      file_completer: FileCompleter,
1062  }
1063  
1064  impl CommandCompleter {
1065      fn new<P: AsRef<Path>>(cwd: P, exts: &[&str]) -> Self {
1066          Self {
1067              file_completer: FileCompleter::new(cwd, exts),
1068          }
1069      }
1070  }
1071  
1072  impl autocomplete::Completer for CommandCompleter {
1073      type Options = ();
1074  
1075      fn complete(&self, input: &str, _opts: ()) -> Vec<String> {
1076          let p = Commands::default().parser();
1077  
1078          match p.parse(input) {
1079              Ok((cmd, _)) => match cmd {
1080                  Command::ChangeDir(path) | Command::WriteFrames(path) => self.complete_path(
1081                      path.as_ref(),
1082                      input,
1083                      FileCompleterOpts { directories: true },
1084                  ),
1085                  Command::Source(path) | Command::Write(path) => {
1086                      self.complete_path(path.as_ref(), input, Default::default())
1087                  }
1088                  Command::Edit(paths) | Command::EditFrames(paths) => {
1089                      self.complete_path(paths.last(), input, Default::default())
1090                  }
1091                  _ => vec![],
1092              },
1093              Err(_) => vec![],
1094          }
1095      }
1096  }
1097  
1098  impl CommandCompleter {
1099      fn complete_path(
1100          &self,
1101          path: Option<&String>,
1102          input: &str,
1103          opts: FileCompleterOpts,
1104      ) -> Vec<String> {
1105          use crate::autocomplete::Completer;
1106  
1107          let empty = "".to_owned();
1108          let path = path.unwrap_or(&empty);
1109  
1110          // If there's whitespace between the path and the cursor, don't complete the path.
1111          // Instead, complete as if the input was empty.
1112          match input.chars().next_back() {
1113              Some(c) if c.is_whitespace() => self.file_completer.complete("", opts),
1114              _ => self.file_completer.complete(path, opts),
1115          }
1116      }
1117  }
1118  
1119  #[cfg(test)]
1120  mod test {
1121      use super::*;
1122      use std::{fs, fs::File};
1123  
1124      #[test]
1125      fn test_command_completer() {
1126          let tmp = tempfile::tempdir().unwrap();
1127  
1128          for file_name in &["one.png", "two.png", "three.png"] {
1129              let path = tmp.path().join(file_name);
1130              File::create(path).unwrap();
1131          }
1132  
1133          let cc = CommandCompleter::new(tmp.path(), &["png"]);
1134          let mut auto = Autocomplete::new(cc);
1135  
1136          assert_eq!(auto.next(":e |", 3), Some(("one.png".to_owned(), 3..3)));
1137          auto.invalidate();
1138          assert_eq!(
1139              auto.next(":e |one.png", 3),
1140              Some(("one.png".to_owned(), 3..3))
1141          );
1142  
1143          auto.invalidate();
1144          assert_eq!(
1145              auto.next(":e one.png | two.png", 11),
1146              Some(("one.png".to_owned(), 11..11))
1147          );
1148          assert_eq!(
1149              auto.next(":e one.png one.png| two.png", 20),
1150              Some(("three.png".to_owned(), 11..18))
1151          );
1152          assert_eq!(
1153              auto.next(":e one.png three.png| two.png", 18),
1154              Some(("two.png".to_owned(), 11..20))
1155          );
1156  
1157          fs::create_dir(tmp.path().join("assets")).unwrap();
1158          for file_name in &["four.png", "five.png", "six.png"] {
1159              let path = tmp.path().join("assets").join(file_name);
1160              File::create(path).unwrap();
1161          }
1162  
1163          auto.invalidate();
1164          assert_eq!(
1165              auto.next(":e assets/|", 10),
1166              Some(("five.png".to_owned(), 10..10))
1167          );
1168      }
1169  
1170      #[test]
1171      fn test_command_line() {
1172          let tmp = tempfile::tempdir().unwrap();
1173  
1174          fs::create_dir(tmp.path().join("assets")).unwrap();
1175          for file_name in &["one.png", "two.png", "three.png"] {
1176              let path = tmp.path().join(file_name);
1177              File::create(path).unwrap();
1178          }
1179          for file_name in &["four.png", "five.png"] {
1180              let path = tmp.path().join("assets").join(file_name);
1181              File::create(path).unwrap();
1182          }
1183  
1184          let mut cli = CommandLine::new(tmp.path(), &tmp.path().join(".history"), &["png"]);
1185  
1186          cli.puts(":e one");
1187          cli.completion_next();
1188          assert_eq!(cli.input(), ":e one.png");
1189  
1190          cli.completion_next();
1191          assert_eq!(cli.input(), ":e one.png");
1192  
1193          cli.clear();
1194          cli.puts(":e ");
1195          cli.completion_next();
1196          assert_eq!(cli.input(), ":e assets");
1197  
1198          cli.completion_next();
1199          assert_eq!(cli.input(), ":e one.png");
1200  
1201          cli.completion_next();
1202          assert_eq!(cli.input(), ":e three.png");
1203  
1204          cli.completion_next();
1205          assert_eq!(cli.input(), ":e two.png");
1206  
1207          cli.completion_next();
1208          assert_eq!(cli.input(), ":e assets");
1209  
1210          cli.putc('/');
1211          cli.completion_next();
1212          assert_eq!(cli.input(), ":e assets/five.png");
1213  
1214          cli.completion_next();
1215          assert_eq!(cli.input(), ":e assets/four.png");
1216  
1217          cli.completion_next();
1218          assert_eq!(cli.input(), ":e assets/five.png");
1219  
1220          cli.putc(' ');
1221          cli.completion_next();
1222          assert_eq!(cli.input(), ":e assets/five.png assets");
1223          cli.completion_next();
1224          assert_eq!(cli.input(), ":e assets/five.png one.png");
1225  
1226          cli.putc(' ');
1227          cli.putc('t');
1228          cli.completion_next();
1229          assert_eq!(cli.input(), ":e assets/five.png one.png three.png");
1230  
1231          cli.completion_next();
1232          assert_eq!(cli.input(), ":e assets/five.png one.png two.png");
1233  
1234          cli.completion_next();
1235          assert_eq!(cli.input(), ":e assets/five.png one.png three.png");
1236  
1237          for _ in 0..10 {
1238              cli.cursor_backward();
1239          }
1240          cli.putc(' ');
1241          cli.putc('o');
1242          cli.completion_next();
1243          assert_eq!(cli.input(), ":e assets/five.png one.png one.png three.png");
1244  
1245          cli.clear();
1246          cli.puts(":e assets");
1247          cli.completion_next();
1248          assert_eq!(cli.input(), ":e assets/");
1249  
1250          cli.clear();
1251          cli.puts(":e asset");
1252  
1253          cli.completion_next();
1254          assert_eq!(cli.input(), ":e assets/");
1255  
1256          cli.completion_next();
1257          assert_eq!(cli.input(), ":e assets/five.png");
1258      }
1259  
1260      #[test]
1261      fn test_command_line_change_dir() {
1262          let tmp = tempfile::tempdir().unwrap();
1263  
1264          fs::create_dir(tmp.path().join("assets")).unwrap();
1265          for file_name in &["four.png", "five.png"] {
1266              let path = tmp.path().join("assets").join(file_name);
1267              File::create(path).unwrap();
1268          }
1269  
1270          let mut cli = CommandLine::new(tmp.path(), Path::new("/dev/null"), &["png"]);
1271  
1272          cli.set_cwd(tmp.path().join("assets/").as_path());
1273          cli.puts(":e ");
1274  
1275          cli.completion_next();
1276          assert_eq!(cli.input(), ":e five.png");
1277  
1278          cli.completion_next();
1279          assert_eq!(cli.input(), ":e four.png");
1280      }
1281  
1282      #[test]
1283      fn test_command_line_cd() {
1284          let tmp = tempfile::tempdir().unwrap();
1285  
1286          fs::create_dir(tmp.path().join("assets")).unwrap();
1287          fs::create_dir(tmp.path().join("assets").join("1")).unwrap();
1288          fs::create_dir(tmp.path().join("assets").join("2")).unwrap();
1289          File::create(tmp.path().join("assets").join("rx.png")).unwrap();
1290  
1291          let mut cli = CommandLine::new(tmp.path(), Path::new("/dev/null"), &["png"]);
1292  
1293          cli.clear();
1294          cli.puts(":cd assets/");
1295  
1296          cli.completion_next();
1297          assert_eq!(cli.input(), ":cd assets/1");
1298  
1299          cli.completion_next();
1300          assert_eq!(cli.input(), ":cd assets/2");
1301  
1302          cli.completion_next();
1303          assert_eq!(cli.input(), ":cd assets/1");
1304      }
1305  
1306      #[test]
1307      fn test_command_line_cursor() {
1308          let mut cli = CommandLine::new("/dev/null", "/dev/null", &[]);
1309  
1310          cli.puts(":echo");
1311          cli.delc();
1312          assert_eq!(cli.input(), ":ech");
1313          cli.delc();
1314          assert_eq!(cli.input(), ":ec");
1315          cli.delc();
1316          assert_eq!(cli.input(), ":e");
1317          cli.delc();
1318          assert_eq!(cli.input(), ":");
1319          cli.delc();
1320          assert_eq!(cli.input(), "");
1321  
1322          cli.clear();
1323          cli.puts(":e");
1324  
1325          assert_eq!(cli.peek(), None);
1326          cli.cursor_backward();
1327  
1328          assert_eq!(cli.peek(), Some('e'));
1329          cli.cursor_backward();
1330  
1331          assert_eq!(cli.peek(), Some('e'));
1332          assert_eq!(cli.peek_back(), Some(':'));
1333  
1334          cli.delc();
1335          assert_eq!(cli.input(), ":e");
1336  
1337          cli.clear();
1338          cli.puts(":echo");
1339  
1340          assert_eq!(cli.peek(), None);
1341          cli.cursor_back();
1342  
1343          assert_eq!(cli.peek(), Some('e'));
1344          assert_eq!(cli.peek_back(), Some(':'));
1345  
1346          cli.cursor_front();
1347          assert_eq!(cli.peek(), None);
1348          assert_eq!(cli.peek_back(), Some('o'));
1349      }
1350  
1351      #[test]
1352      fn test_parser() {
1353          let p = Commands::default().line_parser();
1354  
1355          assert_eq!(
1356              p.parse(":set foo = value"),
1357              Ok((
1358                  Command::Set("foo".to_owned(), Value::Ident(String::from("value"))),
1359                  ""
1360              ))
1361          );
1362          assert_eq!(
1363              p.parse(":set scale = 1.0"),
1364              Ok((Command::Set("scale".to_owned(), Value::F64(1.0)), ""))
1365          );
1366          assert_eq!(
1367              p.parse(":set foo=value"),
1368              Ok((
1369                  Command::Set("foo".to_owned(), Value::Ident(String::from("value"))),
1370                  ""
1371              ))
1372          );
1373          assert_eq!(
1374              p.parse(":set foo"),
1375              Ok((Command::Set("foo".to_owned(), Value::Bool(true)), ""))
1376          );
1377  
1378          assert_eq!(
1379              param::<platform::Key>()
1380                  .parse("<hello>")
1381                  .unwrap_err()
1382                  .0
1383                  .to_string(),
1384              "unknown key <hello>"
1385          );
1386  
1387          assert_eq!(p.parse(":").unwrap(), (Command::Noop, ""));
1388      }
1389  
1390      #[test]
1391      fn test_echo_command() {
1392          let p = Commands::default().line_parser();
1393  
1394          p.parse(":echo 42").unwrap();
1395          p.parse(":echo \"hello.\"").unwrap();
1396          p.parse(":echo \"\"").unwrap();
1397      }
1398  
1399      #[test]
1400      fn test_zoom_command() {
1401          let p = Commands::default().line_parser();
1402  
1403          assert!(p.parse(":zoom -").is_ok());
1404          assert!(p.parse(":zoom 3.0").is_ok());
1405          assert!(p.parse(":zoom -1.0").is_err());
1406      }
1407  
1408      #[test]
1409      fn test_vfill_commands() {
1410          let p = Commands::default().line_parser();
1411  
1412          p.parse(":v/fill").unwrap();
1413          p.parse(":v/fill #ff00ff").unwrap();
1414      }
1415  
1416      #[test]
1417      fn test_unknown_command() {
1418          let p = Commands::default().line_parser();
1419  
1420          let (err, rest) = p.parse(":fnord").unwrap_err();
1421          assert_eq!(rest, "fnord");
1422          assert_eq!(err.to_string(), "unknown command: fnord");
1423  
1424          let (err, rest) = p.parse(":mode fnord").unwrap_err();
1425          assert_eq!(rest, "fnord");
1426          assert_eq!(err.to_string(), "unknown mode: fnord");
1427      }
1428  
1429      #[test]
1430      fn test_keymapping_parser() {
1431          let p = string("map")
1432              .skip(whitespace())
1433              .then(KeyMapping::parser(&[]));
1434  
1435          let (_, rest) = p.parse("map <tab> :q! {:q}").unwrap();
1436          assert_eq!(rest, "");
1437  
1438          let (_, rest) = p
1439              .parse("map <tab> :brush/set erase {:brush/unset erase}")
1440              .unwrap();
1441          assert_eq!(rest, "");
1442  
1443          let (_, rest) = p.parse("map <ctrl> :tool sampler {:tool/prev}").unwrap();
1444          assert_eq!(rest, "");
1445      }
1446  
1447      #[test]
1448      fn tes_value_parser() {
1449          let p = Value::parser();
1450  
1451          assert_eq!(p.parse("1.0 2.0").unwrap(), (Value::F32Tuple(1.0, 2.0), ""));
1452          assert_eq!(p.parse("1.0").unwrap(), (Value::F64(1.0), ""));
1453          assert_eq!(p.parse("1").unwrap(), (Value::U32(1), ""));
1454          assert_eq!(p.parse("1 2").unwrap(), (Value::U32Tuple(1, 2), ""));
1455          assert_eq!(p.parse("on").unwrap(), (Value::Bool(true), ""));
1456          assert_eq!(p.parse("off").unwrap(), (Value::Bool(false), ""));
1457          assert_eq!(
1458              p.parse("#ff00ff").unwrap(),
1459              (Value::Rgba8(Rgba8::new(0xff, 0x0, 0xff, 0xff)), "")
1460          );
1461      }
1462  
1463      #[test]
1464      fn test_parser_errors() {
1465          let p = Commands::default().line_parser();
1466  
1467          let (err, _) = p
1468              .parse(":map <ctrl> :tool sampler {:tool/prev")
1469              .unwrap_err();
1470          assert_eq!(err.to_string(), "unclosed '{' delimiter".to_string());
1471  
1472          let (err, _) = p.parse(":map <ctrl> :tool sampler :tool/prev").unwrap_err();
1473          assert_eq!(
1474              err.to_string(),
1475              "extraneous input found: :tool/prev".to_string()
1476          );
1477      }
1478  }