Allow/document to jump to beginning/end of functions
Is your feature request related to a problem? Please describe.
One of the most useful motions for me is being able to jump to the beginning/end of a function. Unfortunately, using neovim's native ]m and ]M doesn't work for every language/file (https://neovim.io/doc/user/motion.html#%5Bm), as it doesn't uses treesitter.
Describe the solution you'd like
I would like a solution that lets me jump to beginning/end of function/class using treesitter's objects.
If this is already supported by the plugin, I would like it to be listed in the README examples, as I don't know how to make it behave like that.
I'm looking for something that overrides ]m and ]M using treesitter's objects.
Describe alternatives you've considered
The keymaps for ["af"] = "@function.outer", ["if"] = "@function.inner", are almost a perfect match of what I'm looking for, so I try to use them whenever possible. I would like something that can replace the ]m [m [M ]M motions though.
Additional context I'm a bit new to treesitter, so if this doesn't sound like a reasonable request, let me know.
It's a reasonable idea, and I've tried to achieve it multiple times since I installed this plugin.
you can add:
goto_next_start = {
["]m"] = "@function.outer",
}
But it would go to a sibling start, not the parent function, like the af or if does.
It would be great to have something like: goto_parent_start and goto_parent_end jumps.
same problem Have you solved it yet or find alternative solutions
I wrote some code to achieve the jump to the parent function start/end
It currently works for lua, js, and ts:
You should able to add a new function definition to the list, and it would work for your language.
local function_node_types = {
"arrow_function",
"function_declaration",
"function_definition",
}
local function is_function_node(node)
return vim.tbl_contains(function_node_types, node:type())
end
--- @param node TSNode|nil
--- @param types string[]
--- @return TSNode|nil
local function find_parent(node, types)
if node == nil then
return nil
end
if is_function_node(node) then
return node
end
return find_parent(node:parent(), types)
end
--- @param current_node TSNode|nil
--- @param to_end? boolean
local function jump_to_node(current_node, to_end)
if not current_node then
return
end
--- @type TSNode|nil
local function_node
if is_function_node(current_node) then
function_node = current_node
else
function_node = find_parent(
current_node:parent(), -- getting parent to avoid being stuck in the same function
function_node_types
)
end
if not function_node then
return
end
local start_row, start_col, end_row, end_col = function_node:range()
local current_row, current_col = unpack(vim.api.nvim_win_get_cursor(0))
local dest_row = (to_end and end_row or start_row) + 1
local dest_col = to_end and (end_col - 1) or start_col
if current_row == dest_row and current_col == dest_col then
jump_to_node(function_node:parent(), to_end)
return
end
vim.cmd("normal! m'") -- set jump list so I can jump back
vim.api.nvim_win_set_cursor(0, { dest_row, dest_col })
end
vim.keymap.set("n", "[f", function()
local current_node = vim.treesitter.get_node({ ignore_injections = false })
jump_to_node(current_node, false)
end, {
desc = "Jump to the start of the current function",
})
vim.keymap.set("n", "]f", function()
local current_node = vim.treesitter.get_node({ ignore_injections = false })
jump_to_node(current_node, true)
end, {
desc = "Jump to the end of the current function",
})
I wrote some code to achieve the jump to the parent function start/end
It currently works for lua, js, and ts:
You should able to add a new function definition to the list, and it would work for your language.
local function_node_types = { "arrow_function", "function_declaration", "function_definition", }
local function is_function_node(node) return vim.tbl_contains(function_node_types, node:type()) end
--- @param node TSNode|nil --- @param types string[] --- @return TSNode|nil local function find_parent(node, types) if node == nil then return nil end
if is_function_node(node) then return node end return find_parent(node:parent(), types)end
--- @param current_node TSNode|nil --- @param to_end? boolean local function jump_to_node(current_node, to_end) if not current_node then return end
--- @type TSNode|nil local function_node if is_function_node(current_node) then function_node = current_node else function_node = find_parent( current_node:parent(), -- getting parent to avoid being stuck in the same function function_node_types ) end if not function_node then return end local start_row, start_col, end_row, end_col = function_node:range() local current_row, current_col = unpack(vim.api.nvim_win_get_cursor(0)) local dest_row = (to_end and end_row or start_row) + 1 local dest_col = to_end and (end_col - 1) or start_col if current_row == dest_row and current_col == dest_col then jump_to_node(function_node:parent(), to_end) return end vim.cmd("normal! m'") -- set jump list so I can jump back vim.api.nvim_win_set_cursor(0, { dest_row, dest_col })end
vim.keymap.set("n", "[f", function() local current_node = vim.treesitter.get_node({ ignore_injections = false }) jump_to_node(current_node, false) end, { desc = "Jump to the start of the current function", })
vim.keymap.set("n", "]f", function() local current_node = vim.treesitter.get_node({ ignore_injections = false }) jump_to_node(current_node, true) end, { desc = "Jump to the end of the current function", })
Running well, thank you so much
require'nvim-treesitter.configs'.setup {
textobjects = {
move = {
enable = true,
set_jumps = true, -- whether to set jumps in the jumplist
goto_next_start = {
["]m"] = "@function.outer",
["]]"] = { query = "@class.outer", desc = "Next class start" },
--
-- You can use regex matching (i.e. lua pattern) and/or pass a list in a "query" key to group multiple queries.
["]o"] = "@loop.*",
-- ["]o"] = { query = { "@loop.inner", "@loop.outer" } }
--
-- You can pass a query group to use query from `queries/<lang>/<query_group>.scm file in your runtime path.
-- Below example nvim-treesitter's `locals.scm` and `folds.scm`. They also provide highlights.scm and indent.scm.
["]s"] = { query = "@local.scope", query_group = "locals", desc = "Next scope" },
["]z"] = { query = "@fold", query_group = "folds", desc = "Next fold" },
},
goto_next_end = {
["]M"] = "@function.outer",
["]["] = "@class.outer",
},
goto_previous_start = {
["[m"] = "@function.outer",
["[["] = "@class.outer",
},
goto_previous_end = {
["[M"] = "@function.outer",
["[]"] = "@class.outer",
},
-- Below will go to either the start or the end, whichever is closer.
-- Use if you want more granular movements
-- Make it even more gradual by adding multiple queries and regex.
goto_next = {
["]d"] = "@conditional.outer",
},
goto_previous = {
["[d"] = "@conditional.outer",
}
},
},
}
This is from the README. I don't understand if what you're trying to do isn't listed here?
It's a reasonable idea, and I've tried to achieve it multiple times since I installed this plugin.
you can add:
goto_next_start = { ["]m"] = "@function.outer", }
But it would go to a sibling start, not the
parentfunction, like theaforifdoes.It would be great to have something like:
goto_parent_startandgoto_parent_endjumps.
Sorry I just read this. So instead of going forward you wanted to go backward. I guess you could use [m instead but this wouldn't work if you have an nested function.
Well, for such a customised behaviour it's inevitable to let you make a custom function. I don't think the suggested feature will be documented or implemented, but thanks for sharing your tip.
It's a reasonable idea, and I've tried to achieve it multiple times since I installed this plugin. you can add: goto_next_start = { ["]m"] = "@function.outer", } But it would go to a sibling start, not the
parentfunction, like theaforifdoes. It would be great to have something like:goto_parent_startandgoto_parent_endjumps.Sorry I just read this. So instead of going forward you wanted to go backward. I guess you could use
[minstead but this wouldn't work if you have an nested function.Well, for such a customised behaviour it's inevitable to let you make a custom function. I don't think the suggested feature will be documented or implemented, but thanks for sharing your tip.
vaf can work normally but like this [m cursor jump seems to be unable to jump properly in my lua file maybe it's related to some dialect like language I use
Just to be clear, the ["[m"] = "@function.outer", doesn't go to the parent function, but tho ANY start of a function, it can be a sibling or either be nested.
In this example, there are 3 functions, and I would have to press [m 3 times to reach the top level:
And what we want is 1 action to reach the top level function, or to reach its end
Just to be clear, the
["[m"] = "@function.outer",doesn't go to the parent function, but tho ANY start of a function, it can be a sibling or either be nested.In this example, there are 3 functions, and I would have to press
[m3 times to reach the top level:
And what we want is 1 action to reach the top level function, or to reach its
end
I have used Prev_Sibling(). Thank you for your reminder
@carlos-algms Would something like this meet your need? (but it only works in normal mode)
nmap [m vamo<esc>
Where vam selects a function.
That would accomplish the go-to step, but it seems to NOT set a jump point, so I can't go back with Ctrl-o, another side-effect is it would override my "last selected" mark, and gv wouldn't behave as expected.
But it's a neat solution.
Maybe providing an API of the selection area would be useful, and would make it easier to test.