Contextive is a Visual Studio Code extension that
supports developers where a complex domain or project specific language is in use by surfacing definitions everywhere specific words are used - code, comments, config or documentation.
Contextive VS Code extension homepage
What will make it particularly interesting to readers of this blog is that it is implemented in F#!
The Contextive README on GitHub provides some more background information:
Contextive is inspired by the concept of the Ubiquitous Language from the practice of Domain Driven Design (DDD) and is intended to support ubiquitous language management practices on DDD projects.
Even if you're not using Domain Driven Design, Contextive should still be very helpful in any software project where it's important that developers are aligned on the meaning of terms.
By defining terms in a central definitions file, Contextive can surface definitions and usage examples in auto-complete suggestions & hover panels wherever the terms are used - in code (of any language across the stack), comments, config, and documentation (e.g. markdown).
The key idea is that you define terms in a YAML file in your repo and use an extension to make your IDE aware of those terms. This gives you features in your editor like completions and hover text. As an example, you can get the following editor support with the below definitions file.
contexts:
- name: Cargo
domainVisionStatement: To manage the routing of cargo through transportation legs
paths:
- CargoDemo
terms:
- name: Cargo
definition: A unit of transportation that needs moving and delivery to its delivery location.
examples:
- Multiple Customers are involved with a Cargo, each playing a different role.
- The Cargo delivery goal is specified.
aliases:
- unit
...
Background: the Language Server Protocol
The Language Server Protocol (LSP) defines the protocol used between an editor or IDE and a language server that provides language features like auto complete, go to definition, find all references etc.
Language Server Protocol homepage
Implementing support for features like autocomplete, goto definition, or documentation on hover for a programming language is a significant effort. Traditionally this work must be repeated for each development tool, as each provides different APIs for implementing the same features.
The idea behind a Language Server is to provide the language-specific smarts inside a server that can communicate with development tooling over a protocol that enables inter-process communication.
The idea behind the Language Server Protocol (LSP) is to standardize the protocol for how tools and servers communicate, so a single Language Server can be re-used in multiple development tools, and tools can support languages with minimal effort.
LSP is a win for both language providers and tooling vendors!
Contextive makes use of a language server that reads definitions of terms in the Ubiquitous Language from a YAML file, and uses those terms to respond with appropriate hover text or completions when prompted.
Leveraging Fable to create a VS Code extension
VS Code extensions are usually written in JavaScript. Thanks to the Fable F# to JavaScript compiler, it's possible to write the extension code in F#.
The extension starts a language server client.
let activate (context: ExtensionContext) =
promise {
let client = clientFactory ()
do! client.start ()
...
}
It is configured with some server options.
let private clientFactory () =
LanguageClient(
ExtensionId,
"Contextive",
serverOptions = serverOptions,
clientOptions = clientOptions,
forceDebug = Option.isSome process.env?CONTEXTIVE_DEBUG
)
Those server options ultimately launch the language server as an executable. Its behaviour is defined elsewhere in the codebase.
let private runServerOptions =
executable (fun x ->
x.command <- "./Contextive.LanguageServer"
...)
Some of the code for working with the language server makes use of Ionide-Helpers
Leveraging .NET libraries to implement a language server
The OmniSharp.Extensions.LanguageServer package, written by the OmniSharp team, is designed to make it easier to write language servers that run on .NET. Of course, F# can target .NET as well as JavaScript, so it's possible to make use of this library to write language servers in F#, and this is exactly what Contextive does. Doing so allows Contextive to, for example, easily hook hover behaviour defined in a custom function into the language server that is eventually created.
let private configureServer (input: Stream) (output: Stream) (opts: LanguageServerOptions) =
let definitions = Definitions.create ()
opts
.WithInput(input)
.WithOutput(output)
...
.OnHover(Hover.handler <| Definitions.find definitions <| TextDocument.findToken, Hover.registrationOptions)
Aside: automated semantic releases
I first came across Contextive at a .NET meetup in July, during which Chris Simon (Contextive's author) made and released an improvement to the extension. The F# goodness and a solid test suite, along with some well-defined GitHub actions made it possible for Chris to be confident enough to do this in front of a live audience. In particular, making use of the semantic-release npm package allowed Chris to trigger a release, and have the version number and changelog updated automatically (based on commit messages he'd written following a formalized convention). I definitely think it's a package worth library maintainers exploring.
Summary
There are a few things to say at the end of this blog post. The first is that Contextive looks like a really useful extension! If you're using VS Code, definitely check it out. It's also another great demonstration of how blessed we are in the F# community to be able to target .NET and JavaScript from the same language; just like writing SAFE Stack code doesn't require changing languages between client and server, in Contextive there's less context switching when moving between extension code and language server code. Finally, Contextive could be a great project to contribute to if you're looking to learn more about writing VS Code extensions (or even an extension for another editor!) or to better understand language server implementation.
Thanks Chris for the great tool, and for giving me an excuse to write some more about the power of F#!