Skip to content

About

Neovim only plugin to find or create related files

Resources

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

 

History

10 Commits

Folders and files

Repository files navigation

related_files.nvim

related_files.nvim is a Neovim plugin for jumping between files that follow project-specific naming conventions.

Examples:

  • source file -> test file
  • public header -> private implementation
  • C source/header -> C++ test
  • files in matching namespaces or directory trees

If the related file exists, the plugin opens it. If it does not exist, the plugin can create it.

Why

Many projects encode relationships in file paths:

lua/related_files/init.lua
test/related_files/init_spec.lua

c99/public/example/media/v1/parser/PacketReader.h
test/private/example/media/v1/parser/PacketReader_tests.cpp

Vim's alternate-file behavior only gives you one other file. This plugin uses numbered relation slots instead, so one file can have several related targets. By default, <leader>1 through <leader>5 jump to relation slots 1 through 5.

Installation

Install it with your plugin manager, then call setup():

require("related_files").setup()

Default options:

require("related_files").setup({
    nr_keymaps = 5,
    enable_default_keymaps = true,
    use_default_related_files_info = true,
    global_related_files_info = nil,
})

If default keymaps are enabled, the plugin creates:

<leader>1  open or create relation slot 1
<leader>2  open or create relation slot 2
<leader>3  open or create relation slot 3
...

It also defines <Plug>RelatedFileGetOrCreate1, <Plug>RelatedFileGetOrCreate2, and so on, so you can map them yourself.

The Core Idea

A related-file setup has two parts:

  1. pargens: path parsers/generators
  2. relations: which pargens belong together

A pargen describes one kind of file. It can parse a filename into shared parts and generate a filename again from those parts.

For simple paths, use pargen_from_expression():

local from_expr = require("related_files").pargen_from_expression

from_expr("source", "{parent}/{name}.py")
from_expr("test", "{parent}/{name}_test.py")

Both expressions share the same fields:

{
    parent = "/path/to/project",
    name = "file_a",
}

That shared data lets the plugin turn file_a.py into file_a_test.py, and back again.

Example 1: Source and Test Files

Create a .related_files_info.lua file in your project:

local from_expr = require("related_files").pargen_from_expression

return {
    pargens = {
        from_expr("source", "{parent}/{name}.py"),
        from_expr("test", "{parent}/{name}_test.py"),
    },
    relations = {
        { "source", "test" },
    },
}

With this setup:

file_a.py       -- <leader>2 --> file_a_test.py
file_a_test.py  -- <leader>1 --> file_a.py

The position in the relation list is the keymap slot. In { "source", "test" }, slot 1 is source and slot 2 is test.

Example 2: Namespaced Files

Use {namespace} when files keep the same nested path below different roots:

local from_expr = require("related_files").pargen_from_expression

return {
    pargens = {
        from_expr("source", "{parent}/src/{namespace}/{name}.py"),
        from_expr("test", "{parent}/test/{namespace}/test_{name}.py"),
    },
    relations = {
        { "source", "test" },
    },
}

This maps:

src/ns1/ns2/file_a.py
test/ns1/ns2/test_file_a.py

The fixed path segment before {namespace} matters. It tells the expression parser where the namespace starts.

Example 3: More Than Two Related Files

Relations can contain more than two slots:

return {
    relations = {
        { "header", "source", "test" },
    },
}

Then the same current file can jump to different targets:

<leader>1  header
<leader>2  source
<leader>3  test

Relations can also overlap. For C and C++ projects, one public header may relate to several implementation types:

relations = {
    { "public_h", "private_c" },
    { "public_h", "private_cpp" },
    { "public_hpp", "private_cpp" },
}

If more than one existing related file matches, the plugin asks you to choose. If none exists, it offers to create one of the generated candidates.

Built-In Defaults

The plugin ships with a default related-file configuration. It covers simple same-directory C/C++ files, Ruby source/test files, Rust source/test files, and a larger C/C++ layout with access levels and language versions.

For example, the default config understands paths like:

c99/public/example/media/v1/parser/PacketReader.h
c99/public/example/media/v1/parser/PacketReader.c
test/private/example/media/v1/parser/PacketReader_tests.cpp

The .h, .c, and private C++ test are related through slot 3 for tests.

Disable the built-in defaults if you only want project-local rules:

require("related_files").setup({
    use_default_related_files_info = false,
})

Project and Global Configuration

The plugin looks for .related_files_info.lua files in parent directories of the current file. The closest file is tried first, then higher parent directories, then optional global config, then built-in defaults.

You can also provide a global config directly:

require("related_files").setup({
    global_related_files_info = {
        pargens = {
            -- your pargens here
        },
        relations = {
            -- your relations here
        },
    },
})

Debugging

Use the :RFInfo command to print how the plugin understands the current file:

:RFInfo

This is useful when a file matches the wrong parser, no parser, or generates an unexpected related filename.

Development

Run tests with Plenary:

nvim --headless -c "PlenaryBustedFile test/related_files/infos_spec.lua"
nvim --headless -c "PlenaryBustedDirectory test/"

About

Neovim only plugin to find or create related files

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages