Tarides Logo
Seven bulbs, one yellow lit; a hand pulls it.

Creating the SyntaxDocumentation Command - Part 3: VSCode Platform Extension

Posted on Wed, 24 Jul 2024

In the final installment of our series on the SyntaxDocumentation command, we delve into its integration within the OCaml VSCode Platform extension. Building on our previous discussions about Merlin and OCaml LSP, this article explores how to make SyntaxDocumentation an opt-in feature in the popular VSCode editor.

In the first part of this series, Creating the SyntaxDocumentation Command - Part 1: Merlin, we explored how to create a new command in Merlin, particularly the SyntaxDocumentation command. In the second part, Creating the SyntaxDocumentation Command - Part 2: OCaml LSP, we looked at how to implement this feature in OCaml LSP in order to enable visual editors to trigger the command with actions such as hovering. In this third and final installment, you will learn how SyntaxDocumentation integrates into the OCaml VSCode Platform extension as an opt-in feature, enabling users to toggle it on/off depending on their preference.

VSCode Editor

Visual Studio Code is a free open-source, cross-platform code editor from Microsoft that is very popular among developers. Some of its features include:

  • Built-in Git support
  • Easy debugging of code right from the editor with an interactive console
  • Built-in extension manager with lots of available extensions to download
  • Supports a huge number of programming languages, including syntax highlighting
  • Integrated terminal and many more features

OCaml Platform Extension for VSCode

The VSCode OCaml Platform extension enhances the development experience for OCaml programmers. It is itself written in the OCaml programming language using bindings to the VSCode API and then compiled into Javascript with js_of_ocaml. It provides language support features such as syntax-highlighting, go-to-definition, auto-completion, and type-on-hover. These key functionalities are powered by the OCaml Language Server (ocamllsp), which can be installed using popular package managers like opam and esy. Users can easily configure the extension to work with different sandbox environments, ensuring a tailored setup for various project needs. Additionally, the extension includes comprehensive settings and command options, making it very versatile for both beginner and advanced OCaml developers.

The OCaml Platform Extension for VSCode gives us a nice UI for interacting with OCaml-LSP. We can configure settings for the server as well as interact with switches, browse the AST, and many more features. Our main focus is on adding a checkbox that allows users to activate or deactivate SyntaxDocumentation in OCaml LSP's hover response. I limited this article's scope to just the files relevant in implementing this, while giving a brief tour of how the extension is built.

The Implementation

Extension Manifest

Every VSCode extension has a manifest file, package.json, at the root of the extension directory. The package.json contains a mix of Node.js fields, such as scripts and devDependencies, and VS Code specific fields, like publisher, activationEvents, and contributes. Our manifest file contains general information such as:

  • Name: OCaml Platform
  • Description: Official OCaml language extension for VSCode
  • Version: 1.14.2
  • Publisher: OCaml Labs
  • Categories: Programming Languages, Debuggers

We also have commands that act as action events for our extension. These commands are used to perform a wide range of things, like navigating the AST, upgrading packages, deleting a switch, etc. An example of a command to open the AST explorer is written as:

{
    "command": "ocaml.open-ast-explorer-to-the-side",
    "category": "OCaml",
    "title": "Open AST explorer"
}

For our case, enabling/disabling SyntaxDocumentation is a configuration setting for our language server, so we indicate this in the configurations section:

"ocaml.server.syntaxDocumentation": {
    "type": "boolean",
    "default": false,
    "markdownDescription": "Enable/Disable syntax documentation"
}

Extension Instance

The file extension_instance.ml handles the setup and configuration of various components of the OCaml VSCode extension and ensures that features like the language server and documentation are properly initialised. Its key functionalities are:

  • Managing the Extension State: It uses a record type that encapsulates the state of the extension, holding information about the sandbox, REPL, OCaml version, LSP client, documentation server, and various other settings.
type t = {
  mutable sandbox : Sandbox.t;
  mutable repl : Terminal_sandbox.t option;
  mutable ocaml_version : Ocaml_version.t option;
  mutable lsp_client : (LanguageClient.t * Ocaml_lsp.t) option;
  mutable documentation_server : Documentation_server.t option;
  documentation_server_info : StatusBarItem.t;
  sandbox_info : StatusBarItem.t;
  ast_editor_state : Ast_editor_state.t;
  mutable codelens : bool option;
  mutable extended_hover : bool option;
  mutable dune_diagnostics : bool option;
  mutable syntax_documentation : bool option;
}
  • Interacting With the Language Server: This extension needs to interact with the OCaml language server (ocamllsp) to provide features like code completion, diagnostics, and other language-specific functionalities.

  • Documentation Server Management: The file includes functionality to start, stop, and manage the documentation server, which provides documentation lookup for installed OCaml packages.

  • Handling Configuration: This extension allows users to configure settings such as code lens, extended hover, diagnostics, and syntax documentation. These settings are sent to the language server to adjust its behaviour accordingly. For SyntaxDocumentation, whenever the user toggles the checkbox, the server should set the correct configuration parameters. This is done mainly using two functions set_configuration and send_configuration.

...

