import os import time import copilot import dotenv import prompts import pynvim dotenv.load_dotenv() @pynvim.plugin class CopilotChatPlugin(object): def __init__(self, nvim: pynvim.Nvim): self.nvim = nvim self.copilot = copilot.Copilot(os.getenv("COPILOT_TOKEN")) if self.copilot.github_token is None: req = self.copilot.request_auth() self.nvim.out_write( f"Please visit {req['verification_uri']} and enter the code {req['user_code']}\n" ) current_time = time.time() wait_until = current_time + req["expires_in"] while self.copilot.github_token is None: self.copilot.poll_auth(req["device_code"]) time.sleep(req["interval"]) if time.time() > wait_until: self.nvim.out_write("Timed out waiting for authentication\n") return self.nvim.out_write("Successfully authenticated with Copilot\n") self.copilot.authenticate() @pynvim.command("CopilotChat", nargs="1") def copilotChat(self, args: list[str]): if self.copilot.github_token is None: self.nvim.out_write("Please authenticate with Copilot first\n") return # Start the spinner self.nvim.exec_lua('require("CopilotChat.spinner").show()') prompt = " ".join(args) if prompt == "/fix": prompt = prompts.FIX_SHORTCUT elif prompt == "/test": prompt = prompts.TEST_SHORTCUT elif prompt == "/explain": prompt = prompts.EXPLAIN_SHORTCUT # Get code from the unnamed register code = self.nvim.eval("getreg('\"')") file_type = self.nvim.eval("expand('%')").split(".")[-1] # Get the view option from the command view_option = self.nvim.eval("g:copilot_chat_view_option") def is_buffer_in_current_tab(buffer): # check if the buffer is in the current tab. # if so, switch to it for window in self.nvim.current.tabpage.windows: if window.buffer.number == buffer['bufnr']: self.nvim.current.window = window return True return False buffer_name = "CopilotChat" existing_buffers = self.nvim.eval('getbufinfo()') for buffer in existing_buffers: if os.path.basename(buffer['name']) == buffer_name: # If the buffer exists, switch to it if is_buffer_in_current_tab(buffer): # If the buffer is already in the current tab, switch to the window break if (buffer['bufnr'] != self.nvim.current.buffer.number and view_option == "split"): # open a split if the buffer is not the current one self.nvim.command("vnew") self.nvim.command('buffer {}'.format(buffer['bufnr'])) break else: # Create a new scratch buffer to hold the chat if view_option == "split": self.nvim.command("vnew") else: self.nvim.command("enew") # Set the buffer type to nofile and hide it when it's not active self.nvim.command("setlocal buftype=nofile bufhidden=hide noswapfile") # Set filetype as markdown and wrap with linebreaks self.nvim.command("setlocal filetype=markdown wrap linebreak") self.nvim.command('file {}'.format(buffer_name)) # Get the current buffer buf = self.nvim.current.buffer self.nvim.api.buf_set_option(buf, "fileencoding", "utf-8") # Add start separator start_separator = f"""### User {prompt} ### Copilot """ buf.append(start_separator.split("\n"), -1) # Add chat messages for token in self.copilot.ask(prompt, code, language=file_type): buffer_lines = self.nvim.api.buf_get_lines(buf, 0, -1, 0) last_line_row = len(buffer_lines) - 1 last_line = buffer_lines[-1] last_line_col = len(last_line.encode("utf-8")) self.nvim.api.buf_set_text( buf, last_line_row, last_line_col, last_line_row, last_line_col, token.split("\n"), ) # Stop the spinner self.nvim.exec_lua('require("CopilotChat.spinner").hide()') # Add end separator end_separator = "\n---\n" buf.append(end_separator.split("\n"), -1)