Re: [dev] [wmii] wimenu custom completion

From: Kris Maglione <maglione.k_AT_gmail.com>
Date: Thu, 12 Aug 2010 21:19:52 -0400

On Thu, Aug 12, 2010 at 10:51:59PM +0200, LuX wrote:
>I would really like to use the 'custom completion' feature documented
>in the man page of wimenu-3.9.2, just as in the provided example:
>
>Unfortunately the example script given there goes far beyond my
>skills. I have copied and pasted it in a file ~/wim. Then 'sh wim'
>does exactly what is expected (fortunately enough I didn't have any
>important file called 'fifo' in my home directory ;)) except that once
>I have selected something, for example 'foo 1', and pressed Return, I
>would like to recover the string 'foo 1' in order to do something with
>it. Instead of this wim quits silently and I haven't found a way to
>recover the selected string.
>
>Any hint?

Yes... Unfortunately awk dies on SIGPIPE in that example, so it
never runs the END block. This slight variation should work:

     wimenu -c <fifo | awk '
         BEGIN {
              # Define the completion results
              cmds = "foo\nbar\nbaz\n"
              cmd["foo"] = "1\n2\n3\n"
              cmd["bar"] = "4\n5\n6\n"
              cmd["baz"] = "7\n8\n9\n"
     
              # Print the first set of completions to wimenu’s fifo
              fifo = "fifo"
              print cmds >fifo; fflush(fifo)
         }
     
         //
     
         # Push out a new set of completions
         function update(str, opts) {
              print length(str) >fifo # Print the length of the preceding string
              print opts >fifo # and the options themself
              fflush(fifo)
         }
     
         # Ensure correct argument count with trailing spaces
         / $/ { $0 = $0 "#"; }
     
         { # Process the input and provide the completions
              if (NF == 1)
                   update("", cmds) # The first arg, command choices
              else
                   update($1 " ", cmd[$1]) # The second arg, command arguments
              # Skip the trailing part of the command
              getline rest
         }
     ' | tail -1

As for modifying it, you'd probably be happier with python,
perl, or ruby, but I make a point of only using POSIX utilities
in examples. But if you're looking for a start, this is the
above modified to (crudely) complete a command and then files in
the current directory:

     script=$(cat <<'!'
         # Push out a new set of completions
         function update(offset, cmd) {
             # Only push out the completion if the offset has
             # changed. The behavior will be the same regardless,
             # but this is a minor optimization
             if (offset != loffset) {
                 loffset = offset

                 print offset >fifo
                 print read(cmd) >fifo
                 fflush(fifo)
             }
         }

         # Quote a string. This requires that your shell supports
         # rc quoting syntax (zsh with 'setopt rcquotes' or rc, namely)
         function quote(str) {
             if (!match(str, /[\[\](){}$'"^#~!&;*?|<>]/))
                 return str
             gsub(/'/, "''", str)
             return "'" str "'"
         }

         # Read the output of a command and return it
         function read(cmd) {
             # We need to use a cache since every use of an
             # input command refers to the same process, which
             # means it can only be run once.
             if (cmd in cache)
                 return cache[cmd]

             res = ""
             while (cmd | getline)
                 res = res quote($0) "\n"
             return cache[cmd] = res
         }

         BEGIN {
             fifo = "fifo"
             progs = ". wmii.sh; wi_proglist $PATH"

             # Print the first set of completions to wimenu’s fifo
             print read(progs) >fifo
             fflush(fifo)
         }

         // # Print the line

         # Process the input and provide the completions
         {
             if (!match($0, /.*[ \t]/))
                 # First argument, provide the program list
                 update(0, progs)
             else {
                 # Set the offset to the location of the last
                 # space, and save that part of the completion
                 offset = RLENGTH # Set by match() above
                 str = substr($0, offset + 1)

                 # If we're completing a sub-directory, adjust
                 # the offset to the position of the last /
                 if (match(str, ".*/"))
                     offset += RLENGTH

                 cmd = "ls " quote(str)
                 if (str && !match(cmd, "/$"))
                     # Use dirname to chop off the end of the
                     # last directory component
                     cmd = "ls \"$(dirname " quote(str) ")\""

                 update(offset, cmd)
             }

             # Skip the trailing part of the command
             getline rest
         }
     !
     )
     wimenu -c <fifo | awk "$script" | tail -1

Although that may confuse you more than help. I've been told
that awk is a fairly arcane language.

Ideally, it should be fairly simple to use a simple bash script
to provide completions based on its programmable completion
system, but I've never gotten far enough into its internals to
make it work. If you'd like to try, though, I'm sure that
someone in #bash could help.

>By the way, is it intended to add to dmenu a similar feature (and also
>the -S option of wimenu)? I'm asking this because at my office wmii is
>not available (there is no wmii package in fedora) thus I'm using i3
>with dmenu instead. On the other hand the vertical mode of dmenu is
>much more comfortable than the horizontal mode of wimenu on the small
>screen of my laptop…

I can't speak for the dmenu developers, but new features are
usually added only after someone's written a patch and it
becomes popular. You might be able to find an rpm somewhere. I
would provide one, but I hate rpm with a passion. Personally, I
just install wmii to my home directory.

     make PREFIX=$HOME/wmiinst install

and adding ~/wmiinst/bin to $PATH does the trick. If you don't
have a compiler available, you should be able to copy the tree
from any system, if you 'export LDFLAGS=-static' when you build.

I might add a horizontal mode to wimenu. I'm personally not very
fond of it, but others seem to find it useful and it's a simple
enough change.

-- 
Kris Maglione
The object-oriented model makes it easy to build up programs by
accretion.  What this often means, in practice, is that it provides a
structured way to write spaghetti code.
	--Paul Graham
Received on Fri Aug 13 2010 - 03:19:52 CEST

This archive was generated by hypermail 2.2.0 : Fri Aug 13 2010 - 03:24:02 CEST