anuvyklack / hydra.nvim

Create custom submodes and menus
1.03k stars 24 forks source link

LSP Hydra Example #5

Closed dgrbrady closed 2 years ago

dgrbrady commented 2 years ago

I've never used emacs before, but was super intrigued when I came across this plugin. Right now I'm using AstroVim and navigating between LSP diagnostics and applying fixes to each one means I have to do ]d<leader>la to go to the next diagnostic, then open the telescope window to pick a code action for each diagnostic. I whipped this up and added some symbol navigation using Aerial. My hope is that others find it just as useful as I did :)

local Hydra = require('hydra')
local aerial = require('aerial')

local hint = [[
 _d_: next diagnostic   _D_: previous diagnostic  _a_: code action
 _n_: next symbol       _N_: previous symbol      _s_: toggle symbol tree
 ^                      _q_: exit
]]

Hydra({
  hint = hint,
  name = 'LSP Hydra',
  config = {
    invoke_on_body = true,
    color = 'pink',
    hint = {
      position = 'bottom',
      border = 'rounded'
    },
    on_enter = function ()
        aerial.open()
    end,
    on_exit = function ()
        aerial.close()
    end
  },
  mode = {'n'},
  body = '<leader>L',
  heads = {
    {
      'd',
      function ()
        vim.diagnostic.goto_next()
      end,
    },
    {
      'D',
      function ()
        vim.diagnostic.goto_prev()
      end,
    },
    {
      'n',
      function ()
        aerial.next()
      end,
    },
    {
      'N',
      function ()
        aerial.next(-1)
      end,
    },
    {
      's',
      function ()
        if aerial.is_open() then
          aerial.close()
        else
          aerial.open()
        end
      end,
    },
    {
      'a',
      function ()
        vim.lsp.buf.code_action()
        return '<Ignore>'
      end,
    },
    {
      'q',
      nil,
      { exit = true, nowait = true}
    }
  }
})

image

LamprosPitsillos commented 2 years ago

I think it would be great if we added this to the wiki

anuvyklack commented 2 years ago

I agree

dgrbrady commented 2 years ago

Exactly what I was hoping for! I wanted a few more navigation options, so I brought in Syntax Tree Surfer (2.1 branch) and updated my config like so:

local Hydra = require('hydra')
local aerial = require('aerial')
local telescope_builtin = require('telescope.builtin')
local sts = require('syntax-tree-surfer')

local hint = [[
LSP heads
---------
^ _d_: next diagnostic   _D_: previous diagnostic
^ _n_: next symbol       _N_: previous symbol
^ _r_: references        _a_: code action
^ _s_: toggle tree

STS heads
---------
^ _p_: next property     _P_: previous property    
^ _m_: next method       _M_: previous method
^ _i_: next import       _I_: previous import
^ _c_: next comment      _C_: previous comment

^ _q_: quit
]]

local heads = {
  {
    'd',
    function ()
      vim.diagnostic.goto_next()
    end,
  },
  {
    'D',
    function ()
      vim.diagnostic.goto_prev()
    end,
  },
  {
    'a',
    function ()
      vim.lsp.buf.code_action()
      return '<Ignore>'
    end,
  },
  {
    'n',
    function ()
      aerial.next()
    end,
  },
  {
    'N',
    function ()
      aerial.next(-1)
    end,
  },
  {
    's',
    function ()
      if aerial.is_open() then
        aerial.close()
      else
        aerial.open()
      end
    end,
  },
  {
    'r',
    function ()
      telescope_builtin.lsp_references()
      return '<Ignore>'
    end,
  },
  {
    'q',
    nil,
    { exit = true, nowait = true}
  },
  {
    'p',
    function ()
      sts.filtered_jump({'public_field_definitio'}, true)
    end
  },
  {
    'P',
    function ()
      sts.filtered_jump({'public_field_definition'}, false)
    end
  },
  {
    'm',
    function ()
      sts.filtered_jump({'method_definition'}, true)
    end
  },
  {
    'M',
    function ()
      sts.filtered_jump({'method_definition'}, false)
    end
  },
  {
    'i',
    function ()
      sts.filtered_jump({'import_statement'}, true)
    end
  },
  {
    'I',
    function ()
      sts.filtered_jump({'import_statement'}, false)
    end
  },
  {
    'c',
    function ()
      sts.filtered_jump({'comment'}, true)
    end
  },
  {
    'C',
    function ()
      sts.filtered_jump({'comment'}, false)
    end
  },
}

