Skip to content

gsuuon/tshjkl.nvim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

64 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tshjkl.nvim 🌳

Tree-sitter hjkl mode

image

Usage

Toggle into tshjkl mode, then use hjkl to change scope or select a sibling node. Toggle is mapped to <M-v> (Alt-v) and nodes are visually selected by default. Toggle, movement keys, extmark highlights and select mode can be configured - check init.lua to see configuration and defaults.

Demo

tshjkl_intro.mp4

You can use v again with tshjkl toggled on to enter something like a nodewise-visual mode which works like charwise-visual.

tshjkl-nodewise.mp4

Example

Unwrapping a function

  • toggle with cursor over the inner body
  • change scope if you need to
  • d
  • toggle the node to replace
  • p
tshjkl_example_remove_wrap.mp4

Install

lazy.nvim:

{
  'gsuuon/tshjkl.nvim',
  config = true
}

packer.nvim:

use {
  'gsuuon/tshjkl.nvim',
  config = function()
    require('tshjkl').setup()
  end
}

Configure

You can override the default config with lazy opts:

{
  'gsuuon/tshjkl.nvim',
  opts = {
    -- false to highlight only. Note that enabling this will hide the highlighting of child nodes
    select_current_node = true,
    keymaps = {
      toggle = '<leader>ct',
    },
    marks = {
      parent = {
        virt_text = { {'h', 'ModeMsg'} },
        virt_text_pos = 'overlay'
      },
      child = {
        virt_text = { {'l', 'ModeMsg'} },
        virt_text_pos = 'overlay'
      },
      prev = {
        virt_text = { {'k', 'ModeMsg'} },
        virt_text_pos = 'overlay'
      },
      next = {
        virt_text = { {'j', 'ModeMsg'} },
        virt_text_pos = 'overlay'
      }
    },
    binds = function(bind, tshjkl)
      bind('<Esc>', function()
        tshjkl.exit(true)
      end)

      bind('q', function()
        tshjkl.exit(true)
      end)

      bind('t', function()
        print(tshjkl.current_node():type())
      end)
    end,
  }
}

Or packer in require('tshjkl').setup({}):

use {
  'gsuuon/tshjkl.nvim',
  config = function()
    require('tshjkl').setup({
      keymaps = {
        toggle = '<leader>N',
      }
    })
  end
}

The default options will visual select the current node - since the visual highlight will render over other highlights, you won't see the child extmarks. If you prefer to see those, set select_current_node = false and use the v keybind in ts-mode to manually select the current node instead.

Keymaps

These keymaps are added when tshjkl is toggled on. Check binds for more.

v — enter node-wise visual mode if select_current_node is true, else visual select the current node
b — visual select backwards

h — parent
j — next sibling
k — previous sibling
l — child

H — top-most parent
J — last sibling
K — first sibling
L — inner-most child

Binds

You can bind additional keys for 'tshjkl' mode with the binds option. This takes a function which takes bind and tshjkl - bind lets you bind additional keys, and tshjkl exposes tshjkl.current_node(), tshjkl.set_node() and tshjkl.exit(). Pass true to tshjkl.exit to drop to normal mode (if select_current_node is true).

You can also add binds per buffer by setting vim.b.tshjkl_binds, for example in ftplugin/lua.lua:

vim.b.tshjkl_binds = function(bind, tshjkl)
  bind('f', function()
    local node = tshjkl.current_node()

    while node do
      local type = node:type()

      if type:match('function_de') then -- declaration or definition
        tshjkl.set_node(node)
        return
      end

      node = node:parent()
    end
  end)
end

Motivation

This plugin makes it easier to work with tree-sitter nodes - I've found it often surprising which node is under the cursor so I want to make navigating nodes as easy as basic navigation in Neovim. Visual select by default lets you do normal operations without too much extra thought - this just helps you easily select the node you're interested in.