Skip to content

Language Server Protocol (LSP)

A modern approach to code intelligence

The Language Server Protocol (LSP) is a standardized communication protocol developed by Microsoft to enable real-time language analysis and intelligent code editing across different editors and IDE (Integrated Development Environment).

By decoupling language-specific logic from the editor itself, LSP allows developers to use advanced features such as autocompletion, diagnostics, code navigation and refactoring, regardless of the programming language or toolchain.

This protocol has revolutionized the way developers interact with code, making it possible to offer consistent, high-quality language support in any editor that implements LSP.

LSP in Neovim

Neovim includes a built-in LSP client that integrates with language servers to give IDE features. The implementation maintains Neovim's performance and extensibility while delivering code completion, diagnostics and symbol navigation.
This native support ensures a consistent experience across languages and workflows.

LSP in Rocksmarker

Rocksmarker leverages Neovim's native LSP client to enhance editing for code-intensive documentation and multi-language projects. The integration uses mason.nvim, nvim-lspconfig, and mason-lspconfig.nvim to streamline language server management.

This setup provides:

Simplified installation and configuration of LSP servers

Consistent access to code intelligence, diagnostics, and refactoring tools

Maintained editor responsiveness during language server operations

The result is a unified workflow for coding, debugging, and technical writing within the same environment.

Core Functions

client_supports_method(client, method, bufnr)

Utility function to verify if an LSP client supports a specific method (e.g., textDocument/completion). Ensures feature compatibility before enabling client-specific functionalities.

--- Check if a client supports a specific LSP method
--- @param client table The LSP client
--- @param method string The LSP method to check (e.g., "textDocument/completion")
--- @param bufnr integer|nil The buffer number (optional)
--- @return boolean
local function client_supports_method(client, method, bufnr)
  return client:supports_method(method, bufnr)
end

Parameters:

  • client (table): The LSP client.
  • method (string): The LSP method to check.
  • bufnr (integer|nil): Optional buffer number.

Returns:

  • boolean: true if the client supports the method, false otherwise.

on_attach(client, bufnr)

Global callback triggered when an LSP client attaches to a buffer. Configures buffer-specific features such as document highlighting and inlay hints.

Code:

--- Global on_attach function for LSP clients
--- @param client table The LSP client
--- @param bufnr integer The buffer number
local function on_attach(client, bufnr)
  -- Document highlight on cursor hold
  if client_supports_method(client, "textDocument/documentHighlight", bufnr) then
    local highlight_augroup = vim.api.nvim_create_augroup("lsp-highlight", { clear = false })
    vim.api.nvim_create_autocmd({ "CursorHold", "CursorHoldI" }, {
      buffer = bufnr,
      group = highlight_augroup,
      callback = vim.lsp.buf.document_highlight,
    })
    vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, {
      buffer = bufnr,
      group = highlight_augroup,
      callback = vim.lsp.buf.clear_references,
    })
    vim.api.nvim_create_autocmd("LspDetach", {
      group = vim.api.nvim_create_augroup("lsp-detach", { clear = true }),
      callback = function(event)
        vim.lsp.buf.clear_references()
        vim.api.nvim_clear_autocmds({ group = "lsp-highlight", buffer = event.buf })
      end,
    })
  end

  -- Inlay hints: enable on attach, toggle keymap
  if client_supports_method(client, "textDocument/inlayHint", bufnr) then
    vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })
  end
end

Behavior:

  • Creates autocommand groups for:
    • CursorHold / CursorHoldI: Triggers vim.lsp.buf.document_highlight.
    • CursorMoved / CursorMovedI: Clears references.
    • LspDetach: Cleans up autocommands and references.
  • Enables inlay hints if the client supports textDocument/inlayHint.

get_lsp_capabilities()

Constructs and extends the default LSP client capabilities to support advanced features like Markdown documentation, snippets, and completion enhancements. Ensures compatibility with modern LSP servers and plugins like blink.cmp.

Code:

