feat: commands with cmp (#183)
This commit is contained in:
		
							parent
							
								
									f2173c9a3c
								
							
						
					
					
						commit
						3dbdba198a
					
				@ -28,6 +28,7 @@ local Sidebar = {}
 | 
			
		||||
 | 
			
		||||
---@class avante.Sidebar
 | 
			
		||||
---@field id integer
 | 
			
		||||
---@field registered_cmp boolean
 | 
			
		||||
---@field augroup integer
 | 
			
		||||
---@field code avante.CodeState
 | 
			
		||||
---@field winids table<string, integer> this table stores the winids of the sidebar components (result_container, result, selected_code_container, selected_code, input_container, input), even though they are destroyed.
 | 
			
		||||
@ -42,6 +43,7 @@ local Sidebar = {}
 | 
			
		||||
function Sidebar:new(id)
 | 
			
		||||
  return setmetatable({
 | 
			
		||||
    id = id,
 | 
			
		||||
    registered_cmp = false,
 | 
			
		||||
    code = { bufnr = 0, winid = 0, selection = nil },
 | 
			
		||||
    winids = {
 | 
			
		||||
      result_container = 0,
 | 
			
		||||
@ -67,6 +69,7 @@ end
 | 
			
		||||
 | 
			
		||||
function Sidebar:reset()
 | 
			
		||||
  self:delete_autocmds()
 | 
			
		||||
  self.registered_cmp = false
 | 
			
		||||
  self.code = { bufnr = 0, winid = 0, selection = nil }
 | 
			
		||||
  self.winids = { result_container = 0, result = 0, selected_code = 0, input = 0 }
 | 
			
		||||
  self.result_container = nil
 | 
			
		||||
@ -544,9 +547,11 @@ function Sidebar:on_mount()
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  local function unbind_jump_keys()
 | 
			
		||||
    if self.result and self.result.bufnr and api.nvim_buf_is_valid(self.result.bufnr) then
 | 
			
		||||
      pcall(vim.keymap.del, "n", "]c", { buffer = self.result.bufnr })
 | 
			
		||||
      pcall(vim.keymap.del, "n", "[c", { buffer = self.result.bufnr })
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, {
 | 
			
		||||
    buffer = self.result.bufnr,
 | 
			
		||||
@ -593,8 +598,6 @@ function Sidebar:on_mount()
 | 
			
		||||
  self:render_input_container()
 | 
			
		||||
  self:render_selected_code_container()
 | 
			
		||||
 | 
			
		||||
  -- api.nvim_set_option_value("buftype", "nofile", { buf = self.input.bufnr })
 | 
			
		||||
 | 
			
		||||
  self.augroup = api.nvim_create_augroup("avante_" .. self.id .. self.result.winid, { clear = true })
 | 
			
		||||
 | 
			
		||||
  local filetype = api.nvim_get_option_value("filetype", { buf = self.code.bufnr })
 | 
			
		||||
@ -1105,6 +1108,111 @@ function Sidebar:get_content_between_separators()
 | 
			
		||||
  return content
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function Sidebar:get_commands()
 | 
			
		||||
  local function get_help_text(items_)
 | 
			
		||||
    local help_text = ""
 | 
			
		||||
    for _, item in ipairs(items_) do
 | 
			
		||||
      help_text = help_text .. "- " .. item.name .. ": " .. item.description .. "\n"
 | 
			
		||||
    end
 | 
			
		||||
    return help_text
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  local items = {
 | 
			
		||||
    { name = "help", description = "Show this help message", command = "help" },
 | 
			
		||||
    { name = "clear", description = "Clear chat history", command = "clear" },
 | 
			
		||||
    { name = "lines <start>-<end> <question>", description = "Ask a question about specific lines", command = "lines" },
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  local cbs = {
 | 
			
		||||
    {
 | 
			
		||||
      command = "help",
 | 
			
		||||
      ---@diagnostic disable-next-line: unused-local
 | 
			
		||||
      callback = function(args, cb)
 | 
			
		||||
        local help_text = get_help_text(items)
 | 
			
		||||
        self:create_input()
 | 
			
		||||
        self:update_content(help_text, { focus = false, scroll = false })
 | 
			
		||||
        if cb then
 | 
			
		||||
          cb(args)
 | 
			
		||||
        end
 | 
			
		||||
      end,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      command = "clear",
 | 
			
		||||
      ---@diagnostic disable-next-line: unused-local
 | 
			
		||||
      callback = function(args, cb)
 | 
			
		||||
        local chat_history = {}
 | 
			
		||||
        save_chat_history(self, chat_history)
 | 
			
		||||
        self:create_input()
 | 
			
		||||
        self:update_content("Chat history cleared", { focus = false, scroll = false })
 | 
			
		||||
        vim.defer_fn(function()
 | 
			
		||||
          self:close()
 | 
			
		||||
          if cb then
 | 
			
		||||
            cb(args)
 | 
			
		||||
          end
 | 
			
		||||
        end, 1000)
 | 
			
		||||
      end,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      command = "lines",
 | 
			
		||||
      callback = function(args, cb)
 | 
			
		||||
        if cb then
 | 
			
		||||
          cb(args)
 | 
			
		||||
        end
 | 
			
		||||
      end,
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  local commands = {}
 | 
			
		||||
  for _, item in ipairs(items) do
 | 
			
		||||
    table.insert(commands, {
 | 
			
		||||
      name = item.name,
 | 
			
		||||
      command = item.command,
 | 
			
		||||
      description = item.description,
 | 
			
		||||
      callback = function(args, cb)
 | 
			
		||||
        for _, cb_ in ipairs(cbs) do
 | 
			
		||||
          if cb_.command == item.command then
 | 
			
		||||
            cb_.callback(args, cb)
 | 
			
		||||
            break
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
      end,
 | 
			
		||||
    })
 | 
			
		||||
  end
 | 
			
		||||
  return commands
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function Sidebar:create_selected_code()
 | 
			
		||||
  if self.selected_code ~= nil then
 | 
			
		||||
    self.selected_code:unmount()
 | 
			
		||||
    self.selected_code = nil
 | 
			
		||||
  end
 | 
			
		||||
  if self.selected_code_container ~= nil then
 | 
			
		||||
    self.selected_code_container:unmount()
 | 
			
		||||
    self.selected_code_container = nil
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  local selected_code_size = self:get_selected_code_size()
 | 
			
		||||
 | 
			
		||||
  if self.code.selection ~= nil then
 | 
			
		||||
    self.selected_code_container = Split({
 | 
			
		||||
      enter = false,
 | 
			
		||||
      relative = {
 | 
			
		||||
        type = "win",
 | 
			
		||||
        winid = self.result_container.winid,
 | 
			
		||||
      },
 | 
			
		||||
      buf_options = buf_options,
 | 
			
		||||
      win_options = get_win_options(),
 | 
			
		||||
      position = "bottom",
 | 
			
		||||
      size = {
 | 
			
		||||
        height = selected_code_size,
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
    self.selected_code_container:mount()
 | 
			
		||||
    self.selected_code = self:create_floating_window_for_split({ split_winid = self.selected_code_container.winid })
 | 
			
		||||
    self.selected_code:mount()
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function Sidebar:create_input()
 | 
			
		||||
  if
 | 
			
		||||
    not self.input_container
 | 
			
		||||
@ -1145,25 +1253,22 @@ function Sidebar:create_input()
 | 
			
		||||
 | 
			
		||||
    if request:sub(1, 1) == "/" then
 | 
			
		||||
      local command, args = request:match("^/(%S+)%s*(.*)")
 | 
			
		||||
      if command == "help" then
 | 
			
		||||
        local help_text = [[
 | 
			
		||||
Available commands:
 | 
			
		||||
/clear - Clear chat history
 | 
			
		||||
/help - Show this help message
 | 
			
		||||
/lines <start>-<end> <question> - Ask a question about specific lines
 | 
			
		||||
]]
 | 
			
		||||
        self:update_content(help_text, { focus = false, scroll = false })
 | 
			
		||||
      if command == nil then
 | 
			
		||||
        self:update_content("Invalid command", { focus = false, scroll = false })
 | 
			
		||||
        return
 | 
			
		||||
      elseif command == "lines" then
 | 
			
		||||
        ---@diagnostic disable-next-line: no-unknown
 | 
			
		||||
        local start_line, end_line, question = args:match("(%d+)-(%d+)%s+(.*)")
 | 
			
		||||
        ---@cast question string
 | 
			
		||||
 | 
			
		||||
        if selected_code_content_with_line_numbers ~= nil then
 | 
			
		||||
          Utils.warn("/lines is mutually exclusive with visual selection on blocks.", { once = true, title = "Avante" })
 | 
			
		||||
          request = question
 | 
			
		||||
        else
 | 
			
		||||
          if start_line and end_line and question then
 | 
			
		||||
      end
 | 
			
		||||
      local cmds = self:get_commands()
 | 
			
		||||
      local cmd
 | 
			
		||||
      for _, c in ipairs(cmds) do
 | 
			
		||||
        if c.command == command then
 | 
			
		||||
          cmd = c
 | 
			
		||||
          break
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
      if cmd then
 | 
			
		||||
        if command == "lines" then
 | 
			
		||||
          cmd.callback(args, function(args_)
 | 
			
		||||
            local start_line, end_line, question = args_:match("(%d+)-(%d+)%s+(.*)")
 | 
			
		||||
            ---@cast start_line integer
 | 
			
		||||
            start_line = tonumber(start_line)
 | 
			
		||||
            ---@cast end_line integer
 | 
			
		||||
@ -1177,24 +1282,13 @@ Available commands:
 | 
			
		||||
              start_line
 | 
			
		||||
            )
 | 
			
		||||
            request = question
 | 
			
		||||
          end)
 | 
			
		||||
        else
 | 
			
		||||
            self:update_content(
 | 
			
		||||
              "Invalid format. Use: /lines <start>-<end> <question>",
 | 
			
		||||
              { focus = false, scroll = false }
 | 
			
		||||
            )
 | 
			
		||||
          cmd.callback(args)
 | 
			
		||||
          return
 | 
			
		||||
        end
 | 
			
		||||
        end
 | 
			
		||||
      elseif command == "clear" then
 | 
			
		||||
        chat_history = {}
 | 
			
		||||
        save_chat_history(self, chat_history)
 | 
			
		||||
        self:update_content("Chat history cleared", { focus = false, scroll = false })
 | 
			
		||||
        vim.defer_fn(function()
 | 
			
		||||
          self:close()
 | 
			
		||||
        end, 1000)
 | 
			
		||||
        return
 | 
			
		||||
      else
 | 
			
		||||
        -- Unknown command
 | 
			
		||||
        self:create_input()
 | 
			
		||||
        self:update_content("Unknown command: " .. command, { focus = false, scroll = false })
 | 
			
		||||
        return
 | 
			
		||||
      end
 | 
			
		||||
@ -1204,9 +1298,16 @@ Available commands:
 | 
			
		||||
 | 
			
		||||
    local filetype = api.nvim_get_option_value("filetype", { buf = self.code.bufnr })
 | 
			
		||||
 | 
			
		||||
    local is_first_chunk = true
 | 
			
		||||
 | 
			
		||||
    ---@type AvanteChunkParser
 | 
			
		||||
    local on_chunk = function(chunk)
 | 
			
		||||
      full_response = full_response .. chunk
 | 
			
		||||
      if is_first_chunk then
 | 
			
		||||
        is_first_chunk = false
 | 
			
		||||
        self:update_content(content_prefix .. chunk, { stream = false, scroll = true })
 | 
			
		||||
        return
 | 
			
		||||
      end
 | 
			
		||||
      self:update_content(chunk, { stream = true, scroll = true })
 | 
			
		||||
      vim.schedule(function()
 | 
			
		||||
        vim.cmd("redraw")
 | 
			
		||||
@ -1261,6 +1362,14 @@ Available commands:
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  if
 | 
			
		||||
    not self.input_container
 | 
			
		||||
    or not self.input_container.winid
 | 
			
		||||
    or not api.nvim_win_is_valid(self.input_container.winid)
 | 
			
		||||
  then
 | 
			
		||||
    return
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  local win_width = api.nvim_win_get_width(self.input_container.winid)
 | 
			
		||||
 | 
			
		||||
  self.input = Input({
 | 
			
		||||
@ -1289,6 +1398,31 @@ Available commands:
 | 
			
		||||
 | 
			
		||||
  self.input:mount()
 | 
			
		||||
 | 
			
		||||
  api.nvim_set_option_value("filetype", "AvanteInput", { buf = self.input.bufnr })
 | 
			
		||||
 | 
			
		||||
  -- Setup completion
 | 
			
		||||
  api.nvim_create_autocmd("InsertEnter", {
 | 
			
		||||
    group = self.augroup,
 | 
			
		||||
    buffer = self.input.bufnr,
 | 
			
		||||
    once = true,
 | 
			
		||||
    desc = "Setup the completion of helpers in the input buffer",
 | 
			
		||||
    callback = function()
 | 
			
		||||
      local has_cmp, cmp = pcall(require, "cmp")
 | 
			
		||||
      if has_cmp then
 | 
			
		||||
        if not self.registered_cmp then
 | 
			
		||||
          self.registered_cmp = true
 | 
			
		||||
          cmp.register_source("avante_commands", require("cmp_avante.commands").new(self))
 | 
			
		||||
        end
 | 
			
		||||
        cmp.setup.buffer({
 | 
			
		||||
          enabled = true,
 | 
			
		||||
          sources = {
 | 
			
		||||
            { name = "avante_commands" },
 | 
			
		||||
          },
 | 
			
		||||
        })
 | 
			
		||||
      end
 | 
			
		||||
    end,
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  self:refresh_winids()
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -1314,7 +1448,7 @@ end
 | 
			
		||||
---@field float_opts table | nil
 | 
			
		||||
 | 
			
		||||
---@param opts CreateFloatingWindowForSplitOptions
 | 
			
		||||
local function create_floating_window_for_split(opts)
 | 
			
		||||
function Sidebar:create_floating_window_for_split(opts)
 | 
			
		||||
  local win_opts_ = vim.tbl_deep_extend("force", get_win_options(), opts.win_opts or {})
 | 
			
		||||
 | 
			
		||||
  local buf_opts_ = vim.tbl_deep_extend("force", buf_options, opts.buf_opts or {})
 | 
			
		||||
@ -1346,7 +1480,7 @@ function Sidebar:render()
 | 
			
		||||
 | 
			
		||||
  self.result_container:mount()
 | 
			
		||||
 | 
			
		||||
  self.result = create_floating_window_for_split({
 | 
			
		||||
  self.result = self:create_floating_window_for_split({
 | 
			
		||||
    split_winid = self.result_container.winid,
 | 
			
		||||
    buf_opts = {
 | 
			
		||||
      modifiable = false,
 | 
			
		||||
@ -1402,24 +1536,7 @@ function Sidebar:render()
 | 
			
		||||
 | 
			
		||||
  self:create_input()
 | 
			
		||||
 | 
			
		||||
  if self.code.selection ~= nil then
 | 
			
		||||
    self.selected_code_container = Split({
 | 
			
		||||
      enter = false,
 | 
			
		||||
      relative = {
 | 
			
		||||
        type = "win",
 | 
			
		||||
        winid = self.result_container.winid,
 | 
			
		||||
      },
 | 
			
		||||
      buf_options = buf_options,
 | 
			
		||||
      win_options = get_win_options(),
 | 
			
		||||
      position = "bottom",
 | 
			
		||||
      size = {
 | 
			
		||||
        height = selected_code_size,
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
    self.selected_code_container:mount()
 | 
			
		||||
    self.selected_code = create_floating_window_for_split({ split_winid = self.selected_code_container.winid })
 | 
			
		||||
    self.selected_code:mount()
 | 
			
		||||
  end
 | 
			
		||||
  self:create_selected_code()
 | 
			
		||||
 | 
			
		||||
  self:on_mount()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										49
									
								
								lua/cmp_avante/commands.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								lua/cmp_avante/commands.lua
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
			
		||||
---@class source
 | 
			
		||||
---@field sidebar avante.Sidebar
 | 
			
		||||
local source = {}
 | 
			
		||||
 | 
			
		||||
---@param sidebar avante.Sidebar
 | 
			
		||||
function source.new(sidebar)
 | 
			
		||||
  return setmetatable({
 | 
			
		||||
    sidebar = sidebar,
 | 
			
		||||
  }, { __index = source })
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function source:is_available()
 | 
			
		||||
  return vim.bo.filetype == "AvanteInput"
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
source.get_position_encoding_kind = function()
 | 
			
		||||
  return "utf-8"
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function source:get_trigger_characters()
 | 
			
		||||
  return { "/" }
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function source:get_keyword_pattern()
 | 
			
		||||
  return [[\%(@\|#\|/\)\k*]]
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
function source:complete(_, callback)
 | 
			
		||||
  local kind = require("cmp").lsp.CompletionItemKind.Function
 | 
			
		||||
 | 
			
		||||
  local items = {}
 | 
			
		||||
 | 
			
		||||
  local commands = self.sidebar:get_commands()
 | 
			
		||||
 | 
			
		||||
  for _, command in ipairs(commands) do
 | 
			
		||||
    table.insert(items, {
 | 
			
		||||
      label = "/" .. command.name,
 | 
			
		||||
      kind = kind,
 | 
			
		||||
      detail = command.description,
 | 
			
		||||
    })
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  callback({
 | 
			
		||||
    items = items,
 | 
			
		||||
    isIncomplete = false,
 | 
			
		||||
  })
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
return source
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user