Vis: to serve(r) or not?

Since its inception vis drew inspiration from both Oberon (see chapter 5 The Text System), with respect to its core text management data structure, and the Plan 9 editors sam and acme with their structural regular expression based command language. Both environments provide editable user interfaces, combining window management, text editing and shell functionality.

In a more traditional (pseudo) terminal based Unix setting, editors are typically equipped with window management and a terminal buffer type to provide some form of integration. However, what makes editor windows special? Why can’t they be managed by the regular window manager? Thereby taking advantage of existing, sophisticated window layout and tagging mechanisms.

For these reasons I have so far implemented only very simplistic window management and rejected requests to enhance it. Instead, external standalone utilities like vis-menu provide part of the user interface. However, reusing the native (terminal) window management capabilities would require a multi process design.

The main design decision of such a client/server architecture is the provided API and the resulting state distribution. Should a server only provide a very barebone interface, supporting just load/insert/delete/save operations? Enabling manifold user interfaces, but also introducing concurrency and consistency issues. At the other end of the design spectrum, the whole editing logic resides server side and the clients merely forward input events.

The involved objects have varying scope and ownership:

  • Global: the list of opened buffers, registers.

  • Per buffer: text/buffer content, history tree, marks.

  • Per window: selections.

  • Per client: current working directory, key mappings?

The flexibility, performance and complexity of an exposed API depends on how fine grained changes are allowed, how they are grouped logically and how their conflicts are (eventually) resolved.

A second, much more mundane, problem concerns the concrete realization of such an API. Unlike on Plan 9, where 9P is used consistently throughout the system as a composable inter-process communication mechanism, a cross-platform implementation needs to chose a suitable serialization format and RPC protocol. Relevant decision criteria include:

  • Binary data support: the design should support binary files (e.g. for use in a hex editor).

  • Standardized: in widespread use with robust implementations available for various programming languages. For vis specifically, C and Lua are of particular interest.

  • Schema vs schema-less: the former facilitates code generation, strict automatic validation, fuzzing etc. but also typically requires more complex build system integration and heavier runtime dependencies. The latter is probably more suited for experimentation and rapid prototyping when backward compatibility is not of upmost importance.

As such a simple RPC protocol based on CBOR or MessagePack seems appropriate.

On a local system, Unix domain sockets can be used as a transport mechanism. Each RPC message could be augmented with optional file descriptors, although their cross-platform behavior needs to be handled carefully.

A client/server architecture provides a great deal of flexibility and has a lot of potential, but depending on the chosen design and concrete implementation also introduces a lot of complexity.

The question thus remains, should we serve(r) or not?