add text output mode

solves #104
This commit is contained in:
Alexander Mohr 2025-08-20 22:06:09 +02:00
parent 251fcfd518
commit a5ae9785ed
6 changed files with 95 additions and 23 deletions

View file

@ -151,7 +151,7 @@ The command line options have precedence over the configuration file.
| matching | MatchMethod | Contains | Defines the matching method | | matching | MatchMethod | Contains | Defines the matching method |
| insensitive | bool | true | Control if search is case-insensitive | | insensitive | bool | true | Control if search is case-insensitive |
| parse_search | bool | None | Parse search option | | parse_search | bool | None | Parse search option |
| location | [Anchor] | None | Set where the window is displayed | | location | Anchor | None | Set where the window is displayed |
| no_actions | bool | false | If true, sub actions will be disabled | | no_actions | bool | false | If true, sub actions will be disabled |
| lines | int | None | Number of lines to show | | lines | int | None | Number of lines to show |
| lines_additional_space | int | 0 | Additional space for lines | | lines_additional_space | int | 0 | Additional space for lines |
@ -191,6 +191,7 @@ The command line options have precedence over the configuration file.
| submit_with_expand | bool | true | Allow submit with expand key | | submit_with_expand | bool | true | Allow submit with expand key |
| auto_select_on_search | bool | false | Auto select when only 1 choice left | | auto_select_on_search | bool | false | Auto select when only 1 choice left |
| rollover | bool | true | Jump to first/last entry at end/start | | rollover | bool | true | Jump to first/last entry at end/start |
| text_output_mode | TextOutputMode | Clipboard | Output for text modes (i.e. math and emoji) |
### Enum Values ### Enum Values
- **MatchMethod**: Fuzzy, Contains, MultiContains, None - **MatchMethod**: Fuzzy, Contains, MultiContains, None

View file

