use gpui
This commit is contained in:
+150
-14
@@ -1,18 +1,88 @@
|
||||
//! Документы редактора и состояние вкладок.
|
||||
//!
|
||||
//! Это доменная модель приложения: текст, имена и языки вкладок живут здесь,
|
||||
//! а виджет `Editor` из gpui-component показывает активный документ. Такое
|
||||
//! разделение позволяет Lua-сценариям менять документы без доступа к `Window`
|
||||
//! и `App`: вид обновляет редактор, когда меняется [`Workspace::revision`].
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use crate::syntax::{Language, SyntaxState};
|
||||
|
||||
/// Счётчик идентификаторов документов.
|
||||
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
|
||||
|
||||
/// Языки, для которых в редакторе включена подсветка tree-sitter.
|
||||
///
|
||||
/// Названия намеренно короткие (`js`, а не `javascript`): именно их возвращает
|
||||
/// [`Language::name`] и показывает Lua-API. gpui-component принимает такие
|
||||
/// псевдонимы и сам приводит их к имени грамматики.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Language {
|
||||
Rust,
|
||||
Go,
|
||||
JavaScript,
|
||||
Json,
|
||||
Python,
|
||||
}
|
||||
|
||||
impl Language {
|
||||
/// Все поддерживаемые языки.
|
||||
#[allow(dead_code)]
|
||||
// Часть публичного интерфейса модуля: удобно для перебора языков снаружи.
|
||||
pub const ALL: [Self; 5] = [
|
||||
Self::Rust,
|
||||
Self::Go,
|
||||
Self::JavaScript,
|
||||
Self::Json,
|
||||
Self::Python,
|
||||
];
|
||||
|
||||
/// Разбирает название языка из API/сценария.
|
||||
///
|
||||
/// Основные имена — `rust`, `go`, `js`, `json`, `python`; короткие
|
||||
/// (`rs`, `golang`, `javascript`, `py`) и регистр тоже принимаются.
|
||||
pub fn from_name(name: &str) -> Option<Self> {
|
||||
match name.trim().to_ascii_lowercase().as_str() {
|
||||
"rust" | "rs" => Some(Self::Rust),
|
||||
"go" | "golang" => Some(Self::Go),
|
||||
"js" | "javascript" | "node" => Some(Self::JavaScript),
|
||||
"json" => Some(Self::Json),
|
||||
"python" | "py" => Some(Self::Python),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Каноническое имя языка (то, что возвращает `AppApi::language`).
|
||||
pub fn name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Rust => "rust",
|
||||
Self::Go => "go",
|
||||
Self::JavaScript => "js",
|
||||
Self::Json => "json",
|
||||
Self::Python => "python",
|
||||
}
|
||||
}
|
||||
|
||||
/// Имя языка для `EditorState::set_highlighter`.
|
||||
///
|
||||
/// Здесь `js` разворачивается в `javascript`: так грамматика находится
|
||||
/// без дополнительных псевдонимов на нашей стороне.
|
||||
pub fn highlight_name(self) -> &'static str {
|
||||
match self {
|
||||
Self::Rust => "rust",
|
||||
Self::Go => "go",
|
||||
Self::JavaScript => "javascript",
|
||||
Self::Json => "json",
|
||||
Self::Python => "python",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Открытый в редакторе документ.
|
||||
pub struct Document {
|
||||
/// Стабильный идентификатор, пока документ открыт.
|
||||
///
|
||||
/// По нему `TextEdit` и `ScrollArea` хранят курсор, выделение и прокрутку,
|
||||
/// поэтому переключение вкладок не сбрасывает состояние редактора.
|
||||
/// По нему вид отличает «ту же вкладку» от «другой вкладки с тем же
|
||||
/// индексом» после закрытия соседей.
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub content: String,
|
||||
@@ -21,8 +91,8 @@ pub struct Document {
|
||||
/// `None` — подсветка выключена (поведение по умолчанию). Включается
|
||||
/// только явно через `AppApi::set_language`, например из Lua-сценария.
|
||||
pub language: Option<Language>,
|
||||
/// Разбор tree-sitter и кэш подсветки для этого документа.
|
||||
pub syntax: SyntaxState,
|
||||
/// Позиция курсора в байтах на момент ухода с вкладки.
|
||||
pub cursor: usize,
|
||||
/// Содержимое на момент последнего «сохранения».
|
||||
saved_content: String,
|
||||
}
|
||||
@@ -36,7 +106,7 @@ impl Document {
|
||||
saved_content: content.clone(),
|
||||
content,
|
||||
language: None,
|
||||
syntax: SyntaxState::default(),
|
||||
cursor: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,13 +125,19 @@ impl Document {
|
||||
///
|
||||
/// Инвариант: `documents` никогда не пуст — редактор всегда показывает хотя бы
|
||||
/// один документ. Это позволяет `active`/`active_mut` не возвращать `Option`.
|
||||
pub struct EditorState {
|
||||
pub struct Workspace {
|
||||
documents: Vec<Document>,
|
||||
active: usize,
|
||||
next_untitled: u32,
|
||||
/// Номер «поколения» состояния.
|
||||
///
|
||||
/// Вид перечитывает активный документ в редактор только когда ревизия
|
||||
/// изменилась. Набор текста сам обновляет документ и ревизию не трогает,
|
||||
/// иначе редактор переставлял бы курсор после каждого символа.
|
||||
revision: u64,
|
||||
}
|
||||
|
||||
impl Default for EditorState {
|
||||
impl Default for Workspace {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
documents: vec![
|
||||
@@ -70,11 +146,12 @@ impl Default for EditorState {
|
||||
],
|
||||
active: 0,
|
||||
next_untitled: 0,
|
||||
revision: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EditorState {
|
||||
impl Workspace {
|
||||
pub fn documents(&self) -> &[Document] {
|
||||
&self.documents
|
||||
}
|
||||
@@ -96,15 +173,26 @@ impl EditorState {
|
||||
self.documents.get(index)
|
||||
}
|
||||
|
||||
/// Изменяемый документ по номеру вкладки (для API).
|
||||
pub fn document_mut(&mut self, index: usize) -> Option<&mut Document> {
|
||||
self.documents.get_mut(index)
|
||||
/// Изменяемый документ по стабильному идентификатору.
|
||||
pub fn document_mut_by_id(&mut self, id: u64) -> Option<&mut Document> {
|
||||
self.documents.iter_mut().find(|doc| doc.id == id)
|
||||
}
|
||||
|
||||
/// Текущая ревизия состояния.
|
||||
pub fn revision(&self) -> u64 {
|
||||
self.revision
|
||||
}
|
||||
|
||||
/// Сообщает виду, что документы нужно перечитать.
|
||||
fn bump(&mut self) {
|
||||
self.revision = self.revision.wrapping_add(1);
|
||||
}
|
||||
|
||||
/// Делает вкладку активной, если индекс существует.
|
||||
pub fn set_active(&mut self, index: usize) {
|
||||
if index < self.documents.len() {
|
||||
if index < self.documents.len() && index != self.active {
|
||||
self.active = index;
|
||||
self.bump();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +204,7 @@ impl EditorState {
|
||||
"",
|
||||
));
|
||||
self.active = self.documents.len() - 1;
|
||||
self.bump();
|
||||
}
|
||||
|
||||
/// Закрывает вкладку и выбирает соседнюю.
|
||||
@@ -135,6 +224,53 @@ impl EditorState {
|
||||
} else if index == self.active {
|
||||
self.active = self.active.min(self.documents.len() - 1);
|
||||
}
|
||||
self.bump();
|
||||
}
|
||||
|
||||
/// Обновляет текст активного документа без изменения ревизии.
|
||||
///
|
||||
/// Используется видом после ввода в редакторе: сам виджет уже показывает
|
||||
/// этот текст, поэтому повторная синхронизация только сбросила бы курсор.
|
||||
pub fn replace_active_content(&mut self, content: String) {
|
||||
self.active_mut().content = content;
|
||||
}
|
||||
|
||||
/// Заменяет текст активной вкладки извне (Lua, меню, тесты).
|
||||
pub fn set_active_text(&mut self, text: impl Into<String>) {
|
||||
self.active_mut().content = text.into();
|
||||
self.bump();
|
||||
}
|
||||
|
||||
/// Включает подсветку для вкладки; возвращает `false`, если вкладки нет.
|
||||
pub fn set_language(&mut self, index: usize, language: Language) -> bool {
|
||||
let Some(doc) = self.documents.get_mut(index) else {
|
||||
return false;
|
||||
};
|
||||
if doc.language != Some(language) {
|
||||
doc.language = Some(language);
|
||||
self.bump();
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Выключает подсветку для вкладки; возвращает `false`, если вкладки нет.
|
||||
pub fn clear_language(&mut self, index: usize) -> bool {
|
||||
let Some(doc) = self.documents.get_mut(index) else {
|
||||
return false;
|
||||
};
|
||||
if doc.language.take().is_some() {
|
||||
self.bump();
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Помечает активный документ сохранённым.
|
||||
///
|
||||
/// Ревизия меняется, чтобы индикатор несохранённых изменений на вкладке
|
||||
/// перерисовался даже без отдельного уведомления вида.
|
||||
pub fn mark_active_saved(&mut self) {
|
||||
self.active_mut().mark_saved();
|
||||
self.bump();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user