1 use crate::autocomplete::{self, Autocomplete, FileCompleter, FileCompleterOpts};
2 use crate::brush::{Brush, BrushMode};
3 use crate::history::History;
4 use crate::parser::{Error, Parse, Parser, Result};
5 use crate::platform;
6 use crate::session::{Direction, Mode, PanState, Tool, VisualState};
7 
8 use rgx::kit::Rgba8;
9 use rgx::rect::Rect;
10 
11 use std::fmt;
12 use std::path::Path;
13 use std::result;
14 use std::str::FromStr;
15 
16 pub const COMMENT: char = '-';
17 
18 #[derive(Clone, PartialEq, Debug)]
19 pub enum Op {
20     Incr,
21     Decr,
22     Set(f32),
23 }
24 
25 /// User command. Most of the interactions available to
26 /// the user are modeled as commands that are processed
27 /// by the session.
28 #[derive(PartialEq, Debug, Clone)]
29 pub enum Command {
30     Brush,
31     BrushSet(BrushMode),
32     BrushToggle(BrushMode),
33     BrushSize(Op),
34     BrushUnset(BrushMode),
35     #[allow(dead_code)]
36     Crop(Rect<u32>),
37     ChangeDir(Option<String>),
38     Echo(Value),
39     Edit(Vec<String>),
40     EditFrames(Vec<String>),
41     Fill(Rgba8),
42     ForceQuit,
43     ForceQuitAll,
44     Map(Box<KeyMapping>),
45     MapClear,
46     Mode(Mode),
47     AddFrame,
48     CloneFrame(i32),
49     RemoveFrame,
50     Noop,
51     PaletteAdd(Rgba8),
52     PaletteClear,
53     PaletteSample,
54     Pan(i32, i32),
55     Quit,
56     QuitAll,
57     Reset,
58     Redo,
59     ResizeFrame(u32, u32),
60     SelectionMove(i32, i32),
61     SelectionResize(i32, i32),
62     SelectionOffset(i32, i32),
63     SelectionExpand,
64     SelectionPaste,
65     SelectionYank,
66     SelectionCut,
67     SelectionFill(Option<Rgba8>),
68     SelectionErase,
69     SelectionJump(Direction),
70     Set(String, Value),
71     Slice(Option<usize>),
72     Source(Option<String>),
73     SwapColors,
74     Toggle(String),
75     Tool(Tool),
76     ToolPrev,
77     Undo,
78     ViewCenter,
79     ViewNext,
80     ViewPrev,
81     WriteFrames(Option<String>),
82     Write(Option<String>),
83     WriteQuit,
84     Zoom(Op),
85 }
86 
87 impl Command {
repeats(&self) -> bool88     pub fn repeats(&self) -> bool {
89         match self {
90             Self::Zoom(_)
91             | Self::BrushSize(_)
92             | Self::Pan(_, _)
93             | Self::Undo
94             | Self::Redo
95             | Self::ViewNext
96             | Self::ViewPrev
97             | Self::SelectionMove(_, _)
98             | Self::SelectionJump(_)
99             | Self::SelectionResize(_, _)
100             | Self::SelectionOffset(_, _) => true,
101             _ => false,
102         }
103     }
104 }
105 
106 impl fmt::Display for Command {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result107     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108         match self {
109             Self::Brush => write!(f, "Reset brush"),
110             Self::BrushSet(m) => write!(f, "Set brush mode to `{}`", m),
111             Self::BrushToggle(m) => write!(f, "Toggle `{}` brush mode", m),
112             Self::BrushSize(Op::Incr) => write!(f, "Increase brush size"),
113             Self::BrushSize(Op::Decr) => write!(f, "Decrease brush size"),
114             Self::BrushSize(Op::Set(s)) => write!(f, "Set brush size to {}", s),
115             Self::BrushUnset(m) => write!(f, "Unset brush `{}` mode", m),
116             Self::Crop(_) => write!(f, "Crop view"),
117             Self::ChangeDir(_) => write!(f, "Change the current working directory"),
118             Self::Echo(_) => write!(f, "Echo a value"),
119             Self::Edit(_) => write!(f, "Edit path(s)"),
120             Self::EditFrames(_) => write!(f, "Edit path(s) as animation frames"),
121             Self::Fill(c) => write!(f, "Fill view with {color}", color = c),
122             Self::ForceQuit => write!(f, "Quit view without saving"),
123             Self::ForceQuitAll => write!(f, "Quit all views without saving"),
124             Self::Map(_) => write!(f, "Map a key combination to a command"),
125             Self::MapClear => write!(f, "Clear all key mappings"),
126             Self::Mode(m) => write!(f, "Switch session mode to {}", m),
127             Self::AddFrame => write!(f, "Add a blank frame to the view"),
128             Self::CloneFrame(i) => write!(f, "Clone frame {} and add it to the view", i),
129             Self::RemoveFrame => write!(f, "Remove the last frame of the view"),
130             Self::Noop => write!(f, "No-op"),
131             Self::PaletteAdd(c) => write!(f, "Add {color} to palette", color = c),
132             Self::PaletteClear => write!(f, "Clear palette"),
133             Self::PaletteSample => write!(f, "Sample palette from view"),
134             Self::Pan(x, 0) if *x > 0 => write!(f, "Pan workspace right"),
135             Self::Pan(x, 0) if *x < 0 => write!(f, "Pan workspace left"),
136             Self::Pan(0, y) if *y > 0 => write!(f, "Pan workspace up"),
137             Self::Pan(0, y) if *y < 0 => write!(f, "Pan workspace down"),
138             Self::Pan(x, y) => write!(f, "Pan workspace by {},{}", x, y),
139             Self::Quit => write!(f, "Quit active view"),
140             Self::QuitAll => write!(f, "Quit all views"),
141             Self::Redo => write!(f, "Redo view edit"),
142             Self::ResizeFrame(_, _) => write!(f, "Resize active view frame"),
143             Self::Tool(Tool::Pan(_)) => write!(f, "Pan tool"),
144             Self::Tool(Tool::Brush(_)) => write!(f, "Brush tool"),
145             Self::Tool(Tool::Sampler) => write!(f, "Color sampler tool"),
146             Self::ToolPrev => write!(f, "Switch to previous tool"),
147             Self::Set(s, v) => write!(f, "Set {setting} to {val}", setting = s, val = v),
148             Self::Slice(Some(n)) => write!(f, "Slice view into {} frame(s)", n),
149             Self::Slice(None) => write!(f, "Reset view slices"),
150             Self::Source(_) => write!(f, "Source an rx script (eg. a palette)"),
151             Self::SwapColors => write!(f, "Swap foreground & background colors"),
152             Self::Toggle(s) => write!(f, "Toggle {setting} on/off", setting = s),
153             Self::Undo => write!(f, "Undo view edit"),
154             Self::ViewCenter => write!(f, "Center active view"),
155             Self::ViewNext => write!(f, "Go to next view"),
156             Self::ViewPrev => write!(f, "Go to previous view"),
157             Self::Write(None) => write!(f, "Write view to disk"),
158             Self::Write(Some(_)) => write!(f, "Write view to disk as..."),
159             Self::WriteQuit => write!(f, "Write file to disk and quit"),
160             Self::Zoom(Op::Incr) => write!(f, "Zoom in view"),
161             Self::Zoom(Op::Decr) => write!(f, "Zoom out view"),
162             Self::Zoom(Op::Set(z)) => write!(f, "Set view zoom to {:.1}", z),
163             Self::Reset => write!(f, "Reset all settings to default"),
164             Self::SelectionFill(None) => write!(f, "Fill selection with foreground color"),
165             Self::SelectionYank => write!(f, "Yank (copy) selection"),
166             Self::SelectionCut => write!(f, "Cut selection"),
167             Self::SelectionPaste => write!(f, "Paste selection"),
168             Self::SelectionExpand => write!(f, "Expand selection to frame"),
169             Self::SelectionOffset(1, 1) => write!(f, "Outset selection"),
170             Self::SelectionOffset(-1, -1) => write!(f, "Inset selection"),
171             Self::SelectionOffset(x, y) => write!(f, "Offset selection by {:2},{:2}", x, y),
172             Self::SelectionMove(x, 0) if *x > 0 => write!(f, "Move selection right"),
173             Self::SelectionMove(x, 0) if *x < 0 => write!(f, "Move selection left"),
174             Self::SelectionMove(0, y) if *y > 0 => write!(f, "Move selection up"),
175             Self::SelectionMove(0, y) if *y < 0 => write!(f, "Move selection down"),
176             Self::SelectionJump(Direction::Forward) => {
177                 write!(f, "Move selection forward by one frame")
178             }
179             Self::SelectionJump(Direction::Backward) => {
180                 write!(f, "Move selection backward by one frame")
181             }
182             Self::SelectionErase => write!(f, "Erase selection contents"),
183             _ => write!(f, "..."),
184         }
185     }
186 }
187 
188 impl From<Command> for String {
from(cmd: Command) -> Self189     fn from(cmd: Command) -> Self {
190         match cmd {
191             Command::Brush => format!("brush"),
192             Command::BrushSet(m) => format!("brush/set {}", m),
193             Command::BrushSize(Op::Incr) => format!("brush/size +"),
194             Command::BrushSize(Op::Decr) => format!("brush/size -"),
195             Command::BrushSize(Op::Set(s)) => format!("brush/size {}", s),
196             Command::BrushUnset(m) => format!("brush/unset {}", m),
197             Command::Echo(_) => unimplemented!(),
198             Command::Edit(_) => unimplemented!(),
199             Command::Fill(c) => format!("v/fill {}", c),
200             Command::ForceQuit => format!("q!"),
201             Command::ForceQuitAll => format!("qa!"),
202             Command::Map(_) => format!("map <key> <command> {{<command>}}"),
203             Command::Mode(m) => format!("mode {}", m),
204             Command::AddFrame => format!("f/add"),
205             Command::CloneFrame(i) => format!("f/clone {}", i),
206             Command::RemoveFrame => format!("f/remove"),
207             Command::Noop => format!(""),
208             Command::PaletteAdd(c) => format!("p/add {}", c),
209             Command::PaletteClear => format!("p/clear"),
210             Command::PaletteSample => unimplemented!(),
211             Command::Pan(x, y) => format!("pan {} {}", x, y),
212             Command::Quit => format!("q"),
213             Command::Redo => format!("redo"),
214             Command::ResizeFrame(w, h) => format!("f/resize {} {}", w, h),
215             Command::Set(s, v) => format!("set {} = {}", s, v),
216             Command::Slice(Some(n)) => format!("slice {}", n),
217             Command::Slice(None) => format!("slice"),
218             Command::Source(Some(path)) => format!("source {}", path),
219             Command::SwapColors => format!("swap"),
220             Command::Toggle(s) => format!("toggle {}", s),
221             Command::Undo => format!("undo"),
222             Command::ViewCenter => format!("v/center"),
223             Command::ViewNext => format!("v/next"),
224             Command::ViewPrev => format!("v/prev"),
225             Command::Write(None) => format!("w"),
226             Command::Write(Some(path)) => format!("w {}", path),
227             Command::WriteQuit => format!("wq"),
228             Command::Zoom(Op::Incr) => format!("v/zoom +"),
229             Command::Zoom(Op::Decr) => format!("v/zoom -"),
230             Command::Zoom(Op::Set(z)) => format!("v/zoom {}", z),
231             _ => unimplemented!(),
232         }
233     }
234 }
235 
236 impl<'a> Parse<'a> for platform::Key {
parse(p: Parser<'a>) -> Result<'a, Self>237     fn parse(p: Parser<'a>) -> Result<'a, Self> {
238         if let Ok((_, p)) = p.clone().sigil('<') {
239             let (key, p) = p.alpha()?;
240             let (_, p) = p.sigil('>')?;
241             let key = match key {
242                 "up" => platform::Key::Up,
243                 "down" => platform::Key::Down,
244                 "left" => platform::Key::Left,
245                 "right" => platform::Key::Right,
246                 "ctrl" => platform::Key::Control,
247                 "alt" => platform::Key::Alt,
248                 "shift" => platform::Key::Shift,
249                 "space" => platform::Key::Space,
250                 "return" => platform::Key::Return,
251                 "backspace" => platform::Key::Backspace,
252                 "tab" => platform::Key::Tab,
253                 "end" => platform::Key::End,
254                 "esc" => platform::Key::Escape,
255                 other => return Err(Error::new(format!("unknown key <{}>", other))),
256             };
257             Ok((key, p))
258         } else {
259             let (c, p) = p.parse::<char>()?;
260             let key: platform::Key = c.into();
261 
262             if key == platform::Key::Unknown {
263                 return Err(Error::new(format!("unknown key {:?}", c)));
264             }
265             Ok((key, p))
266         }
267     }
268 }
269 
270 ///////////////////////////////////////////////////////////////////////////////
271 
272 #[derive(PartialEq, Debug, Clone)]
273 pub struct KeyMapping {
274     pub key: platform::Key,
275     pub press: Command,
276     pub release: Option<Command>,
277     pub modes: Vec<Mode>,
278 }
279 
280 impl KeyMapping {
parse<'a>(p: Parser<'a>, modes: &[Mode]) -> Result<'a, Self>281     fn parse<'a>(p: Parser<'a>, modes: &[Mode]) -> Result<'a, Self> {
282         let modes = modes.to_vec();
283 
284         let (key, p) = p.parse::<platform::Key>()?;
285         let (_, p) = p.whitespace()?;
286         let (press, p) = p.parse::<Command>()?;
287         let (_, p) = p.whitespace()?;
288 
289         let (release, p) = if let Ok((_, p)) = p.clone().sigil('{') {
290             let (cmd, p) = p.parse::<Command>()?;
291             let (_, p) = p.sigil('}')?;
292             (Some(cmd), p)
293         } else {
294             (None, p)
295         };
296         Ok((
297             KeyMapping {
298                 key,
299                 press,
300                 release,
301                 modes,
302             },
303             p,
304         ))
305     }
306 }
307 
308 ////////////////////////////////////////////////////////////////////////////////
309 
310 #[derive(Clone, PartialEq, Debug)]
311 pub enum Value {
312     Bool(bool),
313     U32(u32),
314     U32Tuple(u32, u32),
315     F64(f64),
316     F64Tuple(f32, f32),
317     Str(String),
318     Ident(String),
319     Rgba8(Rgba8),
320 }
321 
322 impl Value {
is_set(&self) -> bool323     pub fn is_set(&self) -> bool {
324         if let Value::Bool(b) = self {
325             return *b;
326         }
327         panic!("expected {:?} to be a `bool`", self);
328     }
329 
to_f64(&self) -> f64330     pub fn to_f64(&self) -> f64 {
331         if let Value::F64(n) = self {
332             return *n;
333         }
334         panic!("expected {:?} to be a `float`", self);
335     }
336 
to_u64(&self) -> u64337     pub fn to_u64(&self) -> u64 {
338         if let Value::U32(n) = self {
339             return *n as u64;
340         }
341         panic!("expected {:?} to be a `uint`", self);
342     }
343 
to_rgba8(&self) -> Rgba8344     pub fn to_rgba8(&self) -> Rgba8 {
345         if let Value::Rgba8(rgba8) = self {
346             return *rgba8;
347         }
348         panic!("expected {:?} to be a `Rgba8`", self);
349     }
350 
description(&self) -> &'static str351     pub fn description(&self) -> &'static str {
352         match self {
353             Self::Bool(_) => "on / off",
354             Self::U32(_) => "positive integer, eg. 32",
355             Self::F64(_) => "float, eg. 1.33",
356             Self::U32Tuple(_, _) => "two positive integers, eg. 32, 48",
357             Self::F64Tuple(_, _) => "two floats , eg. 32.17, 48.29",
358             Self::Str(_) => "string, eg. \"fnord\"",
359             Self::Rgba8(_) => "color, eg. #ffff00",
360             Self::Ident(_) => "identifier, eg. fnord",
361         }
362     }
363 }
364 
365 impl Into<(u32, u32)> for Value {
into(self) -> (u32, u32)366     fn into(self) -> (u32, u32) {
367         if let Value::U32Tuple(x, y) = self {
368             return (x, y);
369         }
370         panic!("expected {:?} to be a `(u32, u32)`", self);
371     }
372 }
373 
374 impl Into<f32> for Value {
into(self) -> f32375     fn into(self) -> f32 {
376         if let Value::F64(x) = self {
377             return x as f32;
378         }
379         panic!("expected {:?} to be a `f64`", self);
380     }
381 }
382 
383 impl Into<f64> for Value {
into(self) -> f64384     fn into(self) -> f64 {
385         if let Value::F64(x) = self {
386             return x as f64;
387         }
388         panic!("expected {:?} to be a `f64`", self);
389     }
390 }
391 
392 impl<'a> Parse<'a> for Value {
parse(p: Parser<'a>) -> Result<'a, Self>393     fn parse(p: Parser<'a>) -> Result<'a, Self> {
394         let c = p.peek();
395 
396         if c == Some('"') {
397             let (v, p) = p.string()?;
398             Ok((Value::Str(v.to_string()), p))
399         } else if c == Some('#') {
400             let (v, p) = p.parse::<Rgba8>()?;
401             Ok((Value::Rgba8(v), p))
402         } else if c.map_or(false, |c| c.is_digit(10)) {
403             if let Ok(((x, y), p)) = p.clone().parse::<(u32, u32)>() {
404                 Ok((Value::U32Tuple(x, y), p))
405             } else if let Ok((v, p)) = p.clone().parse::<u32>() {
406                 Ok((Value::U32(v), p))
407             } else if let Ok((v, p)) = p.clone().parse::<f64>() {
408                 Ok((Value::F64(v), p))
409             } else {
410                 let (input, _) = p.until(|c| c.is_whitespace())?;
411                 Err(Error::new(format!("malformed number: `{}`", input)))
412             }
413         } else {
414             let (i, p) = p.identifier()?;
415             match i {
416                 "on" => Ok((Value::Bool(true), p)),
417                 "off" => Ok((Value::Bool(false), p)),
418                 _ => Ok((Value::Ident(i.to_string()), p)),
419             }
420         }
421     }
422 }
423 
424 impl fmt::Display for Value {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result425     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
426         match self {
427             Value::Bool(true) => "on".fmt(f),
428             Value::Bool(false) => "off".fmt(f),
429             Value::U32(u) => u.fmt(f),
430             Value::F64(x) => x.fmt(f),
431             Value::U32Tuple(x, y) => write!(f, "{},{}", x, y),
432             Value::F64Tuple(x, y) => write!(f, "{},{}", x, y),
433             Value::Str(s) => s.fmt(f),
434             Value::Rgba8(c) => c.fmt(f),
435             Value::Ident(i) => i.fmt(f),
436         }
437     }
438 }
439 
440 ////////////////////////////////////////////////////////////////////////////////
441 
442 #[derive(Debug)]
443 pub struct CommandLine {
444     /// The history of commands entered.
445     pub history: History,
446     /// Command auto-complete.
447     pub autocomplete: Autocomplete<CommandCompleter>,
448     /// Input cursor position.
449     pub cursor: usize,
450     /// The current input string displayed to the user.
451     input: String,
452     /// File extensions supported.
453     extensions: Vec<String>,
454 }
455 
456 impl CommandLine {
457     const MAX_INPUT: usize = 256;
458 
new<P: AsRef<Path>>(cwd: P, history_path: P, extensions: &[&str]) -> Self459     pub fn new<P: AsRef<Path>>(cwd: P, history_path: P, extensions: &[&str]) -> Self {
460         Self {
461             input: String::with_capacity(Self::MAX_INPUT),
462             cursor: 0,
463             history: History::new(history_path, 1024),
464             autocomplete: Autocomplete::new(CommandCompleter::new(cwd, extensions)),
465             extensions: extensions.iter().map(|e| (*e).into()).collect(),
466         }
467     }
468 
set_cwd(&mut self, path: &Path)469     pub fn set_cwd(&mut self, path: &Path) {
470         let exts: Vec<_> = self.extensions.iter().map(|s| s.as_str()).collect();
471         self.autocomplete = Autocomplete::new(CommandCompleter::new(path, exts.as_slice()));
472     }
473 
input(&self) -> String474     pub fn input(&self) -> String {
475         self.input.clone()
476     }
477 
is_empty(&self) -> bool478     pub fn is_empty(&self) -> bool {
479         self.input.is_empty()
480     }
481 
history_prev(&mut self)482     pub fn history_prev(&mut self) {
483         let prefix = self.prefix();
484 
485         if let Some(entry) = self.history.prev(&prefix).map(str::to_owned) {
486             self.replace(&entry);
487         }
488     }
489 
history_next(&mut self)490     pub fn history_next(&mut self) {
491         let prefix = self.prefix();
492 
493         if let Some(entry) = self.history.next(&prefix).map(str::to_owned) {
494             self.replace(&entry);
495         } else {
496             self.reset();
497         }
498     }
499 
completion_next(&mut self)500     pub fn completion_next(&mut self) {
501         let prefix = self.prefix();
502 
503         if let Some((completion, range)) = self.autocomplete.next(&prefix, self.cursor) {
504             // Replace old completion with new one.
505             self.cursor = range.start + completion.len();
506             self.input.replace_range(range, &completion);
507         }
508     }
509 
cursor_backward(&mut self) -> Option<char>510     pub fn cursor_backward(&mut self) -> Option<char> {
511         if let Some(c) = self.peek_back() {
512             let cursor = self.cursor - c.len_utf8();
513 
514             // Don't allow deleting the `:` prefix of the command.
515             if c != ':' || cursor > 0 {
516                 self.cursor = cursor;
517                 self.autocomplete.invalidate();
518                 return Some(c);
519             }
520         }
521         None
522     }
523 
cursor_forward(&mut self) -> Option<char>524     pub fn cursor_forward(&mut self) -> Option<char> {
525         if let Some(c) = self.input[self.cursor..].chars().next() {
526             self.cursor += c.len_utf8();
527             self.autocomplete.invalidate();
528             Some(c)
529         } else {
530             None
531         }
532     }
533 
putc(&mut self, c: char)534     pub fn putc(&mut self, c: char) {
535         if self.input.len() + c.len_utf8() > self.input.capacity() {
536             return;
537         }
538         self.input.insert(self.cursor, c);
539         self.cursor += c.len_utf8();
540         self.autocomplete.invalidate();
541     }
542 
puts(&mut self, s: &str)543     pub fn puts(&mut self, s: &str) {
544         // TODO: Check capacity.
545         self.input.push_str(s);
546         self.cursor += s.len();
547         self.autocomplete.invalidate();
548     }
549 
delc(&mut self)550     pub fn delc(&mut self) {
551         match self.peek_back() {
552             // Don't allow deleting the ':' unless it's the last remaining character.
553             Some(c) if self.cursor > 1 || self.input.len() == 1 => {
554                 self.cursor -= c.len_utf8();
555                 self.input.remove(self.cursor);
556                 self.autocomplete.invalidate();
557             }
558             _ => {}
559         }
560     }
561 
clear(&mut self)562     pub fn clear(&mut self) {
563         self.cursor = 0;
564         self.input.clear();
565         self.history.reset();
566         self.autocomplete.invalidate();
567     }
568 
569     ////////////////////////////////////////////////////////////////////////////
570 
replace(&mut self, s: &str)571     fn replace(&mut self, s: &str) {
572         // We don't re-assign `input` here, because it
573         // has a fixed capacity we want to preserve.
574         self.input.clear();
575         self.input.push_str(s);
576         self.autocomplete.invalidate();
577     }
578 
reset(&mut self)579     fn reset(&mut self) {
580         self.clear();
581         self.putc(':');
582     }
583 
prefix(&self) -> String584     fn prefix(&self) -> String {
585         self.input[..self.cursor].to_owned()
586     }
587 
588     #[cfg(test)]
peek(&self) -> Option<char>589     fn peek(&self) -> Option<char> {
590         self.input[self.cursor..].chars().next()
591     }
592 
peek_back(&self) -> Option<char>593     fn peek_back(&self) -> Option<char> {
594         self.input[..self.cursor].chars().next_back()
595     }
596 }
597 
598 ///////////////////////////////////////////////////////////////////////////////
599 
600 impl FromStr for Command {
601     type Err = Error;
602 
from_str(input: &str) -> result::Result<Self, Self::Err>603     fn from_str(input: &str) -> result::Result<Self, Self::Err> {
604         let p = Parser::new(input);
605         match p.parse::<Command>() {
606             Ok((cmd, p)) => {
607                 let (_, p) = p.clone().comment().unwrap_or(("", p));
608                 p.finish()?; // Make sure we've consumed all the input
609                 Ok(cmd)
610             }
611             // TODO: Use `enum` for error.
612             Err(e) => Err(e),
613         }
614     }
615 }
616 
617 impl<'a> Parse<'a> for Command {
parse(p: Parser<'a>) -> Result<'a, Self>618     fn parse(p: Parser<'a>) -> Result<'a, Self> {
619         let (_, p) = p.sigil(':')?;
620         let (_, p) = p.whitespace()?;
621 
622         if p.is_empty() {
623             return Ok((Command::Noop, p));
624         }
625 
626         if Some('#') == p.peek() {
627             let (rgba, p) = p.parse::<Rgba8>()?;
628             return Ok((Command::PaletteAdd(rgba), p));
629         }
630 
631         let (cmd, p) = p.identifier()?;
632         let (_, p) = p.whitespace()?;
633 
634         match cmd {
635             "q" => Ok((Command::Quit, p)),
636             "qa" => Ok((Command::QuitAll, p)),
637             "q!" => Ok((Command::ForceQuit, p)),
638             "qa!" => Ok((Command::ForceQuitAll, p)),
639             "wq" | "x" => Ok((Command::WriteQuit, p)),
640             "w" => {
641                 if p.is_empty() {
642                     Ok((Command::Write(None), p))
643                 } else {
644                     let (path, p) = p.path()?;
645                     Ok((Command::Write(Some(path)), p))
646                 }
647             }
648             "w/frames" => {
649                 if p.is_empty() {
650                     Ok((Command::WriteFrames(None), p))
651                 } else {
652                     let (dir, p) = p.path()?;
653                     Ok((Command::WriteFrames(Some(dir)), p))
654                 }
655             }
656             "e" => {
657                 let (paths, p) = p.paths()?;
658                 Ok((Command::Edit(paths), p))
659             }
660             "e/frames" => {
661                 let (paths, p) = p.paths()?;
662                 Ok((Command::EditFrames(paths), p))
663             }
664             "help" => Ok((Command::Mode(Mode::Help), p)),
665             "set" => {
666                 let (k, p) = p.identifier()?;
667                 let (_, p) = p.whitespace()?;
668 
669                 if p.is_empty() {
670                     Ok((Command::Set(k.to_string(), Value::Bool(true)), p))
671                 } else {
672                     let (_, p) = p.sigil('=')?;
673                     let (_, p) = p.whitespace()?;
674                     let (v, p) = p.parse::<Value>()?;
675                     Ok((Command::Set(k.to_string(), v), p))
676                 }
677             }
678             "unset" => {
679                 let (k, p) = p.identifier()?;
680                 Ok((Command::Set(k.to_string(), Value::Bool(false)), p))
681             }
682             "toggle" => {
683                 let (k, p) = p.identifier()?;
684                 Ok((Command::Toggle(k.to_string()), p))
685             }
686             "echo" => {
687                 let (v, p) = p.parse::<Value>()?;
688                 Ok((Command::Echo(v), p))
689             }
690             "slice" => {
691                 if p.is_empty() {
692                     Ok((Command::Slice(None), p))
693                 } else {
694                     let (n, p) = p.parse::<u32>()?;
695                     Ok((Command::Slice(Some(n as usize)), p))
696                 }
697             }
698             "source" => {
699                 if p.is_empty() {
700                     Ok((Command::Source(None), p))
701                 } else {
702                     let (path, p) = p.path()?;
703                     Ok((Command::Source(Some(path)), p))
704                 }
705             }
706             "cd" => {
707                 if p.is_empty() {
708                     Ok((Command::ChangeDir(None), p))
709                 } else {
710                     let (path, p) = p.path()?;
711                     Ok((Command::ChangeDir(Some(path)), p))
712                 }
713             }
714             "zoom" => {
715                 if let Ok((_, p)) = p.clone().sigil('+') {
716                     Ok((Command::Zoom(Op::Incr), p))
717                 } else if let Ok((_, p)) = p.clone().sigil('-') {
718                     Ok((Command::Zoom(Op::Decr), p))
719                 } else if let Ok((z, p)) = p.parse::<f64>() {
720                     Ok((Command::Zoom(Op::Set(z as f32)), p))
721                 } else {
722                     Err(Error::new("couldn't parse zoom parameter"))
723                 }
724             }
725             "brush" => Ok((Command::Tool(Tool::Brush(Brush::default())), p)),
726             "brush/size" => {
727                 let (c, p) = p.parse::<char>()?;
728                 match c {
729                     '+' => Ok((Command::BrushSize(Op::Incr), p)),
730                     '-' => Ok((Command::BrushSize(Op::Decr), p)),
731                     _ => Err(Error::new("invalid parameter")),
732                 }
733             }
734             "brush/set" => {
735                 let (mode, p) = p.parse::<BrushMode>()?;
736                 Ok((Command::BrushSet(mode), p))
737             }
738             "brush/unset" => {
739                 let (mode, p) = p.parse::<BrushMode>()?;
740                 Ok((Command::BrushUnset(mode), p))
741             }
742             "brush/toggle" => {
743                 let (mode, p) = p.parse::<BrushMode>()?;
744                 Ok((Command::BrushToggle(mode), p))
745             }
746             "mode" => {
747                 let (mode, p) = p.parse::<Mode>()?;
748                 Ok((Command::Mode(mode), p))
749             }
750             "visual" => Ok((Command::Mode(Mode::Visual(VisualState::default())), p)),
751             "sampler" => Ok((Command::Tool(Tool::Sampler), p)),
752             "sampler/off" => Ok((Command::ToolPrev, p)),
753             "v/next" => Ok((Command::ViewNext, p)),
754             "v/prev" => Ok((Command::ViewPrev, p)),
755             "v/center" => Ok((Command::ViewCenter, p)),
756             "v/clear" => {
757                 if let Ok((rgba, p)) = p.clone().parse::<Rgba8>() {
758                     Ok((Command::Fill(rgba), p))
759                 } else {
760                     Ok((Command::Fill(Rgba8::TRANSPARENT), p))
761                 }
762             }
763             "pan" => {
764                 let ((x, y), p) = p.parse::<(i32, i32)>()?;
765                 Ok((Command::Pan(x, y), p))
766             }
767             "map/visual" => {
768                 let (km, p) = KeyMapping::parse(
769                     p,
770                     &[
771                         Mode::Visual(VisualState::selecting()),
772                         Mode::Visual(VisualState::Pasting),
773                     ],
774                 )?;
775                 Ok((Command::Map(Box::new(km)), p))
776             }
777             "map/normal" => {
778                 let (km, p) = KeyMapping::parse(p, &[Mode::Normal])?;
779                 Ok((Command::Map(Box::new(km)), p))
780             }
781             "map" => {
782                 let (km, p) = KeyMapping::parse(
783                     p,
784                     &[
785                         Mode::Normal,
786                         Mode::Visual(VisualState::selecting()),
787                         Mode::Visual(VisualState::Pasting),
788                     ],
789                 )?;
790                 Ok((Command::Map(Box::new(km)), p))
791             }
792             "map/clear!" => Ok((Command::MapClear, p)),
793             "p/add" => {
794                 let (rgba, p) = p.parse::<Rgba8>()?;
795                 Ok((Command::PaletteAdd(rgba), p))
796             }
797             "p/clear" => Ok((Command::PaletteClear, p)),
798             "p/sample" => Ok((Command::PaletteSample, p)),
799             "undo" => Ok((Command::Undo, p)),
800             "redo" => Ok((Command::Redo, p)),
801             "f/new" => Err(Error::new(
802                 "parsing failed: `f/new` has been renamed to `f/add`",
803             )),
804             "f/add" => Ok((Command::AddFrame, p)),
805             "f/clone" => match p.clone().parse::<i32>().or_else(|_| Ok((-1, p))) {
806                 Ok((index, p)) => Ok((Command::CloneFrame(index), p)),
807                 Err(e) => Err(e),
808             },
809             "f/remove" => Ok((Command::RemoveFrame, p)),
810             "f/resize" => {
811                 let ((w, h), p) = p.parse::<(u32, u32)>()?;
812                 Ok((Command::ResizeFrame(w, h), p))
813             }
814             "tool" => {
815                 let (t, p) = p.word()?;
816                 match t {
817                     "pan" => Ok((Command::Tool(Tool::Pan(PanState::default())), p)),
818                     "brush" => Ok((Command::Tool(Tool::Brush(Brush::default())), p)),
819                     "sampler" => Ok((Command::Tool(Tool::Sampler), p)),
820                     _ => Err(Error::new(format!("unknown tool {:?}", t))),
821                 }
822             }
823             "tool/prev" => Ok((Command::ToolPrev, p)),
824             "swap" => Ok((Command::SwapColors, p)),
825             "reset!" => Ok((Command::Reset, p)),
826             "selection/move" => {
827                 let ((x, y), p) = p.parse::<(i32, i32)>()?;
828                 Ok((Command::SelectionMove(x, y), p))
829             }
830             "selection/resize" => {
831                 let ((x, y), p) = p.parse::<(i32, i32)>()?;
832                 Ok((Command::SelectionResize(x, y), p))
833             }
834             "selection/yank" => Ok((Command::SelectionYank, p)),
835             "selection/cut" => Ok((Command::SelectionCut, p)),
836             "selection/paste" => Ok((Command::SelectionPaste, p)),
837             "selection/expand" => Ok((Command::SelectionExpand, p)),
838             "selection/erase" => Ok((Command::SelectionErase, p)),
839             "selection/offset" => {
840                 let ((x, y), p) = p.parse::<(i32, i32)>()?;
841                 Ok((Command::SelectionOffset(x, y), p))
842             }
843             "selection/jump" => {
844                 let (dir, p) = p.parse::<Direction>()?;
845                 Ok((Command::SelectionJump(dir), p))
846             }
847             "selection/fill" => {
848                 if let Ok((rgba, p)) = p.clone().parse::<Rgba8>() {
849                     Ok((Command::SelectionFill(Some(rgba)), p))
850                 } else {
851                     Ok((Command::SelectionFill(None), p))
852                 }
853             }
854             unrecognized => Err(Error::new(format!(
855                 "unrecognized command ':{}'",
856                 unrecognized
857             ))),
858         }
859     }
860 }
861 
862 #[derive(Debug)]
863 pub struct CommandCompleter {
864     file_completer: FileCompleter,
865 }
866 
867 impl CommandCompleter {
new<P: AsRef<Path>>(cwd: P, exts: &[&str]) -> Self868     fn new<P: AsRef<Path>>(cwd: P, exts: &[&str]) -> Self {
869         Self {
870             file_completer: FileCompleter::new(cwd, exts),
871         }
872     }
873 }
874 
875 impl autocomplete::Completer for CommandCompleter {
876     type Options = ();
877 
complete(&self, input: &str, _opts: ()) -> Vec<String>878     fn complete(&self, input: &str, _opts: ()) -> Vec<String> {
879         let p = Parser::new(input);
880 
881         match p.parse::<Command>() {
882             Ok((cmd, _)) => match cmd {
883                 Command::ChangeDir(path) | Command::WriteFrames(path) => self.complete_path(
884                     path.as_ref(),
885                     input,
886                     FileCompleterOpts { directories: true },
887                 ),
888                 Command::Source(path) | Command::Write(path) => {
889                     self.complete_path(path.as_ref(), input, Default::default())
890                 }
891                 Command::Edit(paths) | Command::EditFrames(paths) => {
892                     self.complete_path(paths.last(), input, Default::default())
893                 }
894                 _ => vec![],
895             },
896             Err(_) => vec![],
897         }
898     }
899 }
900 
901 impl CommandCompleter {
complete_path( &self, path: Option<&String>, input: &str, opts: FileCompleterOpts, ) -> Vec<String>902     fn complete_path(
903         &self,
904         path: Option<&String>,
905         input: &str,
906         opts: FileCompleterOpts,
907     ) -> Vec<String> {
908         use crate::autocomplete::Completer;
909 
910         let empty = "".to_owned();
911         let path = path.unwrap_or(&empty);
912 
913         // If there's whitespace between the path and the cursor, don't complete the path.
914         // Instead, complete as if the input was empty.
915         match input.chars().next_back() {
916             Some(c) if c.is_whitespace() => self.file_completer.complete("", opts),
917             _ => self.file_completer.complete(path, opts),
918         }
919     }
920 }
921 
922 #[cfg(test)]
923 mod test {
924     use super::*;
925     use std::{fs, fs::File};
926 
927     #[test]
test_command_completer()928     fn test_command_completer() {
929         let tmp = tempfile::tempdir().unwrap();
930 
931         fs::create_dir(tmp.path().join("assets")).unwrap();
932         for file_name in &["one.png", "two.png", "three.png"] {
933             let path = tmp.path().join(file_name);
934             File::create(path).unwrap();
935         }
936         for file_name in &["four.png", "five.png", "six.png"] {
937             let path = tmp.path().join("assets").join(file_name);
938             File::create(path).unwrap();
939         }
940 
941         let cc = CommandCompleter::new(tmp.path(), &["png"]);
942         let mut auto = Autocomplete::new(cc);
943 
944         assert_eq!(auto.next(":e |", 3), Some(("three.png".to_owned(), 3..3)));
945         auto.invalidate();
946         assert_eq!(
947             auto.next(":e |one.png", 3),
948             Some(("three.png".to_owned(), 3..3))
949         );
950 
951         auto.invalidate();
952         assert_eq!(
953             auto.next(":e one.png | two.png", 11),
954             Some(("three.png".to_owned(), 11..11))
955         );
956         assert_eq!(
957             auto.next(":e one.png three.png| two.png", 20),
958             Some(("two.png".to_owned(), 11..20))
959         );
960         assert_eq!(
961             auto.next(":e one.png two.png| two.png", 18),
962             Some(("one.png".to_owned(), 11..18))
963         );
964 
965         auto.invalidate();
966         assert_eq!(
967             auto.next(":e assets/|", 10),
968             Some(("six.png".to_owned(), 10..10))
969         );
970     }
971 
972     #[test]
test_command_line()973     fn test_command_line() {
974         let tmp = tempfile::tempdir().unwrap();
975 
976         fs::create_dir(tmp.path().join("assets")).unwrap();
977         for file_name in &["one.png", "two.png", "three.png"] {
978             let path = tmp.path().join(file_name);
979             File::create(path).unwrap();
980         }
981         for file_name in &["four.png", "five.png"] {
982             let path = tmp.path().join("assets").join(file_name);
983             File::create(path).unwrap();
984         }
985 
986         let mut cli = CommandLine::new(tmp.path(), &tmp.path().join(".history"), &["png"]);
987 
988         cli.puts(":e one");
989         cli.completion_next();
990         assert_eq!(cli.input(), ":e one.png");
991 
992         cli.completion_next();
993         assert_eq!(cli.input(), ":e one.png");
994 
995         cli.clear();
996         cli.puts(":e ");
997         cli.completion_next();
998         assert_eq!(cli.input(), ":e three.png");
999 
1000         cli.completion_next();
1001         assert_eq!(cli.input(), ":e two.png");
1002 
1003         cli.completion_next();
1004         assert_eq!(cli.input(), ":e one.png");
1005 
1006         cli.completion_next();
1007         assert_eq!(cli.input(), ":e assets");
1008 
1009         cli.putc('/');
1010         cli.completion_next();
1011         assert_eq!(cli.input(), ":e assets/five.png");
1012 
1013         cli.completion_next();
1014         assert_eq!(cli.input(), ":e assets/four.png");
1015 
1016         cli.completion_next();
1017         assert_eq!(cli.input(), ":e assets/five.png");
1018 
1019         cli.putc(' ');
1020         cli.completion_next();
1021         assert_eq!(cli.input(), ":e assets/five.png three.png");
1022 
1023         cli.putc(' ');
1024         cli.putc('t');
1025         cli.completion_next();
1026         assert_eq!(cli.input(), ":e assets/five.png three.png three.png");
1027 
1028         cli.completion_next();
1029         assert_eq!(cli.input(), ":e assets/five.png three.png two.png");
1030 
1031         cli.completion_next();
1032         assert_eq!(cli.input(), ":e assets/five.png three.png three.png");
1033 
1034         for _ in 0..10 {
1035             cli.cursor_backward();
1036         }
1037         cli.putc(' ');
1038         cli.putc('o');
1039         cli.completion_next();
1040         assert_eq!(
1041             cli.input(),
1042             ":e assets/five.png three.png one.png three.png"
1043         );
1044 
1045         cli.clear();
1046         cli.puts(":e assets");
1047         cli.completion_next();
1048         assert_eq!(cli.input(), ":e assets/");
1049 
1050         cli.clear();
1051         cli.puts(":e asset");
1052 
1053         cli.completion_next();
1054         assert_eq!(cli.input(), ":e assets/");
1055 
1056         cli.completion_next();
1057         assert_eq!(cli.input(), ":e assets/five.png");
1058     }
1059 
1060     #[test]
test_command_line_change_dir()1061     fn test_command_line_change_dir() {
1062         let tmp = tempfile::tempdir().unwrap();
1063 
1064         fs::create_dir(tmp.path().join("assets")).unwrap();
1065         for file_name in &["four.png", "five.png"] {
1066             let path = tmp.path().join("assets").join(file_name);
1067             File::create(path).unwrap();
1068         }
1069 
1070         let mut cli = CommandLine::new(tmp.path(), Path::new("/dev/null"), &["png"]);
1071 
1072         cli.set_cwd(tmp.path().join("assets/").as_path());
1073         cli.puts(":e ");
1074 
1075         cli.completion_next();
1076         assert_eq!(cli.input(), ":e five.png");
1077 
1078         cli.completion_next();
1079         assert_eq!(cli.input(), ":e four.png");
1080     }
1081 
1082     #[test]
test_command_line_cd()1083     fn test_command_line_cd() {
1084         let tmp = tempfile::tempdir().unwrap();
1085 
1086         fs::create_dir(tmp.path().join("assets")).unwrap();
1087         fs::create_dir(tmp.path().join("assets").join("1")).unwrap();
1088         fs::create_dir(tmp.path().join("assets").join("2")).unwrap();
1089         File::create(tmp.path().join("assets").join("rx.png")).unwrap();
1090 
1091         let mut cli = CommandLine::new(tmp.path(), Path::new("/dev/null"), &["png"]);
1092 
1093         cli.clear();
1094         cli.puts(":cd assets/");
1095 
1096         cli.completion_next();
1097         assert_eq!(cli.input(), ":cd assets/2");
1098 
1099         cli.completion_next();
1100         assert_eq!(cli.input(), ":cd assets/1");
1101 
1102         cli.completion_next();
1103         assert_eq!(cli.input(), ":cd assets/2");
1104     }
1105 
1106     #[test]
test_command_line_cursor()1107     fn test_command_line_cursor() {
1108         let mut cli = CommandLine::new("/dev/null", "/dev/null", &[]);
1109 
1110         cli.puts(":echo");
1111         cli.delc();
1112         assert_eq!(cli.input(), ":ech");
1113         cli.delc();
1114         assert_eq!(cli.input(), ":ec");
1115         cli.delc();
1116         assert_eq!(cli.input(), ":e");
1117         cli.delc();
1118         assert_eq!(cli.input(), ":");
1119         cli.delc();
1120         assert_eq!(cli.input(), "");
1121 
1122         cli.clear();
1123         cli.puts(":e");
1124 
1125         assert_eq!(cli.peek(), None);
1126         cli.cursor_backward();
1127 
1128         assert_eq!(cli.peek(), Some('e'));
1129         cli.cursor_backward();
1130 
1131         assert_eq!(cli.peek(), Some('e'));
1132         assert_eq!(cli.peek_back(), Some(':'));
1133 
1134         cli.delc();
1135         assert_eq!(cli.input(), ":e");
1136     }
1137 }
1138