nvim-treesitter / nvim-treesitter-textobjects

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

"Change in textobject" has odd behavior

jrwrigh opened this issue · comments

Describe the bug
When running "change in textobject" (ie. cif), there is odd/undesired behavior. For example, I can no longer delete characters as I type, running successive ci{f,a,c} will start to change different parts of the buffer, and autocompletion (like in nvim-cmp) has equally odd behavior (see hrsh7th/nvim-cmp#1414 for gif and details)

To Reproduce
I have created this minimal reproducer config:

Minimal Reproducer config
vim.cmd [[set runtimepath=$VIMRUNTIME]]
vim.cmd [[set packpath=/tmp/nvim/site]]
local package_root = '/tmp/nvim/site/pack'
local install_path = package_root .. '/packer/start/packer.nvim'
local function load_plugins()
  require('packer').startup {
    {
      'wbthomason/packer.nvim',
      {
        'nvim-telescope/telescope.nvim',
        requires = {
          'nvim-lua/plenary.nvim',
          { 'nvim-telescope/telescope-fzf-native.nvim', run = 'make' },
        },
      },
      { 'nvim-treesitter/nvim-treesitter', config = function()
            require('nvim-treesitter.configs').setup {
                ensure_installed = { 'c'},
                textobjects = {
                  select = {
                    enable = true,
                    lookahead = true, -- Automatically jump forward to textobj, similar to targets.vim
                    keymaps = {
                      -- You can use the capture groups defined in textobjects.scm
                      ['af'] = '@function.outer',
                      ['if'] = '@function.inner',
                      ['ac'] = '@class.outer',
                      ['ic'] = '@class.inner',
                      ['aa'] = '@parameter.outer',
                      ['ia'] = '@parameter.inner',
                    },
                  },
                },
              }
            end
      },
      { 'nvim-treesitter/nvim-treesitter-textobjects' },
      -- ADD PLUGINS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
    },
    config = {
      package_root = package_root,
      compile_path = install_path .. '/plugin/packer_compiled.lua',
      display = { non_interactive = true },
    },
  }
end
_G.load_config = function()
  require('telescope').setup()
  require('telescope').load_extension('fzf')
  -- ADD INIT.LUA SETTINGS THAT ARE _NECESSARY_ FOR REPRODUCING THE ISSUE
end
if vim.fn.isdirectory(install_path) == 0 then
  print("Installing Telescope and dependencies.")
  vim.fn.system { 'git', 'clone', '--depth=1', 'https://github.com/wbthomason/packer.nvim', install_path }
end
load_plugins()
require('packer').sync()
vim.cmd [[autocmd User PackerComplete ++once echo "Ready!" | lua load_config()]]

and this example c file:

#include <stdio.h>
int main(int argc, char *argv[]) {
   printf("Hello, World!");
   return 0;
}

Here's is a gif demonstrating the behavior (with screenkeys, so it should be self-documenting):

TSTextobject_bug

Expected behavior
A clear and concise description of what you expected to happen.

Output of :checkhealth nvim-treesitter

nvim-treesitter: require("nvim-treesitter.health").check()
========================================================================
## Installation
  - OK: `tree-sitter` found 0.20.7 (parser generator, only needed for :TSInstallFromGrammar)
  - OK: `node` found v19.3.0 (only needed for :TSInstallFromGrammar)
  - OK: `git` executable found.
  - OK: `cc` executable found. Selected from { vim.NIL, "cc", "gcc", "clang", "cl", "zig" }
    Version: cc (GCC) 12.2.0
  - OK: Neovim was compiled with tree-sitter runtime ABI version 14 (required >=13). Parsers must be compatible with runtime ABI.

## OS Info:
{
  machine = "x86_64",
  release = "5.14.21-2-MANJARO",
  sysname = "Linux",
  version = "#1 SMP PREEMPT Sun Nov 21 22:43:47 UTC 2021"
}

## Parser/Features         H L F I J
  - c                   ✓ ✓ ✓ ✓ ✓

  Legend: H[ighlight], L[ocals], F[olds], I[ndents], In[j]ections
         +) multiple parsers found, only one will be used
         x) errors found in the query, try to run :TSUpdate {lang}

Output of nvim --version

$ nvim --version
NVIM v0.8.1
Build type: Release
LuaJIT 2.1.0-beta3
Compiled by builduser

Features: +acl +iconv +tui
See ":help feature-compile"

   system vimrc file: "$VIM/sysinit.vim"
  fall-back for $VIM: "/usr/share/nvim"

Run :checkhealth for more info

Here's another odd behavior. So I can eventually delete characters, but only after pressing backspace exactly 10 times:

TSTextobject_bug_10backspaces

It doesn't matter how many letters I've entered in, it won't delete any characters until I've pressed <BS> 10 times.

Some more interesting evidence. Installing treesitter playground to inspect the tree during the changes:
TSTextobject_bug_Playground