Hydra({
  hint = hint,
  name = 'LSP Hydra',
  config = {
    invoke_on_body = true,
    color = 'pink',
    hint = {
      position = 'bottom',
      border = 'rounded'
    },
    on_enter = function ()
        aerial.open()
    end,
    on_exit = function ()
        aerial.close()
    end
  },
  mode = {'n'},
  body = '<leader><leader>l',
  heads = heads
})

and the result: image

Might be too much for some folks liking, but I thought I'd share a more robust setup than my original one in case people wanted more options.

Really loving the plugin so far! Thanks for all the hard work I'm sure it took to make it!

dgrbrady commented 2 years ago

I do have a question though. Is it possible to dynamically set the hint/heads? For my latest example, I wanted to only show the STS heads if I activated the body while the current buffer was a typescript file. I tried something like:

local function get_hint()
  if vim.bo.filetype == 'typescript' then
    return [[
LSP heads
<LSP hint here>

STS heads
<STS hint here>
]]
  end
  -- if current buffer not a typescript file, return just LSP hint
  return [[
LSP heads
<LSP hint here>
]]
end

local function get_heads()
  if vim.bo.filetype == 'typescript' then
    return {
    -- LSP heads *and* STS heads
    }
  end
  return {
    -- *only* LSP heads
  }
end

Hydra({
  hint = get_hint(),
  heads = get_heads(),
  -- other setup
})

And when I activated the Hydra on a Typescript file, it only showed me the LSP hint/heads, not the STS ones. Any idea what's going on here? I'm okay with having the STS heads just being shown all the time for now, but would eventually like to only show them when they're relevant. Some files (like JSON) have an LSP, but no notion of methods/properties/imports/etc.

anuvyklack commented 2 years ago

This is happening, because hydra created only once when the file in which it defined executes.

I can add buffer option to make hydra buffer local, and then you can place it in after/ftplugin/json.lua file.

anuvyklack commented 2 years ago

Done in c9c7a85becd737113d7a15f8188a1092f9641255

dgrbrady commented 2 years ago

When using the new buffer = true option, the hydra doesn't seem to launch at all. No error message or anything. Here's my config to test the new option:

local function get_hint()
  if vim.bo.filetype == 'typescript' then
    local hint = [[ _d_: goto next diag  _q_: quit ]]
    return hint
  else
    local hint = [[ _D_: goto prev diag  _q_: quit ]]
    return hint
  end
end

local function get_heads()
  if vim.bo.filetype == 'typescript' then
    return {
      {'d', vim.diagnostic.goto_next},
      {
        'q',
        nil,
        { exit = true, nowait = true}
      },
    }
  else
    return {
      {'D', vim.diagnostic.goto_prev},
      {
        'q',
        nil,
        { exit = true, nowait = true}
      },
    }
  end
end

local hint = get_hint()
local heads = get_heads()

Hydra({
  hint = hint,
  name = 'LSP Hydra',
  config = {
    invoke_on_body = true,
    color = 'pink',
    buffer = true,
    hint = {
      position = 'bottom',
      border = 'rounded'
    },
    on_enter = function ()
        aerial.open()
    end,
    on_exit = function ()
        aerial.close()
    end
  },
  mode = {'n'},
  body = '<leader><leader>l',
  heads = heads
})

I'm pretty new to lua so I'm not sure if it matters if I do local hint = get_hint() and then pass hint = hint in the constructor vs doing hint = get_hint() in the constructor, but I tried both approaches. Am I doing something wrong?

anuvyklack commented 2 years ago

The Hydras buffer = true option works the way the mappings <buffer> does.

It means that mapping only exists in the scope of the buffer where it was defined: i.e. in the buffer which was active when this code was executed.

So to make buffer local hydra, just like buffer-local key mapping, you either need to create autocommand, or, better, place the code in ~/.config/nvim/after/ftplugin/json.lua file to make this hydra exist only in buffers which filetype is json.

You can read more here

anuvyklack commented 2 years ago

In your case, you need to create 2 hydras: the general one without buffer option and place it anywhere, and one for typescript and buffer = true option and place it in after/ftplugin/typescript.lua file, than create

if vim.bo.filetype == 'typescript' then ... end

blocks.

dgrbrady commented 2 years ago

I've never done any per buffer configs before, so I didn't know that was an option! Thanks, for your help with this, really. I'm working on another Hydra for working with DAP (can you tell I really enjoy your plugin by now?). If I want to share it to the community wiki, is a separate issue okay?

dgrbrady commented 2 years ago

Also, just wanted to confirm that placing the typescript specific hydra in after/ftplugin/typescript.lua did the trick!! I didn't have to add any

if vim.bo.filetype == 'typescript' then ... end

line because that hydra is only loaded on typescript buffers anyway.

anuvyklack commented 2 years ago

Thank-you for kind words! You are welcome to open any number of issues:)