Skip to content

Integration: Mini surround

Eric Eldredge edited this page Jan 5, 2026 · 2 revisions

Integrate occurrence.nvim with mini.surround to surround multiple occurrences at once.

Overview

This integration allows you to:

  1. Mark multiple occurrences
  2. Press gs to trigger surround operator
  3. Choose surround characters (quotes, brackets, tags, etc.)
  4. All marked occurrences get surrounded

Configuration

Add this to your occurrence.nvim setup:

require("occurrence").setup({
  operators = {
    ["gs"] = {
      desc = "Surround marked occurrences",
      before = function(marks, ctx)
        local ok, mini_surround = pcall(require, "mini.surround")
        if not ok then
          vim.notify("mini.surround not found", vim.log.levels.WARN)
          return false
        end

        -- HACK: Access mini.surround's internals via debug
        local H = nil
        local info = debug.getinfo(mini_surround.add, "u")
        for i = 1, info.nups do
          local name, value = debug.getupvalue(mini_surround.add, i)
          if name == "H" and type(value) == "table" then
            H = value
            break
          end
        end

        if not H or not H.get_surround_spec then
          vim.notify("Could not access mini.surround internals", vim.log.levels.WARN)
          return false
        end

        ---@diagnostic disable-next-line: redefined-local
        local ok, surr_info = pcall(H.get_surround_spec, "output")

        if not ok or not surr_info or not surr_info.left or not surr_info.right then
          vim.notify("Invalid surround specification", vim.log.levels.WARN)
          return false
        end

        -- Store in context for operator to use
        ctx.surr_info = surr_info
        ctx.respect_selection_type = mini_surround.config.respect_selection_type
      end,
      operator = function(current, ctx)
        if not ctx.surr_info then
          return false
        end
        ctx.register = nil -- Don't yank replaced text to register
        if ctx.respect_selection_type then
          return vim.tbl_map(function(line)
            return ctx.surr_info.left .. line .. ctx.surr_info.right
          end, current.text)
        else
          return ctx.surr_info.left .. table.concat(current.text, "\n") .. ctx.surr_info.right
        end
      end,
    },
  },
})

Usage

Basic Workflow

  1. Mark occurrences: Press go on a word
  2. Trigger surround: Press gs
  3. Choose surround: Type the surround character (e.g., ", (, t for tag)
  4. Done: All marked occurrences are surrounded

Example - Add Quotes:

Before: foo bar foo
         ^
1. Press 'go' on 'foo' -> marks both 'foo'
2. Press 'gs' -> prompts for surround
3. Type '"' -> "foo" bar "foo"

Example - Add Parentheses:

Before: word1 word2 word3
         ^
1. Press 'go' on 'word' pattern
2. Press 'gs'
3. Type '(' -> (word1) (word2) (word3)

Example - Add HTML Tag:

Before: content more content
         ^
1. Mark 'content' occurrences
2. Press 'gs'
3. Type 't' -> prompts for tag
4. Type 'div' -> <div>content</div> more <div>content</div>

How it works

Before Hook

  1. Checks if mini.surround is available
  2. Uses debug library to access mini.surround's internal H.get_surround_spec function
  3. Prompts user for surround characters (this is handled by mini.surround)
  4. Stores the surround specification in context

Why the hack? mini.surround doesn't expose a public API for getting surround specs, so we use the debug library to access its internals. This may break in future versions.

Operator

  1. Gets surround spec from context
  2. Wraps each line with left and right surround characters
  3. Respects respect_selection_type config if set
  4. Sets ctx.register = nil to avoid yanking original text
  5. Returns the modified text to occurrence.nvim for replacement

Selection Type Handling

If respect_selection_type is true:

  • Each line is surrounded individually
  • "foo\nbar" with ""foo"\n"bar"

If false:

  • All lines treated as one block
  • "foo\nbar" with ""foo\nbar"

See Also

Clone this wiki locally