frankroeder / parrot.nvim

parrot.nvim 🦜 - the plugin that brings stochastic parrots to Neovim. This is a gp.nvim-fork focused on simplicity.
Other
219 stars 14 forks source link
anthropic chatgpt claude-3-5-sonnet gemini gpt gpt-4o gpts groq-api large-language-models llm mistral neovim nvim ollama openai openai-api perplexity plugin prompting stochastic-parrot
# parrot.nvim 🦜 This is [parrot.nvim](https://github.com/frankroeder/parrot.nvim), the ultimate [stochastic parrot](https://en.wikipedia.org/wiki/Stochastic_parrot) to support your text editing inside Neovim. [Features](#features) • [Demo](#demo) • [Getting Started](#getting-started) • [Commands](#commands) • [Configuration](#configuration) • [Roadmap](#roadmap) • [FAQ](#faq) parrot.nvim logo

[!WARNING] Version 0.4.x introduces breaking changes, including the removal of agents and modifications to some hook functions. Please review the updated documentation and adjust your configuration accordingly.

Features

parrot.nvim offers a seamless out-of-the-box experience, providing tight integration of current LLM APIs into your Neovim workflows, with a focus solely on text generation. The selected core features include on-demand text completion and editing, as well as chat-like sessions within native Neovim buffers. While this project is still under development, a substantial part of the code is based on an early fork of the brilliant work by Tibor Schmidt's gp.nvim.

Demo

Seamlessly switch between providers and models.


Trigger code completions based on comments.


Let the parrot fix your bugs.


Rewrite a visual selection with `PrtRewrite`.

Append code with the visual selection as context with `PrtAppend`.

Add comments to a function with `PrtPrepend`.
Retry your latest rewrite, append or prepend with `PrtRetry`.

Getting Started

Dependencies

This plugin requires the latest version of Neovim and relies on a carefully selected set of established plugins.

Installation

lazy.nvim ```lua { "frankroeder/parrot.nvim", dependencies = { "ibhagwan/fzf-lua", "nvim-lua/plenary.nvim" }, opts = {} } ```
Packer ```lua require("packer").startup(function() use({ "frankroeder/parrot.nvim", requires = { 'ibhagwan/fzf-lua', 'nvim-lua/plenary.nvim'}, config = function() require("parrot").setup() end, }) end) ```
Neovim native package ```sh git clone --depth=1 https://github.com/frankroeder/parrot.nvim.git \ "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/pack/parrot/start/parrot.nvim ```

Setup

The minimal requirement is to at least set up one provider, hence one from the selection below.

{
  "frankroeder/parrot.nvim",
  dependencies = { 'ibhagwan/fzf-lua', 'nvim-lua/plenary.nvim' },
  -- optionally include "rcarriga/nvim-notify" for beautiful notifications
  config = function()
    require("parrot").setup {
      -- Providers must be explicitly added to make them available.
      providers = {
        anthropic = {
          api_key = os.getenv "ANTHROPIC_API_KEY",
        },
        gemini = {
          api_key = os.getenv "GEMINI_API_KEY",
        },
        groq = {
          api_key = os.getenv "GROQ_API_KEY",
        },
        mistral = {
          api_key = os.getenv "MISTRAL_API_KEY",
        },
        pplx = {
          api_key = os.getenv "PERPLEXITY_API_KEY",
        },
        -- provide an empty list to make provider available (no API key required)
        ollama = {},
        openai = {
          api_key = os.getenv "OPENAI_API_KEY",
        },
        github = {
          api_key = os.getenv "GITHUB_TOKEN",
        },
      },
    }
  end,
}

Commands

Below are the available commands that can be configured as keybindings. These commands are included in the default setup. Additional useful commands are implemented through hooks (see below).

General

Command Description
PrtChatNew <target> Open a new chat
PrtChatToggle <target> Toggle chat (open last chat or new one)
PrtChatPaste <target> Paste visual selection into the latest chat
PrtInfo Print plugin config
PrtContext <target> Edits the local context file
PrtChatFinder Fuzzy search chat files using fzf
PrtChatDelete Delete the current chat file
PrtChatRespond Trigger chat respond (in chat file)
PrtStop Interrupt ongoing respond
PrtProvider <provider> Switch the provider (empty arg triggers fzf)
PrtModel <model> Switch the model (empty arg triggers fzf)
PrtStatus Prints current provider and model selection
Interactive
PrtRewrite Rewrites the visual selection based on a provided prompt
PrtAppend Append text to the visual selection based on a provided prompt
PrtPrepend Prepend text to the visual selection based on a provided prompt
PrtNew Prompt the model to respond in a new window
PrtEnew Prompt the model to respond in a new buffer
PrtVnew Prompt the model to respond in a vsplit
PrtTabnew Prompt the model to respond in a new tab
PrtRetry Repeats the last rewrite/append/prepend
Example Hooks
PrtImplement Takes the visual selection as prompt to generate code
PrtAsk Ask the model a question

