/yacui

Python library for Console User Interfaces inspired by Magit experience

Primary LanguagePythonMIT LicenseMIT

What

Yacui is a Magit inspired Console User Interface library. If you haven’t used Magit, the tl;dr is:

  1. Screen is split into two: the current “view” and available key bindings.
  2. You navigate the interface using a single (or combined) letters.

Magit interface allows for easy discoverability and low learning curve. Simple mnemonic controls let you build muscle memory and use it fast when you’ve learned it. We need more software like that.

“Screenshot” of an imagined cookbook app:

Cookbook application

1. Recipe for cookies
2. Recipe for cake *SELECTED*


--------------------------------------
 q Quit            n Next
 N Add new recipe  p Previous
 D Delete recipe   Enter View recipe
Status: New recipe added successfully!

I need this in my small internal project, but doing this lib with a nice API was my guilty pleasure. I’d love to have a full Jira interface in console (Majira?) but my attention span is too short to code it.

Name is bad, but most names are taken.

Usage

Install pipenv and then use it to download dependencies. TODO: Package as a library

$ sudo apt install pipenv
$ pipenv install
$ pipenv shell
# Run an example:
$ ./example.py

I include this as a subtree in my other projects until it matures.

Docs / doodles.

App ----> Console
    \---> Display  ---> Current View
     \--> Bindings  \-> View history
      \-> Discovery

App

Ties everything together. Can hold additional “global” state required by the app, although most of state should be handled by views.

Event loop

Reads keys, and uses Bindings to resolve an action. If nothing is happening it’s calling a tick on the current view once a second.

This maaaybe nicer using asyncio, but currently works just fine and is simple.

Console

Manages ncurses; initializes/deinitializes screen. Splits screen in windows, offers a low-level input control.

Display (current display)

Holds the current View instance. Manages backward/forward navigation between view instances.

Manages views lifecycle by calling their on_enter/on_leave/on_drop methods.

View / View

A View controls what is displayed. Can always redraw the view data on the screen in case of resize event or view navigation.

It also controls what actions can be executed and what other views can be visited.

Bindings

Converts pressed buttons into executed actions. There’s only one instance of Bindings class.

Current bindings can be pushed/popped from the binding stack.

Discovery

Renders the current bindings in a usable and automatic way while preserving their order.

Handling of state

View can have multiple instances displaying in the same way different data. If necessary can share state via class members or app object. Therefore View instances hold the main app state.

Display manages screens and doesn’t hold additional state information. Global state (db connections, etc.) can be stored in the App instance.

Project management.

This renders bad on Github. Use org-mode/emacs or ignore this part.

Basics 2/2

Display bindings with common offset

Handle bindings with C-, M- prefix and <enter>

Pass unhandled keybindings to the view.

Allow view to instantiate the subview before navigating

So it can use subview as a modal, for example action requires selected item, so it opens a subview for selection, but instructs it first what is it looking for and sets callbacks.

Logging / debugging

Debug window, opened with –debug; helps a bit with development.

Maybe suggested way of handling futures

Resize works.

It’s easy when you don’t miss something obvious - like recreating windows.
  • Note taken on [2020-09-22 Tue 01:17]
    Partially. Via getkey not really, via SIGWINCH mostly YES, but the number of cols is not refreshed everywhere.

Handle escape codes without delays

Current half-delay approach will break when someone types M-o twice or ESC twice fast.

A reusable almost advanced internal editor.

Either embed readline… or implement it. Can’t be too hard. Minimum keys support: movement: C-f, C-b, M-f, M-b, C-a, C-e, C-n, C-p, LEFT, RIGHT edit: M-BACKSPACE, M-d, C-k, BACKSPACE, typing in insert mode C-y (yank previous cut) Maybe: mark (C-Space)

console.textpad after rereading the docs can work without blocking so it might be ok. Although I’m unsure if it’s worth using. The keys would have to be wrapped anyway.

Executing an external editor for advanced edits.

Non-blocking input method for incremental search.

Implemented by passing an unhandled keypresses to the view and then in 6 lines of code - without full edit though.

Negated search tags with !

Configurable default filter (eg. !st=done)

Backlog

Key chords: C-c M-e?

Themable keybindings via config file.

Name view, and name keybindings, allow overriding them via config.

Incremental search of worklogs

[7/7] Implement a runnable core to build on.

View initialization

  • Generate PAD

Key bindings working.

View redraw working.

  • PAD displayed on the screen.

Keybindings render working.

Querying for string

Partially. Embedding readline seems necessary.

Querying for Y/N

View navigation works with keybindings push/pop

Display keybindings discovery

Package this as a library

Add unit tests for core mechanisms.

  • Mock console
  • Test View navigation, droping, bindings push/pop
  • Create mechanism for sending key presses and checking result, to have an api to test applications, not the lib itself.