use crate::input::InputModeKind;
use std::ops::Range;
use gpui::{Context, Window};
use ropey::Rope;
use sum_tree::Bias;
use super::{InputBaseState, RopeExt as _};
use crate::text_boundary::word_range_from_chars;
impl<M: InputModeKind> InputBaseState<M> {
pub(super) fn select_word(&mut self, offset: usize, _: &mut Window, cx: &mut Context<Self>) {
let range = if self.masked {
0..self.text.len()
} else {
let Some(range) = TextSelector::word_range(&self.text, offset) else {
return;
};
range
};
self.undo_manager.break_transaction_coalescing();
self.selected_range = (range.start..range.end).into();
self.selected_word_range = Some(self.selected_range);
cx.notify()
}
pub(super) fn select_line(&mut self, offset: usize, _: &mut Window, cx: &mut Context<Self>) {
let range = TextSelector::line_range(&self.text, offset);
self.undo_manager.break_transaction_coalescing();
self.selected_range = (range.start..range.end).into();
self.selected_word_range = None;
cx.notify()
}
}
struct TextSelector;
impl TextSelector {
pub(crate) fn line_range(text: &Rope, offset: usize) -> Range<usize> {
let offset = text.clip_offset(offset, Bias::Left);
let row = text.offset_to_point(offset).row;
let start = text.line_start_offset(row);
let end = text.line_end_offset(row);
start..end
}
pub(crate) fn word_range(text: &Rope, offset: usize) -> Option<Range<usize>> {
let offset = text.clip_offset(offset, Bias::Left);
let Some(char) = text.char_at(offset) else {
return None;
};
let end = offset + char.len_utf8();
let prev_chars = text.chars_at(offset).reversed().take(128);
let next_chars = text.chars_at(end).take(128);
Some(word_range_from_chars(offset, char, prev_chars, next_chars))
}
}
#[cfg(test)]
mod tests {
use super::*;
use ropey::Rope;
#[test]
fn test_word_range() {
use indoc::indoc;
let rope = Rope::from(indoc! {
r#"
test text:
abcde 中文🎉 test
hello[()]
test_connector ____
Rope
rök
grande île
"#
});
let tests = vec![
(0, 0, Some("test")),
(0, 4, Some(" ")),
(1, 0, Some("abcde")),
(1, 4, Some("abcde")),
(1, 5, Some(" ")),
(1, 6, Some("中")),
(1, 9, Some("文")),
(1, 13, Some("🎉")),
(1, 20, Some("test")),
(2, 5, Some("[")),
(2, 6, Some("(")),
(2, 7, Some(")")),
(2, 8, Some("]")),
(3, 5, Some("test_connector")),
(3, 14, Some(" ")),
(3, 16, Some("____")),
(4, 0, Some("Rope")),
(5, 0, Some("rök")),
(6, 8, Some("île")),
];
for (line, column, expected) in tests {
let line_start_offset = rope.line_start_offset(line);
let offset = line_start_offset + column;
let range = TextSelector::word_range(&rope, offset);
let actual = range.map(|r| rope.slice(r).to_string());
let expect = expected.map(|s| s.to_string());
assert_eq!(actual, expect, "line {}, column {}", line, column);
}
}
#[test]
fn test_line_range() {
let rope = Rope::from("first line\nsecond line\nthird");
let tests = vec![
(0, 0, "first line"),
(0, 5, "first line"),
(1, 3, "second line"),
(2, 1, "third"),
];
for (line, column, expected) in tests {
let line_start_offset = rope.line_start_offset(line);
let offset = line_start_offset + column;
let range = TextSelector::line_range(&rope, offset);
let actual = rope.slice(range).to_string();
assert_eq!(actual, expected, "line {}, column {}", line, column);
}
}
}