(* Set configuration *)
let set_configuration t ~syntax_documentation =
  t.syntax_documentation <- syntax_documentation;
  match t.lsp_client with
  | None -> ()
  | Some (client, (_ : Ocaml_lsp.t)) ->
      send_configuration ~syntax_documentation client
...
...

(* Send configuration *)
let send_configuration ~syntax_documentation client =
  let syntaxDocumentation =
    Option.map syntax_documentation ~f:(fun enable ->
        Ocaml_lsp.OcamllspSettingEnable.create ~enable)
  in
  let settings =
    Ocaml_lsp.OcamllspSettings.create
      ~syntaxDocumentation
  in
  let payload =
    let settings =
      LanguageClient.DidChangeConfiguration.create
        ~settings:(Ocaml_lsp.OcamllspSettings.t_to_js settings)
        ()
    in
    LanguageClient.DidChangeConfiguration.t_to_js settings
  in
  LanguageClient.sendNotification
    client
    "workspace/didChangeConfiguration"
    payload

...

Interacting With OCaml LSP:

The ocaml_lsp.ml file ensures that ocamllsp is set up correctly and up to date. For SyntaxDocumentation, two important modules used from this file are: OcamllspSettingEnable and OcamllspSettings.

OcamllspSettingEnable defines an interface for enabling/disabling specific settings in ocamllsp.

...

module OcamllspSettingEnable = struct
  include Interface.Make ()
  include
    [%js:
    val enable : t -> bool or_undefined [@@js.get]
    val create : enable:bool -> t [@@js.builder]]
end

...

The annotation [@@js.get] is a PPX used to bind OCaml functions to JavaScript property accessors. This allows OCaml code to interact seamlessly with JavaScript objects, accessing properties directly as if they were native OCaml fields, while [@@js.builder] facilitates the creation of JavaScript objects from OCaml functions. They both come from the LexFi/gen_js_api library.

OcamllspSettings aggregrates multiple OcamllspSettingEnable settings into a comprehensive settings interface for ocamllsp.

...
module OcamllspSettings = struct
  include Interface.Make ()
  include
    [%js:
    val syntaxDocumentation : t ->
      OcamllspSettingEnable.t or_undefined [@@js.get]

    val create : ?syntaxDocumentation:OcamllspSettingEnable.t ->
      unit -> t [@@js.builder]]

  let create ~syntaxDocumentation = create ?syntaxDocumentation ()
end
...

Workspace Configuration

The file settings.ml provides a flexible way to manage workspace-specific settings, including:

  • Creating settings with JSON serialisation and deserialisation
  • Retrieving and updating settings from the workspace configuration
  • Resolving and substituting workspace variables within settings
  • Defining specific settings for the OCaml language server, such as extra environment variables, server arguments, and features like codelens and SyntaxDocumentation
...
let create_setting ~scope ~key ~of_json ~to_json =
  { scope; key; to_json; of_json }

let server_syntaxDocumentation_setting =
  create_setting
    ~scope:ConfigurationTarget.Workspace
    ~key:"ocaml.server.syntaxDocumentation"
    ~of_json:Jsonoo.Decode.bool
    ~to_json:Jsonoo.Encode.bool
...

Activating the Extension

The vscode_ocaml_platform.ml file initialises and activates the OCaml Platform extension for VSCode. The key tasks include:

  • Suggesting users select a sandbox environment
  • Notifying the extension instance of configuration changes
  • Registering various components and features of the extension
  • Setting up the sandbox environment and starting the OCaml language server

In the context of SyntaxDocumentation, this code ensures that the extension is correctly configured to handle SyntaxDocumentation settings. The notify_configuration_changes function listens for changes to the server_syntaxDocumentation_setting and updates the extension instance accordingly. This means that any changes the user makes to the SyntaxDocumentation settings in the VSCode workspace configuration will be reflected in the extension's behaviour, ensuring that SyntaxDocumentation is enabled or disabled as per the user's preference.

let notify_configuration_changes instance =
  Workspace.onDidChangeConfiguration
    ~listener:(fun _event ->
      let syntax_documentation =
        Settings.(get server_syntaxDocumentation_setting)
      in
      Extension_instance.set_configuration instance ~syntax_documentation)
    ()

Conclusion

SyntaxDocument toggle

In this final article, we explored how to integrate SyntaxDocumentation into OCaml VSCode Platform extension as a configurable option for OCaml LSP's hover command. We covered key components such as configuring the extension manifest, managing the extension state, interacting with the OCaml language server, and handling workspace configurations. By enabling users to toggle the SyntaxDocumentation feature on or off, we can ensure a flexible and customisable development experience for all users.

Feel free to contribute to this extension on the GitHub repository: vscode-ocaml-platform. Thank you for following along in this series, and happy coding with OCaml and VSCode!

Tarides is an open-source company first. Our top priorities are to establish and tend to the OCaml community. Similarly, we’re dedicated to the development of the OCaml language and enjoy collaborating with industry partners and individual engineers to continue improving the performance and features of OCaml.

We want you to join the OCaml community, test the languages and tools, and actively be part of the language’s evolution.

Contact Tarides to see how OCaml can benefit your business and/or for support while learning OCaml. Follow us on Twitter and LinkedIn to ensure you never miss a post, and join the OCaml discussion on Discuss!