With <target>, we indicate the command to open the chat within one of the following target locations (defaults to toggle_target):

All chat commands (PrtChatNew, PrtChatToggle) and custom hooks support the visual selection to appear in the chat when triggered. Interactive commands require the user to make use of the template placeholders to consider a visual selection within an API request.

Configuration

Options

{
    -- The provider definitions include endpoints, API keys, default parameters,
    -- and topic model arguments for chat summarization, with an example provided for Anthropic.
    providers = {
      anthropic = {
        api_key = os.getenv("ANTHROPIC_API_KEY"),
        -- OPTIONAL: Alternative methods to retrieve API key
        -- Using GPG for decryption:
        -- api_key = { "gpg", "--decrypt", vim.fn.expand("$HOME") .. "/anthropic_api_key.txt.gpg" },
        -- Using macOS Keychain:
        -- api_key = { "/usr/bin/security", "find-generic-password", "-s anthropic-api-key", "-w" },
        endpoint = "https://api.anthropic.com/v1/messages",
        topic_prompt = "You only respond with 3 to 4 words to summarize the past conversation.",
        -- usually a cheap and fast model to generate the chat topic based on
        -- the whole chat history
        topic = {
          model = "claude-3-haiku-20240307",
          params = { max_tokens = 32 },
        },
        -- default parameters for the actual model
        params = {
          chat = { max_tokens = 4096 },
          command = { max_tokens = 4096 },
        },
      },
      ...
    }

    -- default system prompts used for the chat sessions and the command routines
    system_prompt = {
      chat = ...,
      command = ...
    },

    -- the prefix used for all commands
    cmd_prefix = "Prt",

    -- optional parameters for curl
    curl_params = {},

    -- The directory to store persisted state information like the
    -- current provider and the selected models
    state_dir = vim.fn.stdpath("data"):gsub("/$", "") .. "/parrot/persisted",

    -- The directory to store the chats (searched with PrtChatFinder)
    chat_dir = vim.fn.stdpath("data"):gsub("/$", "") .. "/parrot/chats",

    -- Chat user prompt prefix
    chat_user_prefix = "🗨:",

    -- llm prompt prefix
    llm_prefix = "🦜:",

    -- Explicitly confirm deletion of a chat file
    chat_confirm_delete = true,

    -- When available, call API for model selection
    online_model_selection = false,

    -- Local chat buffer shortcuts
    chat_shortcut_respond = { modes = { "n", "i", "v", "x" }, shortcut = "<C-g><C-g>" },
    chat_shortcut_delete = { modes = { "n", "i", "v", "x" }, shortcut = "<C-g>d" },
    chat_shortcut_stop = { modes = { "n", "i", "v", "x" }, shortcut = "<C-g>s" },
    chat_shortcut_new = { modes = { "n", "i", "v", "x" }, shortcut = "<C-g>c" },

    -- Option to move the cursor to the end of the file after finished respond
    chat_free_cursor = false,

     -- use prompt buftype for chats (:h prompt-buffer)
    chat_prompt_buf_type = false,

    -- Default target for  PrtChatToggle, PrtChatNew, PrtContext and the chats opened from the ChatFinder
    -- values: popup / split / vsplit / tabnew
    toggle_target = "vsplit",

    -- The interactive user input appearing when can be "native" for
    -- vim.ui.input or "buffer" to query the input within a native nvim buffer
    -- (see video demonstrations below)
    user_input_ui = "native",

    -- Popup window layout
    -- border: "single", "double", "rounded", "solid", "shadow", "none"
    style_popup_border = "single",

    -- margins are number of characters or lines
    style_popup_margin_bottom = 8,
    style_popup_margin_left = 1,
    style_popup_margin_right = 2,
    style_popup_margin_top = 2,
    style_popup_max_width = 160

    -- Prompt used for interactive LLM calls like PrtRewrite where {{llm}} is
    -- a placeholder for the llm name
    command_prompt_prefix_template = "🤖 {{llm}} ~ ",

    -- auto select command response (easier chaining of commands)
    -- if false it also frees up the buffer cursor for further editing elsewhere
    command_auto_select_response = true,

    -- fzf_lua options for PrtModel and PrtChatFinder when plugin is installed
    fzf_lua_opts = {
        ["--ansi"] = true,
        ["--sort"] = "",
        ["--info"] = "inline",
        ["--layout"] = "reverse",
        ["--preview-window"] = "nohidden:right:75%",
    },

    -- Enables the query spinner animation 
    enable_spinner = true,
    -- Type of spinner animation to display while loading
    -- Available options: "dots", "line", "star", "bouncing_bar", "bouncing_ball"
    spinner_type = "star",
}

