abecodes/tabout.nvim

Luasnip?

Closed this issue ยท 8 comments

9mm commented

Hey, So I see all the examples both in Luasnip and Tabout, but I'm not really familiar with Lua/neovim functions/configurations yet.

I'm trying to get <Tab> to be have properly in the following order:

  1. snippet expansion + jumping
  2. tabout

Here is what I have in Luasnip:

return {
  'L3MON4D3/LuaSnip',
  enabled = true,
  event = 'VeryLazy',
  version = '2.*',
  build = 'make install_jsregexp',
  config = function()
    local ls = require('luasnip')

    -- https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#config-options
    ls.setup({
      update_events = 'TextChanged,TextChangedI',
    })

    require('luasnip.loaders.from_snipmate').lazy_load({
      paths = '~/.config/nvim/snippets',
    })

    local function interp(k) vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(k, true, false, true), 'n', false) end

    vim.keymap.set({'i', 's'}, '<Tab>', function()
      if ls.expand_or_jumpable() then ls.expand_or_jump()
      else interp('<Tab>') end
    end)

    vim.keymap.set({'i', 's'}, '<S-Tab>', function()
      if ls.jumpable(-1) then ls.jump(-1)
      else interp('<S-Tab>') end
    end)
  end
}

Now the problem is trying to get Tabout to work with this.

My tabout looks like this:

-- https://github.com/abecodes/tabout.nvim

return {
  'abecodes/tabout.nvim',
  enabled = true,
  event = 'VeryLazy',
  dependencies = {
    'nvim-treesitter/nvim-treesitter',
    'hrsh7th/nvim-cmp',
    'L3MON4D3/LuaSnip',
  },
  config = function()
    -- https://github.com/abecodes/tabout.nvim#more-complex-keybindings

    require('tabout').setup({
      tabkey = '',
      backwards_tabkey = '',
      act_as_tab = true,
      act_as_shift_tab = false,
      default_tab = '<C-t>',
      default_shift_tab = '<C-d>',
      enable_backwards = true,
      completion = true,
      tabouts = {
        { open = "'", close = "'" },
        { open = '"', close = '"' },
        { open = '`', close = '`' },
        { open = '(', close = ')' },
        { open = '[', close = ']' },
        { open = '{', close = '}' }
      },
      ignore_beginning = true,
      exclude = {},
    })

    -- local function replace_keycodes(c)
    --   return vim.api.nvim_replace_termcodes(c, true, true, true)
    -- end
    --
    -- function _G.tab_binding()
    --   if vim.fn.pumvisible() ~= 0 then
    --     return replace_keycodes("<C-n>")
    --   elseif vim.fn["vsnip#available"](1) ~= 0 then
    --     return replace_keycodes("<Plug>(vsnip-expand-or-jump)")
    --   else
    --     return replace_keycodes("<Plug>(Tabout)")
    --   end
    -- end
    --
    -- function _G.s_tab_binding()
    --   if vim.fn.pumvisible() ~= 0 then
    --     return replace_keycodes("<C-p>")
    --   elseif vim.fn["vsnip#jumpable"](-1) ~= 0 then
    --     return replace_keycodes("<Plug>(vsnip-jump-prev)")
    --   else
    --     return replace_keycodes("<Plug>(TaboutBack)")
    --   end
    -- end
    --
    -- vim.api.nvim_set_keymap("i", "<Tab>", "v:lua.tab_binding()", {expr = true})
    -- vim.api.nvim_set_keymap("i", "<S-Tab>", "v:lua.s_tab_binding()", {expr = true})
  end,
}

So far I have set this:

      tabkey = '',
      backwards_tabkey = '',

But I'm still trying to figure out what to actually put for the tab_binding.

Are you able to give any ideas?

9mm commented

I also tried this but it doesn't seem to tabout

    local ls = require('luasnip')

    local function replace_keycodes(c)
      return vim.api.nvim_replace_termcodes(c, true, true, true)
    end

    function _G.tab_binding()
      if not ls.expand_or_jumpable() then
        return replace_keycodes('<Plug>(Tabout)')
      end
    end

    function _G.s_tab_binding()
      if not ls.jumpable(-1) then
        return replace_keycodes('<Plug>(TaboutBack)')
      end
    end

    vim.api.nvim_set_keymap('i', '<Tab>', 'v:lua.tab_binding()', { expr = true })
    vim.api.nvim_set_keymap('i', '<S-Tab>', 'v:lua.s_tab_binding()', { expr = true })

but it outputs this for the snippet: v:null

9mm commented

UGH the problem was this... I was calling <Plug>(luasnip-expand-or-jump) instead of <Plug>luasnip-expand-or-jump

I didn't realize the parens were a part of the name. Here's my full config if anyone else has same problem:

-- https://github.com/abecodes/tabout.nvim

