1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
//! Provides the main interface to interactive input reader

use std::borrow::Cow;
use std::fmt;
use std::fs::File;
use std::io::{self, BufRead, BufReader, BufWriter, Write as IoWrite};
use std::path::Path;
use std::sync::{Arc, Mutex, MutexGuard};

use command::Command;
use complete::{Completer};
use function::Function;
use inputrc::Directive;
use reader::{Read, Reader, ReadLock, ReadResult};
use terminal::{DefaultTerminal, Signal, Terminal};
use variables::Variable;
use writer::{Write, Writer, WriteLock};

/// The main interface to input reading and other terminal operations
///
/// # Concurrency
///
/// Each `Interface` contains two internal locks to allow concurrent read and write
/// operations. The following types are used to hold a lock and to provide
/// access to a set of operations:
///
/// * [`Reader`] holds the read lock; it provides access to variables and bindings
///   and can initiate a [`read_line`] call. When `read_line` begins, the read
///   lock is acquired and it is held until the function returns. During the
///   `read_line` loop, the `Reader` waits for user input, reads input from the
///   terminal device, then acquires the write lock to process input and run
///   commands which may alter the prompt and the input buffer.
/// * [`Writer`] holds the write lock; it provides an interface to write
///   line-by-line output to the terminal device without interfering with the
///   prompt when a `read_line` may be in progress.
/// * [`Prompter`] holds both the read and write locks; it is created by the
///   `Reader` during the `read_line` loop and destroyed when the write lock is
///   released. It provides access to the current state of user input.
///
/// [`Reader`]: ../reader/struct.Reader.html
/// [`Writer`]: ../writer/struct.Writer.html
/// [`Prompter`]: ../prompter/struct.Prompter.html
/// [`read_line`]: #method.read_line
pub struct Interface<Term: Terminal> {
    term: Term,
    write: Mutex<Write>,
    read: Mutex<Read<Term>>,
}

impl Interface<DefaultTerminal> {
    /// Creates a new `Interface` with the given application name.
    ///
    /// `application` is a string containing the name of the application.
    /// This can be used in user configurations to specify behavior for
    /// particular applications.
    ///
    /// The default terminal interface is used.
    pub fn new<T>(application: T) -> io::Result<Interface<DefaultTerminal>>
            where T: Into<Cow<'static, str>> {
        let term = DefaultTerminal::new()?;
        Interface::with_term(application, term)
    }
}

impl<Term: Terminal> Interface<Term> {
    /// Creates a new `Interface` instance with a particular terminal implementation.
    ///
    /// To use the default terminal interface, call `Interface::new` instead.
    pub fn with_term<T>(application: T, term: Term) -> io::Result<Interface<Term>>
            where T: Into<Cow<'static, str>> {
        let read = Read::new(&term, application.into());

        Ok(Interface{
            term: term,
            write: Mutex::new(Write::new()),
            read: Mutex::new(read),
        })
    }

    /// Acquires the read lock and returns a `Reader` instance.
    ///
    /// The `Reader` instance allows exclusive access to variables, bindings,
    /// and command implementations.
    pub fn lock_reader(&self) -> Reader<Term> {
        Reader::new(self, self.lock_read())
    }

    /// Acquires the write lock and returns a `Writer` instance.
    ///
    /// If a `read_line` call is in progress, this method will erase the prompt,
    /// allowing output to be written without corrupting the prompt text.
    /// The prompt will be redrawn when the `Writer` instance is dropped.
    pub fn lock_writer(&self) -> io::Result<Writer<Term>> {
        Writer::new(self.lock_write())
    }

    fn lock_read(&self) -> ReadLock<Term> {
        ReadLock::new(
            self.term.lock_read(),
            self.read.lock().expect("Interface::lock_read"))
    }

    pub(crate) fn lock_write(&self) -> WriteLock<Term> {
        WriteLock::new(
            self.term.lock_write(),
            self.write.lock().expect("Interface::lock_write"))
    }

    pub(crate) fn lock_write_data(&self) -> MutexGuard<Write> {
        self.write.lock().expect("Interface::lock_write_data")
    }
}

