CopilotC-Nvim / CopilotChat.nvim

Chat with GitHub Copilot in Neovim
https://copilotc-nvim.github.io/CopilotChat.nvim/
GNU General Public License v3.0
1.78k stars 87 forks source link
copilot copilot-chat github github-copilot hacktoberfest neovim-plugin

Copilot Chat for Neovim

Documentation pre-commit.ci Discord Dotfyle All Contributors

image

[!NOTE] Plugin was rewritten to Lua from Python. Please check the migration guide from version 1 to version 2 for more information.

Prerequisites

Ensure you have the following installed:

Verify "Copilot chat in the IDE" is enabled.

Optional:

For Arch Linux user, you can install luajit-tiktoken-bin or lua51-tiktoken-bin from aur!

[!WARNING] If you are on neovim < 0.11.0, you also might want to add noinsert and popup to your completeopt to make the chat completion behave well.

Installation

Lazy.nvim

return {
  {
    "CopilotC-Nvim/CopilotChat.nvim",
    branch = "canary",
    dependencies = {
      { "github/copilot.vim" }, -- or zbirenbaum/copilot.lua
      { "nvim-lua/plenary.nvim" }, -- for curl, log wrapper
    },
    build = "make tiktoken", -- Only on MacOS or Linux
    opts = {
      -- See Configuration section for options
    },
    -- See Commands section for default commands if you want to lazy load on them
  },
}

See @jellydn for configuration

Vim-Plug

Similar to the lazy setup, you can use the following configuration:

call plug#begin()
Plug 'github/copilot.vim'
Plug 'nvim-lua/plenary.nvim'
Plug 'CopilotC-Nvim/CopilotChat.nvim', { 'branch': 'canary' }
call plug#end()

lua << EOF
require("CopilotChat").setup {
  -- See Configuration section for options
}
EOF

Manual

  1. Put the files in the right place
mkdir -p ~/.config/nvim/pack/copilotchat/start
cd ~/.config/nvim/pack/copilotchat/start

git clone https://github.com/github/copilot.vim
git clone https://github.com/nvim-lua/plenary.nvim

git clone -b canary https://github.com/CopilotC-Nvim/CopilotChat.nvim
  1. Add to your configuration (e.g. ~/.config/nvim/init.lua)
require("CopilotChat").setup {
  -- See Configuration section for options
}

See @deathbeam for configuration

Usage

Commands

Chat Mappings

The mappings can be customized by setting the mappings table in your configuration. Each mapping can have:

For example, to change the submit prompt mapping:

{
    mappings = {
      submit_prompt = {
        normal = '<Leader>s',
        insert = '<C-s>'
      }
    }
}

Prompts

You can ask Copilot to do various tasks with prompts. You can reference prompts with /PromptName in chat or call with command :CopilotChat<PromptName>.
Default prompts are:

You can define custom prompts like this (only prompt is required):

{
  prompts = {
    MyCustomPrompt = {
      prompt = 'Explain how it works.',
      system_prompt = 'You are very good at explaining stuff',
      mapping = '<leader>ccmc',
      description = 'My custom prompt description',
    }
  }
}

System Prompts

System prompts specify the behavior of the AI model. You can reference system prompts with /PROMPT_NAME in chat. Default system prompts are:

You can define custom system prompts like this (works same as prompts so you can combine prompt and system prompt definitions):

{
  prompts = {
    Yarrr = {
      system_prompt = 'You are fascinated by pirates, so please respond in pirate speak.',
    }
  }
}

Sticky Prompts

You can set sticky prompt in chat by prefixing the text with > using markdown blockquote syntax.
The sticky prompt will be copied at start of every new prompt in chat window. You can freely edit the sticky prompt, only rule is > prefix at beginning of line.
This is useful for preserving stuff like context and agent selection (see below).
Example usage:

> #files

List all files in the workspace
> @models Using Mistral-small

What is 1 + 11

Models

You can list available models with :CopilotChatModels command. Model determines the AI model used for the chat.
You can set the model in the prompt by using $ followed by the model name or default model via config using model key.
Default models are:

For more information about models, see here
You can use more models from here by using @models agent from here (example: @models Using Mistral-small, what is 1 + 11)

Agents

Agents are used to determine the AI agent used for the chat. You can list available agents with :CopilotChatAgents command.
You can set the agent in the prompt by using @ followed by the agent name or default agent via config using agent key.
Default "noop" agent is copilot.

For more information about extension agents, see here
You can install more agents from here

Contexts

Contexts are used to determine the context of the chat.
You can add context to the prompt by using # followed by the context name or default context via config using context (can be single or array) key.
Any amount of context can be added to the prompt.
If context supports input, you can set the input in the prompt by using : followed by the input (or pressing complete key after :).
Default contexts are:

