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
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'
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
: deletey
: yankc
: cut!
: run selection through an external programgq
: 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?
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
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
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.
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
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
Please feel free to contribute!
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
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.
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