Files
Marcos Paulo 5036ec2dbb feat(editor): wave 7
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-13 20:47:40 -03:00

72 lines
2.0 KiB
Rust

//! Cursor position and document statistics — pure helpers over the text.
/// (line, column) of `cursor` (byte offset), both 0-based; column counts
/// characters of the line, not bytes.
#[must_use]
pub fn line_col(text: &str, cursor: usize) -> (usize, usize) {
let floored = if text.is_char_boundary(cursor) {
usize::min(cursor, text.len())
} else {
let mut p = cursor;
while p > 0 && !text.is_char_boundary(p) {
p -= 1;
}
p
};
let before = text.get(..floored).unwrap_or("");
let line = before.matches('\n').count();
let col = before.chars().rev().take_while(|c| *c != '\n').count();
(line, col)
}
/// Words in the text: runs of characters that are alphanumeric or `_`.
#[must_use]
pub fn word_count(text: &str) -> usize {
let mut count = 0usize;
let mut in_word = false;
for c in text.chars() {
if c.is_alphanumeric() || c == '_' {
if !in_word {
count += 1;
}
in_word = true;
} else {
in_word = false;
}
}
count
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn line_col_counts_chars() {
let text = "ab é\nxyz";
let byte_x = text.find('x').unwrap_or(0);
assert_eq!(line_col(text, byte_x), (1, 0));
assert_eq!(line_col(text, 0), (0, 0));
let end = text.len();
assert_eq!(line_col(text, end), (1, 3));
}
#[test]
fn line_col_at_char_start() {
let mut text = String::from("éx");
let byte_after_e = text.find('x').unwrap_or(0);
assert_eq!(line_col(&text, byte_after_e), (0, 1));
text.push_str("\nq");
let byte_q = text.find('q').unwrap_or(0);
assert_eq!(line_col(&text, byte_q), (1, 0));
}
#[test]
fn word_count_runs() {
assert_eq!(word_count(""), 0);
assert_eq!(word_count("hello world"), 2);
assert_eq!(word_count("éx_ one-two #tag"), 4);
assert_eq!(word_count("...,,, "), 0);
}
}