1use crate::{fs::Fs, syntax::Language, text::Text};
2use camino::Utf8Path;
3use ropey::Rope;
4use std::sync::Arc;
5
6slotmap::new_key_type! {
7 #[must_use]
8 pub struct BufferKey;
9}
10
11#[derive(Clone, Default)]
12pub enum BufferKind {
13 #[default]
15 Scratch,
16 File {
18 path: Arc<Utf8Path>,
19 on_disk: Rope,
21 },
22}
23
24#[derive(Clone, Default)]
25pub struct Buffer {
26 kind: BufferKind,
27 pub text: Text,
28}
29
30impl Buffer {
31 #[must_use]
32 pub fn new() -> Self {
33 Self::default()
34 }
35
36 pub fn open(fs: &impl Fs, path: impl AsRef<Utf8Path>) -> anyhow::Result<Self> {
37 let path = path.as_ref();
38 let exists = fs.exists(path)?;
39 let rope = if exists {
40 let bytes = fs.read(path)?;
41 let string = str::from_utf8(&bytes)?;
42 Rope::from(string)
43 } else {
44 Rope::new()
45 };
46 let mut buffer = Self::from(rope);
47 buffer.kind = BufferKind::File {
50 path: Arc::from(path),
51 on_disk: buffer.text.rope().clone(),
52 };
53 if let Ok(language) = Language::try_from(path) {
54 buffer.text.set_language(language);
55 }
56 Ok(buffer)
57 }
58
59 pub fn save(&mut self, fs: &impl Fs) -> anyhow::Result<()> {
60 if let BufferKind::File { path, on_disk } = &mut self.kind {
61 let mut bytes = Vec::with_capacity(self.text.len());
62 self.text.write_to(&mut bytes)?;
63 fs.write(path, &bytes)?;
64 *on_disk = self.text.rope().clone();
65 }
66 Ok(())
67 }
68
69 pub fn save_as(&mut self, fs: &impl Fs, path: impl AsRef<Utf8Path>) -> anyhow::Result<()> {
70 let path = path.as_ref();
71 let mut bytes = Vec::with_capacity(self.text.len());
72 self.text.write_to(&mut bytes)?;
73 fs.write(path, &bytes)?;
74 self.kind = BufferKind::File {
75 path: Arc::from(path),
76 on_disk: self.text.rope().clone(),
77 };
78 Ok(())
79 }
80
81 #[must_use]
82 pub fn kind(&self) -> &BufferKind {
83 &self.kind
84 }
85
86 #[must_use]
87 pub fn path(&self) -> Option<&Utf8Path> {
88 if let BufferKind::File { path, .. } = &self.kind {
89 Some(path)
90 } else {
91 None
92 }
93 }
94
95 #[must_use]
96 pub fn is_modified(&self) -> Option<bool> {
97 if let BufferKind::File { on_disk, .. } = &self.kind {
98 Some(self.text.rope() != on_disk)
99 } else {
100 None
101 }
102 }
103
104 pub fn assert_invariants(&self) -> anyhow::Result<()> {
105 self.text.assert_invariants()
106 }
107}
108
109impl From<Rope> for Buffer {
110 fn from(rope: Rope) -> Self {
111 Self::from(Text::from(rope))
112 }
113}
114
115impl From<Text> for Buffer {
116 fn from(text: Text) -> Self {
117 Self {
118 text,
119 ..Self::default()
120 }
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::fs::TestFs;
128
129 #[test]
130 fn fs() -> anyhow::Result<()> {
131 let fs = TestFs::default();
132 fs.write("main.rs".into(), b"fn main() {}")?;
133 let mut buffer = Buffer::open(&fs, "main.rs")?;
134 assert_eq!(&buffer.text.to_string(), "fn main() {}\n");
136 assert_eq!(buffer.is_modified(), Some(false));
137 let BufferKind::File { path, .. } = &mut buffer.kind else {
138 unreachable!();
139 };
140 *path = Arc::from(Utf8Path::new("main2.rs"));
141 buffer.save(&fs)?;
142 assert_eq!(fs.read("main2.rs".into())?, b"fn main() {}\n");
143 Ok(())
144 }
145}