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

Feature request: Rust lifetime syntax support #68

Open
mawkler opened this issue Dec 16, 2023 · 19 comments
Open

Feature request: Rust lifetime syntax support #68

mawkler opened this issue Dec 16, 2023 · 19 comments

Comments

@mawkler
Copy link

mawkler commented Dec 16, 2023

Hi! Thanks for making this awesome plugin!

This is a tricky one, but I would love it if this plugin handled creating Rust lifetime syntax. They can look like this.

<'a>

Currently when I type this with this plugin I get the following, since the apostrophe gets paired:

<'a>'

In Rust you also use apostrophes (') to type chars, which are different from strings which are typed with double quotes ("), so simply disabling ultimate-autopairs would be a shame because the pairing is useful when typing chars.

Would it be possible to use Treesitter to determine if you're typing a lifetime or if you're typing a char?

@altermo
Copy link
Owner

altermo commented Dec 16, 2023

Currently no; the treesitter node lifetime is only created after the character is inserted. I'll look into implementing a temporary char insert to get the treesitter node in the next version of the plugin.

@mawkler
Copy link
Author

mawkler commented Dec 18, 2023

I see. Thank you!

You seem to be correct in that Treesiter doesn't recognize the lifetime node until it's been created. However, it does also recognize the invalid lifetime with the extra apostrophe (<'a>') as a lifetime, but followed by an ERROR node as well. Could a fix be to detect if a lifetime node has just been typed that's followed by an apostrophe and an ERROR node, and to then remove the trailing apostrophe?

@altermo
Copy link
Owner

altermo commented Dec 18, 2023

No, the way the plugin is currently set up, it is not possible to do anything after the initial insertion.
However one could create an external program.
Here's an example of how to do this:

vim.api.nvim_create_autocmd('InsertCharPre',{callback=function ()
    local col=vim.fn.col'.'-1
    local line=vim.api.nvim_get_current_line()
    if vim.v.char~="'" then return end
    if line:sub(col,col)~="'" then return end
    local parser=vim.treesitter.get_parser()
    parser:parse()
    local node=vim.treesitter.get_node()
    if not node then return end
    local parent=node:parent()
    if not parent then return end
    if parent:type()~='lifetime' then return end
    vim.v.char=''
    vim.api.nvim_input('<Right>')
end})
require'ultimate-autopair'.setup{
    --your config
    config_internal_pairs={
        {"'","'",cond=function (fn)
            return not fn.in_node('lifetime')
        end}
    }
}

@mawkler
Copy link
Author

mawkler commented Dec 18, 2023

Ok, thanks!

@svanharmelen
Copy link

Hey 👋🏻 I'm trying to switch from nvim-autopairs to UA and it seems to go pretty smooth except for 2 issues, one of them being this exact question 😏

I previously solved this is using the following config:

-- Define locals
local npairs   = require('nvim-autopairs')
local ts_conds = require('nvim-autopairs.ts-conds')

-- Configure Nvim-Autopairs
npairs.setup({
  check_ts = true,
  ts_config = {
    go = {},
    rust = {},
  }
})

-- Add custom rule for Rust lifetime params
npairs.get_rule("'")[2]:with_pair(ts_conds.is_not_ts_node({ 'type_arguments', 'bounded_type' }))

But I'm not sure how to port that to a condition in UA. I tried this, but that didn't work:

  {
    'altermo/ultimate-autopair.nvim',
    branch = 'v0.6',
    event = { 'InsertEnter', 'CmdlineEnter' },
    config = function()
      require('ultimate-autopair').setup {
        config_internal_pairs = {
          { "'", "'", multiline = false, suround = true, cond = function(fn) 
            return not fn.in_node { 'bounded_type', 'type_arguments' }
          end}
        }
      }
    end
  },

Does this additional info help? Or do you still don't see a good way to fix this in UA?

Thanks!

@altermo
Copy link
Owner

altermo commented Mar 12, 2024

Your UA config worked for me with this minimal config:

Click text to open and show minimal config

Open this file with nvim --clean and run :source (it will hang while installing everything).

for _,url in ipairs{'altermo/ultimate-autopair.nvim','nvim-treesitter/nvim-treesitter'} do
    local install_path=vim.fn.fnamemodify(url:gsub('.*/',''),':p')
    if vim.fn.isdirectory(install_path)==0 then
        vim.fn.system{'git','clone','https://github.com/'..url,install_path}
    end
    vim.opt.runtimepath:append(install_path)
end
require'nvim-treesitter.configs'.setup{
  ensure_installed = {'rust'},
  sync_install = true,
}
require('ultimate-autopair').setup {
    config_internal_pairs = {
        { "'", "'", multiline = false, suround = true, cond = function(fn)
            return not fn.in_node { 'bounded_type', 'type_arguments' }
        end}
    }
}
vim.cmd.vnew()
vim.o.buftype='nofile'
vim.fn.setline(1,'let a: Vec<a>;')
vim.fn.setline(2,'//>>> PRESS THE \' KEY <<<')
vim.cmd.setf'rust'
vim.fn.cursor(1,12)
vim.api.nvim_input('i')
vim.o.cmdheight=2

Can you diagnose what may be causing it to not work for you?


Also, I already implemented a feature in the next version that makes this possible, but that won't be released for a long time.

@svanharmelen
Copy link

Thanks for your reaction @altermo! I tried your script and it indeed worked. But it feels finicky. For example, try this one:

require 'nvim-treesitter.configs'.setup {
  ensure_installed = { 'rust' },
  sync_install = true,
}
require('ultimate-autopair').setup {
  { '<', '>', fly = true, dosuround = true, multiline = false, space = true, surround = true },

  config_internal_pairs = {
    { "'", "'", multiline = false, suround = true,
      cond = function(fn) return not fn.in_node { 'bounded_type', 'type_arguments' } end,
    }
  },
}
vim.cmd.vnew()
vim.o.buftype = 'nofile'
vim.fn.setline(1, 'struct A<a> {')
vim.fn.setline(2, '    a: String<a>,')
vim.fn.setline(3, '}')
vim.fn.setline(4, '//>>> PRESS THE \' KEY <<<')
vim.cmd.setf 'rust'
vim.fn.cursor(1, 10)
vim.api.nvim_input('i')
vim.o.cmdheight = 2

I fails when inserting the ' on the first line, but if you move down to the second line, it does work for that one 🤷🏻

@altermo
Copy link
Owner

altermo commented Mar 12, 2024

That's because the failing node's type is type_parameters (rather than type_arguments), so just add it to the list of nodes to make it work.
Generally, if it fails somewhere, use :InspectTree to get the node and add it to the list of nodes.

@svanharmelen
Copy link

That indeed works, thanks! And thanks for the :InspectTree tip!!! That's a real nice one to know 👍🏻

@mawkler
Copy link
Author

mawkler commented Mar 13, 2024

Thank you, that works for me as well!

require('ultimate-autopair').setup({
  { '<', '>', fly = true, dosuround = true, multiline = false, space = true, surround = true },
  config_internal_pairs = {
    { "'", "'", multiline = false, surround = true, cond = function(fn)
        return not fn.in_node({ 'bounded_type', 'type_parameters' })
      end,
    }
  },
})

Perhaps this should be added to the README or the Wiki?

@svanharmelen
Copy link

I actually added 'bounded_type', 'reference_type', 'type_arguments', 'type_parameters' to make it work in other places I encountered it as well...

@svanharmelen
Copy link

But its still not perfect... Guess I'll disable this one (completion of ') for Rust.