/// ## Locking
///
/// The following methods internally acquire the read lock.
///
/// The lock is released before the method returns.
///
/// If the read lock is already held, e.g. because a `read_line` call is in
/// progress, the method will block until the lock is released.
impl<Term: Terminal> Interface<Term> {
    /// Interactively reads a line from the terminal device.
    ///
    /// If the user issues an end-of-file, `ReadResult::Eof` is returned.
    ///
    /// If a reported signal (see [`set_report_signal`]) is received,
    /// it is returned as `ReadResult::Signal(_)`.
    ///
    /// Otherwise, user input is collected until a newline is entered.
    /// The resulting input (not containing a trailing newline character)
    /// is returned as `ReadResult::Input(_)`.
    ///
    /// [`set_report_signal`]: #method.set_report_signal
    pub fn read_line(&self) -> io::Result<ReadResult> {
        self.lock_reader().read_line()
    }

    /// Returns a clone of the current completer instance.
    pub fn completer(&self) -> Arc<Completer<Term>> {
        self.lock_reader().completer().clone()
    }

    /// Replaces the current completer, returning the previous instance.
    pub fn set_completer(&self, completer: Arc<Completer<Term>>)
            -> Arc<Completer<Term>> {
        self.lock_reader().set_completer(completer)
    }

    /// Returns the value of the named variable or `None`
    /// if no such variable exists.
    pub fn get_variable(&self, name: &str) -> Option<Variable> {
        self.lock_reader().get_variable(name)
    }

    /// Sets the value of the named variable and returns the previous
    /// value.
    ///
    /// If `name` does not refer to a variable or the `value` is not
    /// a valid value for the variable, `None` is returned.
    pub fn set_variable(&self, name: &str, value: &str) -> Option<Variable> {
        self.lock_reader().set_variable(name, value)
    }

    /// Sets the prompt that will be displayed when `read_line` is called.
    ///
    /// This method internally acquires the `Interface` write lock.
    ///
    /// # Notes
    ///
    /// If `prompt` contains any terminal escape sequences (e.g. color codes),
    /// such escape sequences should be immediately preceded by the character
    /// `'\x01'` and immediately followed by the character `'\x02'`.
    pub fn set_prompt(&self, prompt: &str) {
        self.lock_reader().set_prompt(prompt)
    }

    /// Returns whether the given `Signal` is ignored.
    pub fn ignore_signal(&self, signal: Signal) -> bool {
        self.lock_reader().ignore_signal(signal)
    }

    /// Sets whether the given `Signal` will be ignored.
    pub fn set_ignore_signal(&self, signal: Signal, set: bool) {
        self.lock_reader().set_ignore_signal(signal, set)
    }

    /// Returns whether the given `Signal` is reported.
    pub fn report_signal(&self, signal: Signal) -> bool {
        self.lock_reader().report_signal(signal)
    }

    /// Sets whether the given `Signal` is reported.
    pub fn set_report_signal(&self, signal: Signal, set: bool) {
        self.lock_reader().set_report_signal(signal, set)
    }

    /// Binds a sequence to a command.
    ///
    /// Returns the previously bound command.
    pub fn bind_sequence<T>(&self, seq: T, cmd: Command) -> Option<Command>
            where T: Into<Cow<'static, str>> {
        self.lock_reader().bind_sequence(seq, cmd)
    }

    /// Binds a sequence to a command, if and only if the given sequence
    /// is not already bound to a command.
    ///
    /// Returns `true` if a new binding was created.
    pub fn bind_sequence_if_unbound<T>(&self, seq: T, cmd: Command) -> bool
            where T: Into<Cow<'static, str>> {
        self.lock_reader().bind_sequence_if_unbound(seq, cmd)
    }

    /// Removes a binding for the given sequence.
    ///
    /// Returns the previously bound command.
    pub fn unbind_sequence(&self, seq: &str) -> Option<Command> {
        self.lock_reader().unbind_sequence(seq)
    }

    /// Defines a named function to which sequences may be bound.
    ///
    /// The name should consist of lowercase ASCII letters and numbers,
    /// containing no spaces, with words separated by hyphens. However,
    /// this is not a requirement.
    ///
    /// Returns the function previously defined with the same name.
    pub fn define_function<T>(&self, name: T, cmd: Arc<Function<Term>>)
            -> Option<Arc<Function<Term>>> where T: Into<Cow<'static, str>> {
        self.lock_reader().define_function(name, cmd)
    }

    /// Removes a function defined with the given name.
    ///
    /// Returns the defined function.
    pub fn remove_function(&self, name: &str) -> Option<Arc<Function<Term>>> {
        self.lock_reader().remove_function(name)
    }

    /// Evaluates a series of configuration directives.
    pub fn evaluate_directives(&self, dirs: Vec<Directive>) {
        self.lock_reader().evaluate_directives(&self.term, dirs)
    }

    /// Evaluates a single configuration directive.
    pub fn evaluate_directive(&self, dir: Directive) {
        self.lock_reader().evaluate_directive(&self.term, dir)
    }
}

