nvim-treesitter / nvim-treesitter-textobjects

Apache License 2.0
2.16k stars 194 forks source link

Delete @function.outer deletes more than it should #575

Open SamuelLarkin opened 8 months ago

SamuelLarkin commented 8 months ago

Describe the bug After configuring daf to delete outer function lines, more than the function is deleted.

To Reproduce Given LazyVim and the following snippet configuration

return {
  {
    -- [Linewise selections should include all empty lines below](https://github.com/nvim-treesitter/nvim-treesitter-textobjects/issues/307)
    -- [Treesitter text objects mapping not working or being overwritten](https://www.reddit.com/r/neovim/comments/12x8mbf/treesitter_text_objects_mapping_not_working_or/)
    -- [example config](https://github.com/augustocdias/dotfiles/blob/main/.config/nvim/lua/setup/treesitter.lua)
    "nvim-treesitter/nvim-treesitter",
    opts = {
      textobjects = {
        select = {
          enable = true,

          -- Automatically jump forward to textobj, similar to targets.vim
          lookahead = true,
          keymaps = {
            -- You can use the capture groups defined in textobjects.scm
            -- You can optionally set descriptions to the mappings (used in the desc parameter of
            -- nvim_buf_set_keymap) which plugins like which-key display
            ["aZ"] = "@function.outer",
            ["af"] = "@function.outer",
            ["if"] = "@function.inner",
            ["ac"] = "@class.outer",
            ["ic"] = { query = "@class.inner", desc = "Select inner part of a class region" },
            ["au"] = "@comment.outer",
            ["iu"] = "@comment.inner",
            ["ab"] = "@block.outer",
            ["ib"] = "@block.inner",
          },
          -- You can choose the select mode (default is charwise 'v')
          --
          -- Can also be a function which gets passed a table with the keys
          -- * query_string: eg '@function.inner'
          -- * method: eg 'v' or 'o'
          -- and should return the mode ('v', 'V', or '<c-v>') or a table
          -- mapping query_strings to modes.
          selection_modes = {
            ["@parameter.outer"] = "v", -- charwise
            ["@function.inner"] = "V", -- linewise
            ["@function.outer"] = "V", -- linewise
            ["@class.inner"] = "V", -- blockwise
            ["@class.outer"] = "V", -- blockwise
          },
          -- If you set this to `true` (default is `false`) then any textobject is
          -- extended to include preceding or succeeding whitespace. Succeeding
          -- whitespace has priority in order to act similarly to eg the built-in
          -- `ap`.
          --
          -- Can also be a function which gets passed a table with the keys
          -- * query_string: eg '@function.inner'
          -- * selection_mode: eg 'v'
          -- and should return true of false
          include_surrounding_whitespace = true,
        },
      },
    },
  },
}

And the following sample code

     1  #!/usr/bin/env python3
     2
     3
     4  class A:
     5      def a(self):
     6          pass
     7
     8
     9  def f1(a: int):
    10      a = 1
    11      pass
    12
    13
    14  def f2(a: int):
    15      a = 2
    16
    17      def ff2(b: int):
    18          b = 2
    19
    20          pass
    21
    22      pass
    23
    24
    25  if __name__ == "__main__":
    26      f1(1)
    27      f2(2)

if you go online :18 and press daf, you will delete

    17      def ff2(b: int):
    18          b = 2
    19
    20          pass
    21
    22      pass

Expected behavior I'm expecting to delete exactly

    17      def ff2(b: int):
    18          b = 2
    19
    20          pass

the pass on line 22 should still be there after dap.

Output of :checkhealth nvim-treesitter

────────────────────────────────────────────────────────────────────────────── nvim-treesitter: require("nvim-treesitter.health").check() Installation - WARNING tree-sitter executable not found (parser generator, only needed for :TSInstallFromGrammar, not required for :TSInstall) - OK node found v21.1.0 (only needed for :TSInstallFromGrammar) - OK git executable found. - OK cc executable found. Selected from { vim.NIL, "cc", "gcc", "clang", "cl", "zig" } Version: cc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.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.4.0-132-generic", sysname = "Linux", version = "#148-Ubuntu SMP Mon Oct 17 16:02:06 UTC 2022" } Parser/Features H L F I J - bash ✓ ✓ ✓ . ✓ - c ✓ ✓ ✓ ✓ ✓ - diff ✓ . . . . - html ✓ ✓ ✓ ✓ ✓ - javascript ✓ ✓ ✓ ✓ ✓ - jsdoc ✓ . . . . - json ✓ ✓ ✓ ✓ . - jsonc ✓ ✓ ✓ ✓ ✓ - lua ✓ ✓ ✓ ✓ ✓ - luadoc ✓ . . . . - luap ✓ . . . . - markdown ✓ . ✓ ✓ ✓ - markdown_inline ✓ . . . ✓ - python ✓ ✓ ✓ ✓ ✓ - query ✓ ✓ ✓ ✓ ✓ - regex ✓ . . . . - toml ✓ ✓ ✓ ✓ ✓ - tsx ✓ ✓ ✓ ✓ ✓ - typescript ✓ ✓ ✓ ✓ ✓ - vim ✓ ✓ ✓ . ✓ - vimdoc ✓ . . . ✓ - yaml ✓ ✓ ✓ ✓ ✓ 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 v0.9.5
Build type: Release
LuaJIT 2.1.1692716794

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

Run :checkhealth for more info

Additional context Add any other context about the problem here.

kiyoon commented 8 months ago

The problem is, if you set include_surrounding_whitespace = true and then use linewise mode, then it will try to include the whitespace before pass and then it will change into a line selection mode. That way the pass also gets deleted.

kiyoon commented 8 months ago

A possible solution is to change the include_surrounding_whitespace mode to activate only when it is a char-wise selection mode.

Akimcx commented 7 months ago

Another option would be to set the selection_modes of @function.outer to charwise, as it is the default. Therefore, it can be removed from the table. I encountered the same issue, and not only did this change fix it, but it also worked very well. If you set include_surrounding_whitespace to false, it will select the function from def to the first pass. Conversely, if you set it to true, it will select until the start of the second pass.

For reference include_surrounding_whitespace = false

  |def ff2(b: int):
    b = 2

    pass|

include_surrounding_whitespace = true

  |def ff2(b: int):
    b = 2

    pass

  |pass
mtrajano commented 3 months ago

Changing @function.outer to charwise works for most operations (delete, yank, etc..) but it doesn't work with commenting actions (gc) since the cursor is still on the beginning of the next line and ends up commenting it out, for example (indentiation is important here) doing gcaf in fun1 (whether it's charwise or linewise):

    def fun1(self):
        pass

    def fun2(self):
        pass

becomes:

    # def fun1(self):
    #     pass

    # def fun2(self):
        pass
mtrajano commented 3 months ago

It is also better to yank/delete a method linewise to move it around. Yanking/deleteting it charwise results in weird indentation issues if you're not accounting for that. I have gotten it to work by adding a check if the selection mode is linewise to update the column end position to 0 before the update.selection call. Realize it's not the best solution but it seems to work well. I can get a pr out for this if the solution seems decent.