Posted on January 15, 2018

Overview

bhoogle is a simple hoogle terminal GUI written using brick. This post is the annotated source code that should give you an idea of how to use brick and how easy brick makes building terminal UIs.

bhoogle

bhoogle is possibly useful as a local hoogle UI as well as a demo app. You can get the full code from github.

Setup

You will need an existing local hoogle database. If you do not already have one or are unsure, then do this

Install hoogle (e.g. stack install hoogle ) Generate the default database ( hoogle generate )

Build

You can then clone the code, or download one of the pre-build linux releases

Usage

Enter a type search in the “type” edit box Press enter to search: focus goes directly to the results list Or press tab to search and focus will go to the “text” edit box You can then filter the results by typing in the “text” edit box, any result containing the sub-string typed will be shown Navigate the results by using arrow or vi (hjkl) keys Pressing ‘s’ in the results list will toggle the sort order Escape to exit Search-ahead is enable for any type search longer than three characters

Brick

There are a few conventions to get used to when building a brick UI, but I don’t think it should take you too long to get the hang of things.

The brick user guide and documentation are fantastic. Brick comes with multiple example apps that show controls and features being used. There are also third party tutorials e.g. Samuel Tay’s brick tutorial

bhoogle 0.1.1.0 source

If you have looked at the user guide or Samuel Tay’s tutorial you’ll already have some idea of the fundamental concepts. Below is the annotated source for bhoogle. As always feel free to email or contact me on twitter if anything is unclear and I’ll do my best to assist.

Import all the modules we’ll need. I’m using protolude as my custom prelude, changing to one of the others e.g. classy should be pretty simple if you prefer that.

I’m also using lens. The brick examples use lens so its worth getting used to. However I’m only using three of the simpler lenses, so if you don’t like lens or template haskell it should be easy enough to remove them.

Next we need to define the type of custom events that our brick application can handle and a sum type defining the “name” for each control we want to use.

In this example there is only a single event EventUpdateTime. It is sent once a second with the current time. This gets displayed by brick in the top right corner

There are three controls

The edit box for the type to search for The edit box for the substring search The results listbox

BrickState contains the current state of the brick application. Any event e.g. the custom update time event, or any key press event can result in the state being updated. There is a separate draw function that renders the state.

I.e. one part of the code deals with events, roughly state -> event -> state and another handles the drawing state -> GUI

Here the state contains

The three controls mentioned above (two edit + one listbox) A focus ring. (A focus ring is a circular list of control names that helps your code keep track of which control has the current focus). The last updated current time The last search result The current sort order, so that it can be toggled between ascending and descending

The App type defines how the brick app operates, but defining how events are handled ( appHandleEvent ) and how the GUI is drawn ( appDraw )

In main some setup is preformed and then brick is started by calling customMain .

For bhoogle the steps are

Construct the channel for brick events (passed to customMain ) Create a new thread to send the current time every second Construct an initial state, with empty controls and search results B.customMain to run brick

handleEvent gets all the brick events, updates the state and decides how to continue.

Here the code matches the custom (B.AppEvent) event looking for our update time event (EventUpdateTime) and then updates the state with the current time. B.continue means that brick continues after updating the state. Note that the UI is not changed in any way here, we are just altering the current state.

Then the code matches any keyboard event (B.VtyEvent) here matching on the escape key (K.KEsc). So when the user clicks the escape key this handler will call B.halt which will terminate the app. As this is done at the top level, this means that no matter which control has the focus, escape will exit.

For the rest of the key press logic, what bhoogle does depends on which control has the focus. BF.focusGetCurrent is used to get that from the state’s focus ring.

If the user is typing in the “type” edit box and tabs out (either tab or shift-tab) then

Perform the search (see doSearch below) Update the current set of results Reset the sort order, default to the order that hoogle uses Move the focus to the next/previous control

If the user presses enter while in the type search edit box, then

Perform the search (see doSearch below) Update the current set of results Reset the sort order, default to the order that hoogle uses Move the focus directly to the results lisbox so they can navigate and see the current item’s details & help text

For all other key events for the type search, let the editor control handle the key press. This gives us editing, navigation etc for free.

For the text edit box

Change focus on tab / shift-tab For all other keys Let the editor handle the key press Filter the hoogle results

For the results listbox

Handle tab / shift-tab

Pressing the ‘s’ key will sort the results. Pressing it again toggles the direction, so keep track of which order was used last.

key will sort the results. Pressing it again toggles the direction, so keep track of which order was used last. For all other keys use BL.handleListEventVi BL.handleListEvent which gives us vi style navigation and uses the standard handleListEvent as the fallback, so that all the normal navigation (arrows) also work.

And finally for handleEvent the doSearch function which calls the searchHoogle function (below) to search on the text from the type editbox.

searchAhead is a helper function that searches hoogle as the user types. As long as there are more than three characters being searched for. Without this limit hoogle seems a bit slow on my machine because of the large number of results.

Filter the hoogle results by doing a sub-string search if the user has entered one

drawUI renders the state and creates the GUI. At first this may take some getting used to, but you will soon be able to see the GUI structure from the code.

<=> means horizontal break, i.e. next “line”

means horizontal break, i.e. next “line” <+> means “next to”

means “next to” I often end up formatting code slightly differently to how I would in the other functions to better communicate the structure

Create small GUI fragments/“controls” and combine them with <+> and <=> For example htitle creates a “title” by Limiting the max width to 20 Setting the attribute to infoTitle Displaying the text using B.txt ( B.txt displays a Text, B.str displays a string/[char])

For example creates a “title” by B.fill ' ' is used to get brick to fill to the maximum width (here 60) rather that having the right detail pain growing/shrinking as the data changes.

The attribute map is where attributes for the controls and custom attributes are defined. This makes it easy to change how the GUI looks. There is even support for themes and basic markup.

The remainder of the code is non-brick code for searching and formatting hoogle results

compareType compares two results by formatting them first and then comparing the resulting text

compares two results by formatting them first and then comparing the resulting text searchHoogle searches hoogle using the default database

searches hoogle using the default database formatResults formats the hoogle results

formats the hoogle results unescapeHTML and stripTags are used to get plain text from the HTML. Note that this code comes from the hakyll and hoogle source code

Hopefully this example helps you get started with brick and demonstrates how easy brick makes creating terminal UIs