commit
This commit is contained in:
+159
@@ -0,0 +1,159 @@
|
||||
//! Документы редактора и состояние вкладок.
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use crate::syntax::{Language, SyntaxState};
|
||||
|
||||
/// Счётчик идентификаторов документов.
|
||||
static NEXT_ID: AtomicU64 = AtomicU64::new(1);
|
||||
|
||||
/// Открытый в редакторе документ.
|
||||
pub struct Document {
|
||||
/// Стабильный идентификатор, пока документ открыт.
|
||||
///
|
||||
/// По нему `TextEdit` и `ScrollArea` хранят курсор, выделение и прокрутку,
|
||||
/// поэтому переключение вкладок не сбрасывает состояние редактора.
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub content: String,
|
||||
/// Язык подсветки синтаксиса.
|
||||
///
|
||||
/// `None` — подсветка выключена (поведение по умолчанию). Включается
|
||||
/// только явно через `AppApi::set_language`, например из Lua-сценария.
|
||||
pub language: Option<Language>,
|
||||
/// Разбор tree-sitter и кэш подсветки для этого документа.
|
||||
pub syntax: SyntaxState,
|
||||
/// Содержимое на момент последнего «сохранения».
|
||||
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,
|
||||
language: None,
|
||||
syntax: SyntaxState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Есть ли изменения, которые ещё не сохранены.
|
||||
pub fn is_dirty(&self) -> bool {
|
||||
self.content != self.saved_content
|
||||
}
|
||||
|
||||
/// Помечает текущее содержимое сохранённым.
|
||||
pub fn mark_saved(&mut self) {
|
||||
self.saved_content = self.content.clone();
|
||||
}
|
||||
}
|
||||
|
||||
/// Открытые документы и активная вкладка.
|
||||
///
|
||||
/// Инвариант: `documents` никогда не пуст — редактор всегда показывает хотя бы
|
||||
/// один документ. Это позволяет `active`/`active_mut` не возвращать `Option`.
|
||||
pub struct EditorState {
|
||||
documents: Vec<Document>,
|
||||
active: usize,
|
||||
next_untitled: u32,
|
||||
}
|
||||
|
||||
impl Default for EditorState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
documents: vec![
|
||||
Document::new("main.rs", SAMPLE_RUST),
|
||||
Document::new("notes.md", SAMPLE_NOTES),
|
||||
],
|
||||
active: 0,
|
||||
next_untitled: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EditorState {
|
||||
pub fn documents(&self) -> &[Document] {
|
||||
&self.documents
|
||||
}
|
||||
|
||||
pub fn active_index(&self) -> usize {
|
||||
self.active
|
||||
}
|
||||
|
||||
pub fn active(&self) -> &Document {
|
||||
&self.documents[self.active]
|
||||
}
|
||||
|
||||
pub fn active_mut(&mut self) -> &mut Document {
|
||||
&mut self.documents[self.active]
|
||||
}
|
||||
|
||||
/// Документ по номеру вкладки (для API).
|
||||
pub fn document(&self, index: usize) -> Option<&Document> {
|
||||
self.documents.get(index)
|
||||
}
|
||||
|
||||
/// Изменяемый документ по номеру вкладки (для API).
|
||||
pub fn document_mut(&mut self, index: usize) -> Option<&mut Document> {
|
||||
self.documents.get_mut(index)
|
||||
}
|
||||
|
||||
/// Делает вкладку активной, если индекс существует.
|
||||
pub fn set_active(&mut self, index: usize) {
|
||||
if index < self.documents.len() {
|
||||
self.active = index;
|
||||
}
|
||||
}
|
||||
|
||||
/// Открывает пустую вкладку и делает её активной.
|
||||
pub fn open_new(&mut self) {
|
||||
self.next_untitled += 1;
|
||||
self.documents.push(Document::new(
|
||||
format!("untitled-{}.txt", self.next_untitled),
|
||||
"",
|
||||
));
|
||||
self.active = self.documents.len() - 1;
|
||||
}
|
||||
|
||||
/// Закрывает вкладку и выбирает соседнюю.
|
||||
///
|
||||
/// Если закрывается последняя вкладка, вместо неё открывается пустой документ.
|
||||
pub fn close(&mut self, index: usize) {
|
||||
if index >= self.documents.len() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.documents.remove(index);
|
||||
|
||||
if self.documents.is_empty() {
|
||||
self.open_new();
|
||||
} else if index < self.active {
|
||||
self.active -= 1;
|
||||
} else if index == self.active {
|
||||
self.active = self.active.min(self.documents.len() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const SAMPLE_RUST: &str = r#"//! Демонстрационный файл.
|
||||
// Правьте код прямо в редакторе: вкладки и нумерация строк уже здесь.
|
||||
|
||||
fn main() {
|
||||
let mut numbers = vec![3, 1, 2];
|
||||
numbers.sort();
|
||||
|
||||
for (i, n) in numbers.iter().enumerate() {
|
||||
println!("{i}: {n}");
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
const SAMPLE_NOTES: &str = r#"# Заметки
|
||||
|
||||
- «+» на панели вкладок открывает новый файл
|
||||
- крестик на вкладке закрывает её
|
||||
- Ctrl+S сохраняет вкладку в файл с её названием
|
||||
"#;
|
||||
Reference in New Issue
Block a user