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
+76 -13
View File
@@ -7,7 +7,9 @@
use std::sync::atomic::{AtomicU64, Ordering};
use crate::gutter::GutterState;
use gpui_kit::base::input::Rope;
use crate::gutter::{GutterLineEdit, GutterShift, GutterState};
/// Счётчик идентификаторов документов.
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
@@ -216,7 +218,12 @@ pub struct Document {
/// индексом» после закрытия соседей.
pub id: u64,
pub name: String,
pub content: String,
/// Текст документа в виде rope: копия из редактора стоит O(1).
content: Rope,
/// Версия содержимого: увеличивается на каждую правку.
content_version: u64,
/// Есть несохранённые изменения.
dirty: bool,
/// Имя иконки вкладки из каталога Lucide (см. `crate::icons`).
///
/// `None` — показывается иконка по умолчанию. Назначается через
@@ -231,18 +238,16 @@ pub struct Document {
pub cursor: usize,
/// Иконки на поле редактора для этой вкладки.
pub gutter: GutterState,
/// Содержимое на момент последнего «сохранения».
saved_content: String,
}
impl Document {
pub fn new(name: impl Into<String>, content: impl Into<String>) -> Self {
let content = content.into();
Self {
id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
name: name.into(),
saved_content: content.clone(),
content,
content: Rope::from(content.into()),
content_version: 0,
dirty: false,
icon: None,
language: None,
cursor: 0,
@@ -250,9 +255,46 @@ impl Document {
}
}
/// Текст документа.
pub fn content(&self) -> &Rope {
&self.content
}
/// Материализованный текст для границ (сохранение, LSP, Lua).
pub fn content_string(&self) -> String {
self.content.to_string()
}
/// Версия содержимого, увеличивается на каждую правку.
pub fn content_version(&self) -> u64 {
self.content_version
}
/// Есть ли изменения, которые ещё не сохранены.
pub fn is_dirty(&self) -> bool {
self.content != self.saved_content
self.dirty
}
/// Заменяет текст и отмечает документ изменённым.
///
/// Это внешняя замена целого текста: иконки gutter привязаны к номерам
/// строк старого текста, поэтому они сбрасываются. Сценарий может
/// расставить их заново по новому тексту.
pub fn set_content(&mut self, content: Rope) {
if self.content == content {
return;
}
self.content = content;
self.content_version = self.content_version.wrapping_add(1);
self.dirty = true;
self.gutter.clear();
}
/// Обновляет текст после правки в редакторе: копия rope стоит O(1).
pub fn replace_content(&mut self, content: &Rope) {
self.content = content.clone();
self.content_version = self.content_version.wrapping_add(1);
self.dirty = true;
}
/// Идентификатор языка для LSP.
@@ -275,7 +317,7 @@ impl Document {
/// Помечает текущее содержимое сохранённым.
pub fn mark_saved(&mut self) {
self.saved_content = self.content.clone();
self.dirty = false;
}
}
@@ -417,9 +459,11 @@ impl Workspace {
.position(|document| document.name == name && !document.is_dirty())
{
let document = &mut self.documents[index];
document.content = content;
document.content = Rope::from(content);
document.content_version = document.content_version.wrapping_add(1);
document.mark_saved();
document.language = language;
document.gutter.clear();
self.active = index;
self.bump();
return index;
@@ -459,13 +503,32 @@ impl Workspace {
///
/// Используется видом после ввода в редакторе: сам виджет уже показывает
/// этот текст, поэтому повторная синхронизация только сбросила бы курсор.
pub fn replace_active_content(&mut self, content: String) {
self.active_mut().content = content;
/// Иконки gutter пересчитываются по точным правкам из `InputEvent::Change`.
///
/// Возвращает иконки, которые съехали.
pub fn replace_active_content(
&mut self,
content: &Rope,
line_edits: &[GutterLineEdit],
collect_shifts: bool,
) -> Vec<GutterShift> {
let document = self.active_mut();
if line_edits.is_empty() && document.content.eq(content) {
return Vec::new();
}
let shifts = if collect_shifts {
document.gutter.apply_line_edits(line_edits)
} else {
document.gutter.apply_line_edits_silently(line_edits);
Vec::new()
};
document.replace_content(content);
shifts
}
/// Заменяет текст активной вкладки извне (Lua, меню, тесты).
pub fn set_active_text(&mut self, text: impl Into<String>) {
self.active_mut().content = text.into();
self.active_mut().set_content(Rope::from(text.into()));
self.bump();
}