@ -10,7 +10,7 @@ use std::{
use clap::Parser; use clap::Parser;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use worf::{ use worf::{
config::{self, Config, CustomKeyHintLocation, Key}, config::{self, Config, CustomKeyHintLocation, Key, TextOutputMode},
desktop::{copy_to_clipboard, spawn_fork}, desktop::{copy_to_clipboard, spawn_fork},
gui::{ gui::{
self, CustomKeyHint, CustomKeys, ExpandMode, ItemProvider, KeyBinding, MenuItem, Modifier, self, CustomKeyHint, CustomKeys, ExpandMode, ItemProvider, KeyBinding, MenuItem, Modifier,
@ -416,8 +416,16 @@ fn show(
} }
} else { } else {
let pw = rbw_get_password(id, true)?; let pw = rbw_get_password(id, true)?;
if let Err(e) = copy_to_clipboard(pw, None) { match config.read().unwrap().text_output_mode() {
log::error!("failed to copy to clipboard: {e}"); TextOutputMode::Clipboard => {
if let Err(e) = copy_to_clipboard(pw, None) {
log::error!("failed to copy to clipboard: {e}");
}
}
TextOutputMode::StandardOutput => {
println!("{pw}");
}
TextOutputMode::None => {}
} }
} }
Ok(()) Ok(())

View file

@ -70,15 +70,22 @@ pub enum Layer {
Overlay, Overlay,
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum TextOutputMode {
None,
Clipboard,
StandardOutput,
}
impl FromStr for Layer { impl FromStr for Layer {
type Err = String; type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s.trim().to_lowercase().as_str() {
"Background" => Ok(Layer::Background), "background" => Ok(Layer::Background),
"Bottom" => Ok(Layer::Bottom), "bottom" => Ok(Layer::Bottom),
"Top" => Ok(Layer::Top), "top" => Ok(Layer::Top),
"Overlay" => Ok(Layer::Overlay), "overlay" => Ok(Layer::Overlay),
_ => Err(format!("{s} is not a valid layer.")), _ => Err(format!("{s} is not a valid layer.")),
} }
} }
@ -88,7 +95,7 @@ impl FromStr for Anchor {
type Err = String; type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim() { match s.trim().to_lowercase().as_str() {
"top" => Ok(Anchor::Top), "top" => Ok(Anchor::Top),
"left" => Ok(Anchor::Left), "left" => Ok(Anchor::Left),
"bottom" => Ok(Anchor::Bottom), "bottom" => Ok(Anchor::Bottom),
@ -102,7 +109,7 @@ impl FromStr for WrapMode {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s.trim().to_lowercase().as_str() {
"none" => Ok(WrapMode::None), "none" => Ok(WrapMode::None),
"word" => Ok(WrapMode::Word), "word" => Ok(WrapMode::Word),
"inherit" => Ok(WrapMode::Inherit), "inherit" => Ok(WrapMode::Inherit),
@ -117,7 +124,7 @@ impl FromStr for SortOrder {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s.trim().to_lowercase().as_str() {
"alphabetical" => Ok(SortOrder::Alphabetical), "alphabetical" => Ok(SortOrder::Alphabetical),
"default" => Ok(SortOrder::Default), "default" => Ok(SortOrder::Default),
_ => Err(Error::InvalidArgument( _ => Err(Error::InvalidArgument(
@ -131,7 +138,7 @@ impl FromStr for KeyDetectionType {
type Err = Error; type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s.trim().to_lowercase().as_str() {
"value" => Ok(KeyDetectionType::Value), "value" => Ok(KeyDetectionType::Value),
"code" => Ok(KeyDetectionType::Code), "code" => Ok(KeyDetectionType::Code),
_ => Err(Error::InvalidArgument( _ => Err(Error::InvalidArgument(
@ -141,6 +148,19 @@ impl FromStr for KeyDetectionType {
} }
} }
impl FromStr for TextOutputMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_lowercase().as_str() {
"clipboard" => Ok(TextOutputMode::Clipboard),
"stdout" | "standardoutput" => Ok(TextOutputMode::StandardOutput),
"none" => Ok(TextOutputMode::None),
_ => Err(format!("{s} is not a valid layer.")),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
pub enum Key { pub enum Key {
None, None,
@ -632,7 +652,18 @@ pub struct Config {
/// Jump to the first/last entry when at the end/start and down/up is pressed /// Jump to the first/last entry when at the end/start and down/up is pressed
/// Defaults to true /// Defaults to true
#[clap(long = "rollover")]
rollover: Option<bool>, rollover: Option<bool>,
/// For text modes, defines which output is used.
/// This is per default used in math and emoji mode.
/// Defaults to `Clipboard`
/// For emoji mode, setting this to None will disable the text output
/// so the mode isn't too useful anymore.
/// For math mode, setting this to None will provide no output but keep running
/// math mode in a loop. Other modes will exit and provide results on selected output.
#[clap(long = "text-output-mode")]
text_output_mode: Option<TextOutputMode>,
} }
impl Config { impl Config {
@ -927,6 +958,13 @@ impl Config {
pub fn rollover(&self) -> bool { pub fn rollover(&self) -> bool {
self.rollover.unwrap_or(true) self.rollover.unwrap_or(true)
} }
#[must_use]
pub fn text_output_mode(&self) -> TextOutputMode {
self.text_output_mode
.clone()
.unwrap_or(TextOutputMode::Clipboard)
}
} }
fn default_false() -> bool { fn default_false() -> bool {

View file

@ -2,7 +2,7 @@ use std::sync::{Arc, Mutex, RwLock};
use crate::{ use crate::{
Error, Error,
config::{Config, SortOrder}, config::{Config, SortOrder, TextOutputMode},
desktop::copy_to_clipboard, desktop::copy_to_clipboard,
gui::{self, ExpandMode, ItemProvider, MenuItem, ProviderData}, gui::{self, ExpandMode, ItemProvider, MenuItem, ProviderData},
}; };
@ -75,6 +75,16 @@ pub fn show(config: &Arc<RwLock<Config>>) -> Result<(), Error> {
let selection_result = gui::show(config, provider, None, None, ExpandMode::Verbatim, None)?; let selection_result = gui::show(config, provider, None, None, ExpandMode::Verbatim, None)?;
match selection_result.menu.data { match selection_result.menu.data {
None => Err(Error::MissingAction), None => Err(Error::MissingAction),
Some(action) => copy_to_clipboard(action, None), Some(action) => match config.read().unwrap().text_output_mode() {
TextOutputMode::Clipboard => {
copy_to_clipboard(action, None)?;
Ok(())
}
TextOutputMode::StandardOutput => {
println!("{action}");
Ok(())
}
TextOutputMode::None => Ok(()),
},
} }
} }

View file

@ -6,7 +6,8 @@ use std::{
use regex::Regex; use regex::Regex;
use crate::{ use crate::{
config::Config, Error,
config::{Config, TextOutputMode},
gui::{ gui::{
self, ArcFactory, ArcProvider, DefaultItemFactory, ExpandMode, ItemProvider, MenuItem, self, ArcFactory, ArcProvider, DefaultItemFactory, ExpandMode, ItemProvider, MenuItem,
ProviderData, ProviderData,
@ -346,7 +347,11 @@ fn calc(input: &str) -> String {
/// Shows the math mode /// Shows the math mode
/// # Panics /// # Panics
/// When failing to unwrap the arc lock /// When failing to unwrap the arc lock
pub fn show(config: &Arc<RwLock<Config>>) { ///
/// # Errors
/// Forwards the errors from `crate::desktop::copy_to_clipboard`
/// if the text output mode is set to `Clipboard`.
pub fn show(config: &Arc<RwLock<Config>>) -> Result<(), Error> {
let mut calc: Vec<MenuItem<()>> = vec![]; let mut calc: Vec<MenuItem<()>> = vec![];
let provider = Arc::new(Mutex::new(MathProvider::new(()))); let provider = Arc::new(Mutex::new(MathProvider::new(())));
let factory: ArcFactory<()> = Arc::new(Mutex::new(DefaultItemFactory::new())); let factory: ArcFactory<()> = Arc::new(Mutex::new(DefaultItemFactory::new()));
@ -361,11 +366,24 @@ pub fn show(config: &Arc<RwLock<Config>>) {
ExpandMode::Verbatim, ExpandMode::Verbatim,
None, None,
); );
if let Ok(mi) = selection_result { if let Ok(mi) = selection_result {
calc.push(mi.menu); match config.read().unwrap().text_output_mode() {
TextOutputMode::Clipboard => {
crate::desktop::copy_to_clipboard(mi.menu.label, None)?;
break;
}
TextOutputMode::StandardOutput => {
println!("{}", mi.menu.label);
break;
}
TextOutputMode::None => calc.push(mi.menu),
}
} else { } else {
log::error!("No item selected"); log::error!("No item selected");
break; break;
} }
} }
Ok(())
} }

View file

@ -44,7 +44,7 @@ pub enum Mode {
)] )]
struct MainConfig { struct MainConfig {
/// Defines the mode worf is running in /// Defines the mode worf is running in
#[clap(long = "show")] #[clap(long = "show", alias = "mode")]
show: Mode, show: Mode,
#[command(flatten)] #[command(flatten)]
@ -124,10 +124,7 @@ fn main() {
Mode::Drun => modes::drun::show(&cfg_arc), Mode::Drun => modes::drun::show(&cfg_arc),
Mode::Dmenu => modes::dmenu::show(&cfg_arc), Mode::Dmenu => modes::dmenu::show(&cfg_arc),
Mode::File => modes::file::show(&cfg_arc), Mode::File => modes::file::show(&cfg_arc),
Mode::Math => { Mode::Math => modes::math::show(&cfg_arc),
modes::math::show(&cfg_arc);
Ok(())
}
Mode::Ssh => modes::ssh::show(&cfg_arc), Mode::Ssh => modes::ssh::show(&cfg_arc),
Mode::Emoji => modes::emoji::show(&cfg_arc), Mode::Emoji => modes::emoji::show(&cfg_arc),
Mode::Auto => modes::auto::show(&cfg_arc), Mode::Auto => modes::auto::show(&cfg_arc),