Nub/clanker.nvim

Rust semantic editing plugin

★ 1Forks 0LuaGitHub ↗Compare

README

clanker.nvim

       ██████╗██╗      █████╗ ███╗   ██╗██╗  ██╗███████╗██████╗
      ██╔════╝██║     ██╔══██╗████╗  ██║██║ ██╔╝██╔════╝██╔══██╗
      ██║     ██║     ███████║██╔██╗ ██║█████╔╝ █████╗  ██████╔╝
      ██║     ██║     ██╔══██║██║╚██╗██║██╔═██╗ ██╔══╝  ██╔══██╗
      ╚██████╗███████╗██║  ██║██║ ╚████║██║  ██╗███████╗██║  ██║
       ╚═════╝╚══════╝╚═╝  ╚═╝╚═╝  ╚═══╝╚═╝  ╚═╝╚══════╝╚═╝  ╚═╝
                                                      for neovim

Semantic code navigation and focused editing for Rust projects.

Stop thinking about files. Start thinking about code.


Features

  • Semantic Search - Find functions, structs, traits, and impl blocks by their qualified path (e.g., crate::handlers::api::MyStruct::new)
  • Focused Editing - Edit a single code unit with the rest of the file hidden away
  • Hierarchical Navigation - Impl blocks are treated as containers; navigate to Type::impl::method
  • Project-Wide Indexing - Search across your entire Rust project, not just open buffers
  • Telescope Integration - Fuzzy finding with preview support
  • Winbar Breadcrumbs - Always know where you are: crate > module > Type > impl > method

Screenshots (ASCII Edition)

Telescope Picker

┌─────────────────────────────────────────────────────────────────────────┐
│ Clanker > MyStruct                                                      │
├─────────────────────────────────────────────────────────────────────────┤
│ > crate::handlers::api::MyStruct              [struct]   pub struct ... │
│   crate::handlers::api::impl MyStruct::new    [method]   pub fn new()   │
│   crate::handlers::api::impl MyStruct::process[method]   pub fn proce...│
│   crate::handlers::api::impl Debug for MyStruct::fmt                    │
│   crate::models::MyStructBuilder              [struct]   pub struct ... │
│                                                                         │
├─────────────────────────────────────────────────────────────────────────┤
│  <CR> edit  <C-v> vsplit  <C-x> split  <Tab> preview                    │
└─────────────────────────────────────────────────────────────────────────┘

Focused Editing Mode

┌─────────────────────────────────────────────────────────────────────────┐
│ [clanker] crate > handlers > api > impl MyStruct > new                  │
├─────────────────────────────────────────────────────────────────────────┤
│   1 │ pub fn new() -> Self {                                            │
│   2 │     Self {                                                        │
│   3 │         data: Vec::new(),                                         │
│   4 │         valid: false,                                             │
│   5 │         timestamp: None,                                          │
│   6 │     }                                                             │
│   7 │ }                                                                 │
│~                                                                        │
│~                                                                        │
├─────────────────────────────────────────────────────────────────────────┤
│ NORMAL  clanker://crate::handlers::api::impl MyStruct::new      1:1     │
└─────────────────────────────────────────────────────────────────────────┘

   :w  = save changes back to original file
   :q  = close and return to original buffer
   <Space>cn = next sibling method
   <Space>cp = previous sibling method
   <Space>cu = go up to impl block

Editing an Impl Block

┌─────────────────────────────────────────────────────────────────────────┐
│ [clanker] crate > handlers > api > impl MyStruct                        │
├─────────────────────────────────────────────────────────────────────────┤
│   1 │ impl MyStruct {                                                   │
│   2 │     pub fn new() -> Self {                                        │
│   3 │         Self {                                                    │
│   4 │             data: Vec::new(),                                     │
│   5 │             valid: false,                                         │
│   6 │         }                                                         │
│   7 │     }                                                             │
│   8 │                                                                   │
│   9 │     pub fn process(&mut self) -> Result<()> {                     │
│  10 │         self.validate()?;                                         │
│  11 │         Ok(())                                                    │
│  12 │     }                                                             │
│  13 │                                                                   │
│  14 │     fn validate(&self) -> Result<()> {                            │
│  15 │         if self.data.is_empty() {                                 │
│  16 │             return Err(Error::Empty);                             │
│  17 │         }                                                         │
│  18 │         Ok(())                                                    │
│  19 │     }                                                             │
│  20 │ }                                                                 │
└─────────────────────────────────────────────────────────────────────────┘

Installation

Requirements

  • Neovim >= 0.9.0
  • telescope.nvim
  • plenary.nvim
  • Tree-sitter Rust parser (for parsing)
  • Optional: rust-analyzer (for enhanced features)

Using lazy.nvim

{
  "your-username/clanker.nvim",
  dependencies = {
    "nvim-telescope/telescope.nvim",
    "nvim-lua/plenary.nvim",
    "nvim-treesitter/nvim-treesitter",
  },
  ft = "rust",
  config = function()
    require("clanker").setup({
      -- options (see Configuration below)
    })
    require("telescope").load_extension("clanker")
  end,
}

Using packer.nvim

use {
  "your-username/clanker.nvim",
  requires = {
    "nvim-telescope/telescope.nvim",
    "nvim-lua/plenary.nvim",
    "nvim-treesitter/nvim-treesitter",
  },
  config = function()
    require("clanker").setup()
    require("telescope").load_extension("clanker")
  end,
}

