1use 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>;
18pub(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 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 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 let buffer: &'static _ = {
109 let global = INTERNED_SYMBOLS.lock().unwrap();
110 let buffer = global.create_buffer(name);
111 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#[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 while !valid_name(&new_name) {
169 if name.starts_with(' ') {
170 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 env.current_buffer.release();
192 killed
193 }
194 }
195}
196
197#[defun]
198fn buffer_base_buffer(_buffer: OptionalFlag) -> bool {
199 false
201}
202
203#[defun]
204fn get_file_buffer(_filename: &str) -> bool {
205 false
207}
208
209#[defun]
210fn buffer_list<'ob>(_frame: OptionalFlag, cx: &'ob Context) -> Object<'ob> {
211 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
220defvar!(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}