Language Server Protocol Support in BBEdit 14

This document describes BBEdit's support for the Language Server Protocol, introduced in version 14.0.

Contents

Introduction
Feature Support
Language Server Placement
Configuring BBEdit for Language Servers
Caveats
Advanced Configuration


Introduction

BBEdit 14.0 and later feature built-in support for the Language Server Protocol, (occasionally referred to here as "LSP", not to be confused with Lightspeed Pascal).

The protocol defines a communication standard by which outboard programs ("servers") can respond to requests from an editor ("client") with appropriate responses based on the content and location of files written in the language(s) that the server supports.

(Despite the term "server", nothing is transmitted over a network, nor does any data otherwise leave your computer by means of LSP support.)

By using LSP when a suitable server is available on your computer, BBEdit can provide improved text completion suggestions, provide enhanced navigation both within and between files, and enhance or enable other features and behaviors.

Feature Support

All of BBEdit's core features work just fine without a language server, so if you never install a server, BBEdit will behave just as it always has.

However, when you install a suitable language server, a number of advanced features and enhancements become available.

Note: the availability of specific features and behaviors described here depends entirely on the specific language server. Not all language server support all features; and not all language servers are guaranteed to behave correctly or predictably for any given source code project.

  • BBEdit will ask the server for completions rather than attempting to compute them on its own. Completions supplied by the language server are significantly more accurate and complete than those available using the built-in mechanisms.

    LSP completions for Objective-C code

  • If a language server supports the "signature help" feature, BBEdit enables the "Show Parameter Help" command on the Edit menu; choosing this will open a panel providing assistance for filling in function parameters at the current insertion point (if applicable).

    LSP parameter help

  • If a language server reports issues (errors and warnings) for a file in which you're editing, ranges corresponding to those issues get highlighted according to their severity, and the corresponding lines are highlighted in the line number bar.

    LSP issue for a line of code

    Click on a highlight in the line number bar to open a popover showing issues on that line; double-click on an item in the popover to select the relevant range of text and dismiss the popover. The popover has a button, "Show All". If enabled this will open a results window showing all of the issues in the current file.

    Use the "Show/Hide Issues" command on the View → Text Display menu to toggle the display of issues in the text view.

    The "Editing" preferences pane has a setting to show tick marks in the scroll bar to indicate lines containing issues reported by the language server.

    When issues are available, there's an item to the right of the function popup with a colored (or gray) dot and a number. The dot is green if the server returned an empty list ("no issues") yellow if there are warnings, red if there are errors, and the number indicates the total number of issues found by the language server in the file.

    LSP issues for entire document

    If the dot is gray and shows a dash (-), the server has not (yet) returned any diagnostics information for this file. Click on the indicator to open a popover which shows all of the issues in the file; clicking on an item in the list will select it in the document. (Double-click an item to select the location and dismiss the popover.)

    You can also use the "Open Issues Panel" keyboard equivalent (settable in the "Navigation Bar" section of the Menus & Shortcuts preferences) to open this panel. If the insertion point is in an underlined issue, this keyboard equivalent will show a panel for just that issue.

    Note: Whether a server returns issues at all, or which issues it reports, are entirely up to the server. In some cases this may be configured externally; in other cases not.

  • If a language server claims that it can format documents, the "Reformat Document" command sends the document's text to the server, along with information about the tab width, whether or not "Auto-Expand Tabs" is turned on, whether "Strip trailing whitespace" is turned on, and whether "Ensure file ends with line break" is turned on. The server is expected to use all of this information to generate correctly formatted text, but is not required or guaranteed to do so.

    Likewise, if the server claims that it can format a range of text in a document, the "Reformat Selection" command sends the selected range of text to the server for reformatting. Note that some servers may support range formatting but be very fussy about the range of text that is selected for formatting.

  • Command-double-click on a word will direct the request to an appropriate language server and perform the equivalent of "Go to Definition", if possible.

  • When right-clicking on a word in a documents served by an LSP server, BBEdit will ask the server for declarations and definitions of the word. If one (of each) is found, the contextual menu will contain "Go to Declaration" and "Go to Definition", as appropriate. (We disclaim all responsibility for nonsense results returned by the language server, we can only do what we're told.)

  • The "Go" menu contains two commands: "Go to Declaration" and "Go to Definition". These both require a running language server for the document's language.

    Choosing either command will ask the language server for the location of the respective symbol declaration/definition, based on the location of the insertion point (or start of the selection range).

    Note: If you are using a C-family language for which clangd is the language server, these commands are effectively synonymous, and will alternate (if appropriate) between the declaration and the definition of the selected symbol when the same command is chosen repeatedly. This is an intentional behavior in clangd itself, and is not a bug in either BBEdit or in clangd.

  • "Find References to Selected Symbol" on the Search menu will ask the server to return all occurrences of places where the symbol is used. The search symbol is determined based on the selection range, when a word is selected or the insertion point is in the middle of a word. The results are presented in a standard results window.

    In addition, right-clicking in or on a selected word will add a "References" submenu to the resulting contextual menu, showing the references (if any) to that symbol.

    Note: Some servers do not support reference discovery; others support references but may not report them until a code index has been completed. This will vary by server.

  • "Find Symbol in Workspace" command on the Search menu will open a modal panel so that you can search for symbols. Each search request goes to the server, which is in complete control of what (if any) results get returned.

    Note: Some servers do not support this feature; others support workspace searching but may not return any results until a code index has been completed. This will vary by server.

  • The "(Go to) Named Symbol" command will present symbols returned by an available language server, if possible.

  • "Find Definition" will ask the language server to locate the requested symbol, if available.

    Note: Some servers do not support this feature; others support "go to definition" but may not find anything until a code index has been completed. This will vary by server.


Language Server Placement

In order for a language server to be usable by BBEdit, it must be installed and (in some cases) configured according to the server developer's instructions. Most of the time, a server may be installed using Homebrew (or other package manager) or npm, but may also be built from source.

BBEdit will look for language server executables in the following locations, in order:

  1. In its "Language Servers" application support folder;

  2. In the "Language Servers" folders in any installed BBEdit packages;

  3. In your $PATH as configured in your login shell environment;

When a server has been installed via package manager or built from source, it is usually installed somewhere in your $PATH, and so BBEdit will locate it automatically without any further configuration.

In some cases you may find it desirable to override a $PATH or BBEdit package install. If so, putting the server executable or (preferably) a symlink to it in the "Language Servers" support folder will accomplish this.

For C-family languages and Swift, BBEdit will locate the prebuilt language servers supplied with Xcode (clangd and sourcekit-lsp, respectively). However, we recommend that you symlink a Homebrew or source-built clangd and/or sourcekit-lsp, so as to avoid using the versions included with Xcode. More about this is in the "Preconfigured Language Servers" section, below.

Package Support

Language servers can be supplied in BBEdit packages, which might be useful for cases where the server is not a convenient standalone installation.

The server's primary executable should be located in the "Language Servers" directory within the package contents, and BBEdit will find it there when the command is configured solely by name in the language's LSP configuration. Please read the "Packages" section of the user manual to learn more about the construction and organization of packages.

back to top


Prerequisites for installing

Installation of any given language server almost always requires the use of some package manager, whether it's Homebrew, MacPorts, or a language-specific package manager such as pip or npm. (We use Homebrew, so the instructions in this document reflect that. MacPorts will do the job as well.) This may in turn require installation of the Xcode command-line tools, but your package manager will prompt you and provide instructions as necessary.

On the versions of macOS that BBEdit supports, pip requires a version of Python that is newer than the version shipped with the OS. For that reason, use Homebrew or MacPorts to install the current Python release; you should then be able to use pip install to install your language server.

To install Node and npm, go to https://nodejs.org/en/download/, download and run the macOS installer. Thereafter you can use "npm install -g" to install any language server that requires you to do so.

Alternatively, if you are using Homebrew, use "brew install node" to install the Node framework, which includes npm; or install node/npm using whatever package manager you have.

Note: Tech Support cannot assist with package manager or server installation issues.

back to top


Configuring BBEdit for Language Servers

Once the server itself is installed and configured, you may need to configure BBEdit to use the server. (This is necessary only if BBEdit is not preconfigured to use that particular server, or if you wish to use a server other than the one that is factory-configured.)

Note: The "Installed Languages" list in the Languages preferences includes a column for the language server status. A gray circle indicates that the server is disabled or not configured; a green circle indicates that the server is ready; and a red circle indicates that the server is configured, but that the server executable could not be found (or is not suitable for use, such as an Intel binary on an M1 Mac).

To configure BBEdit to use a server for a particular language, go to the Languages preferences and add (or edit) a custom language settings entry for the desired language.

The custom language preferences panel has a "Server" tab; this contains the configuration settings for that language's LSP support.

Language server configuration for Swift

The "Command" setting should be the literal Unix executable or script that runs the server. Most of the time this is simply the name of the server executable, e.g. "clangd" or "html-languageserver".

The command you enter must be visible to BBEdit. See the "Language Server Placement" section for details.

Server arguments should be separated by spaces.

The "Language ID" may be left blank and BBEdit will generate one; however, if your server requires a specific language ID (some do) and it isn't the same as the automatically generated placeholder, you should enter it here. If you don't know, check the Language Server Protocol specification (search for "language ID") and see if there is a suitable ID available.

Finding a Server

There are many servers available. Some of BBEdit's built-in language modules are preconfigured to use these servers, but in general is up to you to find, install, and configure your desired language server.

(The exception: BBEdit uses clangd for the C-family languages and sourcekit-lsp for Swift. These are bundled with Xcode, but please see the notes in the "Preconfigured Language Servers" section for important information.)

In many cases BBEdit has preconfigured support for one or more of these, and so all you need to do is install the server according to its specific instructions, and BBEdit will use it automatically.

BBEdit does not come with any servers bundled, and we cannot make any recommendations, nor can we guarantee that any server is fit for purpose, even if we have provided preconfigured support for it.

You can find servers for many different languages in the LSP implementors list as well as on the langserver.org community site.

If you come up with a recipe for installing and configuring a language server, and it works, we will buy you a donut. (Offer valid only in the US, but if you're not in the US we'll figure something out. Void where prohibited,)

Preconfigured Servers

BBEdit has preconfigured support for the following language servers. Please note that in all cases this is intended solely as a convenience. We make no representations as to the fitness or suitability of any of these language servers for any purpose, nor are we responsible for their behavior.

In all cases, BBEdit will use a correctly installed and configured server the next time you open a file written in the language configured to use that server; it is not necessary to quit and relaunch the application.

  • C-family languages (C, C++, Objective-C, Objective-C++) BBEdit uses clangd directly to support these languages. If you have Xcode installed, clangd is included and BBEdit will use it. However, at this writing we recommend that you not rely on the Xcode-bundled clangd, because it is out of date and has bugs that cause it to respond incorrectly to certain requests from BBEdit. Instead we recommend that you use "brew install llvm"" and then symlink /usr/local/opt/llvm/bin/clangd into BBEdit's "Language Servers" folder.

    There are other important considerations to be aware of when using clangd, please see the "Caveats" section, below.

  • CSS To install the server, run "npm install -g vscode-css-languageserver-bin" in a Terminal window.

  • Clojure To install the server, clone the server's Git repository or browse the repository and download install-latest-clojure-lsp.sh. The install script will install the language server in ~/bin/. Make sure that this is in your PATH, otherwise symlink or move the executable to some directory that is.

  • Fortran To install the server run "pip install -U fortran-language-server" in a Terminal window.

  • Go To install the server run "cd /tmp && GO111MODULE=on go get golang.org/x/tools/gopls@latest" in a Terminal window.

  • HTML To install the server run "npm install -g vscode-html-languageserver-bin" in a Terminal window.

  • JavaScript To install the server, run (in a Terminal window) "npm install -g typescript" followed by "npm install -g typescript-language-server" (despite its name, it supports JavaScript as well).

  • JSON To install the server run "npm install -g vscode-json-languageserver". Note: since JSON files can be extremely large, the JSON server itself may have trouble ingesting such files, and/or communication with the server may be very slow. For this reason, if you routinely work with large JSON files, you may find it better to forego using the JSON language server.

  • PHP We recommend Intelephense, and BBEdit is preconfigured to use it. You can install the server with "npm install -g intelephense" and if it works for you, please support the developer and unlock the server's paid features by buying a license. (It's only US$12, you can do it.)

  • Python To install the server run "pip install -U jedi-language-server" in a Terminal window.

    If you have trouble installing the server, you will probably need a more recent Python than the default provided with macOS; and additional setup instructions are available at the language server's GitHub project page.

  • R To install the server, open a Terminal window and start the R REPL. Then run "install.packages("languageserver")". You'll probably get a warning about unable to load shared object '/Library/Frameworks/R.framework/Resources/modules//R_X11.so'; you can ignore this.

  • Ruby BBEdit's Ruby language module is preconfigured to use Solargraph. Installing the server on macOS is not exactly straightforward, but the following seems to work:

    % brew install ruby # to include a newer Ruby than macOS provides
    % sudo /usr/local/opt/ruby/bin/gem install solargraph
    

    You must also ensure that the appropriate RubyGems directory is in your $PATH, so that BBEdit can find the solargraph executable. Once you have done so, you can test this using Terminal:

    % which solargraph
    # will print out the path to the Solargraph executable if correctly installed
    
  • Swift BBEdit uses sourcekit-lsp if it is available. If you have Xcode installed, sourcekit-lsp is included and BBEdit will use that version. If you install an alternative version, it must be somewhere in your $PATH or symlinked into BBEdit's "Language Servers" support folder in order to override the version supplied with Xcode.

  • TeX BBEdit's TeX language module is preconfigured to use texlab.

  • TypeScript To install the server, run (in a Terminal window) "npm install -g typescript" followed by "npm install -g typescript-language-server". This language server also supports JavaScript.

  • Vue.js To install the server, run "npm install -g vls".

  • YAML To install the server run "npm install -g yaml-language-server" in a Terminal window.

