refactoring

This commit is contained in:
2026-10-31 01:06:21 +03:00
parent 27385a3edb
commit 19e05e7ed0
17 changed files with 2673 additions and 671 deletions
+53 -62
View File
@@ -25,17 +25,6 @@ pub enum Language {
}
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`; короткие
@@ -62,25 +51,12 @@ impl Language {
}
}
/// Имя языка для `EditorState::set_highlighter`.
/// Полное имя языка для tree-sitter и LSP.
///
/// Здесь `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",
}
}
/// Идентификатор языка для LSP (`languageId` в `textDocument/didOpen`).
///
/// Отличается от [`Self::name`] тем же, чем `highlight_name`: LSP ждёт
/// `javascript`, а не `js`.
pub fn lsp_name(self) -> &'static str {
/// Отличается от [`Self::name`] только для JavaScript: грамматика и
/// `languageId` используют `javascript`, а короткое `js` остаётся именем
/// в Lua-API.
pub fn syntax_name(self) -> &'static str {
match self {
Self::Rust => "rust",
Self::Go => "go",
@@ -188,17 +164,35 @@ impl Document {
/// по расширению имени вкладки — так LSP работает и без `set_language`.
pub fn lsp_language_id(&self) -> String {
if let Some(language) = self.language {
return language.lsp_name().to_owned();
return language.syntax_name().to_owned();
}
language_id_from_path(&self.name).to_owned()
}
/// Имя иконки вкладки с учётом иконки по умолчанию.
pub fn icon_name(&self) -> &str {
self.icon
.as_deref()
.unwrap_or(crate::icons::DEFAULT_TAB_ICON)
}
/// Помечает текущее содержимое сохранённым.
pub fn mark_saved(&mut self) {
self.saved_content = self.content.clone();
}
}
/// Что сделать с фокусом редактора после синхронизации модели с видом.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FocusAfterSync {
/// Оставить фокус там, где он был (`app:open` без позиции).
Keep,
/// Перевести фокус в редактор и, если задано, поставить курсор.
///
/// Смещение задаётся в байтах; `None` — оставить текущую позицию.
Editor(Option<usize>),
}
/// Открытые документы и активная вкладка.
///
/// Инвариант: `documents` никогда не пуст — редактор всегда показывает хотя бы
@@ -207,6 +201,8 @@ pub struct Workspace {
documents: Vec<Document>,
active: usize,
next_untitled: u32,
/// Действие с фокусом, ожидающее ближайшей синхронизации вида.
focus_after_sync: Option<FocusAfterSync>,
/// Номер «поколения» состояния.
///
/// Вид перечитывает активный документ в редактор только когда ревизия
@@ -218,12 +214,10 @@ pub struct Workspace {
impl Default for Workspace {
fn default() -> Self {
Self {
documents: vec![
Document::new("main.rs", SAMPLE_RUST),
Document::new("notes.md", SAMPLE_NOTES),
],
documents: vec![Document::new("untitled-1.txt", "")],
active: 0,
next_untitled: 0,
next_untitled: 1,
focus_after_sync: None,
revision: 0,
}
}
@@ -261,6 +255,16 @@ impl Workspace {
self.revision
}
/// Откладывает действие с фокусом до ближайшей синхронизации вида.
pub fn set_focus_after_sync(&mut self, focus: FocusAfterSync) {
self.focus_after_sync = Some(focus);
}
/// Забирает отложенное действие с фокусом.
pub fn take_focus_after_sync(&mut self) -> Option<FocusAfterSync> {
self.focus_after_sync.take()
}
/// Сообщает виду, что документы нужно перечитать.
fn bump(&mut self) {
self.revision = self.revision.wrapping_add(1);
@@ -274,6 +278,17 @@ impl Workspace {
}
}
/// Переключает активную вкладку на `delta` позиций по кругу.
///
/// Возвращает новый индекс. Инвариант «хотя бы один документ» делает
/// деление по модулю безопасным.
pub fn cycle_active(&mut self, delta: isize) -> usize {
let count = self.documents.len() as isize;
let next = (self.active as isize + delta).rem_euclid(count) as usize;
self.set_active(next);
next
}
/// Открывает пустую вкладку и делает её активной.
pub fn open_new(&mut self) {
self.next_untitled += 1;
@@ -333,7 +348,9 @@ impl Workspace {
if self.documents.is_empty() {
self.open_new();
} else if index < self.active {
return;
}
if index < self.active {
self.active -= 1;
} else if index == self.active {
self.active = self.active.min(self.documents.len() - 1);
@@ -417,30 +434,4 @@ impl Workspace {
}
true
}
/// Имя иконки вкладки, если оно назначено.
pub fn tab_icon(&self, index: usize) -> Option<&str> {
self.document(index)
.and_then(|document| document.icon.as_deref())
}
}
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 сохраняет вкладку в файл с её названием
"#;