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
//! Controlled files.
//!
//! Files where data is provided by a controller.

use std::borrow::Cow;
use std::ops::Range;
use std::sync::{Arc, Mutex, RwLock};

use thiserror::Error;

use crate::event::{Event, EventSender};
use crate::file::{FileIndex, FileInfo};

/// Errors that may occur during controlled file operations.
#[derive(Debug, Error)]
pub enum ControlledFileError {
    /// Line number out of range.
    #[error("line number {index} out of range (0..{length})")]
    LineOutOfRange {
        /// The index of the line number that is out of range.
        index: usize,
        /// The length of the file (and so the limit for the line number).
        length: usize,
    },

    /// Other error type.
    #[error(transparent)]
    Error(#[from] crate::error::Error),
}

/// Result alias for controlled file operations that may fail.
pub type Result<T> = std::result::Result<T, ControlledFileError>;

/// A controller for a controlled file.
///
/// This contains a logical file which can be mutated by a controlling
/// program.  It can be added to the pager using
/// `Pager::add_controlled_file`.
#[derive(Clone)]
pub struct Controller {
    data: Arc<RwLock<FileData>>,
    notify: Arc<Mutex<Vec<(EventSender, FileIndex)>>>,
}

impl Controller {
    /// Create a new controller.  The controlled file is initially empty.
    pub fn new(title: impl Into<String>) -> Controller {
        Controller {
            data: Arc::new(RwLock::new(FileData::new(title))),
            notify: Arc::new(Mutex::new(Vec::new())),
        }
    }

    /// Returns a copy of the current title.
    pub fn title(&self) -> String {
        let data = self.data.read().unwrap();
        data.title.clone()
    }

    /// Returns a copy of the current file info.
    pub fn info(&self) -> String {
        let data = self.data.read().unwrap();
        data.info.clone()
    }

    /// Apply a sequence of changes to the controlled file.
    pub fn apply_changes(&self, changes: impl IntoIterator<Item = Change>) -> Result<()> {
        let mut data = self.data.write().unwrap();
        for change in changes {
            data.apply_change(change)?;
        }
        // TODO(markbt): more fine-grained notifications.
        // For now, just reload the file.
        let notify = self.notify.lock().unwrap();
        for (event_sender, index) in notify.iter() {
            event_sender.send(Event::Reloading(*index))?;
        }
        Ok(())
    }
}

/// A change to apply to a controlled file.
pub enum Change {
    /// Set the title for the file.
    SetTitle {
        /// The new title.
        title: String,
    },

    /// Set the file information for the file.
    SetInfo {
        /// The text of the new file info.
        info: String,
    },

    /// Append a single line to the file.
    AppendLine {
        /// The content of the new line.
        content: Vec<u8>,
    },

    /// Insert a single line into the file.
    InsertLine {
        /// Index of the line in the file to insert before.
        before_index: usize,
        /// The content of the new line.
        content: Vec<u8>,
    },

    /// Replace a single line in the file.
    ReplaceLine {
        /// Index of the line in fhe file to replace.
        index: usize,
        /// The content of the new line.
        content: Vec<u8>,
    },

    /// Delete a single line from the file.
    DeleteLine {
        /// Index of the line in the file to delete.
        index: usize,
    },

    /// Append multiple lines to the file
    AppendLines {
        /// The contents of the new lines.
        contents: Vec<Vec<u8>>,
    },

    /// Insert some lines before another line in the file.
    InsertLines {
        /// Index of the line in the file to insert before.
        before_index: usize,
        /// The contents of the new lines.
        contents: Vec<Vec<u8>>,
    },

    /// Replace a range of lines with another set of lines.
    /// The range and the new lines do not need to be the same size.
    ReplaceLines {
        /// The range of lines in the file to replace.
        range: Range<usize>,
        /// The contents of the new lines.
        contents: Vec<Vec<u8>>,
    },

    /// Delete a range of lines in the file.
    DeleteLines {
        /// The range of lines in the file to delete.
        range: Range<usize>,
    },

