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

support for jumpout&jumpnext pairs #167

Open
Moukubi opened this issue Oct 23, 2021 · 13 comments
Open

support for jumpout&jumpnext pairs #167

Moukubi opened this issue Oct 23, 2021 · 13 comments
Labels
enhancement New feature or request pinned

Comments

@Moukubi
Copy link

Moukubi commented Oct 23, 2021

Is your feature request related to a problem? Please describe.
Used another autopairs plugin writen in vimscript for a long time, is it possiable support the same feature

Describe the solution you'd like
{ | } ... [ ]
--type C-n to jump out
{ } | ... [ ]
--type C-n to jump next
{ } ...[ | ]

@Moukubi Moukubi added the enhancement New feature or request label Oct 23, 2021
@windwp
Copy link
Owner

windwp commented Oct 23, 2021

https://github.com/abecodes/tabout.nvim
you can try this i don't use it but it is good

@zmrow
Copy link

zmrow commented Jan 13, 2022

This is another good visualization of what is being asked for.

At least for me personally, I'd rather not install treesitter to use tabout just to get this functionality.

In #125 , you mention that adding this functionality might be possible since you export all the rules. In #190, @noib3 makes a good point that the plugin could expose <Plug>autopairs-skip-closing and users could make their own key mapping. That could solve any anxiety about mapping keys in the plugin itself.

I guess I'm just trying to understand if it is not possible in this plugin, or if it's something you will not add. No worries either way! Nice work on this plugin!

@windwp
Copy link
Owner

windwp commented Jan 13, 2022

the problem come from find a match pair if it place on different line and you need treesitter.

if you want to just move a cursor to left you can create a normal mapping to do that or a rule if you want to check condition.
i don't have a personal use case on that so i will not create that mapping plugautopairs-skip-closing

@tlindsay
Copy link

I used to use https://github.com/jiangmiao/auto-pairs which provides jumpout behavior like this:

Cursor starts here
 │
 │ if (someCondition) {
 └──►|
   }|◄──┐
        │
        │
        Type "}" to jump here

Is it possible to configure this plugin to achieve something similar? It's really intuitive to just close whatever pair you're in to jump out.

@Diomendius
Copy link

@tlindsay I managed to get nvim-autopairs to do this via a hack I mentioned in #147.

This is a slightly improved version of the code I posted there:

local nap = require('nvim-autopairs')
local rule = require('nvim-autopairs.rule')
local cond = require('nvim-autopairs.conds')
local utils = require('nvim-autopairs.utils')

local function multiline_close_jump(open, close)
    return rule(close, '')
        :with_pair(function()
            local row, col = utils.get_cursor()
            local line = utils.text_get_current_line()

            if #line ~= col then --not at EOL
                return false
            end

            local unclosed_count = 0
            for c in line:gmatch("[\\" .. open .. "\\" .. close .. "]") do
                if c == open then unclosed_count = unclosed_count + 1 end
                if unclosed_count > 0 and c == close then unclosed_count = unclosed_count - 1 end
            end
            if unclosed_count > 0 then return false end

            local nextrow = row + 1
            if nextrow < vim.api.nvim_buf_line_count(0)
                and vim.regex("^\\s*" .. close):match_line(0, nextrow) then
                return true
            end
            return false
        end)
        :with_move(cond.none())
        :with_cr(cond.none())
        :with_del(cond.none())
        :set_end_pair_length(0)
        :replace_endpair(function()
            return '<esc>xEa'
        end)
end

nap.add_rules {
    multiline_close_jump('(', ')'),
    multiline_close_jump('[', ']'),
    multiline_close_jump('{', '}'),
}

IIRC I fixed it preferring to jump over closing braces instead of inserting a closing brace when an unclosed opener exists within another set of braces.

@tlindsay
Copy link

tlindsay commented Apr 7, 2022

Amazing! Thanks @Diomendius!

@vitek-borovsky
Copy link

Trying your solution does it still work for you guys @tlindsay @Diomendius are there any other plugins I need as prerequisits I seem to keep getting error: E5113: Error while calling lua chunk: .../pack/packer/start/nvim-autopairs/lua/nvim-autopairs.lua:115: attempt to index field 'config' (a nil value).
After reading that is a indexing problem i tried nap.add_rule(multiline_close_jump('(', ')')), but it didin't help. Still getting
image

TLDR; does it still work?

@ravener
Copy link

ravener commented Oct 2, 2022

I'd also like to suggest delete functionality, this is done in https://github.com/jiangmiao/auto-pairs

if (true) {| <-- Press backspace here.
}

Expected output:

if (true) 

@amarakon
Copy link

amarakon commented Oct 2, 2022

@vitek-borovsky it is not working for me either. This could be an issue with Neovim 0.8.

@HawkinsT
Copy link

Not nearly as good a solution, but a simple mapping I've been using for a while that will essentially accomplish the same thing and works in 0.8 is:

vim.keymap.set("i", "<C-l>", "<esc>:exe 'norm! l%%'<CR>a", { silent = true })

It's always worked well within paragraphs for me with the two caveats being that you can't really jump between paragraphs easily and it's only one way (you can't jump backwards).

