Files
qode/src/constants.rs
T
2026-10-30 20:50:04 +03:00

220 lines
8.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Константы оформления окна и сценариев.
use std::ffi::OsString;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
/// Ширина окна при запуске.
pub const WINDOW_WIDTH: f32 = 900.0;
/// Высота окна при запуске.
pub const WINDOW_HEIGHT: f32 = 600.0;
/// Минимальная ширина окна.
pub const MIN_WINDOW_WIDTH: f32 = 420.0;
/// Минимальная высота окна.
pub const MIN_WINDOW_HEIGHT: f32 = 280.0;
/// Подпись окна в панели задач и в заголовке.
pub const WINDOW_TITLE: &str = "Custom Window App";
/// Ширина одного уровня отступа в редакторе.
pub const TAB_SIZE: usize = 4;
/// Ширина правой панели при открытии.
pub const RIGHT_PANEL_WIDTH: f32 = 240.0;
/// Ширина левой панели при открытии.
pub const LEFT_PANEL_WIDTH: f32 = 220.0;
/// Минимальная ширина левой панели при перетаскивании разделителя.
pub const MIN_LEFT_PANEL_WIDTH: f32 = 120.0;
/// Минимальная ширина области редактора при изменении левой панели.
pub const MIN_MAIN_AREA_WIDTH: f32 = 240.0;
/// Высота нижней панели при открытии.
pub const BOTTOM_PANEL_HEIGHT: f32 = 160.0;
/// Каталог, в котором приложение ищет Lua-сценарии по умолчанию.
///
/// Это текущий рабочий каталог процесса — именно там приложение ожидает
/// `init.lua`, если не передан аргумент `--config`. Конкретный каталог всегда
/// возвращает [`script_dir`].
pub const SCRIPT_DIR: &str = ".";
/// Каталог сценариев, заданный через `--config`.
static SCRIPT_DIR_OVERRIDE: OnceLock<PathBuf> = OnceLock::new();
/// Файл, который нужно открыть при старте (первый позиционный аргумент).
static STARTUP_FILE: OnceLock<PathBuf> = OnceLock::new();
/// Разобранные аргументы командной строки.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CommandLine {
/// Каталог, из которого брать `init.lua`.
pub config_dir: Option<PathBuf>,
/// Файл, открываемый при старте как `vim file.go`.
pub file: Option<PathBuf>,
}
/// Каталог, из которого загружаются `init.lua`, `include` и `require`.
///
/// Возвращает значение `--config`, если оно было задано, иначе текущий
/// рабочий каталог.
pub fn script_dir() -> &'static Path {
SCRIPT_DIR_OVERRIDE
.get()
.map_or_else(|| Path::new(SCRIPT_DIR), PathBuf::as_path)
}
/// Файл, который надо открыть при старте редактора.
pub fn startup_file() -> Option<&'static Path> {
STARTUP_FILE.get().map(PathBuf::as_path)
}
/// Запоминает каталог сценариев из аргумента `--config`.
///
/// Путь канонизируется, поэтому относительный `--config ./scripts` работает
/// относительно каталога запуска, а `include`/`require` видят абсолютные пути.
pub fn set_script_dir(path: PathBuf) -> Result<(), String> {
let canonical = std::fs::canonicalize(&path).map_err(|err| {
format!(
"не удалось открыть каталог конфигурации {}: {err}",
path.display()
)
})?;
if !canonical.is_dir() {
return Err(format!(
"--config: {} не является каталогом",
canonical.display()
));
}
SCRIPT_DIR_OVERRIDE
.set(canonical)
.map_err(|_| "каталог конфигурации уже задан".to_owned())
}
/// Запоминает файл, открываемый при старте.
pub fn set_startup_file(path: PathBuf) -> Result<(), String> {
STARTUP_FILE
.set(path)
.map_err(|_| "файл для открытия уже задан".to_owned())
}
/// Разбирает аргументы командной строки.
///
/// Поддерживаются:
/// * `файл` — первый позиционный аргумент, как в `vim file.go`;
/// * `--config <каталог>` и `--config=<каталог>`;
/// * `--` — всё после него считается позиционным (для имён, начинающихся с `-`).
///
/// Неизвестные флаги игнорируются, чтобы запуск не ломался от лишних
/// аргументов окружения/обёрток.
pub fn parse_command_line<I>(args: I) -> Result<CommandLine, String>
where
I: IntoIterator<Item = OsString>,
{
let mut command_line = CommandLine::default();
let mut args = args.into_iter();
let mut positional_only = false;
while let Some(arg) = args.next() {
let text = arg.to_string_lossy();
if positional_only {
if command_line.file.is_none() {
command_line.file = Some(PathBuf::from(arg));
}
continue;
}
if text == "--" {
positional_only = true;
} else if text == "--config" {
let value = args
.next()
.ok_or_else(|| "--config требует путь к каталогу".to_owned())?;
command_line.config_dir = Some(PathBuf::from(value));
} else if let Some(value) = text.strip_prefix("--config=") {
if value.is_empty() {
return Err("--config требует путь к каталогу".to_owned());
}
command_line.config_dir = Some(PathBuf::from(value));
} else if !text.starts_with('-') && command_line.file.is_none() {
command_line.file = Some(PathBuf::from(arg));
}
}
Ok(command_line)
}
/// Имя стартового сценария, который выполняется при запуске приложения.
pub const STARTUP_SCRIPT: &str = "init.lua";
#[cfg(test)]
mod tests {
use super::*;
fn args(values: &[&str]) -> Vec<OsString> {
values.iter().map(OsString::from).collect()
}
#[test]
fn config_dir_is_read_from_both_argument_forms() {
assert_eq!(
parse_command_line(args(&["--config", "/tmp/project"]))
.unwrap()
.config_dir,
Some(PathBuf::from("/tmp/project"))
);
assert_eq!(
parse_command_line(args(&["--config=/tmp/project"]))
.unwrap()
.config_dir,
Some(PathBuf::from("/tmp/project"))
);
assert!(parse_command_line(args(&[])).unwrap().config_dir.is_none());
}
#[test]
fn config_dir_requires_a_value() {
assert!(parse_command_line(args(&["--config"])).is_err());
assert!(parse_command_line(args(&["--config="])).is_err());
}
#[test]
fn unknown_arguments_do_not_hide_the_config_dir() {
let parsed = parse_command_line(args(&["--verbose", "--config", "cfg"])).unwrap();
assert_eq!(parsed.config_dir, Some(PathBuf::from("cfg")));
}
#[test]
fn first_positional_argument_is_the_file_to_open() {
let parsed = parse_command_line(args(&["main.go"])).unwrap();
assert_eq!(parsed.file, Some(PathBuf::from("main.go")));
let parsed = parse_command_line(args(&["--config", "cfg", "main.go"])).unwrap();
assert_eq!(parsed.config_dir, Some(PathBuf::from("cfg")));
assert_eq!(parsed.file, Some(PathBuf::from("main.go")));
let parsed = parse_command_line(args(&["main.go", "--config=cfg"])).unwrap();
assert_eq!(parsed.config_dir, Some(PathBuf::from("cfg")));
assert_eq!(parsed.file, Some(PathBuf::from("main.go")));
}
#[test]
fn double_dash_allows_file_names_starting_with_dash() {
let parsed = parse_command_line(args(&["--", "-weird.go"])).unwrap();
assert_eq!(parsed.file, Some(PathBuf::from("-weird.go")));
}
#[test]
fn only_the_first_positional_argument_is_opened() {
let parsed = parse_command_line(args(&["first.go", "second.go"])).unwrap();
assert_eq!(parsed.file, Some(PathBuf::from("first.go")));
}
}