You can define custom contexts like this:

{
  contexts = {
    birthday = {
      input = function(callback)
        vim.ui.select({ 'user', 'napoleon' }, {
          prompt = 'Select birthday> ',
        }, callback)
      end,
      resolve = function(input)
        input = input or 'user'
        local birthday = input
        if input == 'user' then
          birthday = birthday .. ' birthday is April 1, 1990'
        elseif input == 'napoleon' then
          birthday = birthday .. ' birthday is August 15, 1769'
        end

        return {
          {
            content = birthday,
            filename = input .. '_birthday',
            filetype = 'text',
          }
        }
      end
    }
  }
}
> #birthday:user

What is my birthday

Selections

Selections are used to determine the source of the chat (so basically what to chat about).
Selections are configurable either by default or by prompt.
Default selection is visual or buffer (if no visual selection).
Selection includes content, start and end position, buffer info and diagnostic info (if available). Supported selections that live in local select = require("CopilotChat.select") are:

You can chain multiple selections like this:

{
  selection = function(source)
    return select.visual(source) or select.buffer(source)
  end
}

API

local chat = require("CopilotChat")

-- Open chat window
chat.open()

-- Open chat window with custom options
chat.open({
  window = {
    layout = 'float',
    title = 'My Title',
  },
})

-- Close chat window
chat.close()

-- Toggle chat window
chat.toggle()

-- Toggle chat window with custom options
chat.toggle({
  window = {
    layout = 'float',
    title = 'My Title',
  },
})

-- Reset chat window
chat.reset()

-- Ask a question
chat.ask("Explain how it works.")

-- Ask a question with custom options
chat.ask("Explain how it works.", {
  selection = require("CopilotChat.select").buffer,
})

-- Ask a question and provide custom contexts
chat.ask("Explain how it works.", {
  context = { 'buffers', 'files', 'register:+' },
})

-- Ask a question and do something with the response
chat.ask("Show me something interesting", {
  callback = function(response)
    print("Response:", response)
  end,
})

-- Get all available prompts (can be used for integrations like fzf/telescope)
local prompts = chat.prompts()

-- Get last copilot response (also can be used for integrations and custom keymaps)
local response = chat.response()

-- Pick a prompt using vim.ui.select
local actions = require("CopilotChat.actions")

-- Pick prompt actions
actions.pick(actions.prompt_actions({
    selection = require("CopilotChat.select").visual,
}))

-- Programmatically set log level
chat.log_level("debug")

Configuration

Default configuration

Also see here:

{

  -- Shared config starts here (can be passed to functions at runtime and configured via setup function)

  system_prompt = prompts.COPILOT_INSTRUCTIONS, -- System prompt to use (can be specified manually in prompt via /).
  model = 'gpt-4o', -- Default model to use, see ':CopilotChatModels' for available models (can be specified manually in prompt via $).
  agent = 'copilot', -- Default agent to use, see ':CopilotChatAgents' for available agents (can be specified manually in prompt via @).
  context = nil, -- Default context or array of contexts to use (can be specified manually in prompt via #).
  temperature = 0.1, -- GPT result temperature

  headless = false, -- Do not write to chat buffer and use history(useful for using callback for custom processing)
  callback = nil, -- Callback to use when ask response is received

  -- default selection
  selection = function(source)
    return select.visual(source) or select.buffer(source)
  end,

  -- default window options
  window = {
    layout = 'vertical', -- 'vertical', 'horizontal', 'float', 'replace'
    width = 0.5, -- fractional width of parent, or absolute width in columns when > 1
    height = 0.5, -- fractional height of parent, or absolute height in rows when > 1
    -- Options below only apply to floating windows
    relative = 'editor', -- 'editor', 'win', 'cursor', 'mouse'
    border = 'single', -- 'none', single', 'double', 'rounded', 'solid', 'shadow'
    row = nil, -- row position of the window, default is centered
    col = nil, -- column position of the window, default is centered
    title = 'Copilot Chat', -- title of chat window
    footer = nil, -- footer of chat window
    zindex = 1, -- determines if window is on top or below other floating windows
  },

  show_help = true, -- Shows help message as virtual lines when waiting for user input
  show_folds = true, -- Shows folds for sections in chat
  highlight_selection = true, -- Highlight selection
  highlight_headers = true, -- Highlight headers in chat, disable if using markdown renderers (like render-markdown.nvim)
  auto_follow_cursor = true, -- Auto-follow cursor in chat
  auto_insert_mode = false, -- Automatically enter insert mode when opening window and on new prompt
  insert_at_end = false, -- Move cursor to end of buffer when inserting text
  clear_chat_on_new_prompt = false, -- Clears chat on every new prompt

  -- Static config starts here (can be configured only via setup function)

  debug = false, -- Enable debug logging (same as 'log_level = 'debug')
  log_level = 'info', -- Log level to use, 'trace', 'debug', 'info', 'warn', 'error', 'fatal'
  proxy = nil, -- [protocol://]host[:port] Use this proxy
  allow_insecure = false, -- Allow insecure server connections

  chat_autocomplete = true, -- Enable chat autocompletion (when disabled, requires manual `mappings.complete` trigger)
  history_path = vim.fn.stdpath('data') .. '/copilotchat_history', -- Default path to stored history

  question_header = '## User ', -- Header to use for user questions
  answer_header = '## Copilot ', -- Header to use for AI answers
  error_header = '## Error ', -- Header to use for errors
  separator = '───', -- Separator to use in chat

  -- default contexts
  contexts = {
    buffer = {
      -- see config.lua for implementation
    },
    buffers = {
      -- see config.lua for implementation
    },
    file = {
      -- see config.lua for implementation
    },
    files = {
      -- see config.lua for implementation
    },
    git = {
      -- see config.lua for implementation
    },
    register = {
      -- see config.lua for implementation
    },
  },

  -- default prompts
  prompts = {
    Explain = {
      prompt = '> /COPILOT_EXPLAIN\n\nWrite an explanation for the selected code as paragraphs of text.',
    },
    Review = {
      prompt = '> /COPILOT_REVIEW\n\nReview the selected code.',
      -- see config.lua for implementation
    },
    Fix = {
      prompt = '> /COPILOT_GENERATE\n\nThere is a problem in this code. Rewrite the code to show it with the bug fixed.',
    },
    Optimize = {
      prompt = '> /COPILOT_GENERATE\n\nOptimize the selected code to improve performance and readability.',
    },
    Docs = {
      prompt = '> /COPILOT_GENERATE\n\nPlease add documentation comments to the selected code.',
    },
    Tests = {
      prompt = '> /COPILOT_GENERATE\n\nPlease generate tests for my code.',
    },
    Commit = {
      prompt = '> #git:staged\n\nWrite commit message for the change with commitizen convention. Make sure the title has maximum 50 characters and message is wrapped at 72 characters. Wrap the whole message in code block with language gitcommit.',
    },
  },

  -- default mappings
  mappings = {
    complete = {
      insert = '<Tab>',
    },
    close = {
      normal = 'q',
      insert = '<C-c>',
    },
    reset = {
      normal = '<C-l>',
      insert = '<C-l>',
    },
    submit_prompt = {
      normal = '<CR>',
      insert = '<C-s>',
    },
    toggle_sticky = {
      detail = 'Makes line under cursor sticky or deletes sticky line.',
      normal = 'gr',
    },
    accept_diff = {
      normal = '<C-y>',
      insert = '<C-y>',
    },
    jump_to_diff = {
      normal = 'gj',
    },
    quickfix_diffs = {
      normal = 'gq',
    },
    yank_diff = {
      normal = 'gy',
      register = '"',
    },
    show_diff = {
      normal = 'gd',
    },
    show_system_prompt = {
      normal = 'gp',
    },
    show_user_selection = {
      normal = 'gs',
    },
    show_user_context = {
      normal = 'gc',
    },
    show_help = {
      normal = 'gh',
    },
  },
}

Customizing buffers

You can set local options for the buffers that are created by this plugin: copilot-diff, copilot-system-prompt, copilot-user-selection, copilot-chat.

vim.api.nvim_create_autocmd('BufEnter', {
    pattern = 'copilot-*',
    callback = function()
        vim.opt_local.relativenumber = true

        -- C-p to print last response
        vim.keymap.set('n', '<C-p>', function()
          print(require("CopilotChat").response())
        end, { buffer = true, remap = true })
    end
})

Tips

Quick chat with your buffer To chat with Copilot using the entire content of the buffer, you can add the following configuration to your keymap: ```lua -- lazy.nvim keys -- Quick chat with Copilot { "ccq", function() local input = vim.fn.input("Quick Chat: ") if input ~= "" then require("CopilotChat").ask(input, { selection = require("CopilotChat.select").buffer }) end end, desc = "CopilotChat - Quick chat", } ``` [![Chat with buffer](https://i.gyazo.com/9b8cbf1d78a19f326282a6520bc9aab0.gif)](https://gyazo.com/9b8cbf1d78a19f326282a6520bc9aab0)
Inline chat Change the window layout to `float` and position relative to cursor to make the window look like inline chat. This will allow you to chat with Copilot without opening a new window. ```lua -- lazy.nvim opts { window = { layout = 'float', relative = 'cursor', width = 1, height = 0.4, row = 1 } } ``` ![inline-chat](https://github.com/CopilotC-Nvim/CopilotChat.nvim/assets/5115805/608e3c9b-8569-408d-a5d1-2213325fc93c)
Telescope integration Requires [telescope.nvim](https://github.com/nvim-telescope/telescope.nvim) plugin to be installed. ```lua -- lazy.nvim keys -- Show prompts actions with telescope { "ccp", function() local actions = require("CopilotChat.actions") require("CopilotChat.integrations.telescope").pick(actions.prompt_actions()) end, desc = "CopilotChat - Prompt actions", }, ``` ![image](https://github.com/CopilotC-Nvim/CopilotChat.nvim/assets/5115805/14360883-7535-4ee3-aca1-79f6c39f626b)
fzf-lua integration Requires [fzf-lua](https://github.com/ibhagwan/fzf-lua) plugin to be installed. ```lua -- lazy.nvim keys -- Show prompts actions with fzf-lua { "ccp", function() local actions = require("CopilotChat.actions") require("CopilotChat.integrations.fzflua").pick(actions.prompt_actions()) end, desc = "CopilotChat - Prompt actions", }, ``` ![image](https://github.com/CopilotC-Nvim/CopilotChat.nvim/assets/5115805/743455bb-9517-48a8-a7a1-81215dc3b747)
render-markdown integration Requires [render-markdown](https://github.com/MeanderingProgrammer/render-markdown.nvim) plugin to be installed. ```lua -- Registers copilot-chat filetype for markdown rendering require('render-markdown').setup({ file_types = { 'markdown', 'copilot-chat' }, }) -- You might also want to disable default header highlighting for copilot chat when doing this and set error header style and separator require('CopilotChat').setup({ highlight_headers = false, separator = '---', error_header = '> [!ERROR] Error', -- rest of your config }) ``` ![image](https://github.com/user-attachments/assets/d8dc16f8-3f61-43fa-bfb9-83f240ae30e8)

Roadmap (Wishlist)

Development

Installing Pre-commit Tool

For development, you can use the provided Makefile command to install the pre-commit tool:

make install-pre-commit

This will install the pre-commit tool and the pre-commit hooks.

Contributors ✨

If you want to contribute to this project, please read the CONTRIBUTING.md file.

Thanks goes to these wonderful people (emoji key):

gptlang
gptlang

💻 📖
Dung Duc Huynh (Kaka)
Dung Duc Huynh (Kaka)

💻 📖
Ahmed Haracic
Ahmed Haracic

💻
Trí Thiện Nguyễn
Trí Thiện Nguyễn

💻
He Zhizhou
He Zhizhou

💻
Guruprakash Rajakkannu
Guruprakash Rajakkannu

💻
kristofka
kristofka

💻
PostCyberPunk
PostCyberPunk

📖
Katsuhiko Nishimra
Katsuhiko Nishimra

💻
Erno Hopearuoho
Erno Hopearuoho

💻
Shaun Garwood
Shaun Garwood

💻
neutrinoA4
neutrinoA4

💻 📖
Jack Muratore
Jack Muratore

💻
Adriel Velazquez
Adriel Velazquez

💻 📖
Tomas Slusny
Tomas Slusny

💻 📖
Nisal
Nisal

📖
Tobias Gårdhus
Tobias Gårdhus

📖
Petr Dlouhý
Petr Dlouhý

📖
Dylan Madisetti
Dylan Madisetti

💻
Aaron Weisberg
Aaron Weisberg

💻 📖
Jose Tlacuilo
Jose Tlacuilo

💻 📖
Kevin Traver
Kevin Traver

💻 📖
dTry
dTry

💻
Arata Furukawa
Arata Furukawa

💻
Ling
Ling

💻
Ivan Frolov
Ivan Frolov

💻
Folke Lemaitre
Folke Lemaitre

💻 📖
GitMurf
GitMurf

💻
Dmitrii Lipin
Dmitrii Lipin

💻
jinzhongjia
jinzhongjia

📖
guill
guill

💻
Sjon-Paul Brown
Sjon-Paul Brown

💻
Renzo Mondragón
Renzo Mondragón

💻 📖
fjchen7
fjchen7

💻
Radosław Woźniak
Radosław Woźniak

💻
JakubPecenka
JakubPecenka

💻
thomastthai
thomastthai

📖
Tomáš Janoušek
Tomáš Janoušek

💻
Toddneal Stallworth
Toddneal Stallworth

📖
Sergey Alexandrov
Sergey Alexandrov

💻
Léopold Mebazaa
Léopold Mebazaa

💻

This project follows the all-contributors specification. Contributions of any kind are welcome!

Stargazers over time

Stargazers over time