Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow/document to jump to beginning/end of functions #713

Open
samueloph opened this issue Nov 5, 2024 · 12 comments
Open

Allow/document to jump to beginning/end of functions #713

samueloph opened this issue Nov 5, 2024 · 12 comments
Labels
enhancement New feature or request

Comments

@samueloph
Copy link

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.

@samueloph samueloph added the enhancement New feature or request label Nov 5, 2024
@carlos-algms
Copy link

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.

@reimu1234
Copy link

same problem
Have you solved it yet
or find alternative solutions

@carlos-algms
Copy link

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",
})

@reimu1234
Copy link

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

@kiyoon
Copy link
Collaborator

kiyoon commented Jan 18, 2025

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?

@kiyoon
Copy link
Collaborator

kiyoon commented Jan 18, 2025

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.

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.

@reimu1234
Copy link

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.

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.

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

@carlos-algms
Copy link

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:

Image

And what we want is 1 action to reach the top level function, or to reach its end

@reimu1234
Copy link

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:

Image

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

@kiyoon
Copy link
Collaborator

kiyoon commented Jan 21, 2025

@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.

@carlos-algms
Copy link

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.

@kiyoon
Copy link
Collaborator

kiyoon commented Jan 21, 2025

Maybe providing an API of the selection area would be useful, and would make it easier to test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants