Skip to main content

rune/
editfns.rs

1//! Buffer editing utilities.
2use crate::core::{
3    env::{ArgSlice, Env},
4    gc::{Context, Rt},
5    object::{Object, ObjectType},
6};
7use anyhow::{Result, bail, ensure};
8use rune_macros::defun;
9use std::{fmt::Write as _, io::Write};
10
11#[defun]
12fn message(format_string: &str, args: &[Object]) -> Result<String> {
13    let message = format(format_string, args)?;
14    println!("MESSAGE: {message}");
15    std::io::stdout().flush()?;
16    Ok(message)
17}
18
19defvar!(MESSAGE_NAME);
20defvar!(MESSAGE_TYPE, "new message");
21
22#[defun]
23fn format(string: &str, objects: &[Object]) -> Result<String> {
24    let mut result = String::new();
25    let mut arguments = objects.iter();
26    let mut remaining = string;
27
28    let mut escaped = false;
29    let mut is_format_char = |c: char| {
30        if escaped {
31            escaped = false;
32            false
33        } else if c == '\\' {
34            escaped = true;
35            false
36        } else {
37            c == '%'
38        }
39    };
40    while let Some(start) = remaining.find(&mut is_format_char) {
41        result += &remaining[..start];
42        let Some(specifier) = remaining.as_bytes().get(start + 1) else {
43            bail!("Format string ends in middle of format specifier")
44        };
45        // "%%" inserts a single "%" in the output
46        if *specifier == b'%' {
47            result.push('%');
48        } else {
49            // TODO: currently handles all format types the same. Need to check the modifier characters.
50            let Some(val) = arguments.next() else {
51                bail!("Not enough arguments for format string")
52            };
53            match val.untag() {
54                ObjectType::String(string) => write!(result, "{string}")?,
55                obj => write!(result, "{obj}")?,
56            }
57        }
58        remaining = &remaining[start + 2..];
59    }
60    result += remaining;
61    ensure!(arguments.next().is_none(), "Too many arguments for format string");
62    Ok(result)
63}
64
65#[defun]
66fn format_message(string: &str, objects: &[Object]) -> Result<String> {
67    let formatted = format(string, objects)?;
68    // TODO: implement support for `text-quoting-style`.
69    Ok(formatted
70        .chars()
71        .map(|c| if matches!(c, '`' | '\'') { '"' } else { c })
72        .collect())
73}
74
75#[defun]
76fn string_to_char(string: &str) -> char {
77    string.chars().next().unwrap_or('\0')
78}
79
80#[defun]
81fn char_to_string(chr: char) -> String {
82    format!("{chr}")
83}
84
85#[defun]
86pub(crate) fn insert(args: ArgSlice, env: &mut Rt<Env>, cx: &Context) -> Result<()> {
87    let env = &mut **env; // Deref into rooted type so we can split the borrow
88    let buffer = env.current_buffer.get_mut();
89    let args = Rt::bind_slice(env.stack.arg_slice(args), cx);
90    for arg in args {
91        buffer.insert(*arg)?;
92    }
93    Ok(())
94}
95
96// TODO: this should not throw and error. Buffer will always be present.
97#[defun]
98pub(crate) fn goto_char(position: usize, env: &mut Rt<Env>) -> Result<()> {
99    let buffer = env.current_buffer.get_mut();
100    buffer.text.set_cursor(position);
101    Ok(())
102}
103
104// TODO: this should not throw and error. Buffer will always be present.
105#[defun]
106pub(crate) fn point_max(env: &mut Rt<Env>) -> Result<usize> {
107    let buffer = env.current_buffer.get_mut();
108    // TODO: Handle narrowing
109    Ok(buffer.text.len_chars() + 1)
110}
111
112#[defun]
113pub(crate) fn point_min() -> usize {
114    // TODO: Handle narrowing
115    1
116}
117
118#[defun]
119pub(crate) fn point_marker(env: &mut Rt<Env>) -> usize {
120    // TODO: Implement marker objects
121    env.current_buffer.get_mut().text.cursor().chars()
122}
123
124#[defun]
125fn delete_region(start: usize, end: usize, env: &mut Rt<Env>) -> Result<()> {
126    env.current_buffer.get_mut().delete(start, end)
127}
128
129#[defun]
130fn bolp(env: &Rt<Env>) -> bool {
131    let buf = env.current_buffer.get();
132    let chars = buf.text.cursor().chars();
133    chars == 0 || buf.text.char_at(chars - 1).unwrap() == '\n'
134}
135
136#[defun]
137fn point(env: &Rt<Env>) -> usize {
138    env.current_buffer.get().text.cursor().chars()
139}
140
141#[defun]
142fn system_name() -> String {
143    hostname::get()
144        .expect("Failed to get hostname")
145        .into_string()
146        .expect("Failed to convert OsString to String")
147}
148
149#[defun]
150fn user_uid() -> usize {
151    ids::effective_uid()
152}
153
154#[defun]
155fn user_real_uid() -> usize {
156    ids::real_uid()
157}
158
159#[defun]
160fn group_gid() -> usize {
161    ids::effective_gid()
162}
163
164#[defun]
165fn group_real_gid() -> usize {
166    ids::real_gid()
167}
168
169#[cfg(unix)]
170mod ids {
171    pub(super) fn effective_uid() -> usize {
172        unsafe { libc::geteuid() as usize }
173    }
174
175    pub(super) fn real_uid() -> usize {
176        unsafe { libc::getuid() as usize }
177    }
178
179    pub(super) fn effective_gid() -> usize {
180        unsafe { libc::getegid() as usize }
181    }
182
183    pub(super) fn real_gid() -> usize {
184        unsafe { libc::getgid() as usize }
185    }
186}
187
188#[cfg(not(unix))]
189mod ids {
190    /// Windows accounts have no POSIX ids. Emacs falls back to this value when it
191    /// can't derive one from the account SID, so report the same thing.
192    const UNKNOWN_ID: usize = 123;
193
194    pub(super) fn effective_uid() -> usize {
195        UNKNOWN_ID
196    }
197
198    pub(super) fn real_uid() -> usize {
199        UNKNOWN_ID
200    }
201
202    pub(super) fn effective_gid() -> usize {
203        UNKNOWN_ID
204    }
205
206    pub(super) fn real_gid() -> usize {
207        UNKNOWN_ID
208    }
209}
210
211#[defun]
212fn emacs_pid() -> usize {
213    std::process::id() as usize
214}
215
216#[cfg(test)]
217mod test {
218    use crate::core::object::NIL;
219    use crate::{
220        buffer::{get_buffer_create, set_buffer},
221        core::gc::RootSet,
222    };
223    use rune_core::macros::root;
224
225    use super::*;
226
227    #[test]
228    fn test_format() {
229        assert_eq!(&format("%s", &[1.into()]).unwrap(), "1");
230        assert_eq!(&format("foo-%s", &[2.into()]).unwrap(), "foo-2");
231        assert_eq!(&format("%%", &[]).unwrap(), "%");
232        assert_eq!(&format("_%%_", &[]).unwrap(), "_%_");
233        assert_eq!(&format("foo-%s %s", &[3.into(), 4.into()]).unwrap(), "foo-3 4");
234        let sym = crate::core::env::sym::FUNCTION.into();
235        assert_eq!(&format("%s", &[sym]).unwrap(), "function");
236
237        assert!(&format("%s", &[]).is_err());
238        assert!(&format("%s", &[1.into(), 2.into()]).is_err());
239
240        assert!(format("`%s' %s%s%s", &[0.into(), 1.into(), 2.into(), 3.into()]).is_ok());
241    }
242
243    #[test]
244    fn test_insert() {
245        let roots = &RootSet::default();
246        let cx = &mut Context::new(roots);
247        root!(env, new(Env), cx);
248        let buffer = get_buffer_create(cx.add("test_insert"), Some(NIL), cx).unwrap();
249        set_buffer(buffer, env, cx).unwrap();
250        cx.garbage_collect(true);
251        env.stack.push(104);
252        env.stack.push(101);
253        env.stack.push(108);
254        env.stack.push(108);
255        env.stack.push(111);
256        insert(ArgSlice::new(5), env, cx).unwrap();
257        assert_eq!(env.current_buffer.get(), "hello");
258    }
259
260    #[test]
261    fn test_delete_region() {
262        let roots = &RootSet::default();
263        let cx = &mut Context::new(roots);
264        root!(env, new(Env), cx);
265        let buffer = get_buffer_create(cx.add("test_delete_region"), Some(NIL), cx).unwrap();
266        set_buffer(buffer, env, cx).unwrap();
267        cx.garbage_collect(true);
268        env.stack.push(cx.add("hello"));
269        env.stack.push(cx.add(" world"));
270        insert(ArgSlice::new(2), env, cx).unwrap();
271
272        assert_eq!(env.current_buffer.get(), "hello world");
273        delete_region(2, 4, env).unwrap();
274        assert_eq!(env.current_buffer.get(), "hlo world");
275    }
276}