Skip to content
 
 

Latest commit

 

History

27 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

IOP LSP Server

Language Server Protocol implementation for .iop files, providing:

  • Go-to-definition — jump from a type reference to its definition
  • Hover documentation — show doc comments and type info on hover
  • C file support — go-to-definition and hover for IOP-generated C identifiers (e.g., tstiop__my_struct_a__ttstiop.MyStructA in the .iop source)

Prerequisites

Installation

cd /path/to/iop-lsp
uv sync    # Creates .venv and installs all dependencies

The pyproject.toml references tree-sitter-iop as a local path dependency (../tree-sitter-iop). If your checkout is elsewhere, edit [tool.uv.sources] in pyproject.toml to point to it, then run uv sync again.

Usage

# Start the LSP server (editors do this automatically)
uv run --project /path/to/iop-lsp python -m iop_lsp --stdio

# With logging (useful for debugging)
uv run --project /path/to/iop-lsp python -m iop_lsp --stdio \
    --log-file /tmp/iop-lsp.log -v

Editor Integration

The LSP server uses standard LSP over stdio and works with any editor. On startup, it recursively indexes all .iop files under the workspace root to build a symbol table used for go-to-definition and hover.

Neovim

No plugins required — Neovim ≥ 0.11 has built-in LSP support.

Quick install: run ./install-nvim-iop-lsp.sh to automatically configure your ~/.vimrc (use --uninstall to revert).

Manual setup: add the following to your Neovim configuration (e.g., ~/.config/nvim/init.lua):

-- Teach Neovim about the .iop file extension.
vim.filetype.add({
    extension = { iop = 'iop' },
})

-- Start iop-lsp for .iop.
-- Adjust the --project path to where you cloned iop-lsp.
vim.api.nvim_create_autocmd('FileType', {
    pattern = { 'iop' },
    callback = function()
        vim.lsp.start({
            name = 'iop-lsp',
            cmd = {
                'uv', 'run',
                '--project', '/path/to/iop-lsp',
                'python', '-m', 'iop_lsp', '--stdio',
            },
            -- Walk up to .git so the LSP indexes all .iop files
            -- in the project.  Cross-file go-to-definition only
            -- works within this root.
            root_dir = vim.fs.root(0, { '.git' }),
        })
    end,
})

How it works:

  • root_dir determines which .iop files are indexed. The LSP recursively scans this directory on startup. Set it to the root of your project (e.g., lib-common) so that all type definitions are available for cross-file navigation.
  • vim.lsp.start() reuses the same LSP server for all buffers that share the same root_dir, so opening multiple .iop files in the same project is efficient.

Keybindings: Neovim ≥ 0.11 maps K to hover automatically. Go-to-definition must be mapped manually (e.g., via an LspAttach autocmd or your editor distribution's LSP keybindings). The built-in gd is Vim's "go to local declaration" and does not call the LSP.

Example keybindings:

vim.api.nvim_create_autocmd('LspAttach', {
    callback = function(args)
        local opts = { buffer = args.buf }
        vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
        vim.keymap.set('n', '<leader>e', vim.diagnostic.open_float, opts)
        vim.keymap.set('n', '[d', vim.diagnostic.goto_prev, opts)
        vim.keymap.set('n', ']d', vim.diagnostic.goto_next, opts)
    end,
})

Tree-sitter highlighting (optional)

If you have tree-sitter-iop built locally with its iop.so parser, you can enable syntax highlighting for .iop files without any plugin:

-- Adjust the path to your tree-sitter-iop checkout.
local ts_iop = '/path/to/tree-sitter-iop'

pcall(function()
    vim.treesitter.language.add('iop', { path = ts_iop .. '/iop.so' })
    vim.opt.runtimepath:prepend(ts_iop)  -- finds queries/iop/highlights.scm
end)

vim.api.nvim_create_autocmd('FileType', {
    pattern = 'iop',
    callback = function() vim.treesitter.start() end,
})

C file support (go-to-definition for IOP types in C)

When you use IOP-generated types in C code (e.g., tstiop__my_struct_a__t), iop-lsp can resolve them back to their .iop source definitions. This works for .c, .h, and .blk files.

To enable this, configure your editor to attach iop-lsp to C files before clangd. iop-lsp only responds for identifiers it recognizes (IOP C names) and returns null for everything else, so the editor falls back to clangd for normal C symbols.

Neovim

Add c and h to the FileType pattern so iop-lsp attaches to C files too:

vim.api.nvim_create_autocmd('FileType', {
    pattern = { 'iop', 'c' },
    callback = function()
        vim.lsp.start({
            name = 'iop-lsp',
            cmd = {
                'uv', 'run',
                '--project', '/path/to/iop-lsp',
                'python', '-m', 'iop_lsp', '--stdio',
            },
            root_dir = vim.fs.root(0, { '.git' }),
        })
    end,
})

When both iop-lsp and clangd are attached, Neovim sends requests to all servers. To ensure iop-lsp takes priority for go-to-definition on IOP types, list it first in vim.lsp.enable() or use a custom gd keymap that prefers iop-lsp.

Emacs

Emacs ≥ 29 ships with Eglot, a built-in LSP client. The easiest way to run both clangd and iop-lsp on the same buffer is to use rassumfrassum, an LSP multiplexer.

Create a rassumfrassum preset (e.g. ~/.config/rass/presets/cmode-iop.py):

def servers():
    return [
        ['clangd'],
        ['uv', 'run', '--project', '/path/to/iop-lsp', '-m', 'iop_lsp',
         '--stdio']
    ]

Then register the preset with Eglot:

(add-to-list 'eglot-server-programs
             '((c++-mode c-mode) . ("rass" "cmode-iop")))

Helix

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

[language-server.iop-lsp]
command = "uv"
args = ["run", "--project", "/path/to/iop-lsp",
        "python", "-m", "iop_lsp", "--stdio"]

[[language]]
name = "iop"
scope = "source.iop"
file-types = ["iop"]
comment-token = "//"
block-comment-tokens = { start = "/*", end = "*/" }
indent = { tab-width = 4, unit = "    " }
language-servers = ["iop-lsp"]

Then in Helix:

  • gd — Go to definition (on a type reference)
  • <space>k — Show hover documentation

List iop-lsp before clangd in the language-servers array for C:

[[language]]
name = "c"
language-servers = ["iop-lsp", "clangd"]

The first server in the array gets priority per-feature, so iop-lsp results are preferred when available.

Testing

uv run python -m pytest tests/ -v

Architecture

  • iop_lsp/server.py — LSP server, request handlers
  • iop_lsp/indexer.py — Parse .iop files with tree-sitter, build symbol table
  • iop_lsp/symbols.py — Symbol data structures
  • iop_lsp/c_mapping.py — IOP ↔ C name conversions (CamelCase ↔ snake_case)
  • iop_lsp/doc_comments.py — Extract doc comments from AST
  • iop_lsp/schema.py — IOP type resolution for YAML support
  • iop_lsp/yaml_support.py — YAML parsing and cursor-to-type mapping

About

LSP for the IOP language used in lib-common

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages