rune/
buffer.rs

1//! Buffer operations.
2use crate::{
3    core::{
4        env::{Env, INTERNED_SYMBOLS},
5        error::{Type, TypeError},
6        gc::{Context, Rt},
7        object::{Gc, LispBuffer, NIL, Object, ObjectType, OptionalFlag},
8    },
9    fns::slice_into_list,
10};
11use anyhow::{Result, bail};
12use rune_core::hashmap::HashMap;
13use rune_macros::defun;
14use std::sync::LazyLock;
15use std::sync::Mutex;
16
17type BufferMap = HashMap<String, &'static LispBuffer>;
18// static hashmap containing all the buffers
19pub(crate) static BUFFERS: LazyLock<Mutex<BufferMap>> = LazyLock::new(Mutex::default);
20
21#[defun]
22pub(crate) fn set_buffer<'ob>(
23    buffer_or_name: Object<'ob>,
24    env: &mut Rt<Env>,
25    cx: &'ob Context,
26) -> Result<Object<'ob>> {
27    let buffer = resolve_buffer(buffer_or_name, cx)?;
28    env.set_buffer(buffer);
29    Ok(cx.add(buffer))
30}
31
32fn resolve_buffer<'ob>(buffer_or_name: Object, cx: &'ob Context) -> Result<&'ob LispBuffer> {
33    match buffer_or_name.untag() {
34        ObjectType::Buffer(b) => Ok(b),
35        ObjectType::String(name) => {
36            let buffer_list = BUFFERS.lock().unwrap();
37            let Some(buffer) = buffer_list.get(name.as_ref()) else {
38                bail!("No buffer named {}", name);
39            };
40            Ok(cx.bind(*buffer))
41        }
42        x => Err(TypeError::new(Type::String, x).into()),
43    }
44}
45
46#[defun]
47fn set_buffer_modified_p(flag: Object) -> Object {
48    // TODO: implement
49    flag
50}
51
52#[defun]
53fn buffer_live_p(buffer: Object, env: &Rt<Env>) -> bool {
54    match buffer.untag() {
55        ObjectType::Buffer(b) => env.with_buffer(b, |_| {}).is_ok(),
56        _ => false,
57    }
58}
59
60#[defun]
61fn buffer_name(buffer: Option<Gc<&LispBuffer>>, env: &Rt<Env>) -> Result<String> {
62    match buffer {
63        Some(buffer) => env.with_buffer(buffer.untag(), |b| b.name.to_string()),
64        None => Ok(env.current_buffer.get().name.to_string()),
65    }
66}
67
68#[defun]
69fn rename_buffer(newname: &str, unique: OptionalFlag, env: &mut Rt<Env>) -> Result<String> {
70    let buf = env.current_buffer.get_mut();
71    if buf.name == newname {
72        return Ok(newname.to_string());
73    }
74    let mut buffer_list = BUFFERS.lock().unwrap();
75    let mut replace_buffer = |buffer_list: &mut HashMap<_, _>, newname: &str| {
76        let buffer = buffer_list.remove(&buf.name).unwrap();
77        buffer_list.insert(newname.into(), buffer);
78        buf.name = newname.to_string();
79    };
80    if buffer_list.contains_key(newname) {
81        // there is already a buffer with newname
82        if unique.is_none() {
83            bail!("rename-buffer failed: Buffer name {newname} is in use");
84        }
85        let newname = unique_buffer_name(newname, None, &buffer_list);
86        replace_buffer(&mut buffer_list, &newname);
87        Ok(newname)
88    } else {
89        replace_buffer(&mut buffer_list, newname);
90        Ok(newname.to_string())
91    }
92}
93
94#[defun]
95pub(crate) fn get_buffer_create<'ob>(
96    buffer_or_name: Object<'ob>,
97    _inhibit_buffer_hooks: Option<Object>,
98    cx: &'ob Context,
99) -> Result<Object<'ob>> {
100    match buffer_or_name.untag() {
101        ObjectType::String(name) => {
102            let mut buffer_list = BUFFERS.lock().unwrap();
103            match buffer_list.get(name.as_ref()) {
104                Some(b) => Ok(cx.add(*b)),
105                None => {
106                    // If not already in the global buffer list, create a new
107                    // buffer and add it
108                    let buffer: &'static _ = {
109                        let global = INTERNED_SYMBOLS.lock().unwrap();
110                        let buffer = global.create_buffer(name);
111                        // SAFETY: This can be 'static because it is stored in the
112                        // global block. Eventually it will be garbage collected
113                        unsafe { &*(buffer as *const LispBuffer) }
114                    };
115                    buffer_list.insert(name.to_string(), buffer);
116                    let buf = cx.add(buffer);
117                    Ok(buf)
118                }
119            }
120        }
121        ObjectType::Buffer(_) => Ok(buffer_or_name),
122        other => Err(TypeError::new(Type::BufferOrName, other).into()),
123    }
124}
125
126#[defun]
127pub(crate) fn get_buffer<'ob>(
128    buffer_or_name: Object<'ob>,
129    cx: &'ob Context,
130) -> Result<Object<'ob>> {
131    match buffer_or_name.untag() {
132        ObjectType::String(name) => {
133            let buffer_list = BUFFERS.lock().unwrap();
134            match buffer_list.get(name.as_ref()) {
135                Some(b) => Ok(cx.add(*b)),
136                None => Ok(NIL),
137            }
138        }
139        ObjectType::Buffer(_) => Ok(buffer_or_name),
140        other => Err(TypeError::new(Type::BufferOrName, other).into()),
141    }
142}
143
144/// Return a string that is the name of no existing buffer based on NAME.
145///
146/// If there is no live buffer named NAME, then return NAME.
147/// Otherwise modify name by appending \<NUMBER\>, incrementing NUMBER
148/// (starting at 2) until an unused name is found, and then return that name.
149/// Optional second argument IGNORE specifies a name that is okay to use (if
150/// it is in the sequence to be tried) even if a buffer with that name exists.
151///
152/// If NAME begins with a space (i.e., a buffer that is not normally
153/// visible to users), then if buffer NAME already exists a random number
154/// is first appended to NAME, to speed up finding a non-existent buffer.
155#[defun]
156pub(crate) fn generate_new_buffer_name(name: &str, ignore: Option<&str>) -> String {
157    unique_buffer_name(name, ignore, &BUFFERS.lock().unwrap())
158}
159
160fn unique_buffer_name(name: &str, ignore: Option<&str>, buffer_list: &BufferMap) -> String {
161    let valid_name =
162        |name: &str| ignore.is_some_and(|x| x == name) || !buffer_list.contains_key(name);
163
164    let mut new_name = name.to_string();
165    let mut number = 2;
166
167    // check if the name exists
168    while !valid_name(&new_name) {
169        if name.starts_with(' ') {
170            // use rand to find uniq names faster
171            let rand = rand::random::<u32>();
172            new_name = format!("{name}-{rand}");
173        } else {
174            new_name = format!("{name}<{number}>");
175            number += 1;
176        }
177    }
178    new_name
179}
180
181#[defun]
182fn kill_buffer(buffer_or_name: Option<Object>, cx: &Context, env: &mut Rt<Env>) -> bool {
183    match buffer_or_name {
184        Some(buffer) => match resolve_buffer(buffer, cx) {
185            Ok(b) => env.with_buffer_mut(b, |b| b.kill()).unwrap_or(false),
186            Err(_) => false,
187        },
188        None => {
189            let killed = env.current_buffer.get_mut().kill();
190            // todo, we need to select a new buffer
191            env.current_buffer.release();
192            killed
193        }
194    }
195}
196
197#[defun]
198fn buffer_base_buffer(_buffer: OptionalFlag) -> bool {
199    // TODO: implement indirect buffers
200    false
201}
202
203#[defun]
204fn get_file_buffer(_filename: &str) -> bool {
205    // TODO: implement file buffers
206    false
207}
208
209#[defun]
210fn buffer_list<'ob>(_frame: OptionalFlag, cx: &'ob Context) -> Object<'ob> {
211    // TODO: implement frame parameter
212    // TODO: remove this temp vector
213    let mut buffer_list: Vec<Object> = Vec::new();
214    for buffer in BUFFERS.lock().unwrap().values() {
215        buffer_list.push(cx.add(*buffer));
216    }
217    slice_into_list(&buffer_list, None, cx)
218}
219
220// TODO: buffer local
221defvar!(FILL_COLUMN, 70);
222defvar!(INDENT_TABS_MODE);
223defvar!(LEFT_MARGIN, 0);
224defvar!(INHIBIT_COMPACTING_FONT_CACHES);
225defvar!(NO_UPDATE_AUTOLOADS);
226defvar!(TAB_WIDTH, 8);
227defvar!(TRUNCATE_LINES);
228defvar!(WORD_WRAP);
229defvar!(BIDI_DISPLAY_REORDERING);
230defvar!(BUFFER_FILE_NAME);
231
232#[cfg(test)]
233mod test {
234    use super::*;
235    use crate::core::gc::RootSet;
236
237    #[test]
238    fn test_gen_new_buffer_name() {
239        let roots = &RootSet::default();
240        let cx = &mut Context::new(roots);
241
242        let name = "gen_buffer_test";
243        let new_name = generate_new_buffer_name(name, None);
244        assert_eq!(new_name, "gen_buffer_test");
245
246        get_buffer_create(cx.add(name), Some(NIL), cx).unwrap();
247        let new_name = generate_new_buffer_name(name, None);
248        assert_eq!(new_name, "gen_buffer_test<2>");
249
250        get_buffer_create(cx.add("gen_buffer_test<2>"), Some(NIL), cx).unwrap();
251        let new_name = generate_new_buffer_name(name, None);
252        assert_eq!(new_name, "gen_buffer_test<3>");
253
254        let new_name = generate_new_buffer_name(name, Some("gen_buffer_test<2>"));
255        assert_eq!(new_name, "gen_buffer_test<2>");
256
257        let new_name = generate_new_buffer_name(" gen_buffer_test", None);
258        assert_eq!(new_name, " gen_buffer_test");
259
260        get_buffer_create(cx.add(" gen_buffer_test"), Some(NIL), cx).unwrap();
261        let new_name = generate_new_buffer_name(" gen_buffer_test", None);
262        assert!(new_name.starts_with(" gen_buffer_test-"));
263    }
264
265    #[test]
266    fn test_create_buffer() {
267        let roots = &RootSet::default();
268        let cx = &mut Context::new(roots);
269        let buffer = get_buffer_create(cx.add("test_create_buffer"), Some(NIL), cx).unwrap();
270        assert!(matches!(buffer.untag(), ObjectType::Buffer(_)));
271    }
272}