olimorris / codecompanion.nvim

✨ AI-powered coding, seamlessly in Neovim. Supports Anthropic, Copilot, Gemini, Ollama and OpenAI LLMs
MIT License
598 stars 35 forks source link

feat: Implement concatenating the current chat as context when runnin… #114

Closed SDGLBL closed 3 weeks ago

SDGLBL commented 3 weeks ago

Please note that this is only a demonstration pull request. It is not intended to be merged into this codebase, but rather to provide a possible approach, as I am not certain of the best way to implement this feature.

Description

Sometimes, after discussing and determining modifications with the model in the chat window, this feature allows you to directly request the model to make corresponding changes in the buffer. This can be extremely convenient in many situations, especially when the model's assistance is needed to modify multiple files. In such cases, the inline feature can accomplish many tasks, eliminating the need for manual edits one by one.

This is also one of the features I found most impressive when using Zed.

Screenshots

CleanShot 2024-08-23 at 23 48 17

Other

My inline prompts are as follows:

local template = {
  --[[
  Detailed comments explaining all fields that need to be passed in `data` for the `render` function:

  - `data.language_name`: (string) The name of the programming language the file is written in. If provided, it will be included in the rendered output.
  - `data.is_insert`: (boolean) Indicates whether the operation is an insertion. If true, the output will instruct to insert content in place of `<insert_here></insert_here>` tags. If false, it will instruct to rewrite content within `<rewrite_this></rewrite_this>` tags.
  - `data.document_content`: (string) The content of the document that needs to be edited or inserted into. This will be included in the rendered output.
  - `data.is_truncated`: (boolean) Indicates whether the context around the relevant section has been truncated for brevity. If true, a message about truncation will be included in the output.
  - `data.content_type`: (string) The type of content being edited or inserted (e.g., "code", "text"). This will be used in the instructions for the user.
  - `data.user_prompt`: (string) The prompt provided by the user that guides the editing or insertion. This will be included in the rendered output to inform the user what to generate or edit based on.
  - `data.rewrite_section`: (string) The section of content that needs to be rewritten, if the operation is a rewrite. This will be included in the rendered output for reference.
  --]]
  render = function(data)
    local result = ""

    if data.language_name then
      result = result .. "Here's a file of " .. data.language_name .. " that I'm going to ask you to make an edit to.\n"
    else
      result = result .. "Here's a file of text that I'm going to ask you to make an edit to.\n"
    end

    if data.is_insert then
      result = result .. "The point you'll need to insert at is marked with <insert_here></insert_here>.\n"
    else
      result = result .. "The section you'll need to rewrite is marked with <rewrite_this></rewrite_this> tags.\n"
    end

    result = result .. "<document>\n" .. (data.document_content or "") .. "\n</document>\n"

    if data.is_truncated then
      result = result .. "The context around the relevant section has been truncated (possibly in the middle of a line) for brevity.\n"
    end

    if data.is_insert then
      result = result
        .. "You can't replace "
        .. (data.content_type or "")
        .. ", your answer will be inserted in place of the `<insert_here></insert_here>` tags. Don't include the insert_here tags in your output.\n"
      result = result .. "Generate " .. (data.content_type or "") .. " based on the following prompt:\n"
      result = result .. "<prompt>\n" .. (data.user_prompt or "") .. "\n</prompt>\n"
      result = result
        .. "Match the indentation in the original file in the inserted "
        .. (data.content_type or "")
        .. ", don't include any indentation on blank lines.\n"
    else
      result = result .. "Edit the section of " .. (data.content_type or "") .. " in <rewrite_this></rewrite_this> tags based on the following prompt:\n"
      result = result .. "<prompt>\n" .. (data.user_prompt or "") .. "\n</prompt>\n"

      if data.rewrite_section then
        result = result .. "And here's the section to rewrite based on that prompt again for reference:\n"
        result = result .. "<rewrite_this>\n" .. data.rewrite_section .. "\n</rewrite_this>\n"
      end

      result = result
        .. "Only make changes that are necessary to fulfill the prompt, leave everything else as-is. All surrounding "
        .. (data.content_type or "")
        .. " will be preserved.\n"
      result = result
        .. "Start at the indentation level in the original file in the rewritten "
        .. (data.content_type or "")
        .. ". Don't stop until you've rewritten the entire section, even if you have no more changes to make, always write out the whole section with no unnecessary elisions.\n"
    end

    return result
  end,
}

return template

My action

M.write_in_context = {
  name = "Write in context",
  strategy = "inline",
  description = "Write in context",
  opts = {
    index = 1,
    modes = { "n", "v" },
    placement = "replace",
    stop_context_insertion = true,
    user_prompt = true,
    append_user_prompt = false,
    append_last_chat = true,
    adapter = write_in_context_adapter,
  },
  prompts = {
    {
      role = "user",
      content = function(context)
        local bufnr = context.bufnr
        local user_prompt = context.user_input
        local main_buffer_content = buf_utils.get_content(bufnr)
        local lines = vim.split(main_buffer_content, "\n")

        -- Determine content type based on filetype
        local content_type = (context.filetype == "markdown" or context.filetype == "html") and "text" or "code"

        -- Common values for both insert and visual mode
        local val = {
          language_name = context.filetype,
          document_content = main_buffer_content,
          content_type = content_type,
          user_prompt = user_prompt,
        }

        if not context.is_visual then
          -- Insert mode: Add insert marker at cursor position
          local cursor_line, cursor_col = unpack(context.cursor_pos)
          local current_line = lines[cursor_line]
          local before_cursor = string.sub(current_line, 1, cursor_col - 1)
          local after_cursor = string.sub(current_line, cursor_col)
          lines[cursor_line] = before_cursor .. "<insert_here></insert_here>" .. after_cursor
          val.is_insert = true
        else
          -- Visual mode: Add rewrite markers around selected text
          local start_line, start_col = context.start_line, context.start_col
          local end_line, end_col = context.end_line, context.end_col

          lines[start_line] = string.sub(lines[start_line], 1, start_col - 1) .. "<rewrite_this>\n" .. string.sub(lines[start_line], start_col)
          lines[end_line] = string.sub(lines[end_line], 1, end_col) .. "\n</rewrite_this>" .. string.sub(lines[end_line], end_col + 1)
          val.is_insert = false -- This is actually a rewrite operation
        end

        -- Update main_buffer_content with modified lines
        val.document_content = table.concat(lines, "\n")

        -- Generate and return the prompt
        return content_prompt.render(val)
      end,
    },
  },
}