Demonstrations

With `user_input_ui = "native"`, use `vim.ui.input` as slim input interface.
With `user_input_ui = "buffer"`, your input is simply a buffer. All of the content is passed to the API when closed.
The spinner is a useful indicator for providers that take longer to respond.

Key Bindings

This plugin provides the following default key mappings:

Keymap Description
<C-g>c Opens a new chat via PrtChatNew
<C-g><C-g> Trigger the API to generate a response via PrtChatRespond
<C-g>s Stop the current text generation via PrtStop
<C-g>d Delete the current chat file via PrtChatDelete

Adding a new command

Ask a single-turn question and receive the answer in a popup window

require("parrot").setup {
    -- ...
    hooks = {
      Ask = function(parrot, params)
        local template = [[
          In light of your existing knowledge base, please generate a response that
          is succinct and directly addresses the question posed. Prioritize accuracy
          and relevance in your answer, drawing upon the most recent information
          available to you. Aim to deliver your response in a concise manner,
          focusing on the essence of the inquiry.
          Question: {{command}}
        ]]
        local model_obj = parrot.get_model("command")
        parrot.logger.info("Asking model: " .. model_obj.name)
        parrot.Prompt(params, parrot.ui.Target.popup, model_obj, "🤖 Ask ~ ", template)
      end,
    }
    -- ...
}

Start a chat with a predefined chat prompt to check your spelling.

require("parrot").setup {
    -- ...
    hooks = {
      SpellCheck = function(prt, params)
        local chat_prompt = [[
          Your task is to take the text provided and rewrite it into a clear,
          grammatically correct version while preserving the original meaning
          as closely as possible. Correct any spelling mistakes, punctuation
          errors, verb tense issues, word choice problems, and other
          grammatical mistakes.
        ]]
        prt.ChatNew(params, chat_prompt)
      end,
    }
    -- ...
}

Refer to my personal lazy.nvim setup or those of other users for further hooks and key bindings.

Template Placeholders

Users can utilize the following placeholders in their hook and system templates to inject additional context:

Placeholder Content
{{selection}} Current visual selection
{{filetype}} Filetype of the current buffer
{{filepath}} Full path of the current file
{{filecontent}} Full content of the current buffer
{{multifilecontent}} Full content of all open buffers

Below is an example of how to use these placeholders in a completion hook, which receives the full file context and the selected code snippet as input.

require("parrot").setup {
    -- ...
    hooks = {
        CompleteFullContext = function(prt, params)
          local template = [[
          I have the following code from {{filename}}:

          ```{{filetype}}
          {{filecontent}}
      Please look at the following section specifically:
      ```{{filetype}}
      {{selection}}
      ```

      Please finish the code above carefully and logically.
      Respond just with the snippet of code that should be inserted.
      ]]
      local model_obj = prt.get_model()
      prt.Prompt(params, prt.ui.Target.append, model_obj, nil, template)
    end,
}
-- ...

}


The placeholders `{{filetype}}` and  `{{filecontent}}` can also be used in the `chat_prompt` when
creating custom hooks calling `prt.ChatNew(params, chat_prompt)` to directly inject the whole file content.

```lua
require("parrot").setup {
    -- ...
      CodeConsultant = function(prt, params)
        local chat_prompt = [[
          Your task is to analyze the provided {{filetype}} code and suggest
          improvements to optimize its performance. Identify areas where the
          code can be made more efficient, faster, or less resource-intensive.
          Provide specific suggestions for optimization, along with explanations
          of how these changes can enhance the code's performance. The optimized
          code should maintain the same functionality as the original code while
          demonstrating improved efficiency.

          Here is the code
          ```{{filetype}}
          {{filecontent}}
    ]]
    prt.ChatNew(params, chat_prompt)
  end,
}
-- ...

}


## Statusline Support

Knowing the current chat or command model can be shown using your favorite statusline plugin.
Below, we provide an example for [lualine](https://github.com/nvim-lualine/lualine.nvim):

```lua
  -- define function and formatting of the information
  local function parrot_status()
    local status_info = require("parrot.config").get_status_info()
    local status = ""
    if status_info.is_chat then
      status = status_info.prov.chat.name
    else
      status = status_info.prov.command.name
    end
    return string.format("%s(%s)", status, status_info.model)
  end

  -- add to lueline section
  require('lualine').setup {
    sections = {
      lualine_a = { parrot_status }
  }

Bonus

Access parrot.nvim directly from your terminal:

command nvim -c "PrtChatNew"

Also works by piping content directly into the chat:

ls -l | command nvim - -c "normal ggVGy" -c ":PrtChatNew" -c "normal p"

Roadmap

FAQ

Related Projects

Star History

Star History Chart