/// ## Locking
///
/// The following methods internally acquire the write lock.
///
/// The lock is released before the method returns.
///
/// If the write lock is already held, the method will block until it is released.
impl<Term: Terminal> Interface<Term> {
    /// Returns the current number of history entries.
    pub fn history_len(&self) -> usize {
        self.lock_write().history_len()
    }

    /// Returns the maximum number of history entries.
    ///
    /// Not to be confused with [`history_len`], which returns the *current*
    /// number of history entries.
    ///
    /// [`history_len`]: #method.history_len
    pub fn history_size(&self) -> usize {
        self.lock_write().history_size()
    }

    /// Save history entries to the specified file.
    pub fn save_history<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
        let file = File::create(path)?;
        let mut wtr = BufWriter::new(file);

        for entry in self.lock_write().history() {
            wtr.write_all(entry.as_bytes())?;
            wtr.write_all(b"\n")?;
        }

        wtr.flush()
    }

    /// Load history entries from the specified file.
    pub fn load_history<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
        let mut writer = self.lock_write();

        let file = File::open(&path)?;
        let rdr = BufReader::new(file);

        for line in rdr.lines() {
            writer.add_history(line?);
        }

        Ok(())
    }

    /// Writes formatted text to the terminal display.
    ///
    /// This method enables `Interface` to be used as the receiver to
    /// the [`writeln!`] macro.
    ///
    /// If the text contains any unprintable characters (e.g. escape sequences),
    /// those characters will be escaped before printing.
    ///
    /// # Notes
    ///
    /// If this method is called during a [`read_line`] call, the prompt will
    /// first be erased, then restored after the given string is printed.
    /// Therefore, the written text should end with a newline. If the `writeln!`
    /// macro is used, a newline is automatically added to the end of the text.
    ///
    /// [`read_line`]: #method.read_line
    /// [`writeln!`]: https://doc.rust-lang.org/std/macro.writeln.html
    pub fn write_fmt(&self, args: fmt::Arguments) -> io::Result<()> {
        let s = args.to_string();
        self.write_str(&s)
    }

    fn write_str(&self, line: &str) -> io::Result<()> {
        self.lock_writer()?.write_str(line)
    }
}

/// ## Locking
///
/// The following methods internally acquire both the read and write locks.
///
/// The locks are released before the method returns.
///
/// If either lock is already held, the method will block until it is released.
impl<Term: Terminal> Interface<Term> {
    // Some of these methods don't appear to require a read lock, but do acquire
    // it nonetheless because any operation that truncates history may interefere
    // with an ongoing `read_line` call. Therefore, the read lock is acquired
    // to ensure that no `read_line` call is in progress.

    /// Adds a line to history.
    pub fn add_history(&self, line: String) {
        let _reader = self.lock_read();
        self.lock_write().add_history(line);
    }

    /// Adds a line to history, unless it is identical to the most recent entry.
    pub fn add_history_unique(&self, line: String) {
        let _reader = self.lock_read();
        self.lock_write().add_history_unique(line);
    }

    /// Removes all history entries.
    pub fn clear_history(&self) {
        let _reader = self.lock_read();
        self.lock_write().clear_history();
    }

    /// Removes the history entry at the given index.
    ///
    /// If the index is out of bounds, this method has no effect.
    pub fn remove_history(&self, idx: usize) {
        let _reader = self.lock_read();
        self.lock_write().remove_history(idx);
    }

    /// Sets the maximum number of history entries.
    ///
    /// If `n` is less than the current number of history entries,
    /// the oldest entries are truncated to meet the given requirement.
    pub fn set_history_size(&self, n: usize) {
        let _reader = self.lock_read();
        self.lock_write().set_history_size(n);
    }

    /// Truncates history to the only the most recent `n` entries.
    pub fn truncate_history(&self, n: usize) {
        let _reader = self.lock_read();
        self.lock_write().truncate_history(n);
    }
}