v0.2.0
clean architecture refactor, performance, resilience, DX/UX architecture: - 13 crates with proper domain/application/infrastructure layers - domain crate: newtypes, ports (Plugin, AppLauncher), constants - kernel: pure orchestrator - shared UI state machine (k-launcher-ui-core) - merged plugin-api into domain as ports module - granular file structure (no monolithic lib.rs) - all tests extracted to tests/ directories features: - frecency boost in search results - empty query shows top frecent apps - append-only frecency log with configurable compaction - config-driven styling (all colors, sizes, debounce) - configurable terminal emulator, external plugin timeout - log rotation with max_log_files - loading indicator, descriptive placeholder text - graceful shutdown via iced::exit() + Plugin::shutdown() - --version flag, panic hook, signal handling (SIGINT/SIGTERM) - SpawnInTerminal in external plugin protocol performance: - ~1500 -> ~50 heap allocs per keystroke - reused Matcher, Pattern, char buffer across entries - Arc<str> for shared result fields - pre-filter before fuzzy matching - partial sort for top frecent IDs - cached lowercase names in entries resilience: - parking_lot (no mutex poisoning) - thiserror hierarchy (PluginError, ConfigError, AppError) - all silent error swallowing replaced with tracing::warn - config parse errors logged quality: - named constants (no magic strings/numbers) - named types (no anonymous tuples) - Rgba newtype with validation - domain newtype validation (debug_assert non-empty) - man page, LICENSE (MIT), PKGBUILD, example config - plugin development guide updated - make check (fmt + clippy + test), make dev (RUST_LOG=debug) style: format code for better readability in tests and function signatures fix: update build_entries function signature to ignore frecency parameter fix(review): bugs, arch violations, design smells P1 bugs: - unix_launcher: shell_split respects quoted args (was split_whitespace) - plugin-host: 5s timeout on external plugin search - ui: handle engine init panic, wire error state - ui-egui: read window config instead of always using defaults - plugin-url: use OpenPath action instead of SpawnProcess+xdg-open Architecture: - remove WindowConfig (mirror of WindowCfg); use WindowCfg directly - remove on_select closure from SearchResult (domain leakage) - remove LaunchAction::Custom; add Plugin::on_selected + SearchEngine::on_selected - apps: record frecency via on_selected instead of embedded closure Design smells: - frecency: extract decay_factor helper, write outside mutex - apps: remove cfg(test) cache_path hack; add new_for_test ctor - apps: stable ResultId using name+exec to prevent collision - files: stable ResultId using full path instead of index - plugin-host: remove k-launcher-os-bridge dep (WindowConfig gone) Update iced dependency in Cargo.toml to disable default features and add additional ones feat(app): enhance engine initialization with EngineHandle and update run function signature feat: production hardening (panic isolation, file logging, apps cache) - Kernel::search wraps each plugin in catch_unwind; panics are logged and return [] - init_logging() adds daily rolling file at ~/.local/share/k-launcher/logs/ - AppsPlugin caches entries to ~/.cache/k-launcher/apps.bin via bincode; stale-while-revalidate on subsequent launches - 57 tests pass refactor: remove client module and associated show command logic fix(app): format code for clarity in update function chore: update .gitignore and enhance README with compositor setup instructions chore(docs): remove unused screenshot file feature/prod-ready (#1) Reviewed-on: #1 fix(calc): remove ambiguous log alias, use ln/log2/log10 explicitly fix(calc): fix log/ln naming, cache math context, strengthen sin(pi) test feat(calc): add math functions (sqrt, sin, cos, etc.) and pi/e constants refactor(calc): rename preprocess, extend underscore test assertions feat(calc): strip underscore digit separators feat: update dependencies for improved compatibility and performance feat: add plugin-url for URL handling and open in browser functionality feat: add support for external plugins and enhance plugin management feat: add Makefile for build, run, and installation commands feat: add required features for k-launcher-egui and update dependencies feat: update README and add documentation for installation, configuration, usage, and plugin development feat: enhance configuration management and UI styling, remove unused theme module feat: add k-launcher-config crate for configuration management and integrate with existing components feat: add k-launcher-ui-egui crate for enhanced UI - Introduced a new crate `k-launcher-ui-egui` to provide a graphical user interface using eframe and egui. - Updated the workspace configuration in `Cargo.toml` to include the new crate. - Implemented the main application logic in `src/app.rs`, handling search functionality and user interactions. - Created a library entry point in `src/lib.rs` to expose the `run` function for launching the UI. - Modified the `k-launcher` crate to include a new binary target for the egui-based launcher. - Added a new main file `src/main_egui.rs` to initialize and run the egui UI with the existing kernel and launcher components. feat: implement OS bridge and enhance app launcher functionality feat: add FilesPlugin for file searching and integrate into KLauncher feat: implement frecency tracking for app usage and enhance search functionality feat: add CmdPlugin for executing terminal commands and update workspace configuration refactor: update dependencies and improve keyboard event handling in KLauncherApp refactor: simplify theme usage and enhance AppsPlugin structure feat: restructure k-launcher workspace and add core functionality - Updated Cargo.toml to include a new k-launcher crate and reorganized workspace members. - Introduced a README.md file detailing the project philosophy, architecture, and technical specifications. - Implemented a new Kernel struct in k-launcher-kernel for managing plugins and search functionality. - Created a Plugin trait for plugins to implement, allowing for asynchronous search operations. - Developed k-launcher-ui with an Iced-based UI for user interaction, including search input and result display. - Added AppsPlugin and CalcPlugin to handle application launching and basic calculations, respectively. - Established a theme module for UI styling, focusing on an Aero aesthetic. - Removed unnecessary main.rs files from plugin crates, streamlining the project structure. Initialize k-launcher project structure with multiple crates and basic configurations
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "k-launcher-config"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
@@ -9,5 +9,11 @@ path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
dirs = { workspace = true }
|
||||
k-launcher-domain = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = "1.0"
|
||||
tracing = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
toml = "1.0"
|
||||
|
||||
14
crates/k-launcher-config/src/config.rs
Normal file
14
crates/k-launcher-config/src/config.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::types::*;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
pub window: WindowCfg,
|
||||
pub appearance: AppearanceCfg,
|
||||
pub search: SearchCfg,
|
||||
pub plugins: PluginsCfg,
|
||||
pub logging: LoggingCfg,
|
||||
pub terminal: TerminalCfg,
|
||||
}
|
||||
18
crates/k-launcher-config/src/error.rs
Normal file
18
crates/k-launcher-config/src/error.rs
Normal file
@@ -0,0 +1,18 @@
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ConfigError {
|
||||
#[error("config directory not found")]
|
||||
NoDirFound,
|
||||
#[error("failed to read config at {path}: {source}")]
|
||||
ReadFailed {
|
||||
path: PathBuf,
|
||||
source: std::io::Error,
|
||||
},
|
||||
#[error("failed to parse config at {path}: {source}")]
|
||||
ParseFailed {
|
||||
path: PathBuf,
|
||||
source: toml::de::Error,
|
||||
},
|
||||
}
|
||||
@@ -1,191 +1,8 @@
|
||||
use serde::Deserialize;
|
||||
mod config;
|
||||
pub mod error;
|
||||
mod load;
|
||||
mod types;
|
||||
|
||||
// RGBA: [r, g, b, a] where r/g/b are 0–255 as f32, a is 0.0–1.0
|
||||
pub type Rgba = [f32; 4];
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Default)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
pub window: WindowCfg,
|
||||
pub appearance: AppearanceCfg,
|
||||
pub search: SearchCfg,
|
||||
pub plugins: PluginsCfg,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct WindowCfg {
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
pub decorations: bool,
|
||||
pub transparent: bool,
|
||||
pub resizable: bool,
|
||||
}
|
||||
|
||||
impl Default for WindowCfg {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
width: 600.0,
|
||||
height: 400.0,
|
||||
decorations: false,
|
||||
transparent: true,
|
||||
resizable: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct AppearanceCfg {
|
||||
pub background_rgba: Rgba,
|
||||
pub border_rgba: Rgba,
|
||||
pub border_width: f32,
|
||||
pub border_radius: f32,
|
||||
pub search_font_size: f32,
|
||||
pub title_size: f32,
|
||||
pub desc_size: f32,
|
||||
pub row_radius: f32,
|
||||
pub placeholder: String,
|
||||
}
|
||||
|
||||
impl Default for AppearanceCfg {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
background_rgba: [20.0, 20.0, 30.0, 0.9],
|
||||
border_rgba: [229.0, 125.0, 33.0, 1.0],
|
||||
border_width: 1.0,
|
||||
border_radius: 8.0,
|
||||
search_font_size: 18.0,
|
||||
title_size: 15.0,
|
||||
desc_size: 12.0,
|
||||
row_radius: 4.0,
|
||||
placeholder: "Search...".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct SearchCfg {
|
||||
pub max_results: usize,
|
||||
}
|
||||
|
||||
impl Default for SearchCfg {
|
||||
fn default() -> Self {
|
||||
Self { max_results: 8 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Default)]
|
||||
pub struct ExternalPluginCfg {
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
#[serde(default)]
|
||||
pub args: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct PluginsCfg {
|
||||
pub calc: bool,
|
||||
pub cmd: bool,
|
||||
pub files: bool,
|
||||
pub apps: bool,
|
||||
pub external: Vec<ExternalPluginCfg>,
|
||||
}
|
||||
|
||||
impl Default for PluginsCfg {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
calc: true,
|
||||
cmd: true,
|
||||
files: true,
|
||||
apps: true,
|
||||
external: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load() -> Config {
|
||||
let path = dirs::config_dir().map(|d| d.join("k-launcher").join("config.toml"));
|
||||
let Some(path) = path else {
|
||||
return Config::default();
|
||||
};
|
||||
let Ok(content) = std::fs::read_to_string(&path) else {
|
||||
return Config::default();
|
||||
};
|
||||
toml::from_str(&content).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default_config_has_sane_values() {
|
||||
let cfg = Config::default();
|
||||
assert_eq!(cfg.search.max_results, 8);
|
||||
assert_eq!(cfg.window.width, 600.0);
|
||||
assert_eq!(cfg.window.height, 400.0);
|
||||
assert!(!cfg.window.decorations);
|
||||
assert!(cfg.window.transparent);
|
||||
assert!(!cfg.window.resizable);
|
||||
assert!(cfg.plugins.calc);
|
||||
assert!(cfg.plugins.apps);
|
||||
assert_eq!(cfg.appearance.search_font_size, 18.0);
|
||||
assert_eq!(cfg.appearance.placeholder, "Search...");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_partial_toml_uses_defaults() {
|
||||
let toml = "[search]\nmax_results = 5\n";
|
||||
let cfg: Config = toml::from_str(toml).unwrap();
|
||||
assert_eq!(cfg.search.max_results, 5);
|
||||
assert_eq!(cfg.window.width, 600.0);
|
||||
assert_eq!(cfg.appearance.search_font_size, 18.0);
|
||||
assert!(cfg.plugins.apps);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_full_toml_roundtrip() {
|
||||
let toml = r#"
|
||||
[window]
|
||||
width = 800.0
|
||||
height = 500.0
|
||||
decorations = true
|
||||
transparent = false
|
||||
resizable = true
|
||||
|
||||
[appearance]
|
||||
background_rgba = [10.0, 10.0, 20.0, 0.8]
|
||||
border_rgba = [100.0, 200.0, 255.0, 1.0]
|
||||
border_width = 2.0
|
||||
border_radius = 12.0
|
||||
search_font_size = 20.0
|
||||
title_size = 16.0
|
||||
desc_size = 13.0
|
||||
row_radius = 6.0
|
||||
placeholder = "Type here..."
|
||||
|
||||
[search]
|
||||
max_results = 12
|
||||
|
||||
[plugins]
|
||||
calc = false
|
||||
cmd = true
|
||||
files = false
|
||||
apps = true
|
||||
"#;
|
||||
let cfg: Config = toml::from_str(toml).unwrap();
|
||||
assert_eq!(cfg.window.width, 800.0);
|
||||
assert_eq!(cfg.window.height, 500.0);
|
||||
assert!(cfg.window.decorations);
|
||||
assert!(!cfg.window.transparent);
|
||||
assert_eq!(cfg.appearance.background_rgba, [10.0, 10.0, 20.0, 0.8]);
|
||||
assert_eq!(cfg.appearance.search_font_size, 20.0);
|
||||
assert_eq!(cfg.appearance.placeholder, "Type here...");
|
||||
assert_eq!(cfg.search.max_results, 12);
|
||||
assert!(!cfg.plugins.calc);
|
||||
assert!(!cfg.plugins.files);
|
||||
}
|
||||
}
|
||||
pub use config::*;
|
||||
pub use load::*;
|
||||
pub use types::*;
|
||||
|
||||
25
crates/k-launcher-config/src/load.rs
Normal file
25
crates/k-launcher-config/src/load.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use k_launcher_domain::constants::{APP_NAME, CONFIG_FILENAME};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::error::ConfigError;
|
||||
|
||||
pub fn load() -> Config {
|
||||
match try_load() {
|
||||
Ok(cfg) => cfg,
|
||||
Err(ConfigError::NoDirFound | ConfigError::ReadFailed { .. }) => Config::default(),
|
||||
Err(e @ ConfigError::ParseFailed { .. }) => {
|
||||
tracing::warn!("{e}");
|
||||
Config::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_load() -> Result<Config, ConfigError> {
|
||||
let dir = dirs::config_dir().ok_or(ConfigError::NoDirFound)?;
|
||||
let path = dir.join(APP_NAME).join(CONFIG_FILENAME);
|
||||
let content = std::fs::read_to_string(&path).map_err(|e| ConfigError::ReadFailed {
|
||||
path: path.clone(),
|
||||
source: e,
|
||||
})?;
|
||||
toml::from_str(&content).map_err(|e| ConfigError::ParseFailed { path, source: e })
|
||||
}
|
||||
200
crates/k-launcher-config/src/types.rs
Normal file
200
crates/k-launcher-config/src/types.rs
Normal file
@@ -0,0 +1,200 @@
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Rgba {
|
||||
red: f32,
|
||||
green: f32,
|
||||
blue: f32,
|
||||
alpha: f32,
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for Rgba {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let [red, green, blue, alpha] = <[f32; 4]>::deserialize(deserializer)?;
|
||||
Ok(Self::new(red, green, blue, alpha))
|
||||
}
|
||||
}
|
||||
|
||||
impl Rgba {
|
||||
pub fn new(red: f32, green: f32, blue: f32, alpha: f32) -> Self {
|
||||
Self {
|
||||
red: red.clamp(0.0, 255.0),
|
||||
green: green.clamp(0.0, 255.0),
|
||||
blue: blue.clamp(0.0, 255.0),
|
||||
alpha: alpha.clamp(0.0, 1.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn red(&self) -> f32 {
|
||||
self.red
|
||||
}
|
||||
|
||||
pub fn green(&self) -> f32 {
|
||||
self.green
|
||||
}
|
||||
|
||||
pub fn blue(&self) -> f32 {
|
||||
self.blue
|
||||
}
|
||||
|
||||
pub fn alpha(&self) -> f32 {
|
||||
self.alpha
|
||||
}
|
||||
|
||||
pub fn red_u8(&self) -> u8 {
|
||||
self.red as u8
|
||||
}
|
||||
|
||||
pub fn green_u8(&self) -> u8 {
|
||||
self.green as u8
|
||||
}
|
||||
|
||||
pub fn blue_u8(&self) -> u8 {
|
||||
self.blue as u8
|
||||
}
|
||||
|
||||
pub fn alpha_byte(&self) -> u8 {
|
||||
(self.alpha * 255.0) as u8
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct WindowCfg {
|
||||
pub width: f32,
|
||||
pub height: f32,
|
||||
pub decorations: bool,
|
||||
pub transparent: bool,
|
||||
pub resizable: bool,
|
||||
}
|
||||
|
||||
impl Default for WindowCfg {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
width: 600.0,
|
||||
height: 400.0,
|
||||
decorations: false,
|
||||
transparent: true,
|
||||
resizable: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct AppearanceCfg {
|
||||
pub background_rgba: Rgba,
|
||||
pub border_rgba: Rgba,
|
||||
pub border_width: f32,
|
||||
pub border_radius: f32,
|
||||
pub search_font_size: f32,
|
||||
pub title_size: f32,
|
||||
pub desc_size: f32,
|
||||
pub row_radius: f32,
|
||||
pub placeholder: String,
|
||||
pub selected_row_rgba: Rgba,
|
||||
pub unselected_row_rgba: Rgba,
|
||||
pub description_rgba: Rgba,
|
||||
pub no_results_rgba: Rgba,
|
||||
pub error_rgba: Rgba,
|
||||
pub icon_size: f32,
|
||||
}
|
||||
|
||||
impl Default for AppearanceCfg {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
background_rgba: Rgba::new(20.0, 20.0, 30.0, 0.9),
|
||||
border_rgba: Rgba::new(229.0, 125.0, 33.0, 1.0),
|
||||
border_width: 1.0,
|
||||
border_radius: 8.0,
|
||||
search_font_size: 18.0,
|
||||
title_size: 15.0,
|
||||
desc_size: 12.0,
|
||||
row_radius: 4.0,
|
||||
placeholder: "Search apps, type > for commands, = for math".to_string(),
|
||||
selected_row_rgba: Rgba::new(229.0, 125.0, 33.0, 1.0),
|
||||
unselected_row_rgba: Rgba::new(255.0, 255.0, 255.0, 0.07),
|
||||
description_rgba: Rgba::new(210.0, 215.0, 230.0, 1.0),
|
||||
no_results_rgba: Rgba::new(180.0, 180.0, 200.0, 0.5),
|
||||
error_rgba: Rgba::new(255.0, 80.0, 80.0, 1.0),
|
||||
icon_size: 24.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct SearchCfg {
|
||||
pub max_results: usize,
|
||||
pub debounce_ms: u64,
|
||||
pub frecency_compact_threshold: usize,
|
||||
}
|
||||
|
||||
impl Default for SearchCfg {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_results: 8,
|
||||
debounce_ms: 50,
|
||||
frecency_compact_threshold: 50,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct LoggingCfg {
|
||||
pub max_log_files: usize,
|
||||
}
|
||||
|
||||
impl Default for LoggingCfg {
|
||||
fn default() -> Self {
|
||||
Self { max_log_files: 7 }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Default)]
|
||||
pub struct TerminalCfg {
|
||||
pub cmd: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ExternalPluginCfg {
|
||||
pub name: String,
|
||||
pub path: String,
|
||||
pub args: Vec<String>,
|
||||
pub timeout_secs: u64,
|
||||
}
|
||||
|
||||
impl Default for ExternalPluginCfg {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: String::new(),
|
||||
path: String::new(),
|
||||
args: vec![],
|
||||
timeout_secs: 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct PluginsCfg {
|
||||
pub calc: bool,
|
||||
pub cmd: bool,
|
||||
pub files: bool,
|
||||
pub apps: bool,
|
||||
pub external: Vec<ExternalPluginCfg>,
|
||||
}
|
||||
|
||||
impl Default for PluginsCfg {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
calc: true,
|
||||
cmd: true,
|
||||
files: true,
|
||||
apps: true,
|
||||
external: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
75
crates/k-launcher-config/tests/config.rs
Normal file
75
crates/k-launcher-config/tests/config.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use k_launcher_config::Config;
|
||||
|
||||
#[test]
|
||||
fn default_config_has_sane_values() {
|
||||
let cfg = Config::default();
|
||||
assert_eq!(cfg.search.max_results, 8);
|
||||
assert_eq!(cfg.window.width, 600.0);
|
||||
assert_eq!(cfg.window.height, 400.0);
|
||||
assert!(!cfg.window.decorations);
|
||||
assert!(cfg.window.transparent);
|
||||
assert!(!cfg.window.resizable);
|
||||
assert!(cfg.plugins.calc);
|
||||
assert!(cfg.plugins.apps);
|
||||
assert_eq!(cfg.appearance.search_font_size, 18.0);
|
||||
assert_eq!(
|
||||
cfg.appearance.placeholder,
|
||||
"Search apps, type > for commands, = for math"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_partial_toml_uses_defaults() {
|
||||
let toml_str = "[search]\nmax_results = 5\n";
|
||||
let cfg: Config = toml::from_str(toml_str).unwrap();
|
||||
assert_eq!(cfg.search.max_results, 5);
|
||||
assert_eq!(cfg.window.width, 600.0);
|
||||
assert_eq!(cfg.appearance.search_font_size, 18.0);
|
||||
assert!(cfg.plugins.apps);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_full_toml_roundtrip() {
|
||||
let toml_str = r#"
|
||||
[window]
|
||||
width = 800.0
|
||||
height = 500.0
|
||||
decorations = true
|
||||
transparent = false
|
||||
resizable = true
|
||||
|
||||
[appearance]
|
||||
background_rgba = [10.0, 10.0, 20.0, 0.8]
|
||||
border_rgba = [100.0, 200.0, 255.0, 1.0]
|
||||
border_width = 2.0
|
||||
border_radius = 12.0
|
||||
search_font_size = 20.0
|
||||
title_size = 16.0
|
||||
desc_size = 13.0
|
||||
row_radius = 6.0
|
||||
placeholder = "Type here..."
|
||||
|
||||
[search]
|
||||
max_results = 12
|
||||
|
||||
[plugins]
|
||||
calc = false
|
||||
cmd = true
|
||||
files = false
|
||||
apps = true
|
||||
"#;
|
||||
let cfg: Config = toml::from_str(toml_str).unwrap();
|
||||
assert_eq!(cfg.window.width, 800.0);
|
||||
assert_eq!(cfg.window.height, 500.0);
|
||||
assert!(cfg.window.decorations);
|
||||
assert!(!cfg.window.transparent);
|
||||
assert_eq!(
|
||||
cfg.appearance.background_rgba,
|
||||
k_launcher_config::Rgba::new(10.0, 10.0, 20.0, 0.8)
|
||||
);
|
||||
assert_eq!(cfg.appearance.search_font_size, 20.0);
|
||||
assert_eq!(cfg.appearance.placeholder, "Type here...");
|
||||
assert_eq!(cfg.search.max_results, 12);
|
||||
assert!(!cfg.plugins.calc);
|
||||
assert!(!cfg.plugins.files);
|
||||
}
|
||||
Reference in New Issue
Block a user