@mawkler
Copy link
Author

mawkler commented Mar 13, 2024

@svanharmelen Hmm ok. Is it possible to enable that rule for only rust filetypes?

@altermo
Copy link
Owner

altermo commented Mar 13, 2024

cond takes a function so early-return true if the file type is rust. (fn.get_ft() is treesitter-injected-lang aware)
The config would be:

config_internal_pairs = {
  { "'", "'", multiline = false, surround = true, cond = function(fn)
      if fn.get_ft() ~= 'rust' then return true end
      return not fn.in_node({ 'bounded_type', 'reference_type', 'type_arguments', 'type_parameters' })
    end,
  },
},

@alexmozaidze
Copy link

Why not disable ' pairing altogether for Rust? Lifetimes are usually used more often than char literals. Having it disabled is not great, but it being enabled is even worse because of how often ' is used when making types with explicit lifetimes (which is basically every type holding a reference) or for labeling loops/blocks.

@mawkler
Copy link
Author

mawkler commented Jun 11, 2024

@altermo I see that you disabled Rust completely in 035d92e. How come you didn't go with your solution above instead? It has been working great for me.

@svanharmelen
Copy link

I think it's a good default to have this one disabled for Rust. While it maybe configurable to some extend to work as one would like, it remains a somewhat flaky and incomplete experience for me.

@mawkler
Copy link
Author

mawkler commented Jun 11, 2024

@svanharmelen Fair. How do I re-enable the pairing in Rust files? @altermo's solution above no longer works.

Edit: never mind, the fix still works

@svanharmelen
Copy link

@mawkler I don't and I totally don't miss it to be honest. Of course you can always "undo" the default and enable it for Rust if you want by adding this to you config:

      config_internal_pairs = {
        { "'", "'", alpha = true, multiline=false, nft = { 'tex' }, suround = true,
          cond = function(fn) return not fn.in_lisp() or fn.in_string() end
        }
      },

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

No branches or pull requests

4 participants