Plugin to show recent scrollback activity

Posted by Nick Gammon on Sun 24 May 2009 04:42 AM — 12 posts, 44,016 views.

Australia Forum Administrator #0
There have been a few requests recently for a way of seeing current output in the output window, whilst at the same time scrolling back to review earlier output (eg. to check a map, or what a quest is for).

Without such a feature, it is possible that you are in combat, or someone is asking you a question, and you don't realize it.

There are a number of ways of doing this, one is to open a separate output window from the Window menu -> New Window. This new window can be resized and move around so you can have one window paused, and the other scrolls in the normal way.

The plugin below takes another approach. It uses the OnPluginScreendraw plugin callback to capture recent output (that is, output that has not been omitted by a trigger). It draws the most recent 20 lines (this number can be configured) in a miniwindow at the top of the screen. This miniwindow always shows recent output, even if you are scrolling back.

Based on the font size you choose, and your current output window width, the miniwindow size adjusts to exactly hold the number of lines you want (MAX_LINES = 20). You can configure the font name (FONT_NAME = "Lucida Console") and font size (FONT_SIZE = 9) by changing a couple of lines in the plugin.

A timer which fires every half second checks if the output window is paused. If so, it displays the miniwindow. If not, it hides it. Thus the miniwindow is only visible if you scroll back.

Below is the plugin.

Save between the lines as Current_Output_Window.xml and use the File menu -> Plugins to load that file as a plugin.


<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>

<muclient>
<plugin
   name="Current_Output_Window"
   author="Nick Gammon"
   id="391192793248409895090099"
   language="Lua"
   purpose="Redirects recent output to a miniwindow"
   date_written="2009-02-05 17:00"
   requires="4.37"
   version="1.0"
   >

</plugin>

<timers>
  <timer second="0.50" 
         enabled="y" 
         script="heartbeat"
         >
  </timer>
</timers>


<!--  Script  -->


