Custom CoC Extensions

This post will walk you through a workflow of creating a new GitHub Issue access Plugin for CoC which can help you add GitHub issues to your Git Commit messages with ease. This post can be used as a reference to customize and extend this basic plugin or create one on your own to integrate with any utility such as JIRA or Rally or BitBucket etc.

The Goal

By the end of this post, you should have a working CoC extension that can be used to pull up a list of GitHub issues assigned to you when you open a GitCommit buffer automatically so that you can add a reference for the issue easily. It will provide the following configurable items.

  1. Filter only issues assigned to me
  2. Access Token configuration
  3. A command to pull the GitHub issues from anywhere in the vim buffer if required.

Creating a Skeleton Repo

Like any other node module projects, everything begins with a simple npm init

 ▲ OpenSource/Node/coc-git-complete npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (coc-git-complete)
version: (1.0.0) 0.1.0
description: GitHub issue Completer for CoC
entry point: (index.js)
test command:
git repository:
keywords: coc, git, github
author: Harsha Narayana
license: (ISC) MIT
About to write to /Users/timelord/Work/OpenSource/Node/coc-git-complete/package.json:

{
  "name": "coc-git-complete",
  "version": "0.1.0",
  "description": "GitHub issue Completer for CoC",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "coc",
    "git",
    "github"
  ],
  "author": "Harsha Narayana",
  "license": "MIT"
}


Is this OK? (yes) yes

 ▲ OpenSource/Node/coc-git-complete tree
.
└── package.json


 ▲ OpenSource/Node/coc-git-complete yarn generate-lock-entry
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


coc-git-complete@0.1.0:
  version "0.1.0"
  

▲ OpenSource/Node/coc-git-complete tree
.
├── node_modules
├── package.json
├── yarn-error.log
└── yarn.lock

1 directory, 3 files

Setup CoC configuration

Add the following section to your package.json that was generated in the above step. These configurations will ensure your plugin can integrate well with CoC

"engines": {
  "coc": ">=0.0.57"
}

Setup CoC activation and Contributions

CoC uses the package.json to decide what are the configurable parameters for the given extension. It uses a key in package.json named contributes to include a list of supported commands and configuration entries.

These configuration entries can be used to customize the behavior of the plugin at the runtime and the commands can be used with custom vim keybinding to perform actions on request.

For the use-case in question, we will work on enabling the following.

  1. A command named gh.issues
  2. GitHub token Setup
  3. List of Repositories to watch for
  4. Configuration to enable filters to fetch only issues assigned to me
   "contributes":{
      "commands":[
         {
            "title":"Fetch a list of GitHub issues",
            "category":"gh",
            "command":"gh.issues"
         }
      ],
      "configuration":{
         "type":"object",
         "properties":{
            "gh.user": {
               "type": "string",
               "default": "",
               "description": "GitHub User Name"
            },
            "gh.token":{
               "type":"string",
               "default":"",
               "description":"GitHub Access token to use for fetching Issues"
            },
            "gh.mineOnly":{
               "type":"boolean",
               "default":true,
               "description":"Filter only those GitHub issues assigned to me"
            },
            "gh.repos":{
               "type":"array",
               "default":[

               ],
               "description":"List of GitHub Repositories to watch. Used only if gh.mineOnly is false",
               "items":{
                  "type":"string"
               }
            }
         }
      }
   }

We need to now configure the activationEvents in order to indicate CoC that it needs to activate the plugin for those conditions. For our current usecase, the plugin needs to be activate in two cases.

  1. When you are in Git commit buffer
  2. When you invoke a command for gh.issues
   "activationEvents":[
      "onLanguage:gitcommit",
      "onCommand:gh.issues"
   ]

Setup Basic Integration Dependencies

npm install --save coc.nvim
npm install --save-dev prettier
npm install --save @octokit/rest

Create Base Activation Setup

const { sources, workspace, commands } = require("coc.nvim"); // Import Base configuration for CoC

// This method is used to actually interface with GitHub using the octokit/rest.js api to fetch
// a list of issues
const fetchGitHubIssues = (token, repos, username, mineOnly) => {
  workspace.showMessage(
    "[coc-git-complete] Inside fetchGitHubIssues method",
    "more"
  );
};