    /// Replace all lines with another set of lines.
    ReplaceAll {
        /// The new contents of the file.
        contents: Vec<Vec<u8>>,
    },
}

/// A file whose contents is controlled by a `Controller`.
#[derive(Clone)]
pub struct ControlledFile {
    index: FileIndex,
    data: Arc<RwLock<FileData>>,
}

impl ControlledFile {
    pub(crate) fn new(
        controller: &Controller,
        index: FileIndex,
        event_sender: EventSender,
    ) -> ControlledFile {
        let mut notify = controller.notify.lock().unwrap();
        notify.push((event_sender, index));
        ControlledFile {
            index,
            data: controller.data.clone(),
        }
    }
}

impl FileInfo for ControlledFile {
    /// The file's index.
    fn index(&self) -> FileIndex {
        self.index
    }

    /// The file's title.
    fn title(&self) -> Cow<'_, str> {
        let data = self.data.read().unwrap();
        Cow::Owned(data.title.clone())
    }

    /// The file's info.
    fn info(&self) -> Cow<'_, str> {
        let data = self.data.read().unwrap();
        Cow::Owned(data.info.clone())
    }

    /// True once the file is loaded and all newlines have been parsed.
    fn loaded(&self) -> bool {
        true
    }

    /// Returns the number of lines in the file.
    fn lines(&self) -> usize {
        self.data.read().unwrap().lines.len()
    }

    /// Runs the `call` function, passing it the contents of line `index`.
    /// Tries to avoid copying the data if possible, however the borrowed
    /// line only lasts as long as the function call.
    fn with_line<T, F>(&self, index: usize, mut call: F) -> Option<T>
    where
        F: FnMut(Cow<'_, [u8]>) -> T,
    {
        let data = self.data.read().unwrap();
        if let Some(line) = data.lines.get(index) {
            Some(call(Cow::Borrowed(line.content.as_slice())))
        } else {
            None
        }
    }

    /// Set how many lines are needed.
    ///
    /// If `self.lines()` exceeds that number, pause loading until
    /// `set_needed_lines` is called with a larger number.
    /// This is only effective for "streamed" input.
    fn set_needed_lines(&self, _lines: usize) {}

    /// True if the loading thread has been paused.
    fn paused(&self) -> bool {
        false
    }
}

struct FileData {
    title: String,
    info: String,
    lines: Vec<LineData>,
}

impl FileData {
    fn new(title: impl Into<String>) -> FileData {
        FileData {
            title: title.into(),
            info: String::new(),
            lines: Vec::new(),
        }
    }

    fn line_mut(&mut self, index: usize) -> Result<&mut LineData> {
        let length = self.lines.len();
        if let Some(line) = self.lines.get_mut(index) {
            return Ok(line);
        }
        Err(ControlledFileError::LineOutOfRange { index, length })
    }

    fn apply_change(&mut self, change: Change) -> Result<()> {
        match change {
            Change::SetTitle { title } => {
                self.title = title;
            }
            Change::SetInfo { info } => {
                self.info = info;
            }
            Change::AppendLine { content } => {
                self.lines.push(LineData::with_content(content));
            }
            Change::InsertLine {
                before_index,
                content,
            } => {
                self.lines
                    .insert(before_index, LineData::with_content(content));
            }
            Change::ReplaceLine { index, content } => {
                self.line_mut(index)?.content = content;
            }
            Change::DeleteLine { index } => {
                self.lines.remove(index);
            }
            Change::AppendLines { contents } => {
                let new_lines = contents.into_iter().map(LineData::with_content);
                self.lines.extend(new_lines);
            }
            Change::InsertLines {
                before_index,
                contents,
            } => {
                let new_lines = contents.into_iter().map(LineData::with_content);
                self.lines.splice(before_index..before_index, new_lines);
            }
            Change::ReplaceLines { range, contents } => {
                let new_lines = contents.into_iter().map(LineData::with_content);
                self.lines.splice(range, new_lines);
            }
            Change::DeleteLines { range } => {
                self.lines.splice(range, std::iter::empty());
            }
            Change::ReplaceAll { contents } => {
                let new_lines = contents.into_iter().map(LineData::with_content);
                self.lines = new_lines.collect();
            }
        }
        Ok(())
    }
}

struct LineData {
    content: Vec<u8>,
}

impl LineData {
    fn with_content(content: Vec<u8>) -> LineData {
        LineData { content }
    }
}