<script>
<![CDATA[

-- configuration

-- window size in pixels
MAX_LINES = 20

-- font
FONT_NAME = "Lucida Console"
FONT_SIZE = 9

-- where to put the window
WINDOW_POSITION = 6  -- see below (6 is top right)

--[[
Useful positions:

4 = top left
5 = center left-right at top
6 = top right
7 = on right, center top-bottom
8 = on right, at bottom
9 = center left-right at bottom
--]]


-- colours
WINDOW_BACKGROUND_COLOUR = ColourNameToRGB ("khaki")
WINDOW_TEXT_COLOUR = ColourNameToRGB ("black")

-- offset of text from edge
TEXT_INSET = 5

-- where to store the scrollback line
lines = {}  -- table of recent scrollback lines

-- display one line
function Display_Line (line, text)

  local left = TEXT_INSET
  local top = (line - 1) * font_height + TEXT_INSET
  
  WindowText (win, "f", text, left, top, window_width - TEXT_INSET, 0, WINDOW_TEXT_COLOUR)
  
end -- Display_Line

function add_line (line)
  -- remove first line if filled up
  if #lines >= MAX_LINES then
    table.remove (lines, 1)
  end -- if 

  -- add new line
  table.insert (lines, line)

end -- add_line

-- here on new output
function OnPluginScreendraw (type, log, line)

  -- blank existing window contents
  WindowRectOp (win, 2, 0, 0, 0, 0, WINDOW_BACKGROUND_COLOUR)
  
  -- wrap long lines
  while #line > wrap_column do
    add_line (line:sub (1, wrap_column))
    line = line:sub (wrap_column + 1) 
  end -- while line > max
  
  -- add remainder of line
  add_line (line)
  
  -- display all lines
  for k, v in ipairs (lines) do
    Display_Line (k, v)
  end -- for
  
end -- end OnPluginScreendraw


-- hide window on removal
function OnPluginClose ()
  WindowShow (win,  false)  -- hide it
end -- OnPluginClose

-- hide window on disable
function OnPluginDisable ()
  WindowShow (win,  false)  -- hide it
end -- OnPluginDisable

-- show window on enable
function OnPluginEnable ()
  if #lines > 0 then
    WindowShow (win,  true)  -- show it
  end -- if
end -- OnPluginEnable

function heartbeat (timername)
  
  -- display window if output window paused and we have something to show
  WindowShow (win,  GetInfo (114) and #lines > 0)

end -- heartbeat

-- startup stuff

win = GetPluginID ()  -- get a unique name

-- make the window
WindowCreate (win, 0, 0, 1, 1, WINDOW_POSITION, 0, 
                         WINDOW_BACKGROUND_COLOUR)  -- create window
               
-- grab a font
WindowFont (win, "f", FONT_NAME, FONT_SIZE) -- define font

-- work out how high and wide it is
font_height = WindowFontInfo (win, "f", 1)   -- height of the font  
wrap_column = GetOption ("wrap_column")

window_width = wrap_column * WindowFontInfo (win, "f", 6) + TEXT_INSET * 2
window_height = MAX_LINES * font_height + TEXT_INSET * 2 

-- remake the window with the correct width
WindowCreate (win, 0, 0, window_width, window_height, WINDOW_POSITION, 0, 
                         WINDOW_BACKGROUND_COLOUR)  -- create window

]]>
</script>

</muclient>
Amended on Sun 24 May 2009 04:51 AM by Nick Gammon
Australia Forum Administrator #1

Example of it in operation:

Australia Forum Administrator #2
In the example above you can see the output window is paused (because you can see MORE in reverse font). As soon as you scroll to the bottom of the output window (eg. Ctrl+End) then MORE disappears, and the yellow window disappears too.

The colours are configurable by changing these two lines:


WINDOW_BACKGROUND_COLOUR = ColourNameToRGB ("khaki")
WINDOW_TEXT_COLOUR = ColourNameToRGB ("black")


Note that since the plugin callback OnPluginScreendraw is not given colour information, the miniwindow is monochrome (although you can choose the foreground and background colours).

You can also change the position the window appears (eg. top left). See (WINDOW_POSITION = 6).
#3
When the MUSHclient window loses focus the miniwindow disappears. Could it be made so that it stays?

Aside from that, it works beautifully.
Australia Forum Administrator #4
Hmm, it seems the check for if we are paused does not apply if the world is not active. You could replace the heartbeat function with this:


function heartbeat (timername)
  
  if GetInfo (113) then
    -- display window if output window paused and we have something to show
    WindowShow (win,  GetInfo (114) and #lines > 0)
  end -- if world active
  
end -- heartbeat


What that does is only show or hide the window if the world is active, so if it was visible and loses the focus, it will stay visible.

[EDIT] - fixed problem where the first GetInfo should be 113 ('World is active' flag) and the second one 114 ('Output window paused' flag).
Amended on Tue 26 May 2009 10:28 PM by Nick Gammon
#5
Made that replacement. It now works properly when the window is inactive, however, it no longer hides when you aren't scrolled back.
Australia Forum Administrator #6
I accidentally used GetInfo (114) twice - the first one should be 113, I have amended the post.
#7
Beautiful, thanks.
#8
As a suggestion... Think this script can be edited to search for a space for where it makes a new line, rather than cutting words up? And if that is the case, possibly delineate the lines formed that way by indenting them slightly? Basically, like the client itself does.
Australia Forum Administrator #9
That was supposed to be an exercise for the reader, but oh well ...

In the plugin, change:


  -- wrap long lines
  while #line > wrap_column do
    add_line (line:sub (1, wrap_column))
    line = line:sub (wrap_column + 1) 
  end -- while line > max


to:


  -- wrap long lines, at a space if possible
  while #line > wrap_column do
  
    local col = wrap_column
    if line:match (' ', 3) then  -- if a space past column 2
      while line:sub (col, col) ~= ' ' do  -- find a space
        col = col - 1
      end -- while finding a space, going backwards
      col = col - 1  -- use space in new line, to indent it
    end -- any spaces

    add_line (line:sub (1, col))
    line = line:sub (col + 1) 
    
  end -- while line > max

Australia Forum Administrator #10
To save the loop, I think this regexp will do it:


  -- wrap long lines, at a space if possible
  while #line > wrap_column do
  
    -- find a space not followed by a space, closest to the end of the line
    local col = string.find (line:sub (1, wrap_column), "%s%S*$")
    
    if col and col > 2 then
      col = col - 1  -- use the space to indent
    else
      col = wrap_column  -- just cut off at wrap_column
    end -- if

    add_line (line:sub (1, col))
    line = line:sub (col + 1) 
    
  end -- while line > max

#11
Second one works perfectly it looks like, didn't try the first. Man, if you keep writing all this good stuff in Lua, I'm going to have to learn it as well just to use it all ;)