Semcode LSP Server
October 10, 2025 ยท View on GitHub
A Language Server Protocol (LSP) server that provides navigation features for C/C++ codebases indexed by semcode.
Features
- Go to Definition: Jump to definitions of functions, macros, types, and typedefs using semcode's semantic database
- Find References: Show all places where a symbol is referenced (callers)
- Git-Aware Lookups: Automatically finds the correct version at your current commit
- Configuration Support: Configurable database path through LSP client settings
Building
cargo build --release --bin semcode-lsp
Usage
The LSP server communicates over stdin/stdout using the JSON-RPC 2.0 protocol as specified by the Language Server Protocol.
Prerequisites
-
A semcode-indexed codebase:
semcode-index --source /path/to/your/code -
The resulting
.semcode.dbdatabase in your workspace directory
Neovim Configuration
Using nvim-lspconfig
Add this to your Neovim configuration (~/.config/nvim/init.lua or similar):
-- Ensure you have nvim-lspconfig installed
-- Using lazy.nvim:
-- { 'neovim/nvim-lspconfig' }
-- Configure semcode LSP
local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')
-- Define semcode-lsp if it's not already defined
if not configs.semcode_lsp then
configs.semcode_lsp = {
default_config = {
cmd = { '/path/to/semcode/target/release/semcode-lsp' },
filetypes = { 'c', 'cpp', 'cc', 'h', 'hpp' },
root_dir = function(fname)
-- Look for .semcode.db or use git root
return lspconfig.util.find_git_ancestor(fname) or
lspconfig.util.root_pattern('.semcode.db')(fname) or
vim.fn.getcwd()
end,
settings = {
semcode = {
database_path = nil -- Uses workspace/.semcode.db by default
}
}
}
}
end
-- Setup the LSP
lspconfig.semcode_lsp.setup({
-- Optional: custom database path
settings = {
semcode = {
database_path = "/custom/path/to/.semcode.db" -- Optional
}
}
})
-- Optional: Set up keybindings
vim.api.nvim_create_autocmd('LspAttach', {
group = vim.api.nvim_create_augroup('UserLspConfig', {}),
callback = function(ev)
-- Enable completion triggered by <c-x><c-o>
vim.bo[ev.buf].omnifunc = 'v:lua.vim.lsp.omnifunc'
local opts = { buffer = ev.buf }
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts) -- Go to definition
vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts) -- Find references (callers)
vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts)
vim.keymap.set('n', '<C-k>', vim.lsp.buf.signature_help, opts)
vim.keymap.set('n', '<space>rn', vim.lsp.buf.rename, opts)
vim.keymap.set('n', '<space>ca', vim.lsp.buf.code_action, opts)
end,
})
Manual Configuration
If you prefer manual configuration without nvim-lspconfig:
vim.lsp.start({
name = 'semcode-lsp',
cmd = { '/path/to/semcode/target/release/semcode-lsp' },
root_dir = vim.fs.dirname(vim.fs.find({'.semcode.db', '.git'}, { upward = true })[1]),
settings = {
semcode = {
database_path = nil -- Optional custom path
}
}
})
Configuration Options
The LSP server accepts the following configuration through the semcode section:
database_path(string, optional): Custom path to the semcode database. If not specified, the server will:- Look for
.semcode.dbin the workspace directory - Fall back to
./.semcode.dbin the current directory
- Look for
Testing
To test the LSP server manually, you can run it and send JSON-RPC messages:
# Build the server
cargo build --release --bin semcode-lsp
# Run the server (it will read from stdin and write to stdout)
./target/release/semcode-lsp
Example initialization message:
Content-Length: 246
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":null,"rootUri":"file:///path/to/your/workspace","capabilities":{},"initializationOptions":{},"workspaceFolders":null}}
How It Works
- Database Connection: The server connects to the semcode database (
.semcode.db) in your workspace - Git Awareness: The server detects your current git commit (HEAD) to ensure it finds the correct version of all symbols
- Go to Definition: When you request "go to definition" (
gd) on an identifier, the server:- Extracts the identifier name at the cursor position
- Uses git-aware lookup to query the database at your current commit
- Checks in priority order: function > macro > type > typedef
- Returns the file path and line number where it is defined
- Your editor jumps to the definition location
- Find References: When you request "find references" (
gr) on an identifier, the server:- Extracts the identifier name at the cursor position
- Queries the database for all symbols that reference this identifier (callers)
- Uses git-aware lookup to find references that exist at your current commit
- Returns a list of all locations where it is referenced
- Your editor displays the list of references
Git-Aware Lookups
The LSP server uses git-aware lookups to ensure you always jump to the correct version of a symbol:
- On initialization, it determines your current git commit (
HEAD) - When looking up symbols, it finds the version that exists at your current commit
- If you have indexed multiple versions of your codebase, it intelligently selects the right one
- Falls back to non-git-aware lookup if not in a git repository
Supported Languages
- C (
.c,.h) - C++ (
.cpp,.cc,.cxx,.hpp,.hxx)
Troubleshooting
Database Not Found
If you see "Semcode database not found" messages:
- Ensure you've run
semcode-indexon your codebase - Check that
.semcode.dbexists in your workspace - Verify the database path configuration
Symbol Not Found
If "go to definition" doesn't work for a symbol:
- Ensure the symbol (function, macro, type, or typedef) was indexed by semcode
- Try using the
semcodeCLI tool to verify the symbol exists in the database - Check that you're using the correct identifier name (no typos)
- Git-related issues:
- Make sure your working directory is at a git commit that has been indexed
- If you've checked out a different commit, restart the LSP server to refresh the git SHA
- Try running
semcode-indexon your current commit if it hasn't been indexed yet
Limitations
- Supports functions, macros, types, and typedefs (other symbols may be added in the future)
- Requires a pre-indexed semcode database
- Does not support real-time indexing of file changes
- References show symbol-level locations, not exact usage sites within function bodies