refactoring

This commit is contained in:
2026-10-31 15:33:00 +03:00
parent e8e2d25600
commit 838627fb33
27 changed files with 2715 additions and 1259 deletions
+107 -15
View File
@@ -24,13 +24,26 @@ pub enum Language {
JavaScript,
Json,
Python,
Lua,
Bash,
Yaml,
Make,
Dockerfile,
Sql,
Markdown,
Css,
C,
Cpp,
Html,
Xml,
}
impl Language {
/// Разбирает название языка из API/сценария.
///
/// Основные имена — `rust`, `go`, `js`, `json`, `python`; короткие
/// (`rs`, `golang`, `javascript`, `py`) и регистр тоже принимаются.
/// Основные имена — [`Self::name`]; короткие (`rs`, `golang`,
/// `javascript`, `py`, `sh`, `yml`, `makefile`, `md`, `c++`, …)
/// и регистр тоже принимаются.
pub fn from_name(name: &str) -> Option<Self> {
match name.trim().to_ascii_lowercase().as_str() {
"rust" | "rs" => Some(Self::Rust),
@@ -38,6 +51,18 @@ impl Language {
"js" | "javascript" | "node" => Some(Self::JavaScript),
"json" => Some(Self::Json),
"python" | "py" => Some(Self::Python),
"lua" => Some(Self::Lua),
"bash" | "sh" | "shell" | "shellscript" => Some(Self::Bash),
"yaml" | "yml" => Some(Self::Yaml),
"make" | "makefile" | "mk" => Some(Self::Make),
"dockerfile" | "containerfile" | "docker" => Some(Self::Dockerfile),
"sql" => Some(Self::Sql),
"markdown" | "md" | "mdx" => Some(Self::Markdown),
"css" => Some(Self::Css),
"c" | "h" => Some(Self::C),
"cpp" | "c++" | "cxx" | "cc" | "hpp" | "hxx" | "hh" => Some(Self::Cpp),
"html" | "htm" => Some(Self::Html),
"xml" | "xsd" | "xsl" | "xslt" | "svg" => Some(Self::Xml),
_ => None,
}
}
@@ -50,6 +75,18 @@ impl Language {
Self::JavaScript => "js",
Self::Json => "json",
Self::Python => "python",
Self::Lua => "lua",
Self::Bash => "bash",
Self::Yaml => "yaml",
Self::Make => "make",
Self::Dockerfile => "dockerfile",
Self::Sql => "sql",
Self::Markdown => "markdown",
Self::Css => "css",
Self::C => "c",
Self::Cpp => "cpp",
Self::Html => "html",
Self::Xml => "xml",
}
}
@@ -60,37 +97,88 @@ impl Language {
/// в Lua-API.
pub fn syntax_name(self) -> &'static str {
match self {
Self::Rust => "rust",
Self::Go => "go",
Self::JavaScript => "javascript",
Self::Json => "json",
Self::Python => "python",
_ => self.name(),
}
}
/// Идентификатор языка для LSP.
///
/// Совпадает с [`Self::syntax_name`], кроме Bash и Make: грамматики
/// называются `bash` и `make`, а `languageId` в LSP — `shellscript` и
/// `makefile`.
pub fn lsp_language_id(self) -> &'static str {
match self {
Self::Bash => "shellscript",
Self::Make => "makefile",
_ => self.syntax_name(),
}
}
/// Определяет язык подсветки по имени файла.
///
/// Используется при открытии файла с диска (`app:open(path)`), чтобы
/// Используется при открытии файла с диска (`api.editor:open(path)`), чтобы
/// подсветка включалась автоматически там, где для неё есть грамматика.
/// Кроме расширений учитываются файлы без расширения (`Makefile`,
/// `Dockerfile`, `Containerfile`).
pub fn from_path(path: &str) -> Option<Self> {
let extension = path.rsplit_once('.')?.1.to_ascii_lowercase();
match extension.as_str() {
if let Some(language) = special_file_language(path) {
return Some(language);
}
let file_name = file_name(path);
let (_, extension) = file_name.rsplit_once('.')?;
Self::from_extension(extension)
}
/// Язык по расширению файла без учёта регистра.
fn from_extension(extension: &str) -> Option<Self> {
match extension.to_ascii_lowercase().as_str() {
"rs" => Some(Self::Rust),
"go" => Some(Self::Go),
"js" | "mjs" | "cjs" | "jsx" => Some(Self::JavaScript),
"json" => Some(Self::Json),
"py" => Some(Self::Python),
"lua" => Some(Self::Lua),
"sh" | "bash" => Some(Self::Bash),
"yaml" | "yml" => Some(Self::Yaml),
"mk" => Some(Self::Make),
"dockerfile" => Some(Self::Dockerfile),
"sql" => Some(Self::Sql),
"md" | "markdown" | "mdx" => Some(Self::Markdown),
"css" => Some(Self::Css),
"c" | "h" => Some(Self::C),
"cc" | "cpp" | "cxx" | "c++" | "hpp" | "hxx" | "hh" => Some(Self::Cpp),
"html" | "htm" => Some(Self::Html),
"xml" | "xsd" | "xsl" | "xslt" | "svg" => Some(Self::Xml),
_ => None,
}
}
}
/// Имя файла без каталогов.
fn file_name(path: &str) -> &str {
path.rsplit(['/', '\\']).next().unwrap_or(path)
}
/// Языки, у которых имя файла не содержит расширения.
fn special_file_language(path: &str) -> Option<Language> {
match file_name(path).to_ascii_lowercase().as_str() {
"makefile" | "gnumakefile" | "bsdmakefile" => Some(Language::Make),
"dockerfile" | "containerfile" => Some(Language::Dockerfile),
_ => None,
}
}
/// Язык LSP по расширению имени файла.
///
/// Нужен для документов, у которых подсветка ещё не включена: LSP-сервер
/// выбирается и получает `languageId` независимо от tree-sitter.
pub fn language_id_from_path(name: &str) -> &'static str {
let extension = name
if let Some(language) = special_file_language(name) {
return language.lsp_language_id();
}
let extension = file_name(name)
.rsplit_once('.')
.map(|(_, extension)| extension.to_ascii_lowercase());
@@ -103,15 +191,19 @@ pub fn language_id_from_path(name: &str) -> &'static str {
Some("tsx") => "typescriptreact",
Some("json") => "json",
Some("py") => "python",
Some("md" | "markdown") => "markdown",
Some("md" | "markdown" | "mdx") => "markdown",
Some("toml") => "toml",
Some("yaml" | "yml") => "yaml",
Some("lua") => "lua",
Some("c" | "h") => "c",
Some("cc" | "cpp" | "cxx" | "hpp" | "hh") => "cpp",
Some("cc" | "cpp" | "cxx" | "c++" | "hpp" | "hxx" | "hh") => "cpp",
Some("sh" | "bash") => "shellscript",
Some("mk") => "makefile",
Some("dockerfile") => "dockerfile",
Some("sql") => "sql",
Some("html" | "htm") => "html",
Some("css") => "css",
Some("xml" | "xsd" | "xsl" | "xslt" | "svg") => "xml",
_ => "plaintext",
}
}
@@ -169,7 +261,7 @@ impl Document {
/// по расширению имени вкладки — так LSP работает и без `set_language`.
pub fn lsp_language_id(&self) -> String {
if let Some(language) = self.language {
return language.syntax_name().to_owned();
return language.lsp_language_id().to_owned();
}
language_id_from_path(&self.name).to_owned()
}
@@ -190,7 +282,7 @@ impl Document {
/// Что сделать с фокусом редактора после синхронизации модели с видом.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FocusAfterSync {
/// Оставить фокус там, где он был (`app:open` без позиции).
/// Оставить фокус там, где он был (`api.editor:open` без позиции).
Keep,
/// Перевести фокус в редактор и, если задано, поставить курсор.
///
@@ -308,7 +400,7 @@ impl Workspace {
/// Открывает файл с диска как вкладку и делает её активной.
///
/// Если вкладка с таким именем уже открыта и не содержит несохранённых
/// изменений, её содержимое обновляется из файла — повторный `app:open`
/// изменений, её содержимое обновляется из файла — повторный `api.editor:open`
/// не создаёт дубликат. Грязная вкладка не трогается: открывается новая.
pub fn open_file(
&mut self,