├── after └── ftplugin │ └── gitcommit.lua ├── README.md ├── LICENSE.md └── lua └── cmp-conventionalcommits └── init.lua /after/ftplugin/gitcommit.lua: -------------------------------------------------------------------------------- 1 | if vim.g.cmp_conventionalcommits_source_id ~= nil then 2 | require('cmp').unregister_source(vim.g.cmp_conventionalcommits_source_id) 3 | end 4 | vim.g.cmp_conventionalcommits_source_id = require('cmp').register_source('conventionalcommits', require('cmp-conventionalcommits').new()) 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cmp-conventionalcommits 2 | 3 | **Alpha — Work in progress** 4 | 5 | [Conventional Commits](https://www.conventionalcommits.org) source for [`nvim-cmp`](https://github.com/hrsh7th/nvim-cmp). 6 | 7 | Reads your configured scopes from [commitlint](https://commitlint.js.org/#/): 8 | 9 | ![example_1](https://user-images.githubusercontent.com/9190258/139169092-a44587c8-725c-4296-b2bf-24fb6dbc381a.png) 10 | 11 | If you have [Lerna](https://lerna.js.org/), completes your local packages in the scope: 12 | 13 | ![example_2](https://user-images.githubusercontent.com/9190258/139169114-d6832cab-a123-4a96-a92f-6b84e11f028b.png) 14 | 15 | ## Setup 16 | 17 | Setup in `after/ftplugin/gitcommit.lua` 18 | 19 | ```lua 20 | require'cmp'.setup.buffer { 21 | sources = require'cmp'.config.sources( 22 | {{ name = 'conventionalcommits' }}, 23 | {{ name = 'buffer' }} 24 | ), 25 | } 26 | ``` 27 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 David Balam Sierra DiazGranados 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lua/cmp-conventionalcommits/init.lua: -------------------------------------------------------------------------------- 1 | local source = {} 2 | 3 | local typesDict = {} 4 | typesDict['build'] = { 5 | label = 'build', 6 | documentation = 'Changes that affect the build system or external dependencies', 7 | } 8 | typesDict['chore'] = { 9 | label = 'chore', 10 | documentation = 'Other changes that dont modify src or test files', 11 | } 12 | typesDict['ci'] = { 13 | label = 'ci', 14 | documentation = 'Changes to our CI configuration files and scripts', 15 | } 16 | typesDict['docs'] = { 17 | label = 'docs', 18 | documentation = 'Documentation only changes', 19 | } 20 | typesDict['feat'] = {label = 'feat', documentation = 'A new feature'} 21 | typesDict['fix'] = {label = 'fix', documentation = 'A bug fix'} 22 | typesDict['perf'] = { 23 | label = 'perf', 24 | documentation = 'A code change that improves performance', 25 | } 26 | typesDict['refactor'] = { 27 | label = 'refactor', 28 | documentation = 'A code change that neither fixes a bug nor adds a feature', 29 | } 30 | typesDict['revert'] = { 31 | label = 'revert', 32 | documentation = 'Reverts a previous commit', 33 | } 34 | typesDict['style'] = { 35 | label = 'style', 36 | documentation = 'Changes that do not affect the meaning of the code', 37 | } 38 | typesDict['test'] = { 39 | label = 'test', 40 | documentation = 'Adding missing tests or correcting existing tests', 41 | } 42 | 43 | function source.new() 44 | local result = io.popen( 45 | [[node --eval 'console.log(process.argv[1])' $(npm list --long --parseable --depth=0 @commitlint/cli | awk -F@ '{print $NF}') 2>/dev/null]]):read( 46 | "*a") 47 | -- dump(result) 48 | if result == "undefined\n" then 49 | -- Error handling. 50 | source.types = { 51 | typesDict['build'], typesDict['chore'], typesDict['ci'], 52 | typesDict['docs'], typesDict['feat'], typesDict['fix'], typesDict['perf'], 53 | typesDict['refactor'], typesDict['revert'], typesDict['style'], 54 | typesDict['test'], 55 | } 56 | else 57 | if string.match(result, "^1[2|3]") then 58 | local newcommitlint = io.popen( 59 | [[node --eval 'eval("var obj ="+process.argv[1]);console.log(JSON.stringify(obj?.rules?.["type-enum"]?.[2]));' "$(npx commitlint --print-config --no-color | tr '\n' ' ')" 2>/dev/null]]):read( 60 | "*a") 61 | -- dump(newcommitlint) 62 | if newcommitlint ~= "" then 63 | -- Success handling. 64 | local result_decoded = vim.fn.json_decode(newcommitlint) 65 | local types = {} 66 | for _, v in ipairs(result_decoded) do 67 | if typesDict[v] then 68 | table.insert(types, typesDict[v]) 69 | else 70 | table.insert(types, {label = v}) 71 | end 72 | end 73 | source.types = types 74 | else 75 | -- Error handling. 76 | source.types = { 77 | typesDict['build'], typesDict['chore'], typesDict['ci'], 78 | typesDict['docs'], typesDict['feat'], typesDict['fix'], 79 | typesDict['perf'], typesDict['refactor'], typesDict['revert'], 80 | typesDict['style'], typesDict['test'], 81 | } 82 | end 83 | else 84 | local oldcommitlint = io.popen( 85 | [[node --eval 'console.log(JSON.stringify(require("@commitlint/config-conventional")?.rules?.["type-enum"]?.[2]));' 2>/dev/null]]):read( 86 | "*a") 87 | -- dump(oldcommitlint) 88 | if oldcommitlint ~= "" then 89 | -- Success handling. 90 | local result_decoded = vim.fn.json_decode(oldcommitlint) 91 | local types = {} 92 | for _, v in ipairs(result_decoded) do 93 | if typesDict[v] then 94 | table.insert(types, typesDict[v]) 95 | else 96 | table.insert(types, {label = v}) 97 | end 98 | end 99 | source.types = types 100 | else 101 | -- Error handling. 102 | source.types = { 103 | typesDict['build'], typesDict['chore'], typesDict['ci'], 104 | typesDict['docs'], typesDict['feat'], typesDict['fix'], 105 | typesDict['perf'], typesDict['refactor'], typesDict['revert'], 106 | typesDict['style'], typesDict['test'], 107 | } 108 | end 109 | end 110 | end 111 | 112 | local lernaresult = io.popen( 113 | [[./node_modules/.bin/lerna --loglevel silent list --all --long --parseable 2>/dev/null | cut --delimiter=':' --fields=2 | cut --delimiter='/' --fields=2]]):read( 114 | "*a") 115 | if lernaresult ~= "" then 116 | -- Success handling. 117 | local lines = {} 118 | for s in lernaresult:gmatch("[^\r\n]+") do table.insert(lines, s) end 119 | source.scopes = lines 120 | else 121 | -- Error handling. 122 | source.scopes = {} 123 | end 124 | 125 | return setmetatable({}, {__index = source}) 126 | end 127 | 128 | function source:is_available() return vim.bo.filetype == "gitcommit" end 129 | 130 | function source:get_keyword_pattern() return [[\w\+]] end 131 | 132 | local function candidates(entries) 133 | local items = {} 134 | for k, v in ipairs(entries) do 135 | items[k] = { 136 | label = v.label, 137 | kind = require('cmp').lsp.CompletionItemKind.Keyword, 138 | documentation = v.documentation, 139 | } 140 | end 141 | return items 142 | end 143 | 144 | local function candidatesLernaScope(entries) 145 | local items = {} 146 | for k, v in ipairs(entries) do 147 | items[k] = {label = v, kind = require('cmp').lsp.CompletionItemKind.Folder} 148 | end 149 | return items 150 | end 151 | 152 | function source:complete(request, callback) 153 | if request.context.option.reason == "manual" and request.context.cursor.row == 154 | 1 and request.context.cursor.col == 1 then 155 | callback({items = candidates(self.types), isIncomplete = true}) 156 | elseif request.context.option.reason == "auto" and request.context.cursor.row == 157 | 1 and request.context.cursor.col == 2 then 158 | callback({items = candidates(self.types), isIncomplete = true}) 159 | elseif request.context.cursor_after_line == ")" and request.context.cursor.row == 160 | 1 then 161 | callback({items = candidatesLernaScope(self.scopes), isIncomplete = true}) 162 | else 163 | callback() 164 | end 165 | end 166 | 167 | return source 168 | --------------------------------------------------------------------------------