fix: quality — config-based terminal/engine, emoji init perf, safer shell commands

This commit is contained in:
2026-03-26 16:50:17 +01:00
parent 37edb8e1df
commit 9bcbacd9d7
8 changed files with 66 additions and 30 deletions
Generated
+3
View File
@@ -971,6 +971,7 @@ dependencies = [
"abi_stable",
"dirs",
"owlry-plugin-api",
"toml",
]
[[package]]
@@ -1007,7 +1008,9 @@ name = "owlry-plugin-websearch"
version = "1.0.0"
dependencies = [
"abi_stable",
"dirs",
"owlry-plugin-api",
"toml",
]
[[package]]
+1 -1
View File
@@ -136,7 +136,7 @@ fn evaluate_expression(expr: &str) -> Option<PluginItem> {
PluginItem::new(
format!("calc:{}", expr),
result_str.clone(),
format!("sh -c 'echo -n \"{}\" | wl-copy'", result_str),
format!("printf '%s' '{}' | wl-copy", result_str.replace('\'', "'\\''")),
)
.with_description(format!("= {}", expr))
.with_icon(PROVIDER_ICON)
+1 -1
View File
@@ -92,7 +92,7 @@ extern "C" fn provider_query(_handle: ProviderHandle, query: RStr<'_>) -> RVec<P
PluginItem::new(
format!("conv:{}:{}:{}", parsed.from_unit, r.target_symbol, r.value),
r.display_value.clone(),
format!("sh -c 'echo -n \"{}\" | wl-copy'", r.raw_value),
format!("printf '%s' '{}' | wl-copy", r.raw_value.replace('\'', "'\\''")),
)
.with_description(format!(
"{} {} = {} {}",
+12 -20
View File
@@ -33,7 +33,9 @@ struct EmojiState {
impl EmojiState {
fn new() -> Self {
Self { items: Vec::new() }
let mut state = Self { items: Vec::new() };
state.load_emojis();
state
}
fn load_emojis(&mut self) {
@@ -471,12 +473,9 @@ extern "C" fn provider_refresh(handle: ProviderHandle) -> RVec<PluginItem> {
}
// SAFETY: We created this handle from Box<EmojiState>
let state = unsafe { &mut *(handle.ptr as *mut EmojiState) };
let state = unsafe { &*(handle.ptr as *const EmojiState) };
// Load emojis
state.load_emojis();
// Return items
// Return cached items (loaded once at init)
state.items.to_vec().into()
}
@@ -515,20 +514,15 @@ mod tests {
#[test]
fn test_emoji_state_new() {
let state = EmojiState::new();
assert!(state.items.is_empty());
}
#[test]
fn test_emoji_count() {
let mut state = EmojiState::new();
state.load_emojis();
assert!(state.items.len() > 100, "Should have more than 100 emojis");
assert!(
state.items.len() > 100,
"Should have more than 100 emojis loaded at init"
);
}
#[test]
fn test_emoji_has_grinning_face() {
let mut state = EmojiState::new();
state.load_emojis();
let state = EmojiState::new();
let grinning = state
.items
@@ -542,8 +536,7 @@ mod tests {
#[test]
fn test_emoji_command_format() {
let mut state = EmojiState::new();
state.load_emojis();
let state = EmojiState::new();
let item = &state.items[0];
assert!(item.command.as_str().contains("wl-copy"));
@@ -552,8 +545,7 @@ mod tests {
#[test]
fn test_emojis_have_keywords() {
let mut state = EmojiState::new();
state.load_emojis();
let state = EmojiState::new();
// Check that items have keywords for searching
let heart = state.items.iter().find(|i| i.name.as_str() == "red heart");
+3
View File
@@ -21,3 +21,6 @@ abi_stable = "0.11"
# For finding ~/.ssh/config
dirs = "5.0"
# For reading owlry config.toml
toml = "0.8"
+24 -6
View File
@@ -28,9 +28,6 @@ const PROVIDER_PREFIX: &str = ":ssh";
const PROVIDER_ICON: &str = "utilities-terminal";
const PROVIDER_TYPE_ID: &str = "ssh";
// Default terminal command (TODO: make configurable via plugin config)
const DEFAULT_TERMINAL: &str = "kitty";
/// SSH provider state - holds cached items
struct SshState {
items: Vec<PluginItem>,
@@ -39,15 +36,36 @@ struct SshState {
impl SshState {
fn new() -> Self {
// Try to detect terminal from environment, fall back to default
let terminal = std::env::var("TERMINAL").unwrap_or_else(|_| DEFAULT_TERMINAL.to_string());
let terminal = Self::load_terminal_from_config();
Self {
items: Vec::new(),
terminal_command: terminal,
}
}
fn load_terminal_from_config() -> String {
// Try [plugins.ssh] in config.toml
let config_path = dirs::config_dir().map(|d| d.join("owlry").join("config.toml"));
if let Some(content) = config_path.and_then(|p| fs::read_to_string(p).ok())
&& let Ok(toml) = content.parse::<toml::Table>()
{
if let Some(plugins) = toml.get("plugins").and_then(|v| v.as_table())
&& let Some(ssh) = plugins.get("ssh").and_then(|v| v.as_table())
&& let Some(terminal) = ssh.get("terminal").and_then(|v| v.as_str())
{
return terminal.to_string();
}
}
// Fall back to $TERMINAL env var
if let Ok(terminal) = std::env::var("TERMINAL") {
return terminal;
}
// Last resort
"xdg-terminal-exec".to_string()
}
fn ssh_config_path() -> Option<PathBuf> {
dirs::home_dir().map(|h| h.join(".ssh").join("config"))
}
+4
View File
@@ -18,3 +18,7 @@ owlry-plugin-api = { git = "https://somegit.dev/Owlibou/owlry.git", tag = "plugi
# ABI-stable types (re-exported from owlry-plugin-api, but needed for RString etc)
abi_stable = "0.11"
# For reading owlry config.toml
toml = "0.8"
dirs = "5.0"
+18 -2
View File
@@ -13,6 +13,7 @@ use owlry_plugin_api::{
API_VERSION, PluginInfo, PluginItem, ProviderHandle, ProviderInfo, ProviderKind,
ProviderPosition, owlry_plugin,
};
use std::fs;
// Plugin metadata
const PLUGIN_ID: &str = "websearch";
@@ -143,6 +144,21 @@ impl WebSearchState {
}
}
fn load_engine_from_config() -> String {
let config_path = dirs::config_dir().map(|d| d.join("owlry").join("config.toml"));
if let Some(content) = config_path.and_then(|p| fs::read_to_string(p).ok())
&& let Ok(toml) = content.parse::<toml::Table>()
{
if let Some(plugins) = toml.get("plugins").and_then(|v| v.as_table())
&& let Some(websearch) = plugins.get("websearch").and_then(|v| v.as_table())
&& let Some(engine) = websearch.get("engine").and_then(|v| v.as_str())
{
return engine.to_string();
}
}
DEFAULT_ENGINE.to_string()
}
// ============================================================================
// Plugin Interface Implementation
// ============================================================================
@@ -172,8 +188,8 @@ extern "C" fn plugin_providers() -> RVec<ProviderInfo> {
}
extern "C" fn provider_init(_provider_id: RStr<'_>) -> ProviderHandle {
// TODO: Read search engine from config when plugin config is available
let state = Box::new(WebSearchState::new());
let engine = load_engine_from_config();
let state = Box::new(WebSearchState::with_engine(&engine));
ProviderHandle::from_box(state)
}