flatten.nvim

repository·main·Indexed 20 days ago

https://github.com/willothy/flatten.nvim

A Neovim plugin that allows opening files from terminal buffers, Wezterm, or Kitty into an existing Neovim instance to prevent nested sessions. It supports blocking terminal processes (such as git commits) until the editor session is closed and provides configurable window opening behaviors, terminal integrations, and extensibility via hooks.

Tokens
2.5K
Snippets
5
Records
9
Agent score
22%

What's inside flatten.nvim

  1. Install flatten.nvim with lazy.nvim

    main

    To install flatten.nvim using folke/lazy.nvim, add the following to your configuration. It is recommended to set lazy = false and a high priority to ensure the plugin is ready immediately when opening files from a terminal to minimize delay.

    require("lazy").setup({
      {
        "willothy/flatten.nvim",
        config = true,
        -- or pass configuration with
        -- opts = {  }
        -- Ensure that it runs first to minimize delay when opening file from terminal
        lazy = false,
        priority = 1001,
      },
      --- ...
    })
  2. Integrate flatten.nvim with Toggleterm

    main

    If you use a toggleable terminal like toggleterm.nvim, you can configure flatten.nvim to open new buffers in your last active window instead of the current terminal window by setting window.open to "alternate".

    To handle blocking modes (like git commits) gracefully, you can use hooks to hide and reopen the terminal. This prevents the terminal from being inaccessible or cluttered during blocking operations.

    Key hooks used in this integration:

    • hooks.should_block: Determines if a command should trigger blocking mode (e.g., checking for the -b flag in argv).
    • hooks.pre_open: Used to capture the current terminal instance before a file opens.
    • hooks.post_open: Used to hide the terminal if the file is blocking, or switch windows if it is not.
    • hooks.block_end: Used to reopen the terminal once the blocking operation completes.
    local flatten = {
      "willothy/flatten.nvim",
      opts = function()
        ---@type Terminal?
        local saved_terminal
    
        return {
          window = {
            open = "alternate",
          },
          hooks = {
            should_block = function(argv)
              return vim.tbl_contains(argv, "-b")
            end,
            pre_open = function()
              local term = require("toggleterm.terminal")
              local termid = term.get_focused_id()
              saved_terminal = term.get(termid)
            end,
            post_open = function(bufnr, winnr, ft, is_blocking)
              if is_blocking and saved_terminal then
                saved_terminal:close()
              else
                vim.api.nvim_set_current_win(winnr)
              end
    
              if ft == "gitcommit" or ft == "gitrebase" then
                vim.api.nvim_create_autocmd("BufWritePost", {
                  buffer = bufnr,
                  once = true,
                  callback = vim.schedule_wrap(function()
                    vim.api.nvim_buf_delete(bufnr, {})
                  end),
                })
              end
            end,
            block_end = function()
              vim.schedule(function()
                if saved_terminal then
                  saved_terminal:open()
                  saved_terminal = nil
                end
              end)
            end,
          },
        }
      end,
    }
  3. Use flatten.nvim for terminal and editor workflows

    main

    Flatten allows you to open files from terminal buffers, Wezterm, or Kitty into your current Neovim instance instead of creating nested sessions.

    Common CLI Usage

    • Normal opening: nvim file1 file2
    • Diff mode: nvim -d file1 file2
    • Force blocking: Use --cmd 'let g:flatten_wait=1' to force the terminal to wait for the editor to close (useful for git commits).
    • Command execution:
      • Run a command before opening files: nvim --cmd <cmd>
      • Run a command after opening files: nvim +<cmd>

    Shell Integrations

    • Enable blocking for $VISUAL: Allows edit-exec <C-x><C-e> to work by forcing the editor to block. export VISUAL="nvim --cmd 'let g:flatten_wait=1'"
    • Manpage formatting: Use Neovim as your manpage pager. export MANPAGER="nvim +Man!"
    # Force blocking for a file
    nvim --cmd 'let g:flatten_wait=1' file1
    
    # Enable blocking for $VISUAL
    export VISUAL="nvim --cmd 'let g:flatten_wait=1'"
    
    # Enable manpage formatting
    export MANPAGER="nvim +Man!"
  4. Install flatten.nvim with rocks.nvim

    main

    If you use nvim-neorocks/rocks.nvim, install the plugin via the :Rocks command and then call setup() in your plugin configuration.

    :Rocks install flatten.nvim
    -- in plugins/flatten.lua
    require("flatten").setup({
      -- your config
    })
  5. Configure flatten.nvim general settings

    main

    The general configuration controls how flatten.nvim decides whether to nest a new instance or flatten into the current one.

    • block_for: A table of filetype = boolean. By default, gitcommit and gitrebase are set to true. Add other filetypes here to always block the guest instance.
    • nest_if_no_args: If true, will nest (create a new instance) if no arguments are passed to nvim.
    • nest_if_cmds: If true, will nest even if a command is passed to nvim with no arguments.
    • disable_cmd_passthrough: If true, prevents commands from being passed from the guest instance to the host instance.
  6. Configure window opening behavior

    main

    You can control how files are opened in the host window using the window configuration table.

    window.open

    Determines where a single file is opened. Options:

    • "current": Current window.
    • "alternate": The alternate window (<C-w>p).
    • "split": Horizontal split.
    • "vsplit": Vertical split.
    • "tab": New tabpage.
    • "smart": Automatically chooses between alternate, available windows, or a new split.
    • Flatten.OpenHandler: A custom function fun(opts: Flatten.OpenContext): window, buffer?.

    window.diff

    Determines where files are opened when using nvim -d. Options:

    • "split", "vsplit", "tab_split", "tab_vsplit".
    • Flatten.OpenHandler: A custom function fun(opts: Flatten.OpenContext): window, buffer?.

    window.focus

    • "first": Focus the first file (default).
    • "last": Focus the last file.
  7. Configure terminal integrations (Kitty and Wezterm)

    main

    Enable automatic flattening for specific terminal emulators:

    • integrations.wezterm: If true, Wezterm tabs running in the same working directory will be flattened into the same Neovim instance.
    • integrations.kitty: If true, Kitty tabs will be flattened into the same Neovim instance (Note: Flatten-by-cwd is not yet supported for Kitty).
  8. Extend flatten.nvim with hooks

    main

    Flatten provides several hooks to customize workflows. All hooks are available in the flatten.hooks table.

    HookSignatureDescription
    should_blockfun(argv: string[]): booleanReturn true if the guest should wait for the host to close the file.
    should_nestfun(host: channel): booleanReturn true if the guest should not be flattened (i.e., create a new instance).
    pre_openfun(opts: Flatten.PreOpenContext)Called before opening files.
    post_openfun(opts: Flatten.PostOpenContext)Called after opening files.
    block_endfun(opts: Flatten.BlockEndContext)Called when the host closes the file.
    no_filesfun(opts: Flatten.NoFilesArgs): Flatten.NoFilesBehaviorDetermines behavior when no files are passed. Returns boolean or { nest: boolean, block: boolean }.
    guest_datafun(): anyAllows the guest to send custom data to the host.
    pipe_pathfun(): stringDetermines if the instance is a host or guest and should connect to a host.
  9. Implement a custom pipe_path function

    main

    The pipe_path function allows flatten.nvim to communicate with the host Neovim instance through terminal-specific protocols. While Kitty and Wezterm are supported by default, you can implement this function for other terminal emulators or multiplexers.

    If running inside a Neovim terminal, you should return vim.env.NVIM. For other terminals, you must return a path or address that the host Neovim instance can listen on or connect to via RPC.

    local pipe_path = function()
      -- If running in a terminal inside Neovim:
      if vim.env.NVIM then
        return vim.env.NVIM
      end
      -- If running in a Kitty terminal:
      if vim.env.KITTY_PID then
        local addr = ("%s/%s"):format(
          vim.fn.stdpath("run"),
          "kitty.nvim-" .. vim.env.KITTY_PID
        )
        if not vim.uv.fs_stat(addr) then
          vim.fn.serverstart(addr)
        end
        return addr
      end
    end