Skip to content
This repository has been archived by the owner on Aug 12, 2023. It is now read-only.

perf: modularize built-ins #288

Merged
merged 7 commits into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ body:
- type: textarea
attributes:
label: "null-ls config"
description: "The minimal configuration required to reproduce your bug. Do not post or link to your entire Neovim configuration!"
validations:
required: true

Expand Down
73 changes: 48 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ for external processes.

## Status

null-ls is in **beta status**. Please open an issue if something doesn't
work the way you expect (or doesn't work at all).
null-ls is in **beta status**. Please see below for steps to follow if something
doesn't work the way you expect (or doesn't work at all).

At the moment, null-is is compatible with Neovim 0.5 (stable) and 0.6 (head),
but you'll get the best experience from the latest version you can run.
Expand All @@ -44,6 +44,8 @@ null-ls sources are able to hook into the following LSP features:

- Hover

- Completion

null-ls includes built-in sources for each of these features to provide
out-of-the-box functionality. See [BUILTINS](doc/BUILTINS.md) for instructions on
how to set up sources and a list of available sources.
Expand All @@ -69,12 +71,14 @@ integration with nvim-lspconfig, as in this example:
```lua
-- example configuration! (see CONFIG above to make your own)
require("null-ls").config({
sources = { require("null-ls").builtins.formatting.stylua }
sources = {
require("null-ls").builtins.formatting.stylua,
require("null-ls").builtins.completion.spell,
},
})
require("lspconfig")["null-ls"].setup({
on_attach = my_custom_on_attach
on_attach = my_custom_on_attach,
})

```

## Documentation
Expand Down Expand Up @@ -184,13 +188,23 @@ from a command to generate code actions.

## FAQ

### How do I set the path to the Neovim binary?
### Something isn't working! What do I do?

Set it while calling the lspconfig `setup` method for null-ls.
1. Make sure your configuration is in line with the latest version of this
document.
2. Enable debug mode (see below) and check the output of your source(s). If
the CLI program is not properly configured or is otherwise not running as
expected, that's an issue with the program, not null-ls.
3. Check the documentation for available configuration options that might solve
your issue.
4. If you're having trouble configuring null-ls or want to know how to achieve a
specific result, open a discussion.
5. If you believe the issue is with null-ls itself or you want to request a new
feature, open an issue and provide all the requested information.

```lua
require("lspconfig")["null-ls"].setup({ cmd = { "/path/to/nvim" }, ... })
```
Please **do not** link to or post your entire Neovim configuration. null-ls is
in beta status, and if you cannot make the effort to isolate your issue, then I
recommend using another solution.

### How do I format files?

Expand All @@ -217,6 +231,30 @@ on_attach = function(client)
end
```

### How do I enable debug mode and get debug output?

1. Set `debug` flag to `true` in the config like so:

```lua
require("null-ls").config({
debug = true
})
```

2. Use `:NullLsInfo` to get the path to your debug log, which you can open
direclty in Neovim or with another program.

As with LSP logging, `debug` will slow down Neovim. Make sure to disable the
option after you've collected the information you're looking for.

### How do I set the path to the Neovim binary?

Set it when calling the lspconfig `setup` method for null-ls.

```lua
require("lspconfig")["null-ls"].setup({ cmd = { "/path/to/nvim" }, ... })
```

### Does it work with (other plugin)?

In most cases, yes. null-ls tries to act like an actual LSP server as much as
Expand All @@ -243,21 +281,6 @@ memory without any external processes, in most cases it should run faster than
similar solutions. If you notice that performance is worse with null-ls than
with an alternative, please open an issue!

### How to enable and use debug mode

1. Set `debug` flag to `true` in the config like so:

```lua
require("null-ls").config({
debug = true
})
```

2. Use `:NullLsInfo` to get the path to your debug log.

As with LSP logging, `debug` will slow down Neovim. Make sure to disable the
option after you've collected the information you're looking for.

## Tests

The test suite includes unit and integration tests and depends on plenary.nvim.
Expand Down
61 changes: 57 additions & 4 deletions doc/BUILTINS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ null_ls.builtins.formatting

-- hover sources
null_ls.builtins.hover

-- completion sources
null_ls.builtins.completion
```

You can then register sources by passing a `sources` list into your `config`
Expand Down Expand Up @@ -615,6 +618,24 @@ local sources = { null_ls.builtins.formatting.isort }
- `command = "isort"`
- `args = { "--stdout", "--profile", "black", "-" }`

#### [reorder_python_imports](https://github.com/asottile/reorder_python_imports)

##### About

`python` utility tool for automatically reordering python imports. Similar to isort but uses static analysis more.

##### Usage

```lua
local sources = { null_ls.builtins.formatting.reorder_python_imports }
```

##### Defaults

- `filetypes = { "python" }`
- `command = "reorder-python-imports"`
- `args = { "-", "--exit-zero-even-if-changed" }`

#### [json.tool](https://docs.python.org/3/library/json.html#module-json.tool)

##### About
Expand Down Expand Up @@ -1018,7 +1039,7 @@ local sources = { null_ls.builtins.formatting.shfmt }

##### Defaults

- `filetypes = { "sh" }`
- `filetypes = { "sh", "zsh" }`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if shfmt supports zsh actually.
mvdan/sh#120

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's strange – I've been using shfmt for zsh since we added it about 2 weeks ago and haven't run into any problems. I don't know much about the project, but maybe the issue you linked is about a different feature?

