WMII-like tag management

WMII - like tag management
Note: You can also accomplish the below more easily with the Shifty library for Awesome.

WMII deals with tags in a bit more dynamic way than awesome does. On the beginning of session only one tag is created (named '1'). If we enter a tag that didn't exist before, it's created, when we leave an empty tag, it's destroyed.

It's a behaviour that I (a WMII refugee) missed a lot. Luckly it was not too hard to implement with some lua code and help of Garoth and psychon on IRC.

Note: all bits and pieces of the config file are for awesome 3.3-rc2. Other versions may require some tweaking.

First, we need some utility functions we'll use later on:

function dictlen(tbl) local result = 0 for _, __ in pairs(tbl) do           result = result + 1 end return result end

function get_keys(tbl) local result = {} for key, _ in pairs(tbl) do           if key then table.insert(result, key) end end return result end

function make_completer(choices) return function(cmd, cur_pos, ncomp) local matches = {} -- abort completion under certain circumstances if #cmd == 0 or (cur_pos ~= #cmd + 1 and cmd:sub(cur_pos, cur_pos) ~= " ") then return cmd, cur_pos end -- match for _, match in pairs(choices) do               if match:find("^" .. cmd:sub(1,cur_pos)) then table.insert(matches, match) end end -- if there are no matches if #matches == 0 then return cmd, cur_pos end -- cycle while ncomp > #matches do               ncomp = ncomp - #matches end -- return match and position return matches[ncomp], cur_pos end end

Replace loop that add MOD+number keybindings (it's shown below):

-- Compute the maximum number of digit we need, limited to 9 keynumber = 0 for s = 1, screen.count do      keynumber = math.min(9, math.max(#tags[s], keynumber)); end

for i = 1, keynumber do       table.foreach(awful.key({ modkey }, i,                      function  -- [cut] end), function(_, k) table.insert(globalkeys, k) end) -- [cut] end

with something like this:

for i = 0, 9 do       table.foreach(awful.key({ modkey }, i,                      function local screen = mouse.screen awful.tag.viewonly(get_tag(screen, i)) end), function(_, k) table.insert(globalkeys, k) end) table.foreach(awful.key({ modkey, "Control" }, i,                     function local screen = mouse.screen local tag = get_tag(screen, i)                         if tag then tag.selected = not tag.selected end end), function(_, k) table.insert(globalkeys, k) end) table.foreach(awful.key({ modkey, "Shift" }, i,                     function if client.focus then awful.client.movetotag(get_tag(client.focus.screen, i)) end end), function(_, k) table.insert(globalkeys, k) end) table.foreach(awful.key({ modkey, "Control", "Shift" }, i,                     function if client.focus then awful.client.toggletag(get_tag(client.focus.screen, i)) end end), function(_, k) table.insert(globalkeys, k) end) if i > 0 then table.foreach(awful.key({ modkey, "Shift" }, "F" .. i,                         function local screen = mouse.screen if tags[screen][i] then for k, c in pairs(awful.client.getmarked) do                                     awful.client.movetotag(get_tag(screen, i), c)                                  end end end), function(_, k) table.insert(globalkeys, k) end) end end

Having that we can move on and create some tag-manipulating functions. Replace all of the "-- {{{ Tags" fold in your rc.lua with this:

-- {{{ Tags -- Define tags table. tags = {} -- put all clients with no tags to "0" tag on screen 1 function handle_orphans for _, c in pairs(client.get) do           if #c:tags == 0 then c:tags({get_tag(1, "0")}) end end end function sort_tags(screen_no) local all_tags = screen[screen_no]:tags table.sort(all_tags, function (a, b) return a.name < b.name end) screen[screen_no]:tags(all_tags) end function delete_tag(screen_no, name) local strname = '' .. name if protected_tag == screen_no .. strname then return end local result = tags[screen_no][strname] if result ~= nil then result.screen = nil tags[screen_no][strname] = nil end end -- gets or creates tag function get_tag(screen_no, name) local strname = '' .. name local result = tags[screen_no][strname] if not tags[screen_no][strname] then protected_tag = screen_no .. strname result = tag(strname) result.screen = screen_no tags[screen_no][strname] = result awful.layout.set(layouts[1], result) end protected_tag = nil return result end for s = 1, screen.count do       -- Each screen has its own tag table. tags[s] = {} get_tag(s, 1).selected = true end -- }}}

To make unused tags disappear and have them nicely sorted, add the following at the end of awful.hooks.arrange.register hook

if screen == mouse.screen then for n, t in pairs(tags[screen]) do           if #t:clients == 0 and t ~= awful.tag.selected and dictlen(tags[screen]) > 1 then delete_tag(screen, n)           end end end sort_tags(screen) handle_orphans

Now, let's have a prompt for our tag manipulation:

function tag_prompt(txt, callback) return function local screen = client.focus and client.focus.screen or mouse.screen awful.prompt.run({ prompt = txt },            mypromptbox[screen].widget,            function(t) callback(screen, t) end,            make_completer(get_keys(tags[screen])),            awful.util.getdir("cache") .. "/history_tags") end end

Let's bind it to keys (check if those bindings conflict with ones you already have!)

awful.key({ modkey,          },       "t", tag_prompt("go to tag: ",   function (screen, tag) awful.tag.viewonly(get_tag(screen, tag)) end)), awful.key({ modkey, "Shift"  },       "t", tag_prompt("move to tag: ", function (screen, tag) awful.client.movetotag(get_tag(screen, tag)) end)), awful.key({ modkey, "Ctrl", "Shift" }, "t", tag_prompt("toggle tag: ", function (screen, tag) awful.tag.toggletag(get_tag(screen, tag)) end))

That's it, enjoy!

Further work
There are some obvious places that you can change/improve this, like: * +tag and -tag should be supported * dmenu can be used for tag selection instead of awesome's prompt for more WMII - like experience