Open SourceMar 22, 20236 min read

Open sourcing the CircleCI Language Server

Benedetta Dal Canton

Product Manager

A computer screen overlaid on a grid background shows an abstract configuration file containing a gear icon.

The official CircleCI extension for Visual Studio Code is now available for anyone to download on the VS Code Marketplace. This extension was developed by the Developer Experience team of CircleCI and it includes two sets of features: the pipeline manager and the config helper.

The config helper provides language support for CircleCI YAML files. In practice, thanks to the new VS Code extension, developers and DevOps engineers have access to direct feedback about the Config file they are editing, through features like syntax validation and highlighting, rich navigation, on-hover documentation, autocompletion, and usage hints.

Like most VS Code language support packages, the CircleCI config helper is based on a Language Server.

In our case, the Language Server is an implementation of the Language Server Protocol for CircleCI YAML config files. The Language Server Protocol is defined and maintained by Microsoft.

As a team, we have decided to open source this language service because:

  1. We think it’s important to be transparent about the way the config helper works.
  2. We want the whole community to actively contribute to improving this tool.
  3. We built this Language Server to support our VS Code extension and we want to make it available to anyone who wants to integrate it into any other LSP–compatible editor or tool.

Note: We are currently working on a JetBrains plugin.

This article is for anyone who might be interested in either contributing to or using this project.

A quick technical guide

The Language Server is implemented in Go 1.19+. It is a JSON-RPC server that handles different calls specified by the LSP spec.

Editors supporting LSP send JSON RPC requests at key moments like:

  • Opening a file
  • Editing a file
  • User requested auto-completion

You can find a full list of events by exploring the LSP documentation.

The LSP documentation describes the expected response for each request. To create a response to the editor’s requests, the Language Server needs to understand the contents of the YAML file being edited. To do this, the Language Server defines a data structure. The content of the file is transformed into this data structure in a process called “parsing”. Parsing can be long and costly, it is done only on key events: opening, editing, removing a file (when the file changes). The result of parsing is then kept in memory and re-used by other JSON RPC requests like validation and auto-completion.

Currently, the Language Server supports these LSP-defined requests:

  • Opening a document
  • Modifying a document
  • Closing a document
  • Executing an editor command
  • Hovering on YAML keys (for documentation)
  • Semantics (syntax colorization)
  • Go to definition
  • Find references
  • Document autocompletion

View the server.go file for more.

The features that leverage APIs are:

  1. Orbs parsing uses the CircleCI public API to verify the existence, validity, and version of orbs
  2. Docker image autocompletion, which relies on the Docker HUB API to fetch information about valid Docker images.

Here is a schema of the Language Server architecture:

Language Server schema

Project structure

The repository codebase contains:

  • The Language Server code, written in Go
  • Some simple clients that help testing the Language Server in action

Language server

The Language Server is written in Go (1.19+). The code is divided into two directories:

  • cmd
  • packages

This code compiles into a binary, which can then be used by editors to provide Language Support functionality.

The entry point to the Go Language Server program is found in cmd/start_server.go. The main code is divided in multiple packages:

  • parser: This package is responsible for parsing a YAML file into useful structures and interfaces. This is to start if you wanted to add a CCI config field that is not yet handled.
  • server: JSON-RPC server to handle requests from the editor. A few types of LSP calls such as new Language Server capabilities or handling, can be logged/registered in this package
  • services/: This is where the Language Service is exposed to the server. You will find one package per language service feature here (for example: auto-completion in services/complete). If you want to add or change a Language Server feature you can manipulate this package.

Building the Language Server binary requires Go 1.19+, as well as Task to handle Taskfile commands.

The most useful commands are:

  • task init to install the dependencies
  • task build to build the binary in bin/start_server[.exe]
  • task test to test your code

Integrations

The directory editor/ contains basic integrations with editors or IDEs, to serve as development playgrounds.

Today, it contains only one subdirectory: vscode, a basic VS Code extension project written in TypeScript.

The VS Code extension

The repository contains the code you need to run the LSP on VS Code. This sample VS Code extension consists of one class, which spawns or stops the Language Server binary.

To build a VS Code extension, you will need Yarn installed.

The most important commands are:

  • yarn to install all the packages
  • yarn build to produce a single build
  • yarn watch to build on changes

Keep in mind that when you build the VS Code extension available in the repo, a dist/ folder will be created. It contains the transpiled JavaScript for VS Code extensions runtime.

You can find more information about VS Code extension development in the official VS Code Extension Documentation. To run a VS Code instance with the locally built Language Server:

  1. Open a workspace at the root of the repository.
  2. Choose the tab “Run and Debug” from the VS Code activity bar (on the left in the picture below).
  3. Play the “Run Extension” process.
  4. This will open a new VS Code window with a running CircleCI Config Language Server.

Run and debug

New yaml file

Plugging into your editor

The process for VS Code is simplified by the availability of the support of the Language Service Provider and the rich documentation. The Language Service Provider standard is supported by many editors. All you have to do to plug in the CircleCI Language Server is to follow the editor’s documentation. Usually, a child process is invoked by the editor using the compiled binary. You can use our released binaries or build your own.

If you make the LSP available on your IDE through a public plugin or an extension, please share it with us on CircleCI Discuss for the community to enjoy!

Pushing your work to our repo

If you want to collaborate with us and take part in this project, just fork the repository and create pull requests. We review and respond to any pull requests within a couple of days.

Try it!

We hope this article provides the information you need to get started with our open source project. You can always reach out to the Developer Experience team on CircleCI Discuss to ask all your questions.

We are working on extending the capabilities of the Config Helper to include more support for configuring and optimizing your YAML file in the IDE. If you just want to experience the features provided by the CircleCI YAML Language Server, you can download our VS Code Extension and test its behavior on any CircleCI YAML file.

More resources are available in the CircleCI YAML Language Server repository.

Copy to clipboard