Copy link
Contributor

@gegoune gegoune Nov 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, not so sure, I just tried it myself for the first time and it reformatted:

if (( $+commands[ls] )); then

to

if (($ + commands[ls])); then

which is not valid and errors with bad math expression. There is no mention of official support for zsh in shfmt's repository, so I would recommend not setting it as default option here.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it – I'll remove it from the defaults.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, amazing refactor btw, excellent work on null-ls, using it every day!

- `command = "shfmt"`
- `args = {}`

Expand Down Expand Up @@ -1831,9 +1852,6 @@ preview / reset hunks, blame, etc.).

##### Usage

- Requires installing gitsigns.nvim.
- Works in files under Git version control.

```lua
local sources = { null_ls.builtins.code_actions.gitsigns }
```
Expand Down Expand Up @@ -1921,3 +1939,38 @@ local sources = { null_ls.builtins.hover.dictionary }
##### Defaults

- `filetypes = { "txt", "markdown" }`

### Completion

#### Spell

##### About

Spell suggestions completion source.

###### Usage

```lua
local sources = { null_ls.builtins.completion.spell }
```

If you want to disable spell suggestions when `spell` options is not set, you can use the
following snippet:

```lua
runtime_condition = function(_)
return vim.opt_local.spell:get()
end
```

#### Tags

##### About

Tags source for completion. Only works if you have `tags` options set.

###### Usage

```lua
local sources = { null_ls.builtins.completion.tags }
```
18 changes: 18 additions & 0 deletions doc/MAIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,21 @@ null-ls will combine the results of each of its hover sources when calling the
handler, so 2+ _null-ls_ hover sources are okay, but note that running more than
one LSP server with hover capabilities **is not well-supported** (by default,
the second popup will wipe out the first).


#### Completion

```lua
return {
{
items = { label = "Item #1", insertText = "Item #1", documentation = "A test completion item" },
isIncomplete = true,
},
}
```

Completion sources must return a list of
[CompletionList](https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#completionList).
You can leverage the full attributes of `CompletionItem` from LSP specification. They can be used
by other plugins (e.g completion plugins) to provide additional context about the highlighted
completion item.
26 changes: 26 additions & 0 deletions lua/null-ls/builtins/completion/spell.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
local h = require("null-ls.helpers")
local methods = require("null-ls.methods")

local COMPLETION = methods.internal.COMPLETION

return h.make_builtin({
method = COMPLETION,
filetypes = {},
name = "spell",
generator = {
fn = function(params, done)
local get_candidates = function(entries)
local items = {}
for k, v in ipairs(entries) do
items[k] = { label = v, kind = vim.lsp.protocol.CompletionItemKind["Text"] }
end

return items
end

local candidates = get_candidates(vim.fn.spellsuggest(params.word_to_complete))
done({ { items = candidates, isIncomplete = #candidates } })
end,
async = true,
},
})
48 changes: 48 additions & 0 deletions lua/null-ls/builtins/completion/tags.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
local h = require("null-ls.helpers")
local methods = require("null-ls.methods")
local utils = require("null-ls.utils")

local COMPLETION = methods.internal.COMPLETION

return h.make_builtin({
method = COMPLETION,
filetypes = {},
name = "tags",
generator_opts = {
runtime_condition = function(_)
return #vim.fn.tagfiles() > 0
end,
},
generator = {
fn = function(params, done)
-- Tags look up can be expensive.
if #params.word_to_complete < 4 then
done({ { items = {}, isIncomplete = false } })
return
end

local tags = vim.fn.taglist(params.word_to_complete)
if tags == 0 then
done({ { items = {}, isIncomplete = false } })
return
end

local words = {}
local items = {}
for _, tag in ipairs(tags) do
table.insert(words, tag.name)
end

words = utils.table.uniq(words)
for _, word in ipairs(words) do
table.insert(items, {
label = word,
insertText = word,
})
end

done({ { items = items, isIncomplete = #items == 0 } })
end,
async = true,
},
})
47 changes: 47 additions & 0 deletions lua/null-ls/completion.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
local u = require("null-ls.utils")
local methods = require("null-ls.methods")
local config = require("null-ls.config").get()

local M = {}

M.handler = function(method, original_params, handler)
local params = u.make_params(original_params, methods.map[method])
if method == methods.lsp.COMPLETION then
require("null-ls.generators").run_registered({
filetype = params.ft,
method = methods.map[method],
params = params,
callback = function(results)
u.debug_log("received completion results from generators")
u.debug_log(results)
if #results == 0 then
handler({})
else
local items = {}
local isIncomplete = false
for _, result in ipairs(results) do
isIncomplete = isIncomplete or result.isIncomplete

if config.debug then
vim.validate({
items = { result.items, "table" },
isIncomplete = { result.isIncomplete, "boolean" },
})

vim.validate({
item = { result.items[1], "table" },
})
end

vim.list_extend(items, result.items)
end

handler({ isIncomplete = isIncomplete, items = items })
end
end,
})
original_params._null_ls_handled = true
end
end

return M
1 change: 1 addition & 0 deletions lua/null-ls/generators.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
local c = require("null-ls.config")
local u = require("null-ls.utils")
local methods = require("null-ls.methods")

local M = {}

Expand Down
Loading