Re: [dev] [wmii] wimenu custom completion

From: Kris Maglione <maglione.k_AT_gmail.com>
Date: Mon, 16 Aug 2010 21:22:18 -0400

On Tue, Aug 17, 2010 at 02:15:13AM +0200, LuX wrote:
>On Sun, 15 Aug 2010 18:51:55 -0400, Kris Maglione wrote:
>> Yes, I already fixed that problem when I made it into an example
>> file for distribution. Attached.
>
>I have a few unessential remarks, if you allow me:

Of course.

>- Although I don't understand this, it seems that the last 'tail -1' is
> no longer needed.

Ah, you're right. I'd intended to remove that. Fortunately this
changeset hasn't left my patch queue yet. :)

>- Since you replaced the call to dirname by a 'sub' command just after
> the comment '# Strip the trailing filename', it happens that when I
> type 'ls /' in the input area, it is the content of the $HOME
> directory instead of / which is displayed by wimenu. Here is a fix:
>
> # Strip the trailing filename
> if(match(str, "^/[^/]*$"))
> str = "/" str
> sub("(/|^)[^/]*$", "", str)

This would be better:

     sub("[^/]+$", "", str)

I'll update the script.

>- I have read somewhere that /dev/shm was a good place to put files
> used by programs to discuss one with each others. This would apply
> to "fifo" in this case. I don't know if you have an opinion on this.

That would not help in this case. For one thing, /dev/shm is
very system specific. Only certain Linux distributions support
it. For another, its value is in that it's a memory filesystem,
so file contents are stored in RAM rather than on disk. But our
file is a FIFO, anyway. Its contents never touch the disk. It
would probably be better in ~/.wmii/menu_fifo or the like,
though.

>- I liked more the previous behaviour, when the script ends by sending
> the input string to stdout (like wimenu does) so that it can can be
> processed by some independent script like for example this one:

I think that you're right in principle. However, I'm going to
leave the example as is because it illustrates the purpose of
the script better and as it's fully self-contained, it works
without modification.

>In addition I enjoyed adding another minor feature: when a list of
>options has been declared in the script for the initial command of the
>input line, every time a '-' is typed at the beginning of a new
>argument in a line starting with that command, this list of options is
>displayed instead of a list of files. I find it convenient for some
>commands, like 'lp' or 'pdfnup' which accept many options, useful to
>me but that I do not use to remember.

I think this is a good idea, and it was what my help file
example was meant to suggest. I'd suggest some slight changes,
though:

--- menu_thing 2010-08-16 20:32:26.000000000 -0400
+++ - 2010-08-16 20:49:19.051116173 -0400
@@ -5,18 +5,18 @@
  # Program name completion requires that a program list already
  # exist in $(wmiir namespace)/.proglist
  
-fifo="/dev/shm/wim_$USER"
+fifo=$HOME/.wmii/menu_fifo
  mkfifo $fifo 2>/dev/null
  
  script=$(cat <<'!'
      BEGIN {
- progs = "cat $(wmiir namespace)/.proglist"
+ progs = read("cat $(wmiir namespace)/.proglist")
          
          # Favorite options for some programs
          opt["lp"] = "-o media=a4\n-o landscape\n-o sides=two-sided-long-edge\nsides=two-sided-short-edge\n-o number-up=N\n"
  
          # Print the first set of completions to wimenu’s fifo
- print read(progs) >fifo
+ print progs >fifo
          fflush(fifo)
      }
  
@@ -25,13 +25,17 @@
          # Skip the trailing part of the command.
          # If there is none, this is the result.
          if (!getline rest) {
- print
- exit
+ print
+ exit
          }
  
          if (!match($0, /.*[ \t]/))
              # First argument, provide the program list
- update(0, progs)
+ update(0, "", progs)
+ else if($NF ~ /^-/)
+ # If the last argument starts with a -, list
+ # options declared in opt instead of files
+ update(RLENGTH, "", opt[$1])
          else {
              # Set the offset to the location of the last
              # space, and save that part of the completion
@@ -46,31 +50,21 @@
              # If the last component of the path begins with
              # a ., include hidden files
              arg = ""
- if(match(str, "(^|/)\\.[^/]*$"))
- arg = "-A"
+ if(str ~ "(^|/)\\.[^/]*$")
+ arg = "-A "
  
              # Substitute ~/ for $HOME/
              sub("^~/", ENVIRON["HOME"] "/", str)
  
              # Strip the trailing filename
- if(match(str, "^/[^/]*$"))
- str = "/" str
- sub("(/|^)[^/]*$", "", str)
-
- # If the last argument starts with a -, list
- # options declared in opt instead of files
- lscmd = "ls " arg quote(str)
- if(match($0, "\\ -[^\\ ]*$")) {
- lsopt = opt[gensub(/\ .*/, "", 1)]
- lscmd = "echo -n \"" lsopt "\""
- }
+ sub("[^/]+$", "", str)
  
- update(offset, lscmd)
+ update(offset, "ls " arg quote(str))
          }
      }
  
      # Push out a new set of completions
