streampager/
control.rs

1//! Controlled files.
2//!
3//! Files where data is provided by a controller.
4
5use std::borrow::Cow;
6use std::ops::Range;
7use std::sync::{Arc, Mutex, RwLock};
8
9use thiserror::Error;
10
11use crate::event::{Event, EventSender};
12use crate::file::{FileIndex, FileInfo};
13
14/// Errors that may occur during controlled file operations.
15#[derive(Debug, Error)]
16pub enum ControlledFileError {
17    /// Line number out of range.
18    #[error("line number {index} out of range (0..{length})")]
19    LineOutOfRange {
20        /// The index of the line number that is out of range.
21        index: usize,
22        /// The length of the file (and so the limit for the line number).
23        length: usize,
24    },
25
26    /// Other error type.
27    #[error(transparent)]
28    Error(#[from] crate::error::Error),
29}
30
31/// Result alias for controlled file operations that may fail.
32pub type Result<T> = std::result::Result<T, ControlledFileError>;
33
34/// A controller for a controlled file.
35///
36/// This contains a logical file which can be mutated by a controlling
37/// program.  It can be added to the pager using
38/// `Pager::add_controlled_file`.
39#[derive(Clone)]
40pub struct Controller {
41    data: Arc<RwLock<FileData>>,
42    notify: Arc<Mutex<Vec<(EventSender, FileIndex)>>>,
43}
44
45impl Controller {
46    /// Create a new controller.  The controlled file is initially empty.
47    pub fn new(title: impl Into<String>) -> Controller {
48        Controller {
49            data: Arc::new(RwLock::new(FileData::new(title))),
50            notify: Arc::new(Mutex::new(Vec::new())),
51        }
52    }
53
54    /// Returns a copy of the current title.
55    pub fn title(&self) -> String {
56        let data = self.data.read().unwrap();
57        data.title.clone()
58    }
59
60    /// Returns a copy of the current file info.
61    pub fn info(&self) -> String {
62        let data = self.data.read().unwrap();
63        data.info.clone()
64    }
65
66    /// Apply a sequence of changes to the controlled file.
67    pub fn apply_changes(&self, changes: impl IntoIterator<Item = Change>) -> Result<()> {
68        let mut data = self.data.write().unwrap();
69        for change in changes {
70            data.apply_change(change)?;
71        }
72        // TODO(markbt): more fine-grained notifications.
73        // For now, just reload the file.
74        let notify = self.notify.lock().unwrap();
75        for (event_sender, index) in notify.iter() {
76            event_sender.send(Event::Reloading(*index))?;
77        }
78        Ok(())
79    }
80}
81
82/// A change to apply to a controlled file.
83pub enum Change {
84    /// Set the title for the file.
85    SetTitle {
86        /// The new title.
87        title: String,
88    },
89
90    /// Set the file information for the file.
91    SetInfo {
92        /// The text of the new file info.
93        info: String,
94    },
95
96    /// Append a single line to the file.
97    AppendLine {
98        /// The content of the new line.
99        content: Vec<u8>,
100    },
101
102    /// Insert a single line into the file.
103    InsertLine {
104        /// Index of the line in the file to insert before.
105        before_index: usize,
106        /// The content of the new line.
107        content: Vec<u8>,
108    },
109
110    /// Replace a single line in the file.
111    ReplaceLine {
112        /// Index of the line in fhe file to replace.
113        index: usize,
114        /// The content of the new line.
115        content: Vec<u8>,
116    },
117
118    /// Delete a single line from the file.
119    DeleteLine {
120        /// Index of the line in the file to delete.
121        index: usize,
122    },
123
124    /// Append multiple lines to the file
125    AppendLines {
126        /// The contents of the new lines.
127        contents: Vec<Vec<u8>>,
128    },
129
130    /// Insert some lines before another line in the file.
131    InsertLines {
132        /// Index of the line in the file to insert before.
133        before_index: usize,
134        /// The contents of the new lines.
135        contents: Vec<Vec<u8>>,
136    },
137
138    /// Replace a range of lines with another set of lines.
139    /// The range and the new lines do not need to be the same size.
140    ReplaceLines {
141        /// The range of lines in the file to replace.
142        range: Range<usize>,
143        /// The contents of the new lines.
144        contents: Vec<Vec<u8>>,
145    },
146
147    /// Delete a range of lines in the file.
148    DeleteLines {
149        /// The range of lines in the file to delete.
150        range: Range<usize>,
151    },
152
153    /// Replace all lines with another set of lines.
154    ReplaceAll {
155        /// The new contents of the file.
156        contents: Vec<Vec<u8>>,
157    },
158}
159
160/// A file whose contents is controlled by a `Controller`.
161#[derive(Clone)]
162pub struct ControlledFile {
163    index: FileIndex,
164    data: Arc<RwLock<FileData>>,
165}
166
167impl ControlledFile {
168    pub(crate) fn new(
169        controller: &Controller,
170        index: FileIndex,
171        event_sender: EventSender,
172    ) -> ControlledFile {
173        let mut notify = controller.notify.lock().unwrap();
174        notify.push((event_sender, index));
175        ControlledFile {
176            index,
177            data: controller.data.clone(),
178        }
179    }
180}
181
182impl FileInfo for ControlledFile {
183    /// The file's index.
184    fn index(&self) -> FileIndex {
185        self.index
186    }
187
188    /// The file's title.
189    fn title(&self) -> Cow<'_, str> {
190        let data = self.data.read().unwrap();
191        Cow::Owned(data.title.clone())
192    }
193
194    /// The file's info.
195    fn info(&self) -> Cow<'_, str> {
196        let data = self.data.read().unwrap();
197        Cow::Owned(data.info.clone())
198    }
199
200    /// True once the file is loaded and all newlines have been parsed.
201    fn loaded(&self) -> bool {
202        true
203    }
204
205    /// Returns the number of lines in the file.
206    fn lines(&self) -> usize {
207        self.data.read().unwrap().lines.len()
208    }
209
210    /// Runs the `call` function, passing it the contents of line `index`.
211    /// Tries to avoid copying the data if possible, however the borrowed
212    /// line only lasts as long as the function call.
213    fn with_line<T, F>(&self, index: usize, mut call: F) -> Option<T>
214    where
215        F: FnMut(Cow<'_, [u8]>) -> T,
216    {
217        let data = self.data.read().unwrap();
218        if let Some(line) = data.lines.get(index) {
219            Some(call(Cow::Borrowed(line.content.as_slice())))
220        } else {
221            None
222        }
223    }
224
225    /// Set how many lines are needed.
226    ///
227    /// If `self.lines()` exceeds that number, pause loading until
228    /// `set_needed_lines` is called with a larger number.
229    /// This is only effective for "streamed" input.
230    fn set_needed_lines(&self, _lines: usize) {}
231
232    /// True if the loading thread has been paused.
233    fn paused(&self) -> bool {
234        false
235    }
236}
237
238struct FileData {
239    title: String,
240    info: String,
241    lines: Vec<LineData>,
242}
243
244impl FileData {
245    fn new(title: impl Into<String>) -> FileData {
246        FileData {
247            title: title.into(),
248            info: String::new(),
249            lines: Vec::new(),
250        }
251    }
252
253    fn line_mut(&mut self, index: usize) -> Result<&mut LineData> {
254        let length = self.lines.len();
255        if let Some(line) = self.lines.get_mut(index) {
256            return Ok(line);
257        }
258        Err(ControlledFileError::LineOutOfRange { index, length })
259    }
260
261    fn apply_change(&mut self, change: Change) -> Result<()> {
262        match change {
263            Change::SetTitle { title } => {
264                self.title = title;
265            }
266            Change::SetInfo { info } => {
267                self.info = info;
268            }
269            Change::AppendLine { content } => {
270                self.lines.push(LineData::with_content(content));
271            }
272            Change::InsertLine {
273                before_index,
274                content,
275            } => {
276                self.lines
277                    .insert(before_index, LineData::with_content(content));
278            }
279            Change::ReplaceLine { index, content } => {
280                self.line_mut(index)?.content = content;
281            }
282            Change::DeleteLine { index } => {
283                self.lines.remove(index);
284            }
285            Change::AppendLines { contents } => {
286                let new_lines = contents.into_iter().map(LineData::with_content);
287                self.lines.extend(new_lines);
288            }
289            Change::InsertLines {
290                before_index,
291                contents,
292            } => {
293                let new_lines = contents.into_iter().map(LineData::with_content);
294                self.lines.splice(before_index..before_index, new_lines);
295            }
296            Change::ReplaceLines { range, contents } => {
297                let new_lines = contents.into_iter().map(LineData::with_content);
298                self.lines.splice(range, new_lines);
299            }
300            Change::DeleteLines { range } => {
301                self.lines.splice(range, std::iter::empty());
302            }
303            Change::ReplaceAll { contents } => {
304                let new_lines = contents.into_iter().map(LineData::with_content);
305                self.lines = new_lines.collect();
306            }
307        }
308        Ok(())
309    }
310}
311
312struct LineData {
313    content: Vec<u8>,
314}
315
316impl LineData {
317    fn with_content(content: Vec<u8>) -> LineData {
318        LineData { content }
319    }
320}