back to top


Caveats

  • BBEdit does not use language servers to enhance syntax coloring; do not expect any changes in coloring behavior.

  • BBEdit does not use language server data to populate the function menu or generate folding information; this remains under the control of the language module plug-in.

  • clangd relies on a "compilation database" which provides necessary information about compiler options and lists the files relevant to the current project workspace. The compilation database is a JSON file named "compile_commands.json" which lives at the root directory of the project. cmake-based projects can be configured to emit the compilation database.

    clangd does not support Xcode projects as a data source. Here is a recipe (in the form of a series of shell commands, well suited for a Shell Worksheet) for generating a compile_commands.json file. You may need to make some adjustments for your particular setup.

    rm -rf /tmp/cdb # could use some other path, or uniquify the folder name, but this is as good as any
    
    cd /path/to/your/project
    
    xcodebuild clean build -configuration Debug -scheme MyAppName \
        "OTHER_CFLAGS = \$(inherited) -gen-cdb-fragment-path /tmp/cdb" \
        GCC_PRECOMPILE_PREFIX_HEADER=NO \
        CLANG_ENABLE_MODULE_DEBUGGING=NO \
        COMPILER_INDEX_STORE_ENABLE=NO \
        ONLY_ACTIVE_ARCH=YES
    
    cat /tmp/cdb/*.json > compile_commands.json
    sed -i'' -e '1s/^/[/' compile_commands.json # insert a bracket at the beginning
    sed -i'' -e '$ s/,$/\]/' compile_commands.json # remove the last comma and insert a closing bracket
    

    The additional build options to xcodebuild are in order to (respectively) generate compile database JSON fragments (one per file compiled), omit extraneous compiler flags which confuse clangd, and shorten the build time by limiting it to one architecture.

    After the build finishes, we recommend that you clean and build your project in Xcode again.

    Note that it's only necessary to generate the compile database when setting up for the first time, or when making substantive changes to your project (such as adding or removing files). You could come up with a way to regenerate the compile database recipe as a build phase, but that will just consume unnecessary time and energy on every build.

back to top


Advanced Configuration Topics

Some language servers support implementation-specific initialization (startup) options, as well as implementation-specific "workspace" options. LSP does not provide any guidance or standardization of these options, nor does it govern where they are stored or how they are represented.

To customize a language server's initialization and workspace options, place a valid and well-formed JSON file in BBEdit's Language Servers/Configuration/ directory, creating both directories as necessary. Then, in the Languages preferences, choose the JSON file from the "Configuration:" popup menu in the Server tab of the custom settings sheet. Select "Default" to use the language module's default server initialization options, if applicable.

Warning: customizing server initialization or workspace configurations can cause the language server to crash or otherwise behave unexpectedly. We cannot provide assistance or documentation for customizing any language server, nor can we assist when things go wrong other than by recommending that you remove the custom configuration.

The JSON file's structure is as follows:

{
    "initializationOptions":
    {
        //  stuff goes here
    }

    "workspaceConfigurations":
    {
        //  stuff goes here
    }
}

Initialization Options

The initializationOptions key contains values that are sent in the initializationOptions parameter to the server. Ordinarily this is empty, but some servers require additional initialization options.

For example, BBEdit's built-in HTML language module has a custom initialization configuration, because without one, the Microsoft HTML language server will refuse to start. The configuration looks like this:

{
    "initializationOptions":
    {
        {
            "embeddedLanguages":
            {
                "javascript": 1,
                "css": 1
            }
        }
    }
}

If you have a custom configuration file specified in the language server settings, BBEdit will add the values in initializationOptions to the corresponding initializationOptions dictionary that it sends to the server. This allows you to supplement the factory default initializationOptions, if BBEdit has any (it doesn't always).

Warning: This also means that you can override BBEdit's factory defaults, by specifying initialization options that overlap with the ones supplied from the factory. We don't recommend that you do this ("high voltage within; refer servicing to qualified personnel") but if you must, you can.

Workspace-Specific Configuration

The language server, at its sole discretion, may send a request to BBEdit requesting "workspace configuration" options. These are server-specific settings that apply only within the directory that the server believes contains the project in which you are working.

The request from the server includes the file path to the workspace, and the "section", or group of settings, for which the server requests customized settings.

An example of this can be found in the gopls language server; when you open a Go file at (for example) ~/Desktop/LSP Test/main.go, the server will request settings in the gopls section:

scopeUri = "file:///Users/me/Desktop/LSP%20Test/";
section = gopls;

This is an opportunity to customize the behavior of gopls for all documents in ~/Desktop/LSP Test/.

To do this, your JSON configuration file should contain a workspaceConfigurations dictionary, which lists all of the workspaces for which you wish to customize the server's behaviors. Each workspace dictionary is specified by its human-readable path:

"workspaceConfigurations":
{
    "~/Desktop/LSP Test/":
    {
        "gopls":
        {
            "usePlaceholders": true
            "completeUnimported": true
        }
    }
}

So in this example, we've specified usePlaceholders and completeUnimported for gopls, for all documents residing within the LSP Test folder in our home Desktop.

(Note: You can use a fully qualified path name as well, e.g. "/Users/siegel/Desktop/LSP Test", but for directories in your home directory, eliding with "~" is fine. The final "/" directory separator is allowed, but optional.)

In addition to directory-specific configurations, you can specify configurations that apply to all workspace directories. Do this by specifying a dictionary whose name is "*" rather than a directory path.

So, if you'd like to enable placeholders for gopls completions in all workspaces, but completeUnimported only for a specific workspace, your JSON would look like this:

"workspaceConfigurations":
{
    //  global configuration applied to all workspaces
    "*":
    {
        "gopls":
        {
            "usePlaceholders": true
        }
    },

    //  configuration specific to this workspace
    "~/Desktop/LSP Test":
    {
        "gopls":
        {
            "completeUnimported": true
        }
    }
}

Note that each workspace configuration can exist only once in the JSON, so each configuration must contain sections for all of the sections that may be requested by language servers. So, hypothetically:

"workspaceConfigurations":
{

    "~/Desktop/LSP Test":
    {
        "gopls":
        {
            "usePlaceholders": true
        },

        "clangd":
        {
            // clangd stuff goes here
        },

        "css":
        {
            // css stuff goes here
        }
    }
}

Note: A language server is not obligated to ask BBEdit for a workspace configuration, so it's possible that providing a custom configuration will not affect the server's behavior.

Warning: As with custom initialization parameters, specifying incorrect workspace configurations can cause the language server to crash or behave unexpectedly. Also as with custom initialization parameters, we cannot provide assistance or documentation for specifying workspace configurations, nor can we assist when things go wrong other than by recommending that you remove the custom configuration.

In-Folder Workspace Configuration

In addition to global configuration, BBEdit also supports the placement of a JSON file in your workspace's folder, which contains a dictionary for each section. This is generally desirable when you'd like to maintain version control for your workspace configuration, and/or share it with others who are working on the same source code.

For in-folder configuration, create a JSON file named ".BBEditLSPWorkspaceConfig.json" in the workspace directory. (The file's name begins with a period to hide it from the finder and other UI mechanics; but you can use the bbedit command-line tool to edit it in BBEdit.) The JSON file should be a dictionary, which contains an entry for each "section" that may be requested by the language server. Thus, the in-workspace configuration file resembles the global configuration, but does not contain the top two levels of containment.

Using the above example as a reference, to configure workspace-specific options for gopls and other hypothetical language server sections in the same workspace, create a JSON file at "~/Desktop/LSP Test/.BBEditLSPWorkspaceConfig.json", which contains the following:

{
    "gopls":
    {
        "usePlaceholders": true
    },

    "clangd":
    {
        // clangd stuff goes here
    },

    "css":
    {
        // css stuff goes here
    }
}

BBEdit will read .BBEditLSPWorkspaceConfig.json from disk each time the server requests a workspace configuration for that directory (which, again, happens solely at the server's discretion, if at all).

Configuration Syntax Note

BBEdit allows C-style line and block comments in JSON workspace configuration files, so that they can be self-documenting as needed (especially when shared in a source code repository).

Thus, the examples above can be used as-is, even though they are not strictly valid JSON. BBEdit's syntax coloring, and the JSON language server, adhere to the strict definition of JSON in which comments are not allowed. This is intentional.

Language Module Implementors

If you are developing a language module, you can include a ServerInitParams key in your BBLMLanguageServerInfo dictionary. For a compiled language module, this key's value may be either a string, which is used as the name of a configuration file in the language module's bundle Resources directory, or it may be a fully expressed dictionary, whose contents are placed in the initializationOptions dictionary that is sent to the server at startup.

For example, in BBEdit's built-in HTML language module, BBLMLanguageServerInfo looks like this:

<key>BBLMLanguageServerInfo</key>
<dict>
    <key>ServerCommand</key>
    <string>html-languageserver</string>

    <key>ServerArguments</key>
    <array>
        <string>--stdio</string>
    </array>

    <key>ServerLanguageID</key>
    <string>html</string>

    <key>ServerInitParams</key>
    <string>HTMLLSPInitParams.json</string>
</dict>

HTMLLSPInitParams.json is stored in the language module's Resources directory. (You can look, but don't change it or else you'll break BBEdit's code signature and cause all kinds of problems.)

Alternatively, the custom initialization options can be written like this:

<key>ServerInitParams</key>
<dict>
    <key>embeddedLanguages</key>
    <dict>
        <key>javascript</key>
        <true />
        <key>css</key>
        <true />
    </dict>
</dict>

If you're writing a codeless language module, you must use this form instead of referring to an external file, since codeless language modules don't have a bundle to contain a Resources directory.

Note that, in either case, custom initialization options can be supplemented or overridden by a user-specified configuration file in the Language Servers/Configuration directory.

Finally, please note that language modules cannot specify workspace configurations.

back to top


fin