return {
  'abecodes/tabout.nvim',
  event = 'InsertEnter',
  dependencies = {
    'nvim-treesitter/nvim-treesitter',
    'hrsh7th/nvim-cmp',
    'L3MON4D3/LuaSnip',
  },
  config = function()
    -- https://github.com/abecodes/tabout.nvim#more-complex-keybindings

    require('tabout').setup({
      tabkey = '', --'<Tab>',
      backwards_tabkey = '', --'<S-Tab>',
      act_as_tab = true,
      act_as_shift_tab = false,
      default_tab = '<C-t>',
      default_shift_tab = '<C-d>',
      enable_backwards = true,
      completion = true,
      tabouts = {
        { open = "'", close = "'" },
        { open = '"', close = '"' },
        { open = '`', close = '`' },
        { open = '(', close = ')' },
        { open = '[', close = ']' },
        { open = '{', close = '}' },
      },
      ignore_beginning = true,
      exclude = {},
    })

    -- handle <Tab> key to prioritize LuaSnip over Tabout

    local ls = require('luasnip')

    local function replace_keycodes(c)
      return vim.api.nvim_replace_termcodes(c, true, true, true)
    end

    function _G.tab_binding()
      if ls.expand_or_jumpable() then
        return replace_keycodes('<Plug>luasnip-expand-or-jump')
      else
        return replace_keycodes('<Plug>(Tabout)')
      end
    end

    function _G.s_tab_binding()
      if ls.jumpable(-1) then
        return replace_keycodes('<Plug>luasnip-jump-prev')
      else
        return replace_keycodes('<Plug>(TaboutBack)')
      end
    end

    vim.api.nvim_set_keymap('i', '<Tab>', 'v:lua.tab_binding()', { expr = true })
    vim.api.nvim_set_keymap('i', '<S-Tab>', 'v:lua.s_tab_binding()', { expr = true })
  end,
}

@9mm thanks for sharing your journey and the config ๐Ÿ‘

9mm commented

Youlre welcome :D I spent way too long finding those parens

@9mm thanks~

Tks for the ideas here!

I decided to go the other way, telling luasnip to call tabout. Looking at how Lazyvim does the config to the keys for luasnip, and taking into account that lazy.nvim merges the keys table, I got this:

{
   "L3MON4D3/LuaSnip",
   dependencies = { "abecodes/tabout.nvim" },
   keys = {
     {
       "<tab>",
       function()
         return "<Plug>(Tabout)" or (require("luasnip").jumpable(1) and "<Plug>luasnip-jump-next") or "<tab>"
       end,
       expr = true,
       silent = true,
       mode = "i",
     },
   },
 },
 {
   "abecodes/tabout.nvim",
   dependencies = {
     "nvim-treesitter/nvim-treesitter",
     "hrsh7th/nvim-cmp",
   },
   event = "InsertEnter",
   config = true,
 },
uwla commented

I would like to share my config which:

  1. Expands LuaSnip snippets using nvim-cmp (if available)
  2. Inserts current indentation if cursor is at the very beginning of the line (0 motion)
  3. Inserts indentation if the cursor is preceed by only whitespaces or tabs (^ motion)
  4. Tabs out of the code context

The items 2 and 3 are cases where I do not want to tab-out but want to insert indentation.

cmp.setup({
    mapping = cmp.mapping.preset.insert({
        ["<Tab>"] = function(fallback)
            local feedkeys = vim.fn.feedkeys
            local termcodes = vim.api.nvim_replace_termcodes
            local get_cursor = vim.api.nvim_win_get_cursor
            local get_line = vim.api.nvim_get_current_line

            -- trigger the snippet engine
            if luasnip.expand_or_jumpable() then
                feedkeys(
                    termcodes("<Plug>luasnip-expand-or-jump", true, true, true),
                    ""
                )
                return
            end

            -- We will make Vim insert correct indentation for current line
            -- by pressing <TAB> if we are in the beginning of the line.
            local cursor_line, cursor_col = unpack(get_cursor(0))
            if cursor_col == 0 then
                -- We achieve so by:
                --
                -- 1. Simulate backspace (delete character under cursor)
                -- 2. Simulate Enter
                --
                -- This will:
                --
                -- 1. delete the current line,
                -- 2. go to the previous line,
                -- 3. start a new line.
                --
                -- Vim automatically indents new lines.
                -- That is how we achieve <TAB> inserting current indentation
                -- if the cursor is at the beginning of the line
                feedkeys(termcodes('<BS>', true, false, true), "")
                feedkeys(termcodes('<CR>', true, false, true), "")
                return
            end

            -- cursor is preeced by whitespaces-only, so insert indentation
            local line_content = get_line()
            local before_cursor = line_content:sub(1, cursor_col)
            if before_cursor:match("^[\t%s]+$") then
                fallback()
                return
            end

            -- finally, tabs-out of code context
            require("tabout").tabout()
        end,
    })
})

This works best with ignore_beginning=true and completion=false

@uwla Thanks for sharing ๐ŸŽ–๏ธ ๐Ÿ‘