@camilledejoye
Copy link

Updated version of @Diomendius rule, thanks by the way.

local npairs = require('nvim-autopairs')
local Rule = require('nvim-autopairs.rule')
local cond = require('nvim-autopairs.conds')
local utils = require('nvim-autopairs.utils')

local function multiline_close_jump(open, close)
  return Rule(close, '')
    :with_pair(function()
      local row, col = utils.get_cursor(0)
      local line = utils.text_get_current_line(0)

      if #line ~= col then --not at EOL
        return false
      end

      local unclosed_count = 0
      for c in line:gmatch('[\\' .. open .. '\\' .. close .. ']') do
        if c == open then
          unclosed_count = unclosed_count + 1
        end
        if unclosed_count > 0 and c == close then
          unclosed_count = unclosed_count - 1
        end
      end
      if unclosed_count > 0 then
        return false
      end

      local nextrow = row + 1
      if nextrow < vim.api.nvim_buf_line_count(0) and vim.regex('^\\s*' .. close):match_line(0, nextrow) then
        return true
      end
      return false
    end)
    :with_move(cond.none())
    :with_cr(cond.none())
    :with_del(cond.none())
    :set_end_pair_length(0)
    :replace_endpair(function(opts)
      local cleanup = '' == trim(opts.line)
        and 'dd' -- test
        or 'xj' --test
      return ('<esc>%s0f%sa'):format(cleanup, opts.char)
    end)
end

npairs.add_rules({
  multiline_close_jump('(', ')'),
  multiline_close_jump('[', ']'),
  multiline_close_jump('{', '}'),
})

For the index issue it was because of a missing fallback to 0 when not providing a buffer number. I added it to the two calls to avoid using the default value.
There is also a small change to the behavior to match a bit more what I'm used to:

  • Only go after the next closing pair instead of next end of a "WORD"
  • Delete current line if empty
    Before:
local t = {
  y = {
    |
  },
}

After:

local t = {
  y = {
  }|,
}

@brokenbyte
Copy link
Contributor

My take on this:

local function multiline_close_jump(open, close)
    return rule(close, "")
        :with_pair(function()
            local row, col = utils.get_cursor(0)
            local line = utils.text_get_current_line(0)

            if #line ~= col then --not at EOL
                return false
            end

            local unclosed_count = 0
            for c in line:gmatch("[\\" .. open .. "\\" .. close .. "]") do
                if c == open then unclosed_count = unclosed_count + 1 end
                if unclosed_count > 0 and c == close then unclosed_count = unclosed_count - 1 end
            end
            if unclosed_count > 0 then return false end

            local nextrow = row + 1

            if nextrow < vim.api.nvim_buf_line_count(0) and vim.regex("^\\s*" .. close):match_line(0, nextrow) then
                return true
            end
            return false
        end)
        :with_move(cond.none())
        :with_cr(cond.none())
        :with_del(cond.none())
        :set_end_pair_length(0)
        :replace_endpair(function(opts)
            local row, _col = utils.get_cursor(0)
            local action = vim.regex("^" .. close):match_line(0, row + 1) and "a" or ("0f%sa"):format(opts.char)
            return ("<esc>xj%s"):format(action)
        end)
end

@camilledejoye 's version almost works for me, but doesn't leave me in insert mode when the matching } is in the first column for some reason. E.g.:

local t = {
  y = {
    |
  }
}

works fine and results in

local t = {
  y = {
  }|
}

but pressing } again right away puts my cursor on the final } and leaves me in normal mode:

local t = {
  y = {
  }
}| -- Cursor here, but in Normal mode, not Insert

After playing around with different variations for replace_endpair return values, it seems to have something to do with using f{ while in the first column or something around there, because it's only an issue when the matching pair is the first character on the line afaict; even adding a space before it works properly.

My fix just checks if the first character on the line is the matching pair and runs <esc>a, otherwise does <esc>0f}a.
Also note that I don't have it delete blank lines inside the matching pairs as I use the blank line as a reminder to put something there later, so you'll need to change the string if you want to keep that.

@rish987
Copy link

rish987 commented Oct 5, 2023

To get back to the original question, I have put together this quick & dirty solution for using a single key to jump over multiple nested ending pairs, though it only works in the single line case:

local check_next_delim = function(line, col)
  for _, rule in ipairs(npairs.get_buf_rules(vim.api.nvim_get_current_buf())) do
    if rule.start_pair then
      local delim = rule.end_pair
      if line:find(delim, col, true) == col then return delim end
    end
  end
end

local skip_pairs = function()
  local col = vim.fn.col('.')
  local line = vim.fn.getline('.')

  local next_delim = check_next_delim(line, col)
  while next_delim do
    for _ = 1, vim.fn.strdisplaywidth(next_delim) do
      vim.fn.feedkeys(vim.api.nvim_replace_termcodes("<Right>", true, true, true))
    end

    col = col + #next_delim
    if col > #line then return end

    next_delim = check_next_delim(line, col)
  end
end

vim.keymap.set("i", "<Tab>", skip_pairs)

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

No branches or pull requests