edit/
document.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Abstractions over reading/writing arbitrary text containers.
5
6use std::ffi::OsString;
7use std::mem;
8use std::ops::Range;
9use std::path::PathBuf;
10
11use crate::arena::{ArenaString, scratch_arena};
12use crate::helpers::ReplaceRange as _;
13
14/// An abstraction over reading from text containers.
15pub trait ReadableDocument {
16    /// Read some bytes starting at (including) the given absolute offset.
17    ///
18    /// # Warning
19    ///
20    /// * Be lenient on inputs:
21    ///   * The given offset may be out of bounds and you MUST clamp it.
22    ///   * You should not assume that offsets are at grapheme cluster boundaries.
23    /// * Be strict on outputs:
24    ///   * You MUST NOT break grapheme clusters across chunks.
25    ///   * You MUST NOT return an empty slice unless the offset is at or beyond the end.
26    fn read_forward(&self, off: usize) -> &[u8];
27
28    /// Read some bytes before (but not including) the given absolute offset.
29    ///
30    /// # Warning
31    ///
32    /// * Be lenient on inputs:
33    ///   * The given offset may be out of bounds and you MUST clamp it.
34    ///   * You should not assume that offsets are at grapheme cluster boundaries.
35    /// * Be strict on outputs:
36    ///   * You MUST NOT break grapheme clusters across chunks.
37    ///   * You MUST NOT return an empty slice unless the offset is zero.
38    fn read_backward(&self, off: usize) -> &[u8];
39}
40
41/// An abstraction over writing to text containers.
42pub trait WriteableDocument: ReadableDocument {
43    /// Replace the given range with the given bytes.
44    ///
45    /// # Warning
46    ///
47    /// * The given range may be out of bounds and you MUST clamp it.
48    /// * The replacement may not be valid UTF8.
49    fn replace(&mut self, range: Range<usize>, replacement: &[u8]);
50}
51
52impl ReadableDocument for &[u8] {
53    fn read_forward(&self, off: usize) -> &[u8] {
54        let s = *self;
55        &s[off.min(s.len())..]
56    }
57
58    fn read_backward(&self, off: usize) -> &[u8] {
59        let s = *self;
60        &s[..off.min(s.len())]
61    }
62}
63
64impl ReadableDocument for String {
65    fn read_forward(&self, off: usize) -> &[u8] {
66        let s = self.as_bytes();
67        &s[off.min(s.len())..]
68    }
69
70    fn read_backward(&self, off: usize) -> &[u8] {
71        let s = self.as_bytes();
72        &s[..off.min(s.len())]
73    }
74}
75
76impl WriteableDocument for String {
77    fn replace(&mut self, range: Range<usize>, replacement: &[u8]) {
78        // `replacement` is not guaranteed to be valid UTF-8, so we need to sanitize it.
79        let scratch = scratch_arena(None);
80        let utf8 = ArenaString::from_utf8_lossy(&scratch, replacement);
81        let src = match &utf8 {
82            Ok(s) => s,
83            Err(s) => s.as_str(),
84        };
85
86        // SAFETY: `range` is guaranteed to be on codepoint boundaries.
87        unsafe { self.as_mut_vec() }.replace_range(range, src.as_bytes());
88    }
89}
90
91impl ReadableDocument for PathBuf {
92    fn read_forward(&self, off: usize) -> &[u8] {
93        let s = self.as_os_str().as_encoded_bytes();
94        &s[off.min(s.len())..]
95    }
96
97    fn read_backward(&self, off: usize) -> &[u8] {
98        let s = self.as_os_str().as_encoded_bytes();
99        &s[..off.min(s.len())]
100    }
101}
102
103impl WriteableDocument for PathBuf {
104    fn replace(&mut self, range: Range<usize>, replacement: &[u8]) {
105        let mut vec = mem::take(self).into_os_string().into_encoded_bytes();
106        vec.replace_range(range, replacement);
107        *self = unsafe { Self::from(OsString::from_encoded_bytes_unchecked(vec)) };
108    }
109}