use crate::{effect::ExEffect, range::LineRange};
use hjkl_engine::Host;
pub(crate) fn apply_fold_indent<H: Host>(
editor: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
_args: &str,
_range: Option<LineRange>,
) -> Option<ExEffect> {
let lines = editor.buffer().lines().to_vec();
let total = lines.len();
if total == 0 {
return Some(ExEffect::Ok);
}
let indent =
|line: &str| -> usize { line.chars().take_while(|c| *c == ' ' || *c == '\t').count() };
let indents: Vec<usize> = lines.iter().map(|l| indent(l)).collect();
let blank: Vec<bool> = lines.iter().map(|l| l.trim().is_empty()).collect();
let mut new_folds: Vec<(usize, usize)> = Vec::new();
let mut i = 0;
while i + 1 < total {
if blank[i] {
i += 1;
continue;
}
let head_indent = indents[i];
let mut j = i + 1;
while j < total && blank[j] {
j += 1;
}
if j >= total || indents[j] <= head_indent {
i += 1;
continue;
}
let mut end = j;
let mut k = j + 1;
while k < total {
if !blank[k] && indents[k] <= head_indent {
break;
}
end = k;
k += 1;
}
new_folds.push((i, end));
i += 1;
}
if new_folds.is_empty() {
return Some(ExEffect::Info("no indented blocks to fold".into()));
}
let count = new_folds.len();
for (start, end) in new_folds {
editor.apply_fold_op(hjkl_engine::FoldOp::Add {
start_row: start,
end_row: end,
closed: true,
});
}
Some(ExEffect::Info(format!("created {count} fold(s)")))
}
pub(crate) fn apply_fold_syntax<H: Host>(
editor: &mut hjkl_engine::Editor<hjkl_buffer::Buffer, H>,
_args: &str,
_range: Option<LineRange>,
) -> Option<ExEffect> {
let ranges = editor.syntax_fold_ranges().to_vec();
if ranges.is_empty() {
return Some(ExEffect::Info("no syntax block ranges available".into()));
}
let count = ranges.len();
for (start, end) in ranges {
editor.apply_fold_op(hjkl_engine::FoldOp::Add {
start_row: start,
end_row: end,
closed: true,
});
}
Some(ExEffect::Info(format!("created {count} fold(s)")))
}
#[cfg(test)]
mod tests {
use super::*;
use hjkl_engine::{DefaultHost, Editor, Options};
fn make_editor_with_lines(lines: &[&str]) -> Editor<hjkl_buffer::Buffer, DefaultHost> {
let content = lines.join("\n");
let buf = hjkl_buffer::Buffer::from_str(&content);
let host = DefaultHost::new();
Editor::new(buf, host, Options::default())
}
#[test]
fn foldindent_on_indented_buffer_creates_folds() {
let mut editor =
make_editor_with_lines(&["fn foo() {", " let x = 1;", " let y = 2;", "}"]);
let result = apply_fold_indent(&mut editor, "", None);
match result {
Some(ExEffect::Info(msg)) => {
assert!(msg.contains("fold"), "expected fold count msg, got: {msg}");
}
other => panic!("expected Info(_), got {other:?}"),
}
}
#[test]
fn foldindent_on_flat_buffer_returns_no_blocks() {
let mut editor = make_editor_with_lines(&["line1", "line2", "line3"]);
let result = apply_fold_indent(&mut editor, "", None);
assert_eq!(
result,
Some(ExEffect::Info("no indented blocks to fold".into()))
);
}
#[test]
fn foldindent_on_empty_buffer_returns_ok() {
let mut editor = make_editor_with_lines(&[""]);
let result = apply_fold_indent(&mut editor, "", None);
assert!(matches!(
result,
Some(ExEffect::Ok) | Some(ExEffect::Info(_))
));
}
#[test]
fn foldsyntax_with_no_ranges_returns_info() {
let mut editor = make_editor_with_lines(&["fn foo() {", " bar();", "}"]);
let result = apply_fold_syntax(&mut editor, "", None);
assert_eq!(
result,
Some(ExEffect::Info("no syntax block ranges available".into()))
);
}
}