- function update(offset, cmd) {
+ function update(offset, cmd, cmpl) {
          # Only push out the completion if the offset or the
          # ls command has changed. The behavior will be the
          # same regardless, but this is a minor optimization
@@ -78,7 +72,8 @@
              loffset = offset
              lcmd = cmd
  
- cmpl = read(cmd)
+ if(cmd && cmpl == "")
+ cmpl = read(cmd)
              print offset >fifo
              print cmpl >fifo
              fflush(fifo)

>Note: It would be better to declare the 'opt' array outside this
>script, in a sort of configuration file, but I don't know how to do
>this.

The following will do what you want:

cat >>$HOME/.wmii/menu_opts <<!
lp:
     -o media=a4
     -o landscape
     -o sides=two-sided-long-edge
     -o sides=two-sided-short-edge
     -o number-up=N
!

#!/bin/sh
# This script will launch wimenu and provide command
# completion for the first argument and filename completion
# for each following argument, and pass the result to stdout.
# Program name completion requires that a program list already
# exist in $(wmiir namespace)/.proglist

export opts=$HOME/.wmii/menu_opts
fifo=$HOME/.wmii/menu_fifo
mkfifo $fifo 2>/dev/null

script=$(cat <<'!'
     BEGIN {
         progs = "cat $(wmiir namespace)/.proglist"

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

     # Process the input and provide the completions
     {
         # Skip the trailing part of the command.
         # If there is none, this is the result.
         if (!getline rest) {
             print
             exit
         }

         program = $1
         sub(/"/, "\\\"", program)

         if (!match($0, /.*[ \t]/))
             # First argument, provide the program list
             update(0, progs)
         else if($NF ~ /^-/)
             # If the last argument starts with a -, list
             # options declared in opt instead of files
             update(RLENGTH,
                 "awk <$opts '$0 == \"" program ":\", /^$/ {" \
                     "if(/^[ \\t]/) { sub(/^[ \\t]+/, \"\"); print }" \
                 "}'")
         else {
             # Set the offset to the location of the last
             # space, and save that part of the completion
             offset = RLENGTH
             str = substr($0, offset + 1)

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

             # If the last component of the path begins with
             # a ., include hidden files
             arg = ""
             if(str ~ "(^|/)\\.[^/]*$")
                 arg = "-A "

             # Substitute ~/ for $HOME/
             sub("^~/", ENVIRON["HOME"] "/", str)

             # Strip the trailing filename
             sub("[^/]+$", "", str)

             update(offset, "ls " arg quote(str))
         }
     }

     # Push out a new set of completions
     function update(offset, cmd, cmpl) {
         # Only push out the completion if the offset or the
         # ls command has changed. The behavior will be the
         # same regardless, but this is a minor optimization
         if (offset != loffset || cmd != lcmd) {
             loffset = offset
             lcmd = cmd

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

     # Quote a string. This should work in any Bourne
     # or POSIX compatible shell.
     function quote(str) {
         if (!match(str, /[\[\](){}$'"^#~!&;*?|<>]/))
             return str
         gsub(/\\/, "'\\\\'", str)
         gsub(/'/, "'\\''", str)
         return "'" str "'"
     }

     # Read the output of a command and return it
     function read(cmd) {
         if (cmd in cache)
             return cache[cmd]
         print "read " cmd
         res = ""
         while (cmd | getline)
             res = res quote($0) "\n"
         close(cmd)
         return cache[cmd] = res
     }
!
)
wimenu -h "$HOME/.bash_history" -n 5000 -c "$@" <$fifo | awk -v "fifo=$fifo" "$script"

-- 
Kris Maglione
Sufficiently advanced political correctness is indistinguishable from
sarcasm.
	--Eric Naggum
Received on Tue Aug 17 2010 - 03:22:18 CEST

This archive was generated by hypermail 2.2.0 : Tue Aug 17 2010 - 03:24:02 CEST