# How to Implement Smart Goto Definition for LSP in Neovim: Custom Handler Guide

> Master smart goto definition in Neovim. Configure custom LSP handlers to deduplicate results, jumping to single matches or opening a location list for multiple unique definitions.

- Repository: [jdhao/nvim-config](https://github.com/jdhao/nvim-config)
- Tags: how-to-guide
- Published: 2026-03-04

---

**Implement smart goto definition in Neovim by configuring a custom `on_list` callback in `vim.lsp.buf.definition()` that deduplicates LSP results using a filename-plus-line hash, automatically jumping to single matches or opening the location list when multiple unique definitions exist.**

Navigating code efficiently requires eliminating noisy duplicate entries that appear when modules export functions through local variable assignments. This guide demonstrates how to implement **smart goto definition for LSP in Neovim** using a modular approach that filters redundant locations before presenting results, based on the configuration found in the `jdhao/nvim-config` repository.

## How Smart Goto Definition Works in Neovim

Standard LSP goto definition often returns multiple entries for the same physical location when code uses export patterns like `local M.my_fn = function()`. The smart implementation intercepts these results through the `on_list` callback parameter, processing the location items before Neovim decides how to display them.

### The Deduplication Strategy

Inside the custom handler, the code iterates over `options.items` and constructs a hash table using `filename .. lnum` as the unique key. This eliminates duplicate entries that point to identical file-line combinations while preserving distinct definitions across different files or lines. The filtered results replace the original list via `options.items = unique_defs` before being passed to `vim.fn.setloclist()`.

### Presentation Logic

After deduplication, the handler checks the count of unique definitions. If more than one location remains, the results populate the location list and open it with `:lopen`. When only a single unique definition exists, Neovim jumps directly to that location using `silent! lfirst`, providing seamless navigation without intermediate UI steps.

## Implementing the Custom Handler in lua/config/lsp.lua

The core implementation resides in [`lua/config/lsp.lua`](https://github.com/jdhao/nvim-config/blob/main/lua/config/lsp.lua), where the `gd` mapping invokes `vim.lsp.buf.definition` with the custom callback. This approach hooks into Neovim's native LSP API without requiring external plugins.

```lua
map("n", "gd", function()
  vim.lsp.buf.definition {
    on_list = function(options)
      local unique_defs = {}
      local def_loc_hash = {}

      for _, def_location in pairs(options.items) do
        local hash_key = def_location.filename .. def_location.lnum
        if not def_loc_hash[hash_key] then
          def_loc_hash[hash_key] = true
          table.insert(unique_defs, def_location)
        end
      end

      options.items = unique_defs
      vim.fn.setloclist(0, {}, " ", options)

      if #options.items > 1 then
        vim.cmd.lopen()
      else
        vim.cmd([[silent! lfirst]])
      end
    end,
  }
end, { desc = "go to definition" })

```

The `map` function creates a buffer-local keymap with `silent` and `buffer` options, ensuring the mapping only exists when an LSP client is active. This prevents conflicts in buffers without language server support.

## Configuring Buffer-Local Keymaps

The smart definition mapping registers through an `LspAttach` autocmd defined in [`lua/config/lsp.lua`](https://github.com/jdhao/nvim-config/blob/main/lua/config/lsp.lua). This ensures the keymap is created only when an LSP client successfully attaches to the buffer, maintaining clean separation between LSP and non-LSP environments.

```lua
vim.api.nvim_create_autocmd("LspAttach", {
  callback = function(args)
    local bufnr = args.buf
    local map = function(mode, lhs, rhs, opts)
      opts = vim.tbl_extend("force", { silent = true, buffer = bufnr }, opts or {})
      vim.keymap.set(mode, lhs, rhs, opts)
    end
    
    -- Smart definition mapping shown above
    map("n", "gd", function() ... end, { desc = "go to definition" })
  end,
})

```

## Fallback to Default Behavior

When you need the original LSP behavior without deduplication—useful for debugging or examining all symbol references—the configuration provides a fallback mapping. The `<C-]>` key invokes `vim.lsp.buf.definition` directly without the `on_list` callback, preserving the default Neovim behavior.

```lua
map("n", "<C-]>", vim.lsp.buf.definition)

```

## Shared LSP Configuration

The implementation leverages shared capabilities defined in [`lua/lsp_utils.lua`](https://github.com/jdhao/nvim-config/blob/main/lua/lsp_utils.lua) to ensure consistent server behavior. The `get_default_capabilities()` function extends standard client capabilities with folding-range support required by plugins like `nvim-ufo`, applying these settings globally via `vim.lsp.config("*")`.

```lua
local capabilities = require("lsp_utils").get_default_capabilities()
vim.lsp.config("*", { capabilities = capabilities })

```

This modular architecture separates capability configuration from keymap logic, making the smart goto definition handler portable across different Neovim distributions.

## Summary

- **Deduplication mechanism**: Hash table using `filename .. lnum` keys eliminates duplicate entries caused by local variable exports in [`lua/config/lsp.lua`](https://github.com/jdhao/nvim-config/blob/main/lua/config/lsp.lua).
- **Smart navigation**: Single definitions jump directly via `:lfirst`; multiple results open the location list with `:lopen`.
- **Native LSP integration**: Uses `vim.lsp.buf.definition()` with custom `on_list` callback, requiring no external dependencies.
- **Buffer-local scoping**: Keymaps register via `LspAttach` autocmd to ensure availability only when language servers are active.
- **Fallback option**: `<C-]>` provides unfiltered access to standard LSP definition behavior.

## Frequently Asked Questions

### What is smart goto definition in Neovim?

Smart goto definition is a custom handler that intercepts LSP location results before navigation occurs, removing duplicate entries that point to identical file-line combinations. This eliminates noise when a symbol appears multiple times in the results due to module export patterns, providing cleaner navigation than the default `vim.lsp.buf.definition()` behavior.

### How does the deduplication logic work?

The handler iterates through `options.items` in the `on_list` callback and constructs a hash table where the key combines `def_location.filename` and `def_location.lnum`. Only the first occurrence of each unique key is inserted into the `unique_defs` table, effectively filtering redundant entries while preserving distinct definitions across different files or line numbers.

### Can I use this with multiple LSP servers simultaneously?

Yes, the implementation in [`lua/config/lsp.lua`](https://github.com/jdhao/nvim-config/blob/main/lua/config/lsp.lua) applies the smart goto definition mapping through the `LspAttach` autocmd, which triggers for every language server that attaches to a buffer. The deduplication logic works regardless of which LSP server provides the results, as it processes the final location list uniformly before presentation.

### How do I disable the smart behavior for specific filetypes?

To bypass the deduplication logic for specific filetypes, modify the `LspAttach` callback in [`lua/config/lsp.lua`](https://github.com/jdhao/nvim-config/blob/main/lua/config/lsp.lua) to check `vim.bo[bufnr].filetype` before creating the `gd` mapping. For targeted filetypes, map `gd` directly to `vim.lsp.buf.definition` without the `on_list` callback, or use the existing `<C-]>` fallback mapping which invokes the standard LSP behavior unconditionally.