You can see that the first argument to the function is correctly located initially (parameter_declaration [1, 9] - [1, 17]). However, after doing the the cia, TS node changes to parameter_declaration [1, 13] - [1, 13], which has zero width. I'm not sure whether this is a treesitter bug from upstream, or something to do with this plugin. Regardless, this is probably causing the issues with the successive change commands.

Thanks for reporting. I could reproduce some of the stuff you described. For me, it seems to be okay with function and class but it's a problem when there's some trailing content (argument, conditional inner).

Also I can reproduce nvim-cmp showing duplicates of previous selection as well.

I don't know why yet.. I need to check if older commits were okay.

I can only reproduce if the change is within a line (not multi-line) and I don't need the reproduction config file. I can just reproduce with my config.

@theHamsta I tried with a8c86f4 and it still has the same issue, so it's not because of the curse of using lua function (I remember recently I changed the vimscript to lua function for select)

Could it be the upstream treesitter bug?

@theHamsta I confirmed that it's a bug from update_selection in nvim-treesitter/ts_utils.lua.
Using gv to update selection seems to cause problems.

-- Simple text object that selects 3 characters.
-- Implemented in 3 different ways

-- `ciq` works well. 
vim.keymap.set({ "o" }, "iq", function()
  vim.cmd [[normal! vhol]]
end)

-- This is nvim-treesitter's implementation using gv. Minimal reproducible copy.
-- It has the bug (try `cir`)
function update_selection()
  local cursor = vim.api.nvim_win_get_cursor(0)
  vim.api.nvim_buf_set_mark(0, "<", cursor[1], cursor[2] - 1, {})
  vim.api.nvim_buf_set_mark(0, ">", cursor[1], cursor[2] + 1, {})

  vim.api.nvim_cmd({ cmd = "normal", bang = true, args = { "gv" } }, {})
end

vim.keymap.set({ "o" }, "ir", update_selection)

-- The suggested fix that doesn't use `gv`
-- `ciy` works well.
function update_selection_fix()
  local cursor = vim.api.nvim_win_get_cursor(0)

  vim.cmd "normal! v"
  vim.api.nvim_win_set_cursor(0, { cursor[1], cursor[2] - 1 })
  vim.cmd "normal! o"
  vim.api.nvim_win_set_cursor(0, { cursor[1], cursor[2] + 1 })
end

vim.keymap.set({ "o" }, "iy", update_selection_fix)

@theHamsta I confirmed that it's a bug from update_selection in nvim-treesitter/ts_utils.lua. Using gv to update selection seems to cause problems.

-- Simple text object that selects 3 characters.
-- Implemented in 3 different ways

-- `ciq` works well. 
vim.keymap.set({ "o" }, "iq", function()
  vim.cmd [[normal! vhol]]
end)

-- This is nvim-treesitter's implementation using gv. Minimal reproducible copy.
-- It has the bug (try `cir`)
function update_selection()
  local cursor = vim.api.nvim_win_get_cursor(0)
  vim.api.nvim_buf_set_mark(0, "<", cursor[1], cursor[2] - 1, {})
  vim.api.nvim_buf_set_mark(0, ">", cursor[1], cursor[2] + 1, {})

  vim.api.nvim_cmd({ cmd = "normal", bang = true, args = { "gv" } }, {})
end

vim.keymap.set({ "o" }, "ir", update_selection)

-- The suggested fix that doesn't use `gv`
-- `ciy` works well.
function update_selection_fix()
  local cursor = vim.api.nvim_win_get_cursor(0)

  vim.cmd "normal! v"
  vim.api.nvim_win_set_cursor(0, { cursor[1], cursor[2] - 1 })
  vim.cmd "normal! o"
  vim.api.nvim_win_set_cursor(0, { cursor[1], cursor[2] + 1 })
end

vim.keymap.set({ "o" }, "iy", update_selection_fix)

I can't tell much about this code. I was originally written by @kyazdani42 and then dozen times updated to resolve different issues. We should really add unittest about the quirks that we fix in the past so that we don't regress. Especially, since the code is very cryptic. Whatever works is fine for me.

Wasn't it using v and o before and we changed it to gv 😅 ?

I don't know how it has been changed, I never really touched that part of the code. I only remember that it used to be normal gv and it changed to normal! gv

And yeah need to definitely add more kinds of tests as my testing currently only checks the selection range

@theHamsta I see, it was nvim-treesitter/nvim-treesitter#4015
@phgz It looks it the fix introduced another bug. Also I can't really reproduce the flickering UI either. Is it possible to test if you still face the same issues if you use https://github.com/kiyoon/nvim-treesitter/tree/fix/update-selection-change-mode ?

I used vim.api.nvim_win_set_cursor instead of vim.fn.setpos which may have fixed some issues we had earlier

commented

I should be able to check it later today.

I should be able to check it later today.

Thank you!

commented

Sorry for the delay. I've tested it and it seems to be good. Nothing suspect detected. It was originally to fix the vim.keymap.set thing, but it now seems ok apparently, so all good!

@phgz Really appreciate taking your time on the weekends to test this!

commented

@kiyoon Thanks to you on taking the relay on the continuation of this great project!