Using Nix (Flakes)

The repository includes a flake.nix for Nix users that provides a complete development environment.

Try it out

# Clone the repo
git clone https://github.com/your-username/clanker.nvim
cd clanker.nvim

# Enter the development shell
nix develop

# Open a Rust project
cd /path/to/your/rust/project
nvim src/main.rs

Add to your NixOS/home-manager config

# In your flake inputs
inputs.clanker-nvim.url = "github:your-username/clanker.nvim";

# In your neovim configuration
programs.neovim = {
  plugins = [
    {
      plugin = inputs.clanker-nvim.packages.${system}.default;
      type = "lua";
      config = ''
        require("clanker").setup()
        require("telescope").load_extension("clanker")
      '';
    }
  ];
};

Using with nvf (Neovim Flake)

{
  vim.lazy.plugins.clanker-nvim = {
    package = inputs.clanker-nvim.packages.${system}.default;
    setupModule = "clanker";
    setupOpts = {
      winbar_context = true;
      previewer = true;
    };
    after = ''
      require("telescope").load_extension("clanker")
    '';
  };
}

Configuration

require("clanker").setup({
  -- Provider selection: "lsp", "treesitter", or nil (auto-detect)
  provider = nil,

  -- Show original file line numbers in focused edit
  show_original_line_numbers = true,

  -- Lines of context around the code unit (0 = just the unit)
  context_lines = 0,

  -- Auto-close focused edit when leaving the buffer
  auto_close_on_leave = false,

  -- Enable telescope previewer
  previewer = true,

  -- Show breadcrumb path in winbar
  winbar_context = true,

  -- Set default keymaps
  set_default_keymaps = true,

  -- Enable statusline integration
  statusline_integration = true,
})

Commands

Command Description
:Clanker [query] Search all code units
:ClankerFunctions [query] Search functions and methods
:ClankerTypes [query] Search structs, enums, type aliases
:ClankerTraits [query] Search traits
:ClankerImpls [query] Search impl blocks
:ClankerMembers [Type] Show members of a type
:ClankerClose Close focused edit view
:ClankerNext Go to next sibling
:ClankerPrev Go to previous sibling
:ClankerUp Go to parent code unit
:ClankerDown Go to first child

Keymaps

Global (when set_default_keymaps = true)

Keymap Action
<Space>cc Search all code units
<Space>cf Search functions/methods
<Space>ct Search types
<Space>ci Search impl blocks
<Space>cm Search members

In Focused Edit Mode

Keymap Action
:q / :bd Close and return to original buffer
:w Save changes to original file
<Space>cn Next sibling
<Space>cp Previous sibling
<Space>cu Go to parent
<Space>cd Go to first child

Statusline Integration

Add clanker status to your statusline:

-- lualine.nvim example
require("lualine").setup({
  sections = {
    lualine_c = {
      { require("clanker").statusline },
    },
  },
})

When in focused edit mode, displays: [clanker] crate::module::Type::method 1/3


How It Works

  1. Indexing: On first search, clanker scans all .rs files in your project (excluding target/)
  2. Parsing: Uses tree-sitter to extract code units (functions, structs, enums, traits, impl blocks, etc.)
  3. Path Building: Derives qualified paths from file locations and AST structure
  4. Caching: Results are cached for 30 seconds for fast subsequent searches
  5. Focused Editing: Creates a special buffer containing only the selected code unit
  6. Splicing: On save, changes are written back to the exact location in the original file

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                         clanker.nvim                            │
├─────────────────────────────────────────────────────────────────┤
│  ┌─────────┐   ┌─────────┐   ┌─────────┐   ┌─────────────────┐  │
│  │ Pickers │   │  Focus  │   │ Backend │   │   Providers     │  │
│  │         │   │         │   │         │   │                 │  │
│  │Telescope│──▶│ Focused │──▶│ Router  │──▶│ ┌─────────────┐ │  │
│  │ Search  │   │ Editing │   │         │   │ │ Tree-sitter │ │  │
│  └─────────┘   └─────────┘   └─────────┘   │ └─────────────┘ │  │
│                                            │ ┌─────────────┐ │  │
│                                            │ │     LSP     │ │  │
│                                            │ └─────────────┘ │  │
│                                            │ ┌─────────────┐ │  │
│                                            │ │  (sidecar)  │ │  │
│                                            │ └─────────────┘ │  │
│                                            └─────────────────┘  │
└─────────────────────────────────────────────────────────────────┘

The backend is modular - currently supports tree-sitter and LSP providers, with architecture ready for a custom Rust sidecar for enhanced analysis.


FAQ

Q: Why "clanker"? A: It's a Rust plugin. Rust clanks. The name stuck.

Q: Does it work with workspaces? A: Yes! It finds the nearest Cargo.toml and indexes all .rs files from there.

Q: Can I use it with other languages? A: Currently Rust-only. The architecture could support other languages, but that's not planned.

Q: Why not just use LSP goto-definition? A: Different use case. Clanker is for finding code when you know the semantic path but not the file, and for focused editing to reduce visual noise.


License

MIT


Contributing

Contributions welcome! Please open an issue first to discuss major changes.

┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│   "I used to grep through files. Now I just think in paths."   │
│                                        - A Happy Rustacean     │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Contributors

Nub

Issues