Skip to content

KamalF/iop-lsp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors