use super::error::{EvalResult, Flow, signal};
use super::intern::intern;
use super::value::*;
use crate::emacs_core::value::ValueKind;
fn expect_args(name: &str, args: &[Value], n: usize) -> Result<(), Flow> {
if args.len() != n {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_min_args(name: &str, args: &[Value], min: usize) -> Result<(), Flow> {
if args.len() < min {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_max_args(name: &str, args: &[Value], max: usize) -> Result<(), Flow> {
if args.len() > max {
Err(signal(
"wrong-number-of-arguments",
vec![Value::symbol(name), Value::fixnum(args.len() as i64)],
))
} else {
Ok(())
}
}
fn expect_int(value: &Value) -> Result<i64, Flow> {
match value.kind() {
ValueKind::Fixnum(n) => Ok(n),
other => Err(signal(
"wrong-type-argument",
vec![Value::symbol("integerp"), *value],
)),
}
}
#[cfg(test)]
fn expect_string(value: &Value) -> Result<String, Flow> {
match value.kind() {
ValueKind::String => Ok(value.as_str().unwrap().to_string()),
other => Err(signal(
"wrong-type-argument",
vec![Value::symbol("stringp"), *value],
)),
}
}
fn dynamic_or_global_symbol_value(eval: &super::eval::Context, name: &str) -> Option<Value> {
eval.obarray.symbol_value(name).cloned()
}
fn rectangle_strings_to_value(rectangle: &[String]) -> Value {
Value::list(rectangle.iter().map(|s| Value::string(s.clone())).collect())
}
fn rectangle_strings_from_value(value: &Value) -> Result<Vec<String>, Flow> {
let items = list_to_vec(value)
.ok_or_else(|| signal("wrong-type-argument", vec![Value::symbol("listp"), *value]))?;
let mut out = Vec::with_capacity(items.len());
for item in items {
match item.kind() {
ValueKind::String => out.push(item.as_str().unwrap().to_string()),
other => {
return Err(signal(
"wrong-type-argument",
vec![Value::symbol("buffer-or-string-p"), item],
));
}
}
}
Ok(out)
}
#[derive(Clone, Debug)]
pub(crate) struct RectangleState {
pub killed: Vec<String>,
}
impl RectangleState {
pub fn new() -> Self {
Self { killed: Vec::new() }
}
}
impl Default for RectangleState {
fn default() -> Self {
Self::new()
}
}
fn line_col_for_char_index(text: &str, target: usize) -> (usize, usize) {
let mut line = 0usize;
let mut col = 0usize;
for (idx, ch) in text.chars().enumerate() {
if idx == target {
return (line, col);
}
if ch == '\n' {
line += 1;
col = 0;
} else {
col += 1;
}
}
(line, col)
}
fn extract_line_columns(line: &str, start_col: usize, end_col: usize) -> String {
if start_col >= end_col {
return String::new();
}
let chars: Vec<char> = line.chars().collect();
let len = chars.len();
if start_col >= len {
return " ".repeat(end_col - start_col);
}
let mut out: String = chars[start_col..len.min(end_col)].iter().collect();
if end_col > len {
out.push_str(&" ".repeat(end_col - len));
}
out
}
fn rectangle_lines_for_extract(start_line: usize, end_line: usize) -> Vec<usize> {
if start_line <= end_line {
(start_line..=end_line).collect()
} else {
vec![start_line]
}
}
fn char_index_to_byte(s: &str, char_idx: usize) -> usize {
if char_idx == 0 {
return 0;
}
s.char_indices()
.nth(char_idx)
.map(|(byte, _)| byte)
.unwrap_or(s.len())
}
fn line_col_to_char_index(text: &str, line: usize, col: usize) -> usize {
let lines: Vec<&str> = text.split('\n').collect();
let mut pos = 0usize;
for idx in 0..line {
pos += lines.get(idx).copied().unwrap_or("").chars().count();
pos += 1; }
let line_len = lines.get(line).copied().unwrap_or("").chars().count();
pos + col.min(line_len)
}
fn extract_rectangle_from_text(
text: &str,
start_line: usize,
end_line: usize,
left_col: usize,
right_col: usize,
) -> Vec<String> {
let lines: Vec<&str> = text.split('\n').collect();
let mut out = Vec::new();
for line_index in rectangle_lines_for_extract(start_line, end_line) {
let line = lines.get(line_index).copied().unwrap_or("");
out.push(extract_line_columns(line, left_col, right_col));
}
out
}
fn delete_extract_rectangle_from_text(
text: &str,
start_line: usize,
end_line: usize,
left_col: usize,
right_col: usize,
) -> (Vec<String>, String) {
let mut lines: Vec<String> = text.split('\n').map(ToString::to_string).collect();
let mut extracted = Vec::new();
let width = right_col.saturating_sub(left_col);
for line_index in rectangle_lines_for_extract(start_line, end_line) {
let Some(line) = lines.get_mut(line_index) else {
extracted.push(" ".repeat(width));
continue;
};
let line_len = line.chars().count();
if line_len < left_col {
extracted.push(" ".repeat(width));
continue;
}
extracted.push(extract_line_columns(line, left_col, right_col));
let del_end_char = line_len.min(right_col);
let del_start_byte = char_index_to_byte(line, left_col);
let del_end_byte = char_index_to_byte(line, del_end_char);
if del_start_byte < del_end_byte {
line.replace_range(del_start_byte..del_end_byte, "");
}
}
(extracted, lines.join("\n"))
}
fn clamped_rect_inputs(
eval: &super::eval::Context,
start: i64,
end: i64,
) -> Option<(
String,
usize,
usize,
usize,
usize,
usize,
usize,
usize,
usize,
)> {
let buf = eval.buffers.current_buffer()?;
let point_min_char = buf.point_min_char() as i64 + 1;
let point_max_char = buf.point_max_char() as i64 + 1;
let clamped_start = start.clamp(point_min_char, point_max_char);
let clamped_end = end.clamp(point_min_char, point_max_char);
let pmin = buf.point_min();
let pmax = buf.point_max();
let text = buf.buffer_substring(pmin, pmax);
let rel_start = (clamped_start - point_min_char).max(0) as usize;
let rel_end = (clamped_end - point_min_char).max(0) as usize;
let (start_line, start_col) = line_col_for_char_index(&text, rel_start);
let (end_line, end_col) = line_col_for_char_index(&text, rel_end);
let (left_col, right_col) = if start_col <= end_col {
(start_col, end_col)
} else {
(end_col, start_col)
};
Some((
text, pmin, pmax, start_line, start_col, end_line, end_col, left_col, right_col,
))
}
#[cfg(test)]
pub(crate) fn builtin_extract_rectangle_line(args: Vec<Value>) -> EvalResult {
expect_min_args("extract-rectangle-line", &args, 2)?;
expect_max_args("extract-rectangle-line", &args, 3)?;
let start_col = expect_int(&args[0])?;
let end_col = expect_int(&args[1])?;
if start_col < 0 || end_col < 0 {
return Err(signal(
"args-out-of-range",
vec![Value::fixnum(start_col), Value::fixnum(end_col)],
));
}
if args.len() == 3 {
let line = expect_string(&args[2])?;
let mut lo = start_col as usize;
let mut hi = end_col as usize;
if lo > hi {
std::mem::swap(&mut lo, &mut hi);
}
let chars: Vec<char> = line.chars().collect();
lo = lo.min(chars.len());
hi = hi.min(chars.len());
if lo >= hi {
return Ok(Value::string(""));
}
let slice: String = chars[lo..hi].iter().collect();
return Ok(Value::string(slice));
}
Ok(Value::string(""))
}
fn delete_extract_rectangle_eval(
eval: &mut super::eval::Context,
start: i64,
end: i64,
) -> EvalResult {
let Some((text, pmin, pmax, start_line, _start_col, end_line, _end_col, left_col, right_col)) =
clamped_rect_inputs(eval, start, end)
else {
return Ok(Value::list(Vec::new()));
};
let (extracted, rewritten) =
delete_extract_rectangle_from_text(&text, start_line, end_line, left_col, right_col);
if let Some(_current_id) = eval.buffers.current_buffer_id() {
super::editfns::signal_before_change(eval, pmin, pmax)?;
let old_len = super::editfns::current_buffer_byte_span_char_len(eval, pmin, pmax);
if let Some(current_id) = eval.buffers.current_buffer_id() {
let _ = eval.buffers.delete_buffer_region(current_id, pmin, pmax);
let _ = eval.buffers.goto_buffer_byte(current_id, pmin);
let _ = eval.buffers.insert_into_buffer(current_id, &rewritten);
}
let new_end = pmin + rewritten.len();
super::editfns::signal_after_change(eval, pmin, new_end, old_len)?;
}
Ok(Value::list(
extracted.into_iter().map(Value::string).collect(),
))
}
#[cfg(test)]
#[path = "rect_test.rs"]
mod tests;