fix: align Lua ProviderInfo ABI, implement Rune Item type and refresh/query

This commit is contained in:
2026-03-26 18:07:46 +01:00
parent 38dda8c44c
commit 7da8f3c249
3 changed files with 90 additions and 37 deletions

View File

@@ -27,7 +27,7 @@ mod manifest;
mod runtime;
use abi_stable::std_types::{ROption, RStr, RString, RVec};
use owlry_plugin_api::{PluginItem, ProviderKind};
use owlry_plugin_api::PluginItem;
use std::collections::HashMap;
use std::path::PathBuf;
@@ -94,24 +94,22 @@ impl RuntimeHandle {
}
/// Provider info from a Lua plugin
///
/// Must match ScriptProviderInfo layout in owlry-core/src/plugins/runtime_loader.rs
#[repr(C)]
pub struct LuaProviderInfo {
/// Full provider ID: "plugin_id:provider_name"
pub id: RString,
/// Plugin ID this provider belongs to
pub plugin_id: RString,
/// Provider name within the plugin
pub provider_name: RString,
/// Provider name (used as vtable refresh/query key: "plugin_id:provider_name")
pub name: RString,
/// Display name
pub display_name: RString,
/// Optional prefix trigger
pub prefix: ROption<RString>,
/// Icon name
pub icon: RString,
/// Provider type (static/dynamic)
pub provider_type: ProviderKind,
/// Type ID for filtering
pub type_id: RString,
/// Icon name
pub default_icon: RString,
/// Whether this is a static provider (true) or dynamic (false)
pub is_static: bool,
/// Optional prefix trigger
pub prefix: ROption<RString>,
}
/// Internal runtime state
@@ -175,21 +173,14 @@ impl LuaRuntimeState {
if let Ok(registrations) = plugin.get_provider_registrations() {
for reg in registrations {
let full_id = format!("{}:{}", plugin_id, reg.name);
let provider_type = if reg.is_dynamic {
ProviderKind::Dynamic
} else {
ProviderKind::Static
};
providers.push(LuaProviderInfo {
id: RString::from(full_id),
plugin_id: RString::from(plugin_id.as_str()),
provider_name: RString::from(reg.name.as_str()),
name: RString::from(full_id),
display_name: RString::from(reg.display_name.as_str()),
prefix: reg.prefix.map(RString::from).into(),
icon: RString::from(reg.default_icon.as_str()),
provider_type,
type_id: RString::from(reg.type_id.as_str()),
default_icon: RString::from(reg.default_icon.as_str()),
is_static: !reg.is_dynamic,
prefix: reg.prefix.map(RString::from).into(),
});
}
}

View File

@@ -2,7 +2,7 @@
//!
//! This module provides the `owlry` module that Rune plugins can use.
use rune::{ContextError, Module};
use rune::{Any, ContextError, Module};
use std::sync::Mutex;
use owlry_plugin_api::{PluginItem, RString};
@@ -20,9 +20,8 @@ pub struct ProviderRegistration {
/// An item returned by a provider
///
/// Used for converting Rune plugin items to FFI format.
#[derive(Debug, Clone)]
#[allow(dead_code)]
/// Exposed to Rune scripts as `owlry::Item`.
#[derive(Debug, Clone, Any)]
pub struct Item {
pub id: String,
pub name: String,
@@ -34,8 +33,38 @@ pub struct Item {
}
impl Item {
/// Create a new item (exposed to Rune as Item::new)
fn rune_new(id: &str, name: &str, command: &str) -> Self {
Self {
id: id.to_string(),
name: name.to_string(),
command: command.to_string(),
description: None,
icon: None,
terminal: false,
keywords: Vec::new(),
}
}
/// Set description (builder pattern for Rune)
fn rune_description(mut self, desc: &str) -> Self {
self.description = Some(desc.to_string());
self
}
/// Set icon (builder pattern for Rune)
fn rune_icon(mut self, icon: &str) -> Self {
self.icon = Some(icon.to_string());
self
}
/// Set keywords (builder pattern for Rune)
fn rune_keywords(mut self, keywords: Vec<String>) -> Self {
self.keywords = keywords;
self
}
/// Convert to PluginItem for FFI
#[allow(dead_code)]
pub fn to_plugin_item(&self) -> PluginItem {
let mut item = PluginItem::new(
RString::from(self.id.as_str()),
@@ -62,7 +91,19 @@ pub static REGISTRATIONS: Mutex<Vec<ProviderRegistration>> = Mutex::new(Vec::new
pub fn module() -> Result<Module, ContextError> {
let mut module = Module::with_crate("owlry")?;
// Register logging functions using builder pattern
// Register Item type with constructor and builder methods
module.ty::<Item>()?;
module
.function("Item::new", Item::rune_new)
.build()?;
module
.associated_function("description", Item::rune_description)?;
module
.associated_function("icon", Item::rune_icon)?;
module
.associated_function("keywords", Item::rune_keywords)?;
// Register logging functions
module.function("log_info", log_info).build()?;
module.function("log_debug", log_debug).build()?;
module.function("log_warn", log_warn).build()?;

View File

@@ -92,16 +92,37 @@ impl LoadedPlugin {
self.registrations.iter().any(|r| r.name == name)
}
/// Refresh a static provider (stub for now)
/// Refresh a static provider by calling the Rune `refresh()` function
pub fn refresh_provider(&mut self, _name: &str) -> Result<Vec<PluginItem>, String> {
// TODO: Implement provider refresh by calling Rune function
Ok(Vec::new())
let mut vm = create_vm(&self.context, self.unit.clone())
.map_err(|e| format!("Failed to create VM: {}", e))?;
let output = vm
.call(rune::Hash::type_hash(["refresh"]), ())
.map_err(|e| format!("refresh() call failed: {}", e))?;
let items: Vec<crate::api::Item> = rune::from_value(output)
.map_err(|e| format!("Failed to parse refresh() result: {}", e))?;
Ok(items.iter().map(|i| i.to_plugin_item()).collect())
}
/// Query a dynamic provider (stub for now)
pub fn query_provider(&mut self, _name: &str, _query: &str) -> Result<Vec<PluginItem>, String> {
// TODO: Implement provider query by calling Rune function
Ok(Vec::new())
/// Query a dynamic provider by calling the Rune `query(q)` function
pub fn query_provider(&mut self, _name: &str, query: &str) -> Result<Vec<PluginItem>, String> {
let mut vm = create_vm(&self.context, self.unit.clone())
.map_err(|e| format!("Failed to create VM: {}", e))?;
let output = vm
.call(
rune::Hash::type_hash(["query"]),
(query.to_string(),),
)
.map_err(|e| format!("query() call failed: {}", e))?;
let items: Vec<crate::api::Item> = rune::from_value(output)
.map_err(|e| format!("Failed to parse query() result: {}", e))?;
Ok(items.iter().map(|i| i.to_plugin_item()).collect())
}
}