Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -14,6 +18,11 @@ require('cmp').setup({
enable_in_context = function()
return true
end,
definition = {
enable = false,
command = { "" },
format = function(_) end
}
},
},
},
Expand Down Expand Up @@ -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)
Expand Down
39 changes: 38 additions & 1 deletion lua/cmp-spell/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ local defaults = {
enable_in_context = function()
return true
end,
definition = {
enable = false,
command = { "" },
format = function(_) end
}
}

function source.new()
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down