zdcthomas/yop.nvim

github github
utility
stars 71
issues 2
subscribers 4
forks 0
CREATED

2022-07-10

UPDATED

11 months ago


CI status

YOP (Your OPerator)

Warning This plugin is currently in ALPHA and bindings very well might change! I'll try to indicate breaking changes in the breakin change issue

Installation

Here are some snippets for your plugin manager of choice. These implement only the sorting operator.

Lazy.nvim:

{"zdcthomas/yop.nvim"}

Packer:

use("zdcthomas/yop.nvim")

Vim-Plug:

Plug 'zdcthomas/yop.nvim'

What is this?

This is a plugin that allows you to easily make you some operators for great good!

Wait what's an Operator?

That's a good question! There's not much talk about operators in the plugin community, but you've almost certainly been using them already. An operator is any key that operates over a selection of text, selected either through a motion (ex: iw: in-a-word, ab: around-a-bracket, etc), or through a visual selection.

Some of the most common built in operators are

  • d: delete
  • y: yank
  • c: cut
  • !: run selection through an external program
  • gq: format selection

There're tons of less widely known operators too, and they're definitely worth checking out! Run :h operator to learn more.

Ok but what does this plugin do?

Normally, defining an operator takes a bit of work, you'll have to get the text covered by motion or visual selection, operate on that text, and then replace the text in the buffer. This plugin handles everything for you except the operation, so you can focus on what you really care about.

With YOP, all you need is a function that transforms and returns the selected lines, or does some other super cool thing.

Alright, I'm sold! How do I make my own operator?

Making your own operators

The primary interface for yop.nvim is the op_map function.

require("yop").op_map

This function takes the same arguments as vim.keymap.set, except that the 3rd argument (normally either a function or a string representing a vim command), now has to be a function that looks like:

function (selections, info)
  ...
  return optional_replacement_lines
end

where:

  • selections: A list with the selected lines or parts of lines.

  • info: a table with extra info about the motion. (Most of the time you won't need this and can just pass in a 1-arity function)

    {
      position = {
        first = {row_number, column_number},
        last = {row_number, column_number},
      },
      type = motion_callback_type
    }
    

    Row_number and column_number are 1-indexed integers, motion_callback_type is one of line|char|block

  • optional_replacement_lines: The function can optionally return a table of lines, which will replace the selected region in the buffer.

Note Remember, in Lua, you can ignore extra arguments by simply not listing them in the declaration. This means that you can pass in a 0-arity, 1-arity, or 2-arity function and all will work!

For some example transformation functions, see the examples section

Putting it all together

A full (but useless) example might look like:

require("yop").op_map({"n", "v"}, "<leader>b", function(lines, info)
  return { "bread" }
end)

This example simply replaces the entire selected area with the text bread. On a buffer that looks like

1 2 3
4 5 6
7 8 9

With your cursor on 4 type in <leader>bw. Now { "4 5"} will be passed in as the first argument to the function you passed in. We could also type vw<leader>b, and since we told op_map that we'd also like this mapping to exist in visual mode, it will behave identically.

Afterward, the buffer will look like:

1 2 3
bread 6
7 8 9

If instead, you type <leader>bk, then lines will be {"1 2 3", "4 5 6"} because k is a line wise motion. This is the same as if you entered visual line mode with V and selected the top two lines before hitting <leader>b. The buffer will then become

bread
7 8 9

Warning The following api is very likely to change.

A version of the same function that operates on the whole line can also be created by includinglinewise = true in the third argument opts. This has to be a differentmapping, since this includes a motion along with the operator. It also must be in normal mode

For example:

require("yop").op_map("n", "<leader>bb", function(lines, info)
  return { "bread" }
end, {linewise = true})

This allows you to run <leader>bb on the first line in this example buffer, and change it to:

bread
4 5 6
7 8 9

Examples

Here's a cool little example of a sorting operator, inspired heavily by the sort motion plugin, but with the added feature of asking the user for a delimiter to split the line on.

Sortin!

function(lines, opts)
  -- We don't care about anything non alphanumeric here
  local sort_without_leading_space = function(a, b)
    -- true = a then b
    -- false = b then a
    local pattern = [[^%W*]]
    return string.gsub(a, pattern, "") < string.gsub(b, pattern, "")
  end
  if #lines == 1 then
    -- If only looking at 1 line, sort that line split by some char gotten from input
    local delimeter = utils.get_input("Delimeter: ")
    local split = vim.split(lines[1], delimeter, { trimempty = true })
    -- Remember! `table.sort` mutates the table itself
    table.sort(split, sort_without_leading_space)
    return { utils.join(split, delimeter) }
  else
    -- If there are many lines, sort the lines themselves
    table.sort(lines, sort_without_leading_space)
    return lines
  end
end

Searchin!

Note This requires Telescope to be installed

Here's a real small little guy that'll search in telescope for the text passed over in a motion, or selected visually.

function(lines)
  -- Multiple lines can't be searched for
  if #lines > 1 then
    return
  end
  require("telescope.builtin").grep_string({ search = lines[1] })
end

Contributing! (Thank you!)

Please feel free to contribute!

Testing

This uses busted, luassert (both through plenary.nvim) and matcher_combinators to define tests in test/spec/ directory.

To run all tests just execute

$ make test

GitHub actions

On each PR and on Main, a GitHub Action will run all the tests, and the linter. Tests will be run using stable and nightly versions of Neovim.

What's in a name

It's a great plugin, but I really hate that name! Yop!? I mean, come on! How do you even pronounce it?

Well, first off, it's YAWP, and second, here's a list of other names that were considered that you hopefully hate a bit more!

  • PSYOP
  • MYOPIC
  • MYOP
  • YOUROP
  • OPPENHEIMER
  • YOPTIMUS PRIME
  • OPTIMUS PRIME
  • YOPOLOPOLIS
  • POP