4 Commits

Author SHA1 Message Date
bf8a31af78 chore: bump all crates to 0.4.7 2026-01-01 22:29:00 +01:00
e23bdf5cee fix(providers): enable submenu support for static native plugins
Static native plugins (systemd, clipboard, etc.) were being boxed as
Box<dyn Provider>, which lost access to the query() method needed for
submenu support. The Provider trait only has refresh() and items().

Add static_native_providers field to keep static native plugins as
NativeProvider instances, preserving their query() method. Update all
search methods and query_submenu_actions() to include this new list.

Fixes systemd plugin submenu not showing actions when selecting a service.
2026-01-01 22:14:43 +01:00
25c4d40d36 docs: add comprehensive usage documentation
- Expand CLI --help with examples, dmenu mode, and search prefixes
- Add dmenu mode section to README with practical examples
- Add plugin management CLI reference to README
- Update argument descriptions with all valid modes listed
2026-01-01 21:45:52 +01:00
b36dd2a438 chore: update bump-all to include core in single commit 2025-12-30 20:32:28 +01:00
22 changed files with 317 additions and 162 deletions

34
Cargo.lock generated
View File

@@ -2373,7 +2373,7 @@ dependencies = [
[[package]]
name = "owlry"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"chrono",
"clap",
@@ -2402,7 +2402,7 @@ dependencies = [
[[package]]
name = "owlry-lua"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"chrono",
@@ -2420,7 +2420,7 @@ dependencies = [
[[package]]
name = "owlry-plugin-api"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"serde",
@@ -2428,7 +2428,7 @@ dependencies = [
[[package]]
name = "owlry-plugin-bookmarks"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"dirs",
@@ -2440,7 +2440,7 @@ dependencies = [
[[package]]
name = "owlry-plugin-calculator"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"meval",
@@ -2449,7 +2449,7 @@ dependencies = [
[[package]]
name = "owlry-plugin-clipboard"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"owlry-plugin-api",
@@ -2457,7 +2457,7 @@ dependencies = [
[[package]]
name = "owlry-plugin-emoji"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"owlry-plugin-api",
@@ -2465,7 +2465,7 @@ dependencies = [
[[package]]
name = "owlry-plugin-filesearch"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"dirs",
@@ -2474,7 +2474,7 @@ dependencies = [
[[package]]
name = "owlry-plugin-media"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"owlry-plugin-api",
@@ -2482,7 +2482,7 @@ dependencies = [
[[package]]
name = "owlry-plugin-pomodoro"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"dirs",
@@ -2494,7 +2494,7 @@ dependencies = [
[[package]]
name = "owlry-plugin-scripts"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"dirs",
@@ -2503,7 +2503,7 @@ dependencies = [
[[package]]
name = "owlry-plugin-ssh"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"dirs",
@@ -2512,7 +2512,7 @@ dependencies = [
[[package]]
name = "owlry-plugin-system"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"owlry-plugin-api",
@@ -2520,7 +2520,7 @@ dependencies = [
[[package]]
name = "owlry-plugin-systemd"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"owlry-plugin-api",
@@ -2528,7 +2528,7 @@ dependencies = [
[[package]]
name = "owlry-plugin-weather"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"dirs",
@@ -2541,7 +2541,7 @@ dependencies = [
[[package]]
name = "owlry-plugin-websearch"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"abi_stable",
"owlry-plugin-api",
@@ -2549,7 +2549,7 @@ dependencies = [
[[package]]
name = "owlry-rune"
version = "0.4.6"
version = "0.4.7"
dependencies = [
"chrono",
"dirs",

View File

@@ -99,12 +99,40 @@ sudo cp target/release/libowlry_plugin_*.so /usr/lib/owlry/plugins/
## Usage
```bash
owlry # Launch with defaults
owlry --mode app # Applications only
owlry --providers app,cmd # Specific providers
owlry --help # Show all options
owlry # Launch with all providers
owlry -m app # Applications only
owlry -m cmd # PATH commands only
owlry -p app,cmd # Multiple specific providers
owlry -m calc # Calculator plugin only (if installed)
owlry --help # Show all options with examples
```
### dmenu Mode
Owlry is dmenu-compatible. Pipe input for interactive selection:
```bash
# Basic selection
echo -e "Option A\nOption B\nOption C" | owlry -m dmenu
# Select from files
ls ~/Documents | owlry -m dmenu
# Git branch checkout
git branch | owlry -m dmenu --prompt "checkout:" | xargs git checkout
# Kill a process
ps -eo comm | sort -u | owlry -m dmenu --prompt "kill:" | xargs pkill
# Select and open a project
find ~/projects -maxdepth 1 -type d | owlry -m dmenu | xargs code
# Package manager search
pacman -Ssq | owlry -m dmenu --prompt "install:" | xargs sudo pacman -S
```
The `--prompt` flag sets a custom label for the search input.
### Keyboard Shortcuts
| Key | Action |
@@ -221,6 +249,38 @@ Add plugin IDs to the disabled list in your config:
disabled = ["emoji", "pomodoro"]
```
### Plugin Management CLI
```bash
# List installed plugins
owlry plugin list
owlry plugin list --enabled # Only enabled
owlry plugin list --available # Show registry plugins
# Search registry
owlry plugin search "weather"
# Install/remove
owlry plugin install <name> # From registry
owlry plugin install ./my-plugin # From local path
owlry plugin remove <name>
# Enable/disable
owlry plugin enable <name>
owlry plugin disable <name>
# Plugin info
owlry plugin info <name>
owlry plugin commands <name> # List plugin CLI commands
# Create new plugin
owlry plugin create my-plugin # Lua (default)
owlry plugin create my-plugin -r rune # Rune
# Run plugin command
owlry plugin run <plugin-id> <command> [args...]
```
### Creating Custom Plugins
See [docs/PLUGIN_DEVELOPMENT.md](docs/PLUGIN_DEVELOPMENT.md) for:

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-lua"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-api"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-bookmarks"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-calculator"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-clipboard"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-emoji"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-filesearch"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-media"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-pomodoro"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-scripts"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-ssh"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-system"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-systemd"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-weather"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-plugin-websearch"
version = "0.4.6"
version = "0.4.7"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry-rune"
version = "0.4.6"
version = "0.4.7"
edition = "2024"
rust-version = "1.90"
description = "Rune scripting runtime for owlry plugins"

View File

@@ -1,6 +1,6 @@
[package]
name = "owlry"
version = "0.4.6"
version = "0.4.7"
edition = "2024"
rust-version = "1.90"
description = "A lightweight, owl-themed application launcher for Wayland"

View File

@@ -10,19 +10,55 @@ use crate::providers::ProviderType;
#[command(
name = "owlry",
about = "An owl-themed application launcher for Wayland",
version
long_about = "An owl-themed application launcher for Wayland, built with GTK4 and Layer Shell.\n\n\
Owlry provides fuzzy search across applications, commands, and plugins.\n\
Native plugins add features like calculator, clipboard, emoji, weather, and more.",
version,
after_help = "\
EXAMPLES:
owlry Launch with all providers
owlry -m app Applications only
owlry -m cmd PATH commands only
owlry -m dmenu dmenu-compatible mode (reads from stdin)
owlry -p app,cmd Multiple providers
owlry -m calc Calculator plugin only (if installed)
DMENU MODE:
Pipe input to owlry for interactive selection:
echo -e \"Option A\\nOption B\" | owlry -m dmenu
ls | owlry -m dmenu
git branch | owlry -m dmenu --prompt \"checkout:\"
SEARCH PREFIXES:
:app firefox Search applications
:cmd git Search PATH commands
= 5+3 Calculator (requires plugin)
? rust docs Web search (requires plugin)
/ .bashrc File search (requires plugin)
For configuration, see ~/.config/owlry/config.toml
For plugin management, see: owlry plugin --help"
)]
pub struct CliArgs {
/// Start in single-provider mode (app, cmd, uuctl)
#[arg(long, short = 'm', value_parser = parse_provider)]
/// Start in single-provider mode
///
/// Core modes: app, cmd, dmenu
/// Plugin modes: calc, clip, emoji, ssh, sys, bm, file, web, uuctl, weather, media, pomodoro
#[arg(long, short = 'm', value_parser = parse_provider, value_name = "MODE")]
pub mode: Option<ProviderType>,
/// Comma-separated list of enabled providers (app,cmd,uuctl)
#[arg(long, short = 'p', value_delimiter = ',', value_parser = parse_provider)]
/// Comma-separated list of enabled providers
///
/// Examples: -p app,cmd or -p app,calc,emoji
#[arg(long, short = 'p', value_delimiter = ',', value_parser = parse_provider, value_name = "PROVIDERS")]
pub providers: Option<Vec<ProviderType>>,
/// Custom prompt text for the search input (useful for dmenu mode)
#[arg(long)]
/// Custom prompt text for the search input
///
/// Useful in dmenu mode to indicate what the user is selecting.
/// Example: --prompt "Select file:"
#[arg(long, value_name = "TEXT")]
pub prompt: Option<String>,
/// Subcommand to run (if any)

View File

@@ -95,8 +95,10 @@ pub trait Provider: Send {
/// Manages all providers and handles searching
pub struct ProviderManager {
/// Static providers (apps, commands, and native static plugins)
/// Core static providers (apps, commands, dmenu)
providers: Vec<Box<dyn Provider>>,
/// Static native plugin providers (need query() for submenu support)
static_native_providers: Vec<NativeProvider>,
/// Dynamic providers from native plugins (calculator, websearch, filesearch)
/// These are queried per-keystroke, not cached
dynamic_providers: Vec<NativeProvider>,
@@ -118,6 +120,7 @@ impl ProviderManager {
pub fn with_native_plugins(native_providers: Vec<NativeProvider>) -> Self {
let mut manager = Self {
providers: Vec::new(),
static_native_providers: Vec::new(),
dynamic_providers: Vec::new(),
widget_providers: Vec::new(),
matcher: SkimMatcherV2::default(),
@@ -149,9 +152,9 @@ impl ProviderManager {
info!("Registered widget provider: {} ({})", provider.name(), type_id);
manager.widget_providers.push(provider);
} else {
// Static providers with Normal position
// Static native providers (keep as NativeProvider for query/submenu support)
info!("Registered static provider: {} ({})", provider.name(), type_id);
manager.providers.push(Box::new(provider));
manager.static_native_providers.push(provider);
}
}
}
@@ -170,7 +173,7 @@ impl ProviderManager {
}
pub fn refresh_all(&mut self) {
// Refresh static providers (fast, local operations)
// Refresh core providers (apps, commands)
for provider in &mut self.providers {
provider.refresh();
info!(
@@ -180,6 +183,16 @@ impl ProviderManager {
);
}
// Refresh static native providers (clipboard, emoji, ssh, etc.)
for provider in &mut self.static_native_providers {
provider.refresh();
info!(
"Static provider '{}' loaded {} items",
provider.name(),
provider.items().len()
);
}
// Widget providers are refreshed separately to avoid blocking startup
// Call refresh_widgets() after window is shown
@@ -201,9 +214,13 @@ impl ProviderManager {
}
/// Find a native provider by type ID
/// Searches in widget providers and dynamic providers
/// Searches in all native provider lists (static, dynamic, widget)
pub fn find_native_provider(&self, type_id: &str) -> Option<&NativeProvider> {
// Check widget providers first (pomodoro, weather, media)
// Check static native providers first (clipboard, emoji, ssh, systemd, etc.)
if let Some(p) = self.static_native_providers.iter().find(|p| p.type_id() == type_id) {
return Some(p);
}
// Check widget providers (pomodoro, weather, media)
if let Some(p) = self.widget_providers.iter().find(|p| p.type_id() == type_id) {
return Some(p);
}
@@ -246,37 +263,40 @@ impl ProviderManager {
}
}
/// Iterate over all static provider items (core + native static plugins)
fn all_static_items(&self) -> impl Iterator<Item = &LaunchItem> {
self.providers
.iter()
.flat_map(|p| p.items().iter())
.chain(self.static_native_providers.iter().flat_map(|p| p.items().iter()))
}
#[allow(dead_code)]
pub fn search(&self, query: &str, max_results: usize) -> Vec<(LaunchItem, i64)> {
if query.is_empty() {
// Return recent/popular items when query is empty
return self.providers
.iter()
.flat_map(|p| p.items().iter().cloned())
return self.all_static_items()
.take(max_results)
.map(|item| (item, 0))
.map(|item| (item.clone(), 0))
.collect();
}
let mut results: Vec<(LaunchItem, i64)> = self.providers
.iter()
.flat_map(|provider| {
provider.items().iter().filter_map(|item| {
// Match against name and description
let name_score = self.matcher.fuzzy_match(&item.name, query);
let desc_score = item.description
.as_ref()
.and_then(|d| self.matcher.fuzzy_match(d, query));
let mut results: Vec<(LaunchItem, i64)> = self.all_static_items()
.filter_map(|item| {
// Match against name and description
let name_score = self.matcher.fuzzy_match(&item.name, query);
let desc_score = item.description
.as_ref()
.and_then(|d| self.matcher.fuzzy_match(d, query));
let score = match (name_score, desc_score) {
(Some(n), Some(d)) => Some(n.max(d)),
(Some(n), None) => Some(n),
(None, Some(d)) => Some(d / 2), // Lower weight for description matches
(None, None) => None,
};
let score = match (name_score, desc_score) {
(Some(n), Some(d)) => Some(n.max(d)),
(Some(n), None) => Some(n),
(None, Some(d)) => Some(d / 2), // Lower weight for description matches
(None, None) => None,
};
score.map(|s| (item.clone(), s))
})
score.map(|s| (item.clone(), s))
})
.collect();
@@ -293,38 +313,45 @@ impl ProviderManager {
max_results: usize,
filter: &crate::filter::ProviderFilter,
) -> Vec<(LaunchItem, i64)> {
// Collect items from core providers
let core_items = self
.providers
.iter()
.filter(|p| filter.is_active(p.provider_type()))
.flat_map(|p| p.items().iter().cloned());
// Collect items from static native providers
let native_items = self
.static_native_providers
.iter()
.filter(|p| filter.is_active(p.provider_type()))
.flat_map(|p| p.items().iter().cloned());
if query.is_empty() {
return self
.providers
.iter()
.filter(|p| filter.is_active(p.provider_type()))
.flat_map(|p| p.items().iter().cloned())
return core_items
.chain(native_items)
.take(max_results)
.map(|item| (item, 0))
.collect();
}
let mut results: Vec<(LaunchItem, i64)> = self
.providers
.iter()
.filter(|provider| filter.is_active(provider.provider_type()))
.flat_map(|provider| {
provider.items().iter().filter_map(|item| {
let name_score = self.matcher.fuzzy_match(&item.name, query);
let desc_score = item
.description
.as_ref()
.and_then(|d| self.matcher.fuzzy_match(d, query));
let mut results: Vec<(LaunchItem, i64)> = core_items
.chain(native_items)
.filter_map(|item| {
let name_score = self.matcher.fuzzy_match(&item.name, query);
let desc_score = item
.description
.as_ref()
.and_then(|d| self.matcher.fuzzy_match(d, query));
let score = match (name_score, desc_score) {
(Some(n), Some(d)) => Some(n.max(d)),
(Some(n), None) => Some(n),
(None, Some(d)) => Some(d / 2),
(None, None) => None,
};
let score = match (name_score, desc_score) {
(Some(n), Some(d)) => Some(n.max(d)),
(Some(n), None) => Some(n),
(None, Some(d)) => Some(d / 2),
(None, None) => None,
};
score.map(|s| (item.clone(), s))
})
score.map(|s| (item, s))
})
.collect();
@@ -384,11 +411,22 @@ impl ProviderManager {
// Empty query (after checking special providers) - return frecency-sorted items
if query.is_empty() {
let items: Vec<(LaunchItem, i64)> = self
// Collect items from core providers
let core_items = self
.providers
.iter()
.filter(|p| filter.is_active(p.provider_type()))
.flat_map(|p| p.items().iter().cloned())
.flat_map(|p| p.items().iter().cloned());
// Collect items from static native providers
let native_items = self
.static_native_providers
.iter()
.filter(|p| filter.is_active(p.provider_type()))
.flat_map(|p| p.items().iter().cloned());
let items: Vec<(LaunchItem, i64)> = core_items
.chain(native_items)
.filter(|item| {
// Apply tag filter if present
if let Some(tag) = tag_filter {
@@ -412,53 +450,70 @@ impl ProviderManager {
}
// Regular search with frecency boost and tag matching
let search_results: Vec<(LaunchItem, i64)> = self
.providers
.iter()
.filter(|provider| filter.is_active(provider.provider_type()))
.flat_map(|provider| {
provider.items().iter().filter_map(|item| {
// Apply tag filter if present
if let Some(tag) = tag_filter
&& !item.tags.iter().any(|t| t.to_lowercase().contains(tag)) {
return None;
}
// Helper closure for scoring items
let score_item = |item: &LaunchItem| -> Option<(LaunchItem, i64)> {
// Apply tag filter if present
if let Some(tag) = tag_filter
&& !item.tags.iter().any(|t| t.to_lowercase().contains(tag))
{
return None;
}
let name_score = self.matcher.fuzzy_match(&item.name, query);
let desc_score = item
.description
.as_ref()
.and_then(|d| self.matcher.fuzzy_match(d, query));
let name_score = self.matcher.fuzzy_match(&item.name, query);
let desc_score = item
.description
.as_ref()
.and_then(|d| self.matcher.fuzzy_match(d, query));
// Also match against tags (lower weight)
let tag_score = item
.tags
.iter()
.filter_map(|t| self.matcher.fuzzy_match(t, query))
.max()
.map(|s| s / 3); // Lower weight for tag matches
// Also match against tags (lower weight)
let tag_score = item
.tags
.iter()
.filter_map(|t| self.matcher.fuzzy_match(t, query))
.max()
.map(|s| s / 3); // Lower weight for tag matches
let base_score = match (name_score, desc_score, tag_score) {
(Some(n), Some(d), Some(t)) => Some(n.max(d).max(t)),
(Some(n), Some(d), None) => Some(n.max(d)),
(Some(n), None, Some(t)) => Some(n.max(t)),
(Some(n), None, None) => Some(n),
(None, Some(d), Some(t)) => Some((d / 2).max(t)),
(None, Some(d), None) => Some(d / 2),
(None, None, Some(t)) => Some(t),
(None, None, None) => None,
};
let base_score = match (name_score, desc_score, tag_score) {
(Some(n), Some(d), Some(t)) => Some(n.max(d).max(t)),
(Some(n), Some(d), None) => Some(n.max(d)),
(Some(n), None, Some(t)) => Some(n.max(t)),
(Some(n), None, None) => Some(n),
(None, Some(d), Some(t)) => Some((d / 2).max(t)),
(None, Some(d), None) => Some(d / 2),
(None, None, Some(t)) => Some(t),
(None, None, None) => None,
};
base_score.map(|s| {
let frecency_score = frecency.get_score(&item.id);
let frecency_boost = (frecency_score * frecency_weight * 10.0) as i64;
(item.clone(), s + frecency_boost)
})
})
base_score.map(|s| {
let frecency_score = frecency.get_score(&item.id);
let frecency_boost = (frecency_score * frecency_weight * 10.0) as i64;
(item.clone(), s + frecency_boost)
})
.collect();
};
results.extend(search_results);
// Search core providers
for provider in &self.providers {
if !filter.is_active(provider.provider_type()) {
continue;
}
for item in provider.items() {
if let Some(scored) = score_item(item) {
results.push(scored);
}
}
}
// Search static native providers
for provider in &self.static_native_providers {
if !filter.is_active(provider.provider_type()) {
continue;
}
for item in provider.items() {
if let Some(scored) = score_item(item) {
results.push(scored);
}
}
}
results.sort_by(|a, b| b.1.cmp(&a.1));
results.truncate(max_results);
@@ -479,7 +534,11 @@ impl ProviderManager {
/// Get all available provider types (for UI tabs)
#[allow(dead_code)]
pub fn available_providers(&self) -> Vec<ProviderType> {
self.providers.iter().map(|p| p.provider_type()).collect()
self.providers
.iter()
.map(|p| p.provider_type())
.chain(self.static_native_providers.iter().map(|p| p.provider_type()))
.collect()
}
/// Get a widget item by type_id (e.g., "pomodoro", "weather", "media")
@@ -519,6 +578,16 @@ impl ProviderManager {
plugin_id, submenu_query
);
// Search in static native providers (clipboard, emoji, ssh, systemd, etc.)
for provider in &self.static_native_providers {
if provider.type_id() == plugin_id {
let actions = provider.query(&submenu_query);
if !actions.is_empty() {
return Some((display_name.to_string(), actions));
}
}
}
// Search in dynamic providers
for provider in &self.dynamic_providers {
if provider.type_id() == plugin_id {
@@ -539,23 +608,6 @@ impl ProviderManager {
}
}
// Search in static providers (boxed)
// Note: Static providers don't typically have submenu support,
// but we check for completeness
for provider in &self.providers {
if let ProviderType::Plugin(type_id) = provider.provider_type()
&& type_id == plugin_id
{
// Static providers use the items() method, not query
// Submenu support requires dynamic query capability
#[cfg(feature = "dev-logging")]
debug!(
"[Submenu] Plugin '{}' is static, cannot query for submenu",
plugin_id
);
}
}
#[cfg(feature = "dev-logging")]
debug!("[Submenu] No submenu actions found for plugin '{}'", plugin_id);

View File

@@ -179,11 +179,18 @@ bump-meta new_version:
done
echo "Meta-packages bumped to {{new_version}}"
# Bump all non-core crates (plugins + runtimes) to same version
# Bump all crates (core + plugins + runtimes) to same version
bump-all new_version:
#!/usr/bin/env bash
set -euo pipefail
# Bump plugins
# Bump core
toml="crates/owlry/Cargo.toml"
old=$(grep '^version' "$toml" | head -1 | sed 's/.*"\(.*\)"/\1/')
if [ "$old" != "{{new_version}}" ]; then
echo "Bumping owlry from $old to {{new_version}}"
sed -i 's/^version = ".*"/version = "{{new_version}}"/' "$toml"
fi
# Bump plugins (including plugin-api)
for toml in crates/owlry-plugin-*/Cargo.toml; do
crate=$(basename $(dirname "$toml"))
old=$(grep '^version' "$toml" | head -1 | sed 's/.*"\(.*\)"/\1/')
@@ -204,9 +211,9 @@ bump-all new_version:
fi
done
cargo check --workspace
git add crates/owlry-plugin-*/Cargo.toml crates/owlry-lua/Cargo.toml crates/owlry-rune/Cargo.toml Cargo.lock
git commit -m "chore: bump all plugins and runtimes to {{new_version}}"
echo "All plugins and runtimes bumped to {{new_version}}"
git add crates/*/Cargo.toml Cargo.lock
git commit -m "chore: bump all crates to {{new_version}}"
echo "All crates bumped to {{new_version}}"
# Bump core version (usage: just bump 0.2.0)
bump new_version: