Skip to content

RFC: User widgets #1225

@Vtec234

Description

@Vtec234

RFC for the user widgets system

Authors: @EdAyers, @Vtec234

Rubik's

Goals

The idea of user-widgets is to give Lean users the ability to write their own GUIs shown within the Lean infoview without having to muck around with custom versions of the infoview code.

The widgets in Lean 3 got some hype, but there were many limitations of the approach, the main one being that no user-written code ran 'client side' on the infoview.

With the Lean 4 extension, we want to instead let the infoview dynamically load React components from JavaScript served by the Lean server. This has the advantage of allowing users to bundle and use arbitrary JavaScript libraries within the infoview. For example, we can now do things like 3d rendering, diagram drawing, TeX rendering and so on without needing to modify the infoview. Moreover Lake integration makes building widget bundles from TypeScript + NPM modules nice and easy.

Proposal

The current prototype widget protocol can be found here.

JavaScript widgets

A widget source is a valid JavaScript ESModule which exports a React component. The module must include import * as React from 'react' in the imports. The React component may accept a props argument whose value will be determined for each particular widget instance (below). Widget sources may import the @lean4/infoview package (which we intend to publish on NPM) in order to use components such as InteractiveMessage to display MessageData interactively. The precise API surface exposed in @lean4/infoview is orthogonal to this RFC.

NB: While only JavaScript is officially supported, we do not actually require that a widget source be JavaScript. As such, other formats can be stored as widget sources and fetched by custom infoview implementations written in other languages.

Widget source storage

On the Lean side, we use the @[widgetSource] attribute on definitions of type String to register them as widget sources. A widget source registered with @[widgetSource] has a unique widgetSourceId : Name which is just the fully qualified definition name. Alternatively, instead of naming sources we could use content-addressing and associated each source with its hash.

We define the Lean.Widget.getWidgetSource RPC call with a widgetSourceId (or again, hash) argument to retrieve widget sources from the Lean server at a source position following the @[widgetSource] registration. On the infoview side, we treat the Lean server as a kind of content delivery network which can send us widget sources via getWidgetSource.

Associating widgets to positions

A widget instance consists of a widgetSourceId and props : Json. Widget instances are stored in the infotree as .ofCustomInfo json!{ widget: { widgetSourceId, props } }. (We could add a new .ofWidgetInfo, although .ofCustomInfo seems to suffice for now.) This method of storage means each widget instance is associated with a position range in the source code. saveWidget is a method that saves these to the infotree.

We add a Lean.Widget.getWidget RPC call which retrieves the widget instance, if one exists, at a position in the source code. The structure of the infotree makes it possible that multiple widgets may exist at the same cursor position, but currently this RPC just returns the most specific widget.

In the infoview, we add a new "widget" panel which, whenever the cursor position changes, makes a call to the Lean.Widget.getWidget. If a widget is returned, it then fetches the source with getWidgetSource and displays it. Note that JS sources can be many megabytes long due to bundled libraries. Because of this, it is important that getWidgetSource is not called frequently, so we cache widget sources in the client. The props from the widget instance are passed as props to the React component.

Restricting props to Json means that they cannot contain references to non-Json-serializable Lean objects such as contexts and expressions. However, widget sources have full access to the RPC interface, and users can write their own RPC methods to invoke from their widget sources. In all our experiments this sufficed for passing data around.

As for custom goal views, see below, but note that a "custom goal widget" can be implemented by placing a widget instance at the entire range of a by tactic script.

User widgets are entirely separate to the tactic state widget.

Note that this design doesn't let you place widgets within the Lean goal view. Instead, user widgets are entirely separate and live in their own panel of the infoview. The original design of user-widgets was to give lots of 'extension points' within the goal view where users could inject their own widgets. For example, in the type tooltips. However, this may become quite complex and it is not clear at the moment whether extension points of this generality are needed. We may reconsider it in the future.

We do propose one, specific, such extension point in #1223. There, a user can click somewhere within an expression in the infoview and is presented with a set of suggestions such as tactics/rewrites available at that expression.

Links

Metadata

Metadata

Assignees

No one assigned

    Labels

    RFCRequest for comments

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions