Hi Silvan,

On Tue, Dec 01, 2015 at 08:04:04PM +0100, Silvan Jegen wrote:
> Do you know if your
> script works with the BSD awk implementation?

Yes, it should: I am trying to use only *nawk* features; nawk shipped first
in 1987; by now every unix under the sun should have it (and most simply
call it 'awk'; plus, if gawk is available, awk will be a symlink to gawk
anyway)

> > awk -F$'\t' '!x[$1]++' "$HISTORY" "$cache"
> 
> For some reason this does not work for me using gawk. I get both cache
> and history results (deduplicated) but the history results are not split
> on tab for some reason.

Probably your shell is not bash: I think the $'\t' construct is a
"bashism", sorry for that. You can insert instead as an argument to -F a
literal tab character (^I), but that is not easily visible in text (or
worse, might be expanded by your editor into spaces). A better and
guaranteed portable way is to move the definition of the field separator
directly into the awk script. With that the command becomes:

    awk 'BEGIN { FS="\t" } !x[$1]++' "$HISTORY" "$cache"

However, that still uses the initial format for the history file:
"$cmdname\t$count" and we are going to swap that ...

> I like your solution using awk better and would vote for putting a
> version of your patch on suckless.org after adjusting it to deal with
> the history file in "$count\t$cmdname" format. Do you agree?

I agree.

Now swapping places for the count would complicate a bit the awk one-liner
I was talking about above and it would need to be like this:

    awk 'NR==FNR { sub("^[0-9]+\t","") } !x[$0]++' "$HISTORY" "$cache"

The 'NR==FNR' test is true only for the first file (the history) and the
sub will cut the count and the tab in the front of the history lines,
leaving just the name; then it's the same de-duplication for all of them.
And because I don't care about fields anymore, I don't even set the FS.


On Wed, Dec 02, 2015 at 09:05:57AM +0100, Silvan Jegen wrote:
> On Tue, Dec 1, 2015 at 8:04 PM, Silvan Jegen <s.je...@gmail.com> wrote:
> >
> > Personally I don't use multiselect so I will let others deal with that
> > use case...
> 
> Maybe I care a bit after all...
> 
> Using something like
> 
> echo -e "echo 1\nsleep 5; echo awesome\necho 200" | while read cmd; do
> ${SHELL:-"/bin/sh"} -c "$cmd" & done
> 
> would start each program after after a newline has been read.

Yep, that's exactly what I was thinking. Another way is to append an '&'
after all names except the last one before sending them into the $SHELL.

> I don't
> know how multiselect is implemented but it should be easily possible
> to send the program name with newline after Ctrl-Return has been
> pressed.

Actually that's how multisel works right now: a Ctrl-Return just outputs
that entry name (as if you'd pressed Return), but instead of exiting, dmenu
highlights the entry and keeps going. It's even possible to hit Ctrl-Return
repeatedly on the same entry and it will be outputted every time, so you
can get it multiple times ... 

For dmenu's bread and butter function -- selecting programs to run -- that
multisel behaviour is of questionable usefulness. However, it can be very
valuable when used in some special menu applications.


On Tue, Dec 01, 2015 at 08:04:04PM +0100, Silvan Jegen wrote:
> The patch did not apply for me, saying that updhist.awk does not exist.
> I think something went wrong with the patch creation because the diff
> mentions an existing (empty?) updhist.awk file. See below.
> 
> [...]
>
> This seems odd because it implies there already being an updhist.awk. I
> assume that's why the patch did not apply for me.
>
Yeah, sorry for that.  I was still trying to figure out how to coerce git
to generate a diff _without_ staging the new file. (I assume the 'git
apply' was the one that complained for you ?  Just use the 'patch' program
to apply the diff directly without git's involvement, that works for me)



Anyway, based on the change in the history format, I reworked all the
modifications and a new diff is attached -- this time there is no new file:
there is no separate awk file, I've just inserted it as a literal awk
program in the dmenu_run shell script.

Other changes:

- making a history file optional; the trigger to use history is a shell
  variable being present and non-null: DMENU_RUN_USE_HISTORY:

    * if its value is "DEFAULT", it will use a default location/name for
      the history, that is in ~/.cache or $HOME; in both cases the default
      name for the history file will mimic the one for the dmenu_run cache

    * otherwise, the variable's value will be used as the history file
      path; so make sure it exists, it's writable, etc.

    * if that variable is not set or empty, no history is used: dmenu_run
      will behave as it does now

- added your change to deal with multisel (the while loop)

The new patch attached. Give it a try.

diff --git a/dmenu_path b/dmenu_path
old mode 100644
new mode 100755
index 338bac4..3f168aa
--- a/dmenu_path
+++ b/dmenu_path
@@ -1,4 +1,10 @@
 #!/bin/sh
+HISTORY="$1"
+
+if stest -v -qfrw "$HISTORY"; then
+       unset HISTORY
+fi
+
 cachedir=${XDG_CACHE_HOME:-"$HOME/.cache"}
 if [ -d "$cachedir" ]; then
        cache=$cachedir/dmenu_run
@@ -7,7 +13,6 @@ else
 fi
 IFS=:
 if stest -dqr -n "$cache" $PATH; then
-       stest -flx $PATH | sort -u | tee "$cache"
-else
-       cat "$cache"
+       stest -flx $PATH | sort -u > "$cache"
 fi
+awk 'NR==FNR { sub("^[0-9]+\t","") } !x[$0]++' "$HISTORY" "$cache"
diff --git a/dmenu_run b/dmenu_run
index 834ede5..444aeb0 100755
--- a/dmenu_run
+++ b/dmenu_run
@@ -1,2 +1,43 @@
 #!/bin/sh
-dmenu_path | dmenu "$@" | ${SHELL:-"/bin/sh"} &
+
+historyfile="$DMENU_RUN_USE_HISTORY"
+
+if [ -n "$historyfile" ]; then
+       if [ "$historyfile" = "DEFAULT" ]; then
+               cachedir=${XDG_CACHE_HOME:-"$HOME/.cache"}
+               if [ -d "$cachedir" ]; then
+                       historyfile=$cachedir/dmenu_history
+               else
+                       historyfile=$HOME/.dmenu_history # if no xdg dir, fall 
back to dotfile in ~
+               fi
+       fi
+fi
+
+dmenu_path $historyfile | dmenu "$@" \
+       | awk -v histfile=$historyfile '
+               BEGIN {
+                       FS=OFS="\t"
+                       if(!histfile)
+                               exit
+                       while ( (getline < histfile) > 0 ) {
+                               count=$1
+                               sub("^[0-9]+\t","")
+                               fname=$0
+                               history[fname]=count
+                       }
+                       close(histfile)
+               }
+
+               {
+                       history[$0]++
+                       print
+               }
+
+               END {
+                       if(!histfile)
+                               exit
+                       for (f in history)
+                               print history[f],f | "sort -t '\t' -k1rn >" 
histfile
+               }
+       ' \
+       | while read cmd; do ${SHELL:-"/bin/sh"} -c "$cmd" & done

Reply via email to