--- Get LSP capabilities with support for Markdown and other features.
--- @return table The LSP client capabilities.
local function get_lsp_capabilities()
  -- Initialize with default client capabilities
  local capabilities = vim.lsp.protocol.make_client_capabilities()

  -- Try to extend capabilities with blink.cmp
  local blink_cmp_ok, blink_cmp = pcall(require, "blink.cmp")
  if blink_cmp_ok then
    capabilities = blink_cmp.get_lsp_capabilities(capabilities)
  else
    vim.notify("blink.cmp not found. Using default capabilities.", vim.log.levels.WARN)
  end

  -- Ensure the structure exists
  capabilities.textDocument = capabilities.textDocument or {}
  capabilities.textDocument.completion = capabilities.textDocument.completion or {}
  capabilities.textDocument.completion.completionItem = capabilities.textDocument.completion.completionItem or {}

  -- Extend completion item capabilities with Markdown support
  local new_capabilities = {
    documentationFormat = { "markdown", "plaintext" },
    snippetSupport = true,
    preselectSupport = true,
    insertReplaceSupport = true,
    labelDetailsSupport = true,
    deprecatedSupport = true,
    commitCharactersSupport = true,
    tagSupport = { valueSet = { 1 } },
    resolveSupport = {
      properties = {
        "documentation",
        "detail",
        "additionalTextEdits",
      },
    },
  }

  -- Manually extend the table
  for key, value in pairs(new_capabilities) do
    capabilities.textDocument.completion.completionItem[key] = value
  end

  return capabilities
end

Integration:

  • Attempts to extend capabilities using blink.cmp.get_lsp_capabilities(). Falls back to defaults if blink.cmp is unavailable.

setup_lsp_server(server_name, config)

Helper function to set up an LSP server with consistent settings, including on_attach and capabilities. Simplifies server configuration and ensures error handling for compatibility with Neovim 0.11+.

Code:

-- Helper function to setup an LSP server with error handling.
--- @param server_name string The name of the LSP server
--- @param config table|nil The configuration table for the server
local function setup_lsp_server(server_name, config)
  -- Check if Neovim 0.11+ is available.
  if not vim.lsp.config then
    vim.notify("vim.lsp.config not available. Ensure you are using Neovim 0.11+.", vim.log.levels.ERROR)
    return
  end

  -- Merge the provided config with on_attach and capabilities
  local final_config = vim.tbl_deep_extend("force", {
    on_attach = on_attach,
    capabilities = get_lsp_capabilities(),
  }, config or {})

  -- Set up the LSP server with the provided configuration.
  local success, err = pcall(function()
    vim.lsp.config(server_name, final_config)
  end)

  if not success then
    vim.notify("Failed to setup LSP server: " .. server_name .. ". Error: " .. err, vim.log.levels.ERROR)
  end
end

Parameters:

  • server_name (string): Name of the LSP server (e.g., "taplo", "marksman").
  • config (table|nil): Optional server-specific configuration.

Behavior:

  • Merges the provided config with default settings (on_attach, capabilities).
  • Logs errors if the server setup fails.


LSP Server Configurations


1. Taplo (TOML Support)

Configures the Taplo LSP server for TOML file support, providing validation, formatting, and completion features. Useful for managing configuration files like rocks.toml.

Code:

-- Configure Taplo language server for TOML file support in Neovim.
setup_lsp_server("taplo", {
  -- Settings specific to Taplo LSP.
  settings = {
    -- Formatting configuration: enable or disable automatic formatting.
    format = {
      enable = true,
    },

    -- Completion configuration: control how code completion works.
    completion = {
      enable = true,
      -- Characters that trigger completion suggestions.
      triggerCharacters = { ".", '"', "'" },
    },

    -- Diagnostics configuration: control how errors and warnings are displayed.
    diagnostics = {
      enable = true,
      severity = "Error",
    },

    -- Schema support configuration: enable or disable JSON schema validation.
    schema = {
      enable = false,
    },
  },

  -- Specify the filetypes for which Taplo should be activated.
  filetypes = { "toml" },
})

Features:

  • Auto-formatting: Enabled by default.
  • Completion: Triggered by ., ", and ' characters.
  • Diagnostics: Reports errors only.
  • Schema validation: Turned off by default.

2. Vale LS (Prose Linting)

Configures the Vale LS server for linting prose in Markdown, plain text, TeX, and reStructuredText files. Ensures consistency and style adherence in documentation.

Code:

-- Configure vale_ls for prose linting in Neovim.
setup_lsp_server("vale_ls", {
  -- Specify the filetypes for which vale_ls should be activated.
  filetypes = { "markdown", "text", "tex", "rst" },

  -- Enable single file support.
  single_file_support = true,
})

Features:

  • Single-file support: Works without requiring a project root directory.
  • Customizable: Can be configured via .vale.ini.

3. Marksman (Markdown Support)

Configures the Marksman LSP server for Markdown files, providing features like diagnostics, completion, and hover previews. Enhances the editing experience for documentation and notes.

Code:

-- Configure Marksman for Markdown language support in Neovim.
setup_lsp_server("marksman", {
  -- Use extended client capabilities for full LSP feature support.
  capabilities = get_lsp_capabilities(),

  -- Specify the filetypes for which Marksman should be activated.
  filetypes = { "markdown" },

  -- Enable single file support.
  single_file_support = true,

  -- Optional: Add any additional settings for Marksman.
  settings = {
    marksman = {
      -- Enable or disable automatic formatting.
      format = {
        enable = false,
      },
      -- Enable or disable diagnostics (e.g., broken links).
      diagnostics = {
        enable = true,
      },
      -- Enable or disable completion for links, references, etc.
      completion = {
        enable = true,
      },
      -- Enable or disable hover information (e.g., link previews).
      hover = {
        enable = true,
      },
    },
  },
})

Features:

  • Diagnostics: Detects issues like broken links.
  • Completion: Provides suggestions for links and references.
  • Hover previews: Shows link previews on hover.
  • Formatting: Disabled by default.

4. Harper LS (Grammar & Style Checking)

Configures the Harper LS server for grammar, style, and spell-checking in prose and Markdown files. Helps maintain high-quality writing by detecting errors and suggesting improvements.

Code:

-- Configure Harper language server for grammar and style checking in Neovim.
setup_lsp_server("harper_ls", {
  settings = {
    -- All Harper-specific settings must be nested under the harper-ls key.
    ["harper-ls"] = {
      -- Linters configuration: enable or disable specific grammar and style checks.
      linters = {
        SpellCheck = true,
        SpelledNumbers = false,
        AnA = true,
        SentenceCapitalization = true,
        UnclosedQuotes = true,
        WrongQuotes = false,
        LongSentences = true,
        RepeatedWords = true,
        Spaces = true,
        Matcher = true,
        CorrectNumberSuffix = true,
        UseTitleCase = false,
      },

      -- Code actions configuration: control how code actions are displayed.
      codeActions = {
        ForceStable = false,
      },

      -- Markdown-specific settings: control how Harper handles Markdown files.
      markdown = {
        IgnoreLinkTitle = false,
        IgnoreCodeBlocks = true,
        IgnoreInlineCode = true,
        CheckLists = true,
        CheckHeadings = true,
      },

      -- Set the severity level for diagnostics.
      -- Options: "error", "warning", "information", "hint"
      diagnosticSeverity = "hint",

      -- Isolate English language checks to avoid false positives in mixed-language documents.
      isolateEnglish = true,
    },
  },
})

Features:

  • Spell-checking: Enabled by default.
  • Markdown-aware: Ignores code blocks and checks headings/lists.
  • Diagnostics: Displayed as hints to avoid intrusiveness.
  • English isolation: Reduces false positives in mixed-language documents.


Mason Integration


Mason Setup

Initializes the Mason plugin, which manages the installation and updating of LSP servers, linters, and formatters. All dependencies are handled by rocks.nvim.

Code:

-- Setup Mason for managing LSP servers and related tools.
local mason_ok, mason = pcall(require, "mason")
if not mason_ok then
  vim.notify("mason.nvim not installed.", vim.log.levels.ERROR)
  return
end

mason.setup({}) -- Initialize Mason with default settings.

Mason-LSPConfig

Bridges Mason and nvim-lspconfig to automatically install and configure LSP servers listed in ensure_installed. Ensures that all required LSP servers are available and properly set up.

Code:

-- Setup Mason-LSPConfig to bridge Mason and nvim-lspconfig.
local mason_lspconfig_ok, mason_lspconfig = pcall(require, "mason-lspconfig")
if not mason_lspconfig_ok then
  vim.notify("mason-lspconfig not installed.", vim.log.levels.ERROR)
  return
end

