gutter refactoring

This commit is contained in:
2026-11-01 01:13:30 +03:00
parent cf1505eca5
commit 51cfc86084
12 changed files with 1264 additions and 199 deletions
+47 -1
View File
@@ -7,7 +7,7 @@
use std::ops::Range;
use gpui_kit::component::input::{EditorState, PrepaintCaretGeometry};
use gpui_kit::component::input::{EditorState, PrepaintCaretGeometry, Rope};
use gpui_kit::{
AnyElement, App, AvailableSpace, Bounds, Element, ElementId, GlobalElementId, Hsla,
InspectorElementId, IntoElement, LayoutId, Pixels, Point, Size, Style, WeakEntity, Window, px,
@@ -196,6 +196,10 @@ fn popup_origin(
}
/// Начало слова под каретой (для замены префикса при вставке).
/// Находит начало слова под курсором в обычной строке.
///
/// Нужен только для тестов: редактор работает с [`word_start_rope`].
#[cfg(test)]
pub fn word_start(text: &str, cursor: usize) -> usize {
let cursor = clamp_offset(text, cursor);
let mut start = cursor;
@@ -209,6 +213,36 @@ pub fn word_start(text: &str, cursor: usize) -> usize {
start
}
/// То же для rope: обходит только символы слова, а не весь документ.
pub fn word_start_rope(text: &Rope, cursor: usize) -> usize {
let cursor = cursor.min(text.len());
let mut start = cursor;
let mut char_index = text.byte_to_char_idx(cursor);
while char_index > 0 {
char_index -= 1;
let byte = text.char_to_byte_idx(char_index);
let ch = text.char(byte);
if ch.is_alphanumeric() || ch == '_' {
start = byte;
} else {
break;
}
}
start
}
/// Символ перед байтовым смещением `cursor`.
pub fn char_before(text: &Rope, cursor: usize) -> Option<char> {
let cursor = cursor.min(text.len());
let mut char_index = text.byte_to_char_idx(cursor);
if char_index == 0 {
return None;
}
char_index -= 1;
let byte = text.char_to_byte_idx(char_index);
text.get_char(byte).ok()
}
/// Разбирает ответ `textDocument/completion`.
pub fn parse_completion_response(value: serde_json::Value) -> Vec<CompletionItem> {
if value.is_null() {
@@ -501,6 +535,18 @@ mod tests {
assert_eq!(word_start("привет", 6), 0);
}
#[test]
fn rope_word_start_matches_string_version() {
for text in ["let value", "obj.field", "obj.", "привет"] {
let rope = Rope::from(text);
for cursor in 0..=text.len() {
if text.is_char_boundary(cursor) {
assert_eq!(word_start_rope(&rope, cursor), word_start(text, cursor));
}
}
}
}
#[test]
fn snippets_lose_placeholders() {
assert_eq!(strip_snippet("println!(${1:value})"), "println!(value)");