Files
k-launcher/crates/plugins/plugin-url/src/main.rs
Gabriel Kaszewski ff9b2b5712 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)
2026-03-18 13:45:48 +01:00

131 lines
2.7 KiB
Rust

use std::io::{self, BufRead, Write};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct Query {
query: String,
}
#[derive(Serialize)]
struct Action {
r#type: &'static str,
path: String,
}
#[derive(Serialize)]
struct Result {
id: &'static str,
title: &'static str,
description: String,
score: u32,
action: Action,
}
fn is_url(query: &str) -> bool {
query.starts_with("http://") || query.starts_with("https://") || query.starts_with("www.")
}
fn normalize(query: &str) -> String {
if query.starts_with("www.") {
format!("https://{query}")
} else {
query.to_string()
}
}
fn search(query: &str) -> Vec<Result> {
if !is_url(query) {
return vec![];
}
let url = normalize(query);
vec![Result {
id: "url-open",
title: "Open in Browser",
description: url.clone(),
score: 95,
action: Action {
r#type: "OpenPath",
path: url.clone(),
},
}]
}
fn main() -> io::Result<()> {
let stdin = io::stdin();
let stdout = io::stdout();
let mut out = stdout.lock();
for line in stdin.lock().lines() {
let line = line?;
let q: Query = match serde_json::from_str(&line) {
Ok(q) => q,
Err(_) => continue,
};
let results = search(&q.query);
writeln!(out, "{}", serde_json::to_string(&results).unwrap())?;
out.flush()?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_url_https() {
assert!(is_url("https://example.com"));
}
#[test]
fn is_url_http() {
assert!(is_url("http://example.com"));
}
#[test]
fn is_url_www() {
assert!(is_url("www.foo.com"));
}
#[test]
fn is_url_plain() {
assert!(!is_url("firefox"));
}
#[test]
fn is_url_empty() {
assert!(!is_url(""));
}
#[test]
fn normalize_www() {
assert_eq!(normalize("www.foo.com"), "https://www.foo.com");
}
#[test]
fn normalize_https() {
assert_eq!(normalize("https://example.com"), "https://example.com");
}
#[test]
fn search_returns_result() {
let results = search("https://example.com");
assert_eq!(results.len(), 1);
assert_eq!(results[0].action.path, "https://example.com");
}
#[test]
fn search_returns_empty() {
assert!(search("firefox").is_empty());
}
#[test]
fn result_serializes() {
let results = search("https://example.com");
let json = serde_json::to_string(&results).unwrap();
assert!(json.contains("OpenPath"));
assert!(json.contains("https://example.com"));
}
}