mason_lspconfig.setup({
  -- List of LSP servers to automatically install.
  ensure_installed = {
    "emmylua_ls",
    "html",
    "cssls",
    "marksman",
    "harper_ls",
    "yamlls",
    "bashls",
    "taplo",
    "jsonls",
    "vimls",
    "vale_ls",
  },
  -- Handler to automatically set up each installed LSP server.
  handlers = {
    function(server_name)
      setup_lsp_server(server_name, {})
    end,
  },
})

Installed Servers:

Server Purpose
emmylua_ls Lua language support
html HTML language support
cssls CSS language support
marksman Markdown language support
harper_ls Grammar and style checking
yamlls YAML language support
bashls Bash language support
taplo TOML language support
jsonls Json language support
vimls Vim script language support
vale_ls Prose linting

Mason Tool Installer

Automatically installs formatting, linting, and utility tools to complement LSP servers. All dependencies are managed via rocks.nvim.

Code:

-- Mason tool installer for additional formatting, linting, and utility tools.
local mason_tool_installer_ok, mason_tool_installer = pcall(require, "mason-tool-installer")
if not mason_tool_installer_ok then
  vim.notify("mason-tool-installer not installed.", vim.log.levels.ERROR)
  return
end

mason_tool_installer.setup({
  -- List of tools to automatically install for formatting, linting, and validation.
  ensure_installed = {
    "stylua",
    "shfmt",
    "yamlfmt",
    "shellcheck",
    "prettier",
    "yamllint",
    "jsonlint",
    "vint",
  },
  auto_update = true,
  run_on_start = true,
})

Tools:

Tool Purpose
stylua Lua formatter
shfmt Shell script formatter
yamlfmt YAML formatter
shellcheck Shell script linter
prettier Multi-language formatter
yamllint YAML linter
jsonlint JSON linter
vint Vim script linter


Autocompletion with blink.cmp

Configures the blink.cmp plugin for autocompletion in Neovim. Provides a fast and customizable completion engine with fuzzy matching and keymap presets. Dependency managed by rocks.nvim.

Code:

-- Configure blink.cmp for autocompletion in Neovim.
local blink_cmp_ok, blink_cmp = pcall(require, "blink.cmp")
if not blink_cmp_ok then
  vim.notify("blink.cmp not installed.", vim.log.levels.ERROR)
  return
end

blink_cmp.setup({
  -- Keymap configuration for completion behavior.
  keymap = {
    preset = "super-tab",
    ["<ESC>"] = { "cancel", "fallback" },
  },
  -- Command-line mode completion settings.
  cmdline = {
    keymap = {
      preset = "default",
    },
  },
  -- Fuzzy matching settings for completion results.
  fuzzy = {
    implementation = "lua",
  },
})

Features:

  • Super-Tab keymap: Uses Tab for navigation and selection.
  • ESC key: Cancels completion or falls back to default behavior.
  • Fuzzy matching: Uses Lua-based implementation for performance.


Inlay Hints Management

Manages inlay hints, which are inline annotations that display additional information (e.g., variable types) directly in the code. Prevents race conditions by disabling hints in Insert mode and re-enabling them afterward.

Code:

-- Inlay hints: disable during insert mode to prevent race conditions
vim.api.nvim_create_autocmd("InsertEnter", {
  group = vim.api.nvim_create_augroup("lsp-inlay-hints-insert", { clear = true }),
  callback = function(event)
    vim.lsp.inlay_hint.enable(false, { bufnr = event.buf })
  end,
})

vim.api.nvim_create_autocmd("InsertLeave", {
  group = vim.api.nvim_create_augroup("lsp-inlay-hints-insert-leave", { clear = true }),
  callback = function(event)
    vim.schedule(function()
      if vim.api.nvim_buf_is_valid(event.buf) then
        vim.lsp.inlay_hint.enable(true, { bufnr = event.buf })
      end
    end)
  end,
})

Integrated LSP management

These plugins combine to create a unified ecosystem for Language Server Protocol (LSP) tools. They automate server configuration, dependency management, and client communication, removing the need for manual setup.
The system manages initialization, error handling, and performance tuning automatically.

This allows developers and technical writers to focus on coding, documentation, and productivity without distractions from underlying infrastructure. The outcome is a responsive, reliable, and maintenance-free environment for advanced language features.