A Better Gitmoji command (for Neovim)

Taylor Talkington : ~

After writing the post about setting up Neovim I got to thinking about the :Gitmoji command I made and a better way of doing it.

It works well enough, but since there are over 70 different emojis, finding one in the list can be a bit difficult. A way to filter the list would be nice.

It turns out that using vim.ui.input for this instead of vim.ui.select is possible, but I couldn't find any examples of using a custom completion function from Lua.

I was able to get it working after some trial and error, but it requires a little more setup than just adding code to init.lua.

Instead, this needs to be housed within it's own module, i.e. ~\nvim\lua\gitmoji.lua folder.

We still need the list of emoji and descriptions:

local gitmojis = {
    {'๐ŸŽจ', 'Improve structure/format of the code.'},
    {'โšก๏ธ', 'Improve performance.'},
    {'๐Ÿ”ฅ', 'Remove code or files.'},
    {'๐Ÿ›', 'Fix a bug.'},
    {'๐Ÿš‘๏ธ', 'Critical hotfix.'},
    {'โœจ', 'Introduce new features.'},
    {'๐Ÿ“', 'Add or update documentation.'},
    {'๐Ÿš€', 'Deploy stuff.'},
    {'๐Ÿ’„', 'Add or update the UI and style files.'},
    {'๐ŸŽ‰', 'Begin a project.'},
    {'โœ…', 'Add, update, or pass tests.'},
    {'๐Ÿ”’๏ธ', 'Fix security or privacy issues.'},
    {'๐Ÿ”', 'Add or update secrets.'},
    {'๐Ÿ”–', 'Release/version tags.'},
    {'๐Ÿšจ', 'Fix compiler/linter warnings.'},
    {'๐Ÿšง', 'Work in progress.'},
    {'๐Ÿ’š', 'Fix CI build.'},
    {'โฌ‡๏ธ' , 'Downgrade dependencies.'},
    {'โฌ†๏ธ' , 'Upgrade dependencies.'},
    {'๐Ÿ“Œ', 'Pin dependencies to specific versions.'},
    {'๐Ÿ‘ท', 'Add or update CI build system.'},
    {'๐Ÿ“ˆ', 'Add or update analytics or track code.'},
    {'โ™ป๏ธ' , 'Refactor code.'},
    {'โž•', 'Add a dependency.'},
    {'โž–', 'Remove a dependency.'},
    {'๐Ÿ”ง', 'Add or update configuration files.'},
    {'๐Ÿ”จ', 'Add or update development scripts.'},
    {'๐ŸŒ', 'Internationalization and localization.'},
    {'โœ๏ธ' , 'Fix typos.'},
    {'๐Ÿ’ฉ', 'Write bad code that needs to be improved.'},
    {'โช๏ธ', 'Revert changes.'},
    {'๐Ÿ”€', 'Merge branches.'},
    {'๐Ÿ“ฆ๏ธ', 'Add or update compiled files or packages.'},
    {'๐Ÿ‘ฝ๏ธ', 'Update code due to external API changes.'},
    {'๐Ÿšš', 'Move or rename resources (e.g.: files, paths, routes).'},
    {'๐Ÿ“„', 'Add or update license.'},
    {'๐Ÿ’ฅ', 'Introduce breaking changes.'},
    {'๐Ÿฑ', 'Add or update assets.'},
    {'โ™ฟ๏ธ', 'Improve accessibility.'},
    {'๐Ÿ’ก', 'Add or update comments in source code.'},
    {'๐Ÿป', 'Write code drunkenly.'},
    {'๐Ÿ’ฌ', 'Add or update text and literals.'},
    {'๐Ÿ—ƒ๏ธ', 'Perform database related changes.'},
    {'๐Ÿ”Š', 'Add or update logs.'},
    {'๐Ÿ”‡', 'Remove logs.'},
    {'๐Ÿ‘ฅ', 'Add or update contributor(s).'},
    {'๐Ÿšธ', 'Improve user experience/usability.'},
    {'๐Ÿ—๏ธ', 'Make architectural changes.'},
    {'๐Ÿ“ฑ', 'Work on responsive design.'},
    {'๐Ÿคก', 'Mock things.'},
    {'๐Ÿฅš', 'Add or update easter egg.'},
    {'๐Ÿ™ˆ', 'Add or update .gitignore file.'},
    {'๐Ÿ“ธ', 'Add or update snapshots.'},
    {'โš—๏ธ' , 'Perform experiments.'},
    {'๐Ÿ”๏ธ', 'Improve SEO.'},
    {'๐Ÿท๏ธ', 'Add or update types.'},
    {'๐ŸŒฑ', 'Add or update seed files.'},
    {'๐Ÿšฉ', 'Add, update, or remove feature flags.'},
    {'๐Ÿฅ…', 'Catch errors.'},
    {'๐Ÿ’ซ', 'Add or update animations and transitions.'},
    {'๐Ÿ—‘๏ธ', 'Deprecate code that needs to be cleaned up.'},
    {'๐Ÿ›‚', 'Work on code related to authorization, roles, and permissions.'},
    {'๐Ÿฉน', 'Simple fix for a non-critical issue.'},
    {'๐Ÿง', 'Data exploration/inspection.'},
    {'โšฐ๏ธ' , 'Remove dead code.'},
    {'๐Ÿงช', 'Add a failing test.'},
    {'๐Ÿ‘”', 'Add or update business logic.'},
    {'๐Ÿฉบ', 'Add or update healthcheck.'},
    {'๐Ÿงฑ', 'Infrastructure related changes.'},
    {'๐Ÿง‘โ€๐Ÿ’ป', 'Improve developer experience.'},
    {'๐Ÿ’ธ', 'Add sponsorships or money related infrastructure.'},
    {'๐Ÿงต', 'Add or update code related to multithreading or concurrency.'},
    {'๐Ÿฆบ', 'Add or update code related to validation.'},
}

Next, define a table to hold our module and the actual functions:

local M = {}

function M.complete_gitmoji(arglead, cmdline, cursorpos)

    local matches = {}
    for i, g in ipairs(gitmojis) do
        if string.match(string.lower(g[2]), string.lower(cmdline)) then
            table.insert(matches, string.format('%s - %s', g[1], g[2]))
        end
    end
    return table.concat(matches, '\n')
end

local function oncomplete(text)
    if not text then return end

    local description = string.match(text, '. %- (.+)')

    if not description then
        print('Invalid selection.')
        return
    end

    for i,g in ipairs(gitmojis) do
        if g[2]==description then
            vim.cmd('normal! i' .. g[1] .. ' ')
            return
        end
    end

    print('Invalid gitmoji: ' .. description)
end

vim.api.nvim_create_user_command('Gitmoji', function()
    vim.ui.input({
        prompt = 'Select Gitmoji (type a partial description and press tab):',
        completion = "custom,v:lua.require'gitmoji'.complete_gitmoji",
    }, oncomplete)
end, { nargs = 0 })

return M

complete_gitmoji is called when the user presses tab when the input window is open. The cmdline parameter contains the entire line of text from the input box. This is used to filter the list of gitmojis, which are returned as a newline separated list.

oncomplete is called when the user either cancels or selects a gitmoji. This function compares the text with all of the gitmoji descriptions. If one matches, it inserts that unicode gitmoji into the buffer. If the there is no match it displays a message instead.

The real magic is in the call to vim.ui.input. The completion option specifies how the autocompletion is performed, in this case, our module is loaded and the complete_gitmoji function is called.

Finally, just load the module in init.lua:

require('gitmoji')