A framework for running functions on Tree-sitter nodes, and updating the buffer with the result.
Lazy.nvim
:
{
'ckolkey/ts-node-action',
dependencies = { 'nvim-treesitter' },
config = function() -- Optional
require("ts-node-action").setup({})
end
},
packer
:
use({
'ckolkey/ts-node-action',
requires = { 'nvim-treesitter' },
config = function() -- Optional
require("ts-node-action").setup({})
end
})
Note: It's not required to call require("ts-node-action").setup()
to initialize the plugin, but a table can be
passed into the setup function to specify new actions for nodes or additional filetypes.
Bind require("ts-node-action").node_action
to something. This is left up to the user.
For example, this would bind the function to K
:
vim.keymap.set({ "n" }, "K", require("ts-node-action").node_action, { desc = "Trigger Node Action" })
The setup()
function accepts a table that conforms to the following schema:
{
['*'] = { -- Global table is checked for all filetypes
["node_type"] = fn,
...
},
filetype = {
["node_type"] = fn,
...
},
...
}
filetype
should be the value ofvim.o.filetype
, or'*'
for the global tablenode_type
should be the value ofvim.treesitter.get_node_at_cursor()
A definition on the filetype
table will take precedence over the *
(global) table.
To define multiple actions for a node type, structure your node_type
value as a table of tables, like so:
["node_type"] = {
{ function_one, name = "Action One" },
{ function_two, name = "Action Two" },
}
vim.ui.select
will use the value of name
to when prompting you on which action to perform.
All node actions should be a function that takes one argument: the tree-sitter node under the cursor.
You can read more about their API via :help tsnode
This function can return one or two values:
-
The first being the text to replace the node with. The replacement text can be either a
"string"
or{ "table", "of", "strings" }
. With a table of strings, each string will be on it's own line. -
The second (optional) returned value is a table of options with a
cursor
orcallback
key. Both are optional. Here's how that can look.
{ cursor = { row = 0, col = 0 }, callback = function() }
or (equivalent to above)
{ cursor = {}, callback = function() }
If the cursor
key is present with an empty table value, the cursor will be moved to the start of the line where the
current node is (row = 0
col = 0
relative to node start_row
and start_col
).
If callback
is present, it will simply get called without arguments after the buffer has been updated, and after the
cursor has been positioned.
Here's a simplified example of how a node-action function gets called:
local action = node_actions[vim.o.filetype][node:type()]
local replacement, opts = action(node)
replace_node(node, replacement, opts or {})
require("ts-node-action").node_action()
Main function for plugin. Should be assigned by user, and when called will attempt to run the assigned function for the node your cursor is currently on.
require("ts-node-action").debug()
Prints some helpful information about the current node, as well as the loaded node actions for all filetypes
require("ts-node-action.helpers").node_text(node)
@node: tsnode
@return: string
Returns the text of the specified node.
require("ts-node-action.helpers").multiline_node(node)
@node: tsnode
@return: boolean
Returns true if node spans multiple lines, and false if it's a single line.
require("ts-node-action.helpers").indent_text(text, indent, offset)
@text: string
@indent: number|tsnode
@offset: number|nil
@return: string
Returns the text (string) left padded by the indent
amount. If indent
is a tsnode, use it's starting column value.
offset
can be used to increase/decrease indentation, but is optional.
require("ts-node-action.helpers").indent_node_text(node, offset)
@node: tsnode
@offset: number|nil
@return: string
Returns the node text left padded by whitespace to match it's start_column position in the buffer.
offset
can be used to increase/decrease indentation, but is optional.
require("ts-node-action.helpers").padded_node_text(node, padding)
@node: tsnode
@padding: table
@return: string
For formatting unnamed tsnodes. For example, if you pass in an unnamed node representing the text ,
, you could pass in
a padding
table (below) to add a trailing whitespace to ,
nodes.
{ [","] = "%s " }
Nodes not specified in table are returned unchanged.
Global (Applies to all filetypes)
{
["true"] = toggle_boolean,
["false"] = toggle_boolean,
["identifier"] = cycle_case,
}
Ruby
{
["true"] = toggle_boolean,
["false"] = toggle_boolean,
["array"] = toggle_multiline,
["hash"] = toggle_multiline,
["argument_list"] = toggle_multiline,
["method_parameters"] = toggle_multiline,
["identifier"] = cycle_case,
["constant"] = cycle_case,
["block"] = toggle_block,
["do_block"] = toggle_block,
["binary"] = toggle_operator,
["if"] = handle_conditional,
["unless"] = handle_conditional,
["if_modifier"] = multiline_conditional,
["unless_modifier"] = multiline_conditional,
["conditional"] = expand_ternary,
["pair"] = toggle_hash_style,
}
JSON
{
["object"] = toggle_multiline,
["array"] = toggle_multiline,
}
Lua
{
["table_constructor"] = toggle_multiline,
["arguments"] = toggle_multiline,
["true"] = toggle_boolean,
["false"] = toggle_boolean,
["identifier"] = cycle_case,
}
Javascript
{
["object"] = toggle_multiline,
["array"] = toggle_multiline,
["statement_block"] = toggle_multiline,
["identifier"] = cycle_case,
["property_identifier"] = cycle_case,
["true"] = toggle_boolean,
["false"] = toggle_boolean,
}
Python
{
["dictionary"] = toggle_multiline,
["list"] = toggle_multiline,
["true"] = toggle_boolean,
["false"] = toggle_boolean,
["identifier"] = cycle_case,
}
If you come up with something that would be a good fit, pull requests for node actions are welcome!