diff --git a/README.md b/README.md index e6dd8e5..a32d1f1 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,12 @@ `spell` source for [`nvim-cmp`](https://github.com/hrsh7th/nvim-cmp) based on vim's `spellsuggest`. +`cmp-spell` optionally supports dictionary definitions in the documentation window of `nvim-cmp`. + ## Setup +These are the configuration default values. + ```lua require('cmp').setup({ sources = { @@ -14,6 +18,11 @@ require('cmp').setup({ enable_in_context = function() return true end, + definition = { + enable = false, + command = { "" }, + format = function(_) end + } }, }, }, @@ -60,6 +69,150 @@ end, Note: this option will be removed when hrsh7th/nvim-cmp#632 is implemented. +### `definition` + +These options are related to what is shown in the documentation window for each item. + +#### `definition.enable` + +If true, definitions for each entry are displayed by running an arbitrary +command (`definition.command`) that retrieves them. + +Type: boolean +Default: `false` + +#### `definition.command` + +This is the external command to run and retrieve the definition of the word. +The field `${word}` will be replaced by the actual selected completion item. + +Type: table of strings +Default: `{ "" }` + +For example, use *WordNet* for a free dictionary + +```lua +command = { "wn", "${word}, "-over" } +``` + +or [sdcv](https://github.com/Dushistov/sdcv) if you have *StarDict* +dictionaries, maybe using [goldendict](https://github.com/xiaoyifang/goldendict-ng). + +```lua +command = { "sdcv", "-j", "-n", "${word}"}, +``` + +#### `definition.format` + +A function to format the output of `definition.command` before showing it in +the documentation window. + +Type: function +Parameter: Table with the output lines of `definition.command` +Return: Text string to display in the documentation window +Default: `function(_) end` + +For example, if you are using the *WordNet* example above, the corresponding +format function would be + +```lua +format = function(text) return table.concat(text, "\n") end +``` + +And if you are using *StarDict* dictionaries and want to output Markdown from +the json data, the format function could be something like this: + +```lua +format = function (text) + local md = {} + local data = vim.fn.json_decode(text) + if data ~= nil then + for _, v in ipairs(data) do + table.insert(md, "# Dictionary: " .. v['dict'] .. "\n") + table.insert(md, "## Word: " .. v['word']) + local definition = v['definition'] + -- Remove html tags + definition = string.gsub(definition, "<.+>", "") + -- Remove reference to pronunciation WAV files + definition = string.gsub(definition, "[^%s]*%.wav%s", "") + -- Swap [] for italics + definition = string.gsub(definition, "%[([^%]]+)%]", "*%1*") + table.insert(md, definition .. "\n\n") + end + end + return table.concat(md) +end +``` + +## Examples + +This example configures `cmp-spell` to query definitions from *WordNet*: + +```lua +require('cmp').setup({ + sources = { + name = 'spell', + keyword_length = 2, + max_item_count = 8, + option = { + -- if this is set to false, fuzzy matching will discard most of + -- the spelling suggestions, even if they are correct + keep_all_entries = true, + definition = { + enable = true, + command = { "wn", "${word}", "-over" }, + format = function(text) return table.concat(text, "\n") end + } + } + } +}) +``` + +Or to get definitions from your *StarDict* dictionaries and convert them into +a simplified Markdown format you can display in the documentation window: + +```lua +local function format_spell_definition(text) + local md = {} + local data = vim.fn.json_decode(text) + if data ~= nil then + for _, v in ipairs(data) do + table.insert(md, "# Dictionary: " .. v['dict'] .. "\n") + table.insert(md, "## Word: " .. v['word']) + local definition = v['definition'] + -- Remove html tags + definition = string.gsub(definition, "<.+>", "") + -- Remove reference to pronunciation WAV files + definition = string.gsub(definition, "[^%s]*%.wav%s", "") + -- Swap [] for italics + definition = string.gsub(definition, "%[([^%]]+)%]", "*%1*") + table.insert(md, definition .. "\n\n") + end + end + return table.concat(md) +end + +require('cmp').setup({ + sources = { + name = 'spell', + keyword_length = 2, + max_item_count = 8, + option = { + -- if this is set to false, fuzzy matching will discard most of + -- the spelling suggestions, even if they are correct + keep_all_entries = true, + definition = { + enable = true, + command = { "sdcv", "-j", "-n", "${word}"}, + format = format_spell_definition, + } + } + } + } +}) +``` + + ## Credit - [compe-spell](https://github.com/hrsh7th/nvim-compe/blob/master/lua/compe_spell/init.lua) diff --git a/lua/cmp-spell/init.lua b/lua/cmp-spell/init.lua index ae5da33..d61960e 100644 --- a/lua/cmp-spell/init.lua +++ b/lua/cmp-spell/init.lua @@ -5,6 +5,11 @@ local defaults = { enable_in_context = function() return true end, + definition = { + enable = false, + command = { "" }, + format = function(_) end + } } function source.new() @@ -24,6 +29,10 @@ local function validate_option(params) vim.validate({ keep_all_entries = { option.keep_all_entries, 'boolean' }, enable_in_context = { option.enable_in_context, 'function' }, + definition = { option.definition, 'table' }, + definition_enable = { option.definition.enable, 'boolean' }, + definition_command = { option.definition.command, 'table' }, + definition_format = { option.definition.format, 'function' } }) return option end @@ -73,8 +82,10 @@ local function candidates(input, option) return items end +local option + function source:complete(params, callback) - local option = validate_option(params) + option = validate_option(params) local input = string.sub(params.context.cursor_before_line, params.offset) if option.enable_in_context(params) then @@ -84,6 +95,32 @@ function source:complete(params, callback) end end +local function run_job(cmd) + local result = {} + local id = vim.fn.jobstart(cmd, { + on_stdout = function(_, data) + for _, v in ipairs(data) do + table.insert(result, v) + end + end, + stdout_buffered = true + }) + vim.fn.jobwait({ id }) + return result +end + +function source:resolve(item, callback) + if item.documentation == nil and option.definition.enable then + local command = vim.tbl_map(function(c) + return c:gsub("${word}", item.label) + end, option.definition.command) + local result = run_job(command) + local text = option.definition.format(result) + item.documentation = text + end + callback(item) +end + local debug_name = 'spell' function source:get_debug_name() return debug_name