Shizcow / dmenu-rs

A pixel perfect port of dmenu, rewritten in Rust with extensive plugin support
GNU General Public License v3.0
197 stars 9 forks source link

Example for plugin reading from stding or subcommand #35

Closed carrascomj closed 3 years ago

carrascomj commented 3 years ago

Hello!

I am writing a plugin that emulates promptSearch from Xmonad's prompt. Basically, the user can configure urls (say duckduckgo or the rust std documentation) to look up to then bind a key for each of the urls.

I have it implemented for duckduckgo, but I'd like to read from stdin or a subcommand to change between different urls. For instance:

would translate to https://duckduckgo.com/ + dmenu's prompt input.

The problem is that I am struggling to understand how to read stdin (or to add a subcommand). When I set a format_stdin function and set nostdin to false, it gets stuck.This is what I have working for now:

use overrider::*;
use std::io::Write;
use std::process::Command;

use crate::drw::Drw;
use crate::result::*;

#[override_flag(flag = lookup)]
impl Drw {
    // I know I need to implement format_stdin and put the logic to select the url there,
    // but I don't know how to access it on dispose
    pub fn format_input(&self) -> CompResult<String> {
    Ok(format!("[Search DDG]: {}",self.input))
    }
    pub fn dispose(&mut self, _output: String, recommendation: bool) -> CompResult<bool> {
    let eval = format!("https://duckduckgo.com/{}", self.input);
    self.input = "".to_owned();
    self.pseudo_globals.cursor = 0;
    if eval.len() > 0 {
        let mut child = Command::new("xdg-open")
        .arg(eval.clone())
        .spawn()
        .map_err(|_| Die::Stderr("Failed to spawn child process".to_owned()))?;

        child.stdin.as_mut().ok_or(Die::Stderr("Failed to open stdin of child process"
                           .to_owned()))?
        .write_all(eval.as_bytes())
        .map_err(|_| Die::Stderr("Failed to write to stdin of child process"
                     .to_owned()))?;
    }
    self.draw()?;
    Ok(!recommendation)
    }
}

use crate::config::{ConfigDefault, DefaultWidth};
#[override_flag(flag = lookup)]
impl ConfigDefault {
    pub fn nostdin() -> bool {
        // setting this to false makes it get stuck
    true
    }
    pub fn render_flex() -> bool {
    true
    }
    pub fn render_default_width() -> DefaultWidth {
    DefaultWidth::Custom(25)
    }
}
Shizcow commented 3 years ago

Hey there, glad to see you interested in this project. If you get this plugin working well, totally submit a PR! I'd actually use this plugin as a daily driver.

As for the solution, I've made a slight edit to the source code -- turns out there was some unintended behavior with format_stdin. It doesn't break anything, but I feel like it should run whether nostdin is selected or not. This fix is on the develop branch and is needed for the solution below, so I recommend testing from there.

Here is a bare bones setup that should help you figure out what to do:

# plugin.yml
about: |
    DESCRIPTION
entry: main.rs

args:
  - lookup:
      help: DESCRIPTION
      long: lookup
  - engine: 
      help: DESCRIPTION
      long: engine
      takes_value: true
      requires: lookup

The above provides the following functionality:

This works with the following main.rs in the plugin directory. The important part is having two overrides, one for --lookup and one for --engine:

use overrider::*;
use crate::clapflags::CLAP_FLAGS;

use crate::drw::Drw;
use crate::result::*;
use crate::config::{ConfigDefault};

// Turns short description into a prompt
// eg "D" -> "[Search DDG]"
fn create_search_input(engine: &str) -> CompResult<String> {
    Ok(format!("[Search {}]", match engine {
    "D" => "DDG",
    // add more engines here if you want
    _ => return Err(Die::Stderr("invalid engine".to_string()))
    }))
}

// Takes the output of create_search_input as prompt
// It's not very clean but hey it works
fn do_dispose(output: &str, prompt: &str) -> CompResult<()> {
    // Extract "ENGINE_LONG" from "[Search ENGINE_LONG]"
    let mut engine: String = prompt.chars().skip("[Search ".len()).collect();
    engine.pop();

    println!("engine: {}, searchterm: {}", engine, output);

    // xdg logic goes here

    Ok(())
}

// Important: engine must become before lookup. It's a bug in overrider.
#[override_flag(flag = engine, priority = 2)]
impl Drw {
    pub fn dispose(&mut self, output: String, recommendation: bool) -> CompResult<bool> {
    do_dispose(&output, &self.config.prompt)?;
    Ok(recommendation)
    }
    pub fn format_stdin(&mut self, _lines: Vec<String>) -> CompResult<Vec<String>> {
    self.config.prompt = create_search_input(CLAP_FLAGS.value_of("engine").unwrap())?;
    Ok(vec![]) // turns into prompt
    }
}

#[override_flag(flag = engine, priority = 2)]
impl ConfigDefault {
    pub fn nostdin() -> bool {
    true // if called with --engine ENGINE, takes no stdin
    }
}

#[override_flag(flag = lookup, priority = 1)]
impl Drw {
    pub fn dispose(&mut self, output: String, recommendation: bool) -> CompResult<bool> {
    do_dispose(&output, &self.config.prompt)?;
    Ok(recommendation)
    }
    pub fn format_stdin(&mut self, lines: Vec<String>) -> CompResult<Vec<String>> {
    self.config.prompt = create_search_input(&lines[0])?;
    Ok(vec![]) // turns into prompt
    }
}

#[override_flag(flag = lookup, priority = 1)]
impl ConfigDefault {
    pub fn nostdin() -> bool {
    false // if called without --engine, takes stdin
    }
}

If I understand your problem, that should be everything you need to get this plugin up and running. Let me know if you run into any other issues.

carrascomj commented 3 years ago

Thank you so much! I'll give it a try

Shizcow commented 3 years ago

Closed by #36