Language Server (LSP)

BioLang includes a full Language Server Protocol implementation that provides intelligent editor support. The LSP server (bl-lsp) supports diagnostics, code completion, hover information, go-to-definition, symbol renaming, and document formatting. It works with any LSP-compatible editor.

Features

  • Diagnostics — Real-time error and warning underlines as you type. Catches undefined variables, type mismatches, unused bindings, and invalid sequence literals.
  • Completion — Context-aware suggestions for variables, builtins, methods, file paths, and API function arguments. Includes documentation snippets and type signatures.
  • Hover — Hover over any identifier to see its type, documentation, and signature. For bio functions, shows parameter descriptions and return types.
  • Go to Definition — Jump to where a variable or function was defined, including across imported files.
  • Rename Symbol — Rename a variable or function across all references in the file and its imports.
  • Document Formatting — Format the entire file or a selection using the same formatter as bl fmt.

VS Code Setup

Install the BioLang extension from the VS Code marketplace, or configure manually:

Option 1: Extension (Recommended)

# Install from the marketplace
code --install-extension bioras.biolang-vscode

The extension includes syntax highlighting, the LSP client, snippet support, and a file icon for .bl files.

Option 2: Manual Configuration

Add to your VS Code settings.json:

{
  "biolang.server.path": "bl-lsp",
  "biolang.server.args": [],
  "biolang.trace.server": "messages",
  "[biolang]": {
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "bioras.biolang-vscode"
  }
}

Neovim Setup

Using nvim-lspconfig:

-- In your Neovim LSP configuration (e.g., init.lua)
local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')

-- Register the BioLang LSP
if not configs.biolang then
  configs.biolang = {
    default_config = {
      cmd = { 'bl-lsp' },
      filetypes = { 'biolang' },
      root_dir = lspconfig.util.root_pattern('biolang.toml', '.git'),
      settings = {},
    },
  }
end

lspconfig.biolang.setup({
  on_attach = function(client, bufnr)
    -- Enable completion triggered by <c-x><c-o>
    vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')

    -- Keybindings
    local opts = { noremap = true, silent = true, buffer = bufnr }
    vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
    vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
    vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, opts)
    vim.keymap.set('n', '<leader>f', vim.lsp.buf.format, opts)
  end,
})

For file type detection, add to ~/.config/nvim/ftdetect/biolang.lua:

vim.filetype.add({
  extension = {
    bl = 'biolang',
  },
})

Emacs Setup

Using lsp-mode and eglot:

With lsp-mode

;; Add BioLang major mode (from MELPA or manual)
(require 'biolang-mode)

;; Register the LSP server
(with-eval-after-load 'lsp-mode
  (add-to-list 'lsp-language-id-configuration '(biolang-mode . "biolang"))
  (lsp-register-client
   (make-lsp-client
    :new-connection (lsp-stdio-connection '("bl-lsp"))
    :activation-fn (lsp-activate-on "biolang")
    :server-id 'biolang-ls)))

;; Enable LSP for BioLang files
(add-hook 'biolang-mode-hook #'lsp)

With eglot (Emacs 29+)

(require 'biolang-mode)
(add-to-list 'eglot-server-programs
             '(biolang-mode "bl-lsp"))
(add-hook 'biolang-mode-hook #'eglot-ensure)

Helix Setup

Add to ~/.config/helix/languages.toml:

[[language]]
name = "biolang"
scope = "source.biolang"
file-types = ["bl"]
language-servers = ["bl-lsp"]
indent = { tab-width = 2, unit = "  " }
comment-token = "#"

[language-server.bl-lsp]
command = "bl-lsp"

Completion Details

The LSP provides rich completion across several categories:

# After typing "seq |> ", completions show:
  gc_content()          Float — GC content as fraction 0.0-1.0
  reverse_complement()  DNA — Reverse complement of sequence
  complement()          DNA — Complement without reversing
  transcribe()          RNA — Transcribe DNA to RNA
  len()                 Int — Sequence length
  slice(start, end)     DNA — Extract subsequence
  validate()            Bool — Validate sequence alphabet
  kmer_count(k)         Map — Count all k-mers

# After typing "ncbi_", completions show:
  ncbi_search(db, term)     — Search an NCBI database
  ncbi_fetch(ids, db)       — Fetch records by ID list
  ncbi_summary(db, id)      — Get document summary
  ncbi_gene(term)           — Quick gene lookup
  ncbi_sequence(id)         — Fetch sequence as FASTA text
  ncbi_pubmed(term)         — Search PubMed

Hover Information

Hovering over identifiers shows contextual information:

# Hovering over gc_content shows:
┌──────────────────────────────────────────────┐
│ gc_content() -> Float                        │
│                                              │
│ Calculate the GC content (fraction of G+C    │
│ bases) of a DNA or RNA sequence.             │
│                                              │
│ Returns: Float between 0.0 and 1.0           │
│                                              │
│ Example:                                     │
│   dna"ATCGATCG" |> gc_content()  # => 0.5   │
└──────────────────────────────────────────────┘

# Hovering over a variable shows its inferred type:
┌──────────────────────────────────────────────┐
│ seq: DNA                                     │
│ Defined at line 3                            │
│ Value: dna"ATCGATCG"                         │
└──────────────────────────────────────────────┘

Diagnostics

The LSP reports errors and warnings in real time:

# Undefined variable (red underline)
  samplse |> print()
  ^^^^^^^
  error: undefined variable 'samplse' (did you mean 'samples'?)

# Type mismatch (red underline)
  dna"ATCG" |> translate()
             ^^^^^^^^^^
  error: 'translate' expects RNA, got DNA (did you mean to call 'transcribe' first?)

# Unused variable (yellow underline)
  let temp = compute_something()
  ^^^^
  warning: unused variable 'temp'

# Invalid sequence literal (red underline)
  let seq = dna"ATCXYZ"
            ^^^
  error: invalid DNA characters 'XYZ' at position 3

Debug Logging

For troubleshooting LSP issues, enable debug logging:

# Start LSP with debug output
bl lsp --log-level debug --log-file /tmp/bl-lsp.log

# Watch the log
tail -f /tmp/bl-lsp.log