// This method is invoked when `CocCommand: gh.issues` is run from the current `vim` buffer
async function listIssuesCommand(token, repos, username, mineOnly) {
  workspace.showMessage(
    "[coc-git-complete] Inside listIssuesCommand method",
    "more"
  );
}

exports.activate = async (context) => {
  // fetch configuration for the extension from CocConfig
  const config = workspace.getConfiguration("gh");

  // Extract Config Dict into respective components.
  const token = config.get("token");
  const repos = config.get("repos");
  const username = config.get("username");
  const mineOnly = config.get("mineOnly");

  // Ensure that the User GitHub token is configured. Without that, this plugin is as
  // useless as Thanos' henchmen.
  if (!token) {
    workspace.showMessage(
      "GitHub Token configuration missing. Please run :CocConfig to setup access Token",
      "warning"
    );
    return;
  }

  let issues = [];
  try {
    issues = await fetchGitHubIssues(token, repos, username, mineOnly);
  } catch (error) {
    workspace.showMessage(
      "[coc-git-complete] Failed to fetch GitHub issus. Check :CocOpenLog for details.",
      "error"
    );
    console.error("Failed to fetch GitHub issues due to ", error);
  }

  let source = {
    name: "git-complete",
    triggerOnly: false,
    doComplete: async () => {
      return {
        items: issues.map((issue) => {
          // This section defines what shows up in your neovim auto complete dropdown
          // and what gets inserted into the buffer when you hit enter. `abbr` is what
          // shows up on the dropdown and `word` is what goes into the buffer.
          return {
            word: `${issue.gid}: ${issue.description}`,
            abbr: `${issue.gid}: ${issue.description}`,
          };
        }),
      };
    },
  };

  // Register Resources and command handlers to be managed by CoC.nvim
  context.subscriptions.push(sources.createSource(source));
  context.subscriptions.push(
    commands.registerCommand("gh.issues", () =>
      listIssuesCommand(token, repos, username, mineOnly)
    )
  );
};

Enable Debug Mode

With a simple configuration of vim you can enable the Debug options for the coc and test your plugin as you develop.

Put the following line at the top of your nvim/init.vim

set runtimepath^=<path_to_your_repo_base>/coc-git-complete"

Testing your Plugin’s Default Behavior

Once you have the above configuration setup, open vim and you will notice something interesting when you run the command :CocCommand gh.issues

Two things to consider here.

  1. The Warning about gh.token not being configured
  2. gh.issues command not being found.

If you look at the code above, there is clear indication that the command will never get registered in case if the gh.token configuration is missing on the CocConfig file.

Fixing this is as simple as running :CocConfig in your neovim buffer and then setting the gh.token to a valid github token.

Now that we have the basic things running, all you need to do is setup right interaction with GitHub to fetch a list of Issues and show them in the buffer as required.

Enable Git Integration

const { Octokit } = require("@octokit/rest");

const fetchGitHubIssues = (token, repos, username, mineOnly) => {
  const octokit = new Octokit({
    auth: token,
  });

  return octokit.issues.list().then((issues) =>
    issues.data.map((issue) => ({
      gid: issue.number,
      description: issue.title,
    }))
  );
};

That is just about it. Now, open a new neovim buffer and start listing all your GitHub Issues.

As you can see, this is not really good enough. There is no easy way to find out what issue is from what Repo. Let us fix that.

  // Tune the return values a bit to include the repo.
  return octokit.issues.list().then((issues) =>
    issues.data.map((issue) => ({
      repo: issue.repository.name,
      gid: issue.number,
      description: issue.title,
    }))
  );
  
  // Configure the Abbreviation display to include repo name.
 let source = {
    name: "git-complete",
    triggerOnly: false,
    doComplete: async () => {
      return {
        items: issues.map((issue) => {
          return {
            word: `${issue.gid}: ${issue.description}`,
            abbr: `${issue.repo} -> ${issue.gid}: ${issue.description}`,
          };
        }),
      };
    },
  };

Now that you have all the basic things you need to know for you to get your custom CoC extension up an running, have fun and don’t forget to drink some coffee when you are at it. Please go ahead and implement the repository filtering support for which the configuration is already pre-enabled in this example. I would love to see what magic you guys end up creating for CoC and neovim.

You can find a link to the GitHub repository below to get your started.

GitHub Repo