Shizcow/dmenu-rs

Example for plugin reading from stding or subcommand

carrascomj opened this issue · 3 comments

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:

  • dmenu --lookup --where=D;
  • "D" | dmenu --lookup;
  • or maybe dmenu --lookup=D

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)
    }
}

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:

  • echo D | dmenu --lookup to search DDG
  • dmenu --lookup --engine D for the same. I changed where to engine, as where is a rust keyword

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.

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

Closed by #36