Skip to content

Instantly share code, notes, and snippets.

@DarwinSenior
Last active February 9, 2017 11:19
Specification for Remote UI for Rasa

General Protocol

There are two main parts of the protocol

  1. Receiving Event protocol.
  2. Update UI protocol.

Update Protocol

Currently we think the UI has the freedom to draw the screen however they want as long as both sides agrees on the content. I think each component could have their own update accordingly. Here are some example componet I could think of.

  1. Status Bar
  2. Cursor
  3. Sidebar (like linenumber or something)
  4. Views (The thing that organize the window, should be aware of statusbar and sidebar)
  5. Window (Or should we call them buffer content?)
  6. CompletionMenu (No plan at this monent)
  7. Snackbar (This is like Error Messages or Warning or just some fancy notification)

The protocol is inspired by Neovim Remote UI protocol and each update consist of bunch of commands. Each command starts with the command name, then follow by parameters. Since we should let the client to decide how to arrange the component, we could treat each component individually and update them one after another.

Thus each of the update would look like this.

redraw_start
redraw_component status_bar <object_id>
left_bar ...
center_bar ...
right_bar ...
redraw_component cursor <object_id>
cursor_move ...
redraw_component window <object_id>
move_to_point ...
put ...
redraw_end

The basic command structure started with

redraw_start

And the command ends with

redraw_ends

And each subupdate should be either of the three commands

attach_component <component_type> <object_id>
detach_component <component_type> <object_id>
update_component <component_type> <object_id>

And then one should go in to each of the subcomponent type and update the following component. And for both attach_component and update_component, we will follow up the command with the subcommand on how we should update.

Things to note on stateful

I think it is just for future concerns. It seems at first that the design is purposefully stateful. However, one could completely do an unstateful by just redraw everything every time a new redraw command is issued. (see below)

It is for efficiency concerns, especially if the component changes frequently and only do partial update.

Implimentation of the Remote UI

I propose to create a UIComponent class. I will start with a RemoteUI => Monad environment.

for one to be consider as a UI component, it must implement the redraw function.

-- UICommand consist of a command name and its arguments
data UICommand = forall a. (MessagePack a) => UICommand Text [a]

-- UIObjectId make sure that each object is unique and can be referred
-- by the remote object and by itself.
data UIObjectId = UIObjectId !Int

-- in order to be a UI component, one has to implement the redraw
-- command, so if a new UI is attached, one could always draw the
-- component correctly.
-- The uiAutoUpdate is enabled by default. It means everytime we do
-- a redraw on the client, it will automatically append the command
-- from redraw, so that there is no need for automatically update
-- Thus, one do not have to worry about state.
class UIComponent a where
    redraw :: a -> RemoteUI([UICommand])
    uiAutoUpdate :: Boolean
    uiAutoUpdate = true
    type :: Text
    type = "Unknown"

-- It will attach the component to the UI
-- and if it is already registered, it would
-- not register twice
uiRegister :: UIComponent a => a -> RemoteUI(ObjectId)

-- It will detach the UI component, and its update will
-- no longer available (your issued command will be null).
uiDeregister :: UIComponent a => a -> RemoteUI()

-- return the Id of the UIComponent is only it is registered
uiId :: UIComponent a => a -> RemoteUI(Maybe ObjectId)

-- issue commands
remoteUICommand' :: ObjectId -> UICommand -> RemoteUI()

-- issue commands by element, and return if it is successful
remoteUICommand :: UIComponent a => a -> UICommand -> RemoteUI(Maybe ())

Basically each ui component should be responsible to attach itself and detach itself. It could also optionally request to update manually and use remoteUICommand to update. However, I think it is also possible to delegate its responsibility to other component (for example view).

Possible Update Rules for some component

Status Bar

Each window could be accompanied with a status bar, and we could have the following command for the statusbar,

our assumption is that each statusbar one line height and have certain width.

  • show - show status bar
  • hide - hide status bar
  • resize <width::Int> - resize status bar
  • update <position::Left|Center|Right> <content:: [String]> <styles::[Style]> - update the status bar of different state

Cursor

Cursor should include with window it shows.

  • pos <object_id> <x::Int> <y::Int> - move the cursor to the object with relative position (x, y)
  • style <style::Block|Underline|Beam|Hide> - change the style of the cursor

Window

Each window is a complicated object, and we have many ways to update it accordingly, however, we could always resize and redraw everything without implement scroll at all.

  • resize <width::Int> <height::Int> ask window to be resized, if it the content inside the window will be invalidated and we would do a completely redraw, so, it is safe to clean the screen.
  • move_to_point <x::Int> <y::Int> move the cursor to the position and we will redraw at that position.
  • put <content::String> start to redraw the text with current style, then push the marker to a new position
  • style <style::Style> change the current style to the style
  • scroll <line::Int> scroll how many lines downwards, if the the number is negative, scroll upwards. After scroll, there are lines that are basically empty, leave it as it is, we will redraw them later.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment