Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ Simply call `require("nvim-surround").setup` or
More information on how to configure this plugin can be found in
[`:h nvim-surround.configuration`](https://github.com/kylechui/nvim-surround/blob/main/doc/nvim-surround.txt).

### Which Key Support

You can turn Which Key for showing surround hints with configured labels by
calling `require("nvim-surround.wk-surround-plugin").set_up()`.

## Contributing

See
Expand Down
19 changes: 19 additions & 0 deletions doc/nvim-surround.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ CONTENTS *nvim-surround.contents*
3.3.4 Cursor ....................... |nvim-surround.setup.move_cursor|
3.3.5 Indentation ................. |nvim-surround.setup.indent_lines|
3.4. Helpers ..................................... |nvim-surround.helpers|
3.5. Which Key Support ...................... |nvim-surround.which_key|
4. Migration Guides ................................ |nvim-surround.migrating|
4.1. Migrating v3 to v4 ............... |nvim-surround.migrating.v3_to_v4|

Expand Down Expand Up @@ -401,6 +402,7 @@ surrounds, for example setting up a `$` surround, but only in bash files:
add = { "${", "}" },
find = "$%b{}",
delete = "^(..)().-(.)()$",
label = "${…}",
},
},
})
Expand Down Expand Up @@ -540,6 +542,11 @@ containing the following keys:
are directly used as the replacement pair. For example, when changing HTML
tag types, only `cst` is needed, instead of `cstt`.

*nvim-surround.setup.surrounds.label*
label: ~
An optional string that represents the label for the surround. This
is used by the Which Key plugin to show a descriptive hint for the
surround.

*nvim-surround.setup.surrounds.invalid_key_behavior*
`invalid_key_behavior` is a special key in the `surrounds` table that defines
Expand Down Expand Up @@ -774,6 +781,18 @@ config.get_selections({args})
The Lua pattern matches just the HTML tag type in both the beginning
and end.

--------------------------------------------------------------------------------
3.5. Which Key Support *nvim-surround.which_key*

|nvim-surround| provides a plugin for |which-key.nvim| that shows a popup
containing the available surrounds and their labels. To enable this, call:
>lua
require("nvim-surround.wk-surround-plugin").set_up()
<
This will enable the Which Key popup for all surround actions. The labels
shown in the popup can be configured via the `label` key in the `surrounds`
table (see |nvim-surround.setup.surrounds.label|).

================================================================================
4. Migration Guides *nvim-surround.migrating*

Expand Down
2 changes: 2 additions & 0 deletions lua/nvim-surround/annotations.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
---@field find find_func
---@field delete delete_func
---@field change change_table
---@field label? string

---@class options
---@field surrounds table<string, surround>
Expand All @@ -51,6 +52,7 @@
---@field find? user_find
---@field delete? user_delete
---@field change? user_change
---@field label? string

---@class user_options
---@field surrounds? table<string, false|user_surround>
Expand Down
36 changes: 36 additions & 0 deletions lua/nvim-surround/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,76 +9,87 @@ M.default_opts = {
return M.get_selection({ motion = "a(" })
end,
delete = "^(. ?)().-( ?.)()$",
label = "( … )",
},
[")"] = {
add = { "(", ")" },
find = function()
return M.get_selection({ motion = "a)" })
end,
delete = "^(.)().-(.)()$",
label = "(…)",
},
["{"] = {
add = { "{ ", " }" },
find = function()
return M.get_selection({ motion = "a{" })
end,
delete = "^(. ?)().-( ?.)()$",
label = "{ … }",
},
["}"] = {
add = { "{", "}" },
find = function()
return M.get_selection({ motion = "a}" })
end,
delete = "^(.)().-(.)()$",
label = "{…}",
},
["<"] = {
add = { "< ", " >" },
find = function()
return M.get_selection({ motion = "a<" })
end,
delete = "^(. ?)().-( ?.)()$",
label = "< … >",
},
[">"] = {
add = { "<", ">" },
find = function()
return M.get_selection({ motion = "a>" })
end,
delete = "^(.)().-(.)()$",
label = "<…>",
},
["["] = {
add = { "[ ", " ]" },
find = function()
return M.get_selection({ motion = "a[" })
end,
delete = "^(. ?)().-( ?.)()$",
label = "[ … ]",
},
["]"] = {
add = { "[", "]" },
find = function()
return M.get_selection({ motion = "a]" })
end,
delete = "^(.)().-(.)()$",
label = "[…]",
},
["'"] = {
add = { "'", "'" },
find = function()
return M.get_selection({ motion = "a'" })
end,
delete = "^(.)().-(.)()$",
label = "'…'",
},
['"'] = {
add = { '"', '"' },
find = function()
return M.get_selection({ motion = 'a"' })
end,
delete = "^(.)().-(.)()$",
label = '"…"',
},
["`"] = {
add = { "`", "`" },
find = function()
return M.get_selection({ motion = "a`" })
end,
delete = "^(.)().-(.)()$",
label = "`…`",
},
["i"] = { -- TODO: Add find/delete/change functions
add = function()
Expand All @@ -90,6 +101,7 @@ M.default_opts = {
end,
find = function() end,
delete = function() end,
label = "?…?",
},
["t"] = {
add = function()
Expand Down Expand Up @@ -123,6 +135,7 @@ M.default_opts = {
end
end,
},
label = "<tag>…</tag>",
},
["T"] = {
add = function()
Expand Down Expand Up @@ -156,6 +169,7 @@ M.default_opts = {
end
end,
},
label = "<tag>…</tag>",
},
["f"] = {
add = function()
Expand Down Expand Up @@ -188,6 +202,7 @@ M.default_opts = {
end
end,
},
label = "foo(…)",
},
invalid_key_behavior = {
-- By default, we ignore control characters for adding/finding because they are more likely typos than
Expand Down Expand Up @@ -333,6 +348,26 @@ M.get_alias = function(char)
return char
end

---Creates a table of available hints given surrounds and aliases.
---
---@param surrounds table<string, surround>
---@param aliases table<string, string|string[]>
---@return table<string, string> hints
---@nodiscard
M.get_hints = function(surrounds, aliases)
local hints = {}
for char, surround in pairs(surrounds) do
-- Throw away "invalid_key_behavior" if present.
if string.len(char) == 1 then
hints[char] = surround.label or char
end
end
for char, alias in pairs(aliases) do
hints[char] = type(alias) == "table" and table.concat(alias, ",") or alias
end
return hints
end

-- Gets a delimiter pair for a user-inputted character.
---@param char string|nil The user-given character.
---@param line_mode boolean Whether or not the delimiters should be put on new lines.
Expand Down Expand Up @@ -485,6 +520,7 @@ M.translate_surround = function(char, user_surround)
find = M.translate_find(user_surround.find),
delete = M.translate_delete(char, user_surround.delete),
change = M.translate_change(char, user_surround.change),
label = user_surround.label,
}
end

Expand Down
11 changes: 10 additions & 1 deletion lua/nvim-surround/input.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,19 @@ M.replace_termcodes = function(char)
return vim.api.nvim_replace_termcodes(char, true, true, true)
end

-- Gets a character input from the user.
-- Gets a surround character input from the user.
--
-- If the user has set up the WK plugin, uses that instead of vim.fn.get_char
---@return string|nil @The input character, or nil if an escape character is pressed.
---@nodiscard
M.get_char = function()
if require("nvim-surround.wk-surround-plugin").plugin_set_up then
local config = require("nvim-surround.config")
return require("nvim-surround.wk-surround-plugin").pick(
config.get_hints(config.get_opts().surrounds, config.get_opts().aliases),
"n"
)
end
local ok, char = pcall(vim.fn.getcharstr)
-- Return nil if input is cancelled (e.g. <C-c> or <Esc>)
if not ok or char == "\27" then
Expand Down
78 changes: 78 additions & 0 deletions lua/nvim-surround/wk-surround-plugin.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---A WK plugin for showing available surrounds with keys.
---
---Users need to set_up this plugin before using it.
---
---To get a char with a WK popup, call pick().

---@diagnostic disable: missing-fields, inject-field
---@type wk.Plugin
local M = {}

M.name = "nvim-surround"

-- WK requires keymaps.
-- We use "⌨" here to make sure we never collide with actual keymaps.
local keys = "⌨S"

M.mappings = {
{
[1] = keys,
plugin = M.name,
icon = { icon = "⌨", color = "blue" },
desc = "Nvim-surround",
mode = { "n", "x" },
},
}

local selected_key = nil
local expand_hints = {}

function M.expand()
---@type wk.Plugin.item[]
local items = {}

for key, label in pairs(expand_hints) do
table.insert(items, {
key = key,
desc = label,
value = "",
action = function()
selected_key = key
end,
})
end

table.sort(items, function(a, b)
return a.key < b.key
end)

return items
end

---Gets a character from the user with the provided hints.
---
---@param hints table<string, string> A table from chars to their labels.
---@param mode "n"|"x"
---@return string? selected_key
function M.pick(hints, mode)
selected_key = nil
expand_hints = hints
require("which-key").show({ keys = keys, mode = mode })
return selected_key
end

---Whether the final user has set up this plugin.
---
---@type boolean
M.plugin_set_up = false

-- This function is called "set_up" to make sure it doesn't conflict with WK’s "setup."
function M.set_up()
local wk = require("which-key")
wk.add(M.mappings)
require("which-key.plugins").plugins[M.name] = M
require("which-key.plugins")._setup(M, {})
M.plugin_set_up = true
end

return M
31 changes: 31 additions & 0 deletions tests/configuration_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -635,4 +635,35 @@ describe("configuration", function()
vim.cmd("normal VSdVSdVSd")
check_lines({ "foo", "foo", "foo", "foobarbaz", "bar", "bar", "bar" })
end)

it("returns correct hints including formatted aliases", function()
require("nvim-surround").setup({})
local hints = require("nvim-surround.config").get_hints(
require("nvim-surround.config").get_opts().surrounds,
require("nvim-surround.config").get_opts().aliases
)
assert.are.same({
['"'] = '"…"',
["'"] = "'…'",
["("] = "( … )",
[")"] = "(…)",
["<"] = "< … >",
[">"] = "<…>",
["["] = "[ … ]",
["]"] = "[…]",
["`"] = "`…`",
["{"] = "{ … }",
["}"] = "{…}",
B = "}",
T = "<tag>…</tag>",
a = ">",
b = ")",
f = "foo(…)",
i = "?…?",
q = "\",',`",
r = "]",
s = "},],),>,\",',`",
t = "<tag>…</tag>",
}, hints)
end)
end)