220 lines
8.5 KiB
Rust
220 lines
8.5 KiB
Rust
//! Константы оформления окна и сценариев.
|
||
|
||
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")));
|
||
}
|
||
}
|