CopilotChat.nvim icon indicating copy to clipboard operation
CopilotChat.nvim copied to clipboard

Feature Request: support sub-commands in CopilotChat.nvim

Open jellydn opened this issue 1 year ago • 13 comments

Context

GitHub Copilot for VS Code offers a range of useful sub-commands like /new, /fix, /explain, and /tests that enhance the coding experience. These commands allow users to generate new code, fix issues, get explanations, and write tests efficiently.

VSCode

Proposal

Integrate these sub-commands into CopilotChat.nvim to bring similar functionalities to Neovim. Below are the specific commands and their intended functionalities:

  • /new: Generate new code blocks or files.
  • /fix: Automatically suggest fixes for highlighted code issues.
  • /explain: Provide detailed explanations for specific code segments.
  • /tests: Generate test cases for existing code.

Technical Considerations

  • We could add a command like GCFIX, GCTests, etc for each sub command.
  • We might need to migrate to pure lua plugin if needed.

Request for Feedback

I am not sure about the feasibility of this without Github Copilot team input. Let me know if you have any thoughts on plan on this. @gptlang Thanks for great work for bring this plugin to Neovim community. 👏

jellydn avatar Dec 21 '23 01:12 jellydn

I was not aware of these features (Not a user of VScode). Thank you.

I will look into this.

gptlang avatar Dec 21 '23 16:12 gptlang

I'm really interested in this, since my main reason for still using Copilot on VS Code is the /workspace sub-command. It lets you make questions to CoPilot on the context of the whole local codebase. It's amazing for exploring and understand projects you had no prior contact with. That's what actually made me renew my copilot license.

I wonder if this kind of feature is even accessible outside VS Code, though, or if it uses VS Code internals to guess which files it have to look at.

thomazmoura avatar Dec 29 '23 18:12 thomazmoura

There are a lot of wasm files as part of the Copilot Chat VSCode plugin. It will take a lot of effort to get it working 1 to 1 but getting to a hacky blind implementation could maybe be done another way. Open to suggestions from others more knowledgeable than I am on how this can be done.

I personally have no clue how Copilot automagically gets code snippets. Some sort of vector db/index + text similarity tuned for code?

gptlang avatar Dec 29 '23 18:12 gptlang

I'll have to abandon porting everything to lua since this might be a bit too complicated for me to do in a new language

gptlang avatar Dec 29 '23 18:12 gptlang

~~I've got some of the sub-commands down but my VSCode is missing @workspace among others from the video~~

Edit: I haven't updated VScode in a loooong time

gptlang avatar Dec 30 '23 01:12 gptlang

Hi @gptlang Just FYI - I have just watched this video about how @workspace works. https://youtu.be/a2DDYMEPwbE?si=rQeeCA-21O5jD8IK

jellydn avatar Dec 31 '23 03:12 jellydn

I'm still working on this. Running into a bit of trouble. During the embedding request, it sends each function/class with ... for omitted code. I don't know how they parse/identify the functions.

e.g.

File: `rplugin/python3/copilot.py`
```py
class Copilot:
    ...
    def request_auth(self):
        url = "https://github.com/login/device/code"

        response = self.session.post(
            url,
            headers=LOGIN_HEADERS,
            data=json.dumps({
                "client_id": "Iv1.b507a08c87ecfe98",
                "scope": "read:user"
            })
        ).json()
        return response
    ...
File: `rplugin/python3/copilot.py`
```py
class Copilot:
    def __init__(self, token: str = None):
        if token is None:
            token = utilities.get_cached_token()
        self.github_token = token
        self.token: dict[str, any] = None
        self.chat_history: list[typings.Message] = []
        self.vscode_sessionid: str = None
        self.machineid = utilities.random_hex()

        self.session = requests.Session()
    ...

gptlang avatar Jan 04 '24 20:01 gptlang

Maybe tree sitter? Not sure how to ensure that the parser for any specific language is installed.

gptlang avatar Jan 04 '24 20:01 gptlang

Looks like I was right. They bundle the parsers into the extension. image

I don't think I have the energy to properly implement tree-sitter. If anyone has a shortcut, let me know.

I don't use AI much so I'm gonna put this in my backlog and work on other stuff first.

gptlang avatar Jan 04 '24 20:01 gptlang

This is awesome work gptlang! I now use copilot and copilotchat with vsplit. works like a charm! made my day! :)

cjoke avatar Jan 06 '24 00:01 cjoke

Thank you for the wonderful plugin. I'm sure there is a better way, but I looked into how to parse python code. Omit everything other than the function and class definition part under the cursor. I hope this helps even a little.

local pythonParseSample = function()
    local ts_utils = require 'nvim-treesitter.ts_utils'
    local current_node = ts_utils.get_node_at_cursor()
    local expr = current_node
    local lines = {}
    local target_function = nil
    local target_class = nil

    -- get function and class definition node
    while expr do
      if target_function == nil and string.find(expr:type(), 'function_definition') then
        target_function = expr
      end

      if string.find(expr:type(), 'class_definition') then
        target_class = expr
        break
      end

      expr = expr:parent()
    end

    if target_class == nil and target_function ~= nil then
      -- return function define body
      lines = ts_utils.get_node_text(target_function)
      return table.concat(lines, "\n")
    end

    if target_class ~= nil then
      local class_lines = ts_utils.get_node_text(target_class)

      if target_function == nil then
        -- return class define body
        return table.concat(class_lines, "\n")
      end

      local class_definition_line = class_lines[1]
      table.insert(lines, class_definition_line)

      local class_start_row = target_class:start()
      local class_end_row = target_class:end_()
      local function_start_row = target_function:start()
      local function_end_row = target_function:end_()

      if function_start_row ~= class_start_row + 1 then
        -- insert ... between class definition and function definition
        table.insert(lines, "...")
      end

      for _, node in ipairs(ts_utils.get_node_text(target_function)) do
        table.insert(lines, node)
      end

      if function_end_row ~= class_end_row then
        -- insert ... between function definition and class definition
        table.insert(lines, "...")
      end

      return table.concat(lines, "\n")
    end
  end

neutrinoA4 avatar Jan 10 '24 12:01 neutrinoA4

@neutrinoA4 thanks for the suggestion! Is this language agnostic (via tree-sitter) or specific to Python?

gptlang avatar Jan 10 '24 13:01 gptlang

@gptlang This code is Python specific. Mainly "function_definition" and "class_definition" in the following parts differ depending on the language.

   -- get function and class definition node
    while expr do
      if target_function == nil and string.find(expr:type(), 'function_definition') then
        target_function = expr
      end

      if string.find(expr:type(), 'class_definition') then
        target_class = expr
        break
      end

For example, struct in rust language is "struct_item", functions are specified as "function_item"

neutrinoA4 avatar Jan 10 '24 13:01 neutrinoA4

I think #360 is better description for remainder of this, as we already have subcommands, we just dont have full context awareness, so closing as duplicate

deathbeam avatar Sep 12 '24 11:09 deathbeam