rune/core/object/
buffer.rs

1use super::{Gc, Object, ObjectType, TagType, WithLifetime};
2use crate::{
3    core::{
4        error::{Type, TypeError},
5        gc::{Block, Context, GcHeap, GcState, Trace},
6    },
7    derive_GcMoveable,
8    intervals::IntervalTree,
9};
10use anyhow::{Result, bail};
11use rune_macros::Trace;
12use std::{
13    fmt::Display,
14    ops::{Deref, DerefMut},
15    sync::{Mutex, MutexGuard},
16};
17use text_buffer::Buffer as TextBuffer;
18
19/// A Handle to an open buffer. Only one thread can hold this at a time.
20#[derive(Debug)]
21pub(crate) struct OpenBuffer<'a> {
22    data: MutexGuard<'a, Option<BufferData>>,
23    back_ref: &'a LispBuffer,
24}
25
26impl OpenBuffer<'_> {
27    pub(crate) fn get(&self) -> &BufferData {
28        // buffer can never be none because we check it as part of `lock`.
29        self.data.as_ref().unwrap()
30    }
31
32    pub(crate) fn get_mut(&mut self) -> &mut BufferData {
33        // buffer can never be none because we check it as part of `lock`.
34        self.data.as_mut().unwrap()
35    }
36
37    // TODO: we shouldn't leave it empty
38    pub(crate) fn kill(&mut self) -> bool {
39        let killed = self.data.is_some();
40        *self.data = None;
41        killed
42    }
43
44    pub(crate) fn lisp_buffer<'ob>(&self, cx: &'ob Context) -> &'ob LispBuffer {
45        cx.bind(self.back_ref)
46    }
47
48    pub(crate) fn insert(&mut self, arg: Object) -> Result<()> {
49        match arg.untag() {
50            ObjectType::Int(i) => {
51                let Ok(u_32) = i.try_into() else { bail!("{i} is an invalid char") };
52                let Some(chr) = char::from_u32(u_32) else { bail!("{i} is an Invalid char") };
53                self.get_mut().text.insert_char(chr);
54            }
55            ObjectType::String(s) => self.get_mut().text.insert(s),
56            x => bail!(TypeError::new(Type::String, x)),
57        }
58        Ok(())
59    }
60
61    pub(crate) fn slice_with_gap(&self, beg: usize, end: usize) -> Result<(&str, &str)> {
62        let beg = self.in_range(beg)?;
63        let end = self.in_range(end)?;
64        Ok(self.get().text.slice(beg..end))
65    }
66
67    pub(crate) fn delete(&mut self, beg: usize, end: usize) -> Result<()> {
68        let beg = self.in_range(beg)?;
69        let end = self.in_range(end)?;
70        self.get_mut().text.delete_range(beg, end);
71        Ok(())
72    }
73
74    fn in_range(&self, pos: usize) -> Result<usize> {
75        if pos == 0 || pos > self.get().text.len_chars() + 1 {
76            bail!("Position {pos} out of range in {}", self.get().name);
77        }
78        Ok(pos - 1)
79    }
80}
81
82impl<'new> WithLifetime<'new> for OpenBuffer<'_> {
83    type Out = OpenBuffer<'new>;
84
85    unsafe fn with_lifetime(self) -> Self::Out {
86        std::mem::transmute(self)
87    }
88}
89
90impl PartialEq<str> for OpenBuffer<'_> {
91    fn eq(&self, other: &str) -> bool {
92        self.get().text == other
93    }
94}
95
96impl Deref for OpenBuffer<'_> {
97    type Target = BufferData;
98
99    fn deref(&self) -> &Self::Target {
100        self.get()
101    }
102}
103
104impl DerefMut for OpenBuffer<'_> {
105    fn deref_mut(&mut self) -> &mut Self::Target {
106        self.get_mut()
107    }
108}
109
110/// The actual data of the buffer. Buffer local variables will be stored here
111/// eventually.
112#[derive(Debug)]
113pub(crate) struct BufferData {
114    pub(crate) name: String,
115    pub(crate) text: TextBuffer,
116    pub(crate) textprops: IntervalTree<'static>,
117}
118
119impl BufferData {
120    pub fn textprops_with_lifetime<'new>(&mut self) -> &mut IntervalTree<'new> {
121        unsafe { std::mem::transmute(&mut self.textprops) }
122    }
123}
124
125#[derive(Debug)]
126struct LispBufferInner {
127    text_buffer: Mutex<Option<BufferData>>,
128}
129
130/// A lisp handle to a buffer. This is a just a reference type and does not give
131/// access to the contents until it is locked and a `OpenBuffer` is returned.
132#[derive(PartialEq, Eq, Trace)]
133pub(crate) struct LispBuffer(GcHeap<LispBufferInner>);
134
135derive_GcMoveable!(LispBuffer);
136
137impl LispBuffer {
138    pub(crate) fn create(name: String, block: &Block<true>) -> &LispBuffer {
139        let buffer = unsafe { Self::new(name, block) };
140        block.objects.alloc(buffer)
141    }
142
143    pub(crate) unsafe fn new(name: String, _: &Block<true>) -> LispBuffer {
144        let textprops = IntervalTree::new();
145        let new = LispBufferInner {
146            text_buffer: Mutex::new(Some(BufferData { name, text: TextBuffer::new(), textprops })),
147        };
148        Self(GcHeap::new(new, true))
149    }
150
151    pub(crate) fn lock(&self) -> Result<OpenBuffer<'_>> {
152        let guard = self.0.text_buffer.lock().unwrap();
153        if guard.is_none() {
154            bail!("selecting deleted buffer");
155        }
156        Ok(OpenBuffer { data: guard, back_ref: self })
157    }
158}
159
160impl PartialEq for LispBufferInner {
161    fn eq(&self, other: &Self) -> bool {
162        std::ptr::eq(self, other)
163    }
164}
165
166impl PartialEq<OpenBuffer<'_>> for LispBuffer {
167    fn eq(&self, other: &OpenBuffer) -> bool {
168        other.back_ref == self
169    }
170}
171
172impl PartialEq<LispBuffer> for OpenBuffer<'_> {
173    fn eq(&self, other: &LispBuffer) -> bool {
174        self.back_ref == other
175    }
176}
177
178impl Eq for LispBufferInner {}
179
180impl Display for LispBuffer {
181    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
182        let data = self.0.text_buffer.lock().unwrap();
183        let name = match data.as_ref() {
184            Some(buf) => &buf.name,
185            None => "deleted buffer",
186        };
187        write!(f, "#<{name}>")
188    }
189}
190
191impl std::fmt::Debug for LispBuffer {
192    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
193        Display::fmt(self, f)
194    }
195}
196
197impl Trace for LispBufferInner {
198    fn trace(&self, state: &mut GcState) {
199        // Implement once we hold gc data in the buffer
200        let buf = self.text_buffer.lock().unwrap();
201        if let Some(buf) = buf.as_ref() {
202            buf.textprops.trace(state);
203        }
204    }
205}
206
207impl<'new> LispBuffer {
208    pub(in crate::core) fn clone_in<const C: bool>(
209        &self,
210        _: &'new Block<C>,
211    ) -> Gc<&'new LispBuffer> {
212        unsafe { self.with_lifetime().tag() }
213    }
214}