Skip to content

Latest commit

 

History

History
414 lines (306 loc) · 12.8 KB

MAIN.md

File metadata and controls

414 lines (306 loc) · 12.8 KB

null-ls Documentation

Splits into the following documents:

  • MAIN (this document), which describes general concepts and implementation details

  • CONFIG, which describes the available configuration options

  • BUILTINS, an automatically generated list of available built-in sources

  • BUILTIN_CONFIG.md, which describes how to configure built-in sources

  • SOURCES, which describes the source API

  • HELPERS, which describes available helpers and how to use them to make new sources

  • TESTING, which describes best practices for testing null-ls integrations

Sources

The basic unit of null-ls operation is a source.

Each source must have a method, which defines when it runs, a generator, which runs in response to a request matching the source's method, and a list of filetypes, which determines when the method is active.

To see a list of active sources for the current buffer in a human-readable format, use the command :NullLsInfo.

Sources must be registered, either by the user or by an integration, before they are active. You can get, set, and manipulate sources using the source API, as described in SOURCES.

Sources can also define a name, which allows integrations to check whether null-ls has already registered them to prevent duplicate registration.

Sources may define a condition, which determines whether null-ls should register the source.

Methods

null-ls methods are analogous to LSP methods, but the plugin uses internal methods to avoid collisions. The safest way to use methods in a source definition is by referencing the methods object.

The list below is non-exhaustive. See the matching source file for a full list.

local null_ls = require("null-ls")

local my_source = {}
-- source will run on LSP code action request
my_source.method = null_ls.methods.CODE_ACTION

-- source will run on LSP diagnostics request
my_source.method = null_ls.methods.DIAGNOSTICS

-- source will run on LSP formatting request
my_source.method = null_ls.methods.FORMATTING

-- source will run on LSP hover request
my_source.method = null_ls.methods.HOVER

-- source will run on LSP completion request
my_source.method = null_ls.methods.COMPLETION

Params

Source callbacks have access to a params table as their first argument, which is broadly analogous to the language server protocol's params object. The table contains information about the current buffer, editor state, and other method-specific information.

local params = {
    client_id, -- null-ls client id (number)
    lsp_method, -- LSP method that triggered request / notification (string)
    lsp_params, -- original LSP params from request / notification (table)
    options, -- options from LSP params (e.g. formattingOptions) (table|nil)
    content, -- current buffer content (string[])
    bufnr, -- current buffer's number (number)
    method, -- null-ls method (string)
    row, -- cursor's current row (number, zero-indexed)
    col, -- cursor's current column (number)
    bufname, -- full path to current buffer (string)
    filetype, -- current buffer's filetype (string)
    root, -- current buffer's root directory (string)

    -- method == null_ls.methods.RANGE_FORMATTING
    range, -- converted LSP range (table)

    -- method == null_ls.methods.COMPLETION
    word_to_complete, -- keyword under cursor (string)
}

Params Methods

The params table has access to methods, which source callbacks can use to access information about the parent source:

params:get_source()

Returns the parent source of the params in question.

params:get_config()

Returns a table containing user-specific configuration for the parent source. If the user hasn't explicitly set this key, this method returns an empty table.

Filetypes

A source has a list of filetypes, which define when the source is active. The list can contain a single filetype, more than one filetype, or nothing, which indicates that the source should activate for all filetypes.

local my_source = {}

-- single
my_source.filetypes = { "lua" }

-- more than one
my_source.filetypes = { "lua", "teal" }

-- all filetypes
my_source.filetypes = {}

Sources can also include a list of disabled_filetypes. null-ls checks these first, so they'll supersede any defined filetypes.

Registration

null-ls can register sources via the setup method (intended for a user's configuration) or via the register method (intended for integrations or advanced use cases).

local null_ls = require("null-ls")

-- using setup method
null_ls.setup({ sources = my_sources })

-- using register method
null_ls.register({ my_sources })

Both options accept a single source, a list of sources, or a table containing more than one source with shared configuration.

local null_ls = require("null-ls")

-- single source
null_ls.register({ my_source })

-- list of sources with independent configuration
null_ls.register({ my_source, my_other_source })

-- more than one source with shared configuration
null_ls.register({ name = "my-sources", filetypes = { "lua" }, sources = { my_source, my_other_source } })

Note that dynamically registering a diagnostic source will refresh diagnostics for buffers affected by the new source.

Names

null-ls optionally saves source names to prevent duplicate registration. This means integrations can call register() any number of times without worrying about duplicate registration.

local null_ls = require("null-ls")

-- registered
null_ls.register({ name = "my-sources", ... })

-- not registered
null_ls.register({ name = "my-sources", ... })

null-ls also exposes a method, is_registered(), that returns a boolean value indicating whether it has already registered a source.

local null_ls = require("null-ls")

local name = "my_sources"
print(null_ls.is_registered(name)) -- false

null_ls.register({ name = "my-sources", ... })
print(null_ls.is_registered(name)) -- true

Conditions

A source may define a condition, which is an optional callback that accepts one argument. The argument (described here as utils) is a table of utilities to handle common conditional checks.

  • utils.root_has_file: accepts either a table (indicating more than one file) or a string (indicating a single file). Returns true if at least one file exists at the project's root.

  • utils.root_matches: accepts a Lua string matcher pattern. Returns true if the root matches the specified pattern.

  • utils.root_has_file_matches: accepts a Lua string matcher pattern. Returns true if a file matches the specified pattern.

On registration, null-ls will store conditional sources in state and check condition at the first opportunity (typically upon entering a named buffer). If condition returns a truthy value at that point, null-ls will register the source.

Once checked, null-ls will not check the same condition again.

Generators

null-ls generators define what a source provides when it receives a request that matches its method. Generators must define the key-value pair fn, which is the callback that runs when null-ls calls the source.

A generator's fn is schedule-wrapped, making it safe to call any API function. It's also wrapped to handle errors, meaning it'll catch errors thrown from within fn and show them to the user as warnings.

local my_source = {}

my_source.generator = {
    fn = function(params)
        return {
            {
                col = 1,
                row = 1,
                message = "There is something wrong with this file!",
                severity = 1,
                source = "my-source",
            },
        }
    end,
}

Asynchronous Generators

Setting async = true inside a generator will run it as an asynchronous generator. Asynchronous generators have access to a done() callback as their second argument. Processing will pause until all async generators have called done(), either with nil or a list of results.

local my_source = {}

my_source.generator = {
    -- must be explicitly set
    async = true,
    fn = function(params, done)
        -- always return done() to prevent timeouts
        if not string.match(params.content, "something wrong") then
            return done()
        end

        -- return results as normal inside the done() callback
        my_async_function(params, function()
            return done({
                {
                    col = 1,
                    row = 1,
                    message = "There is something wrong with this file!",
                    severity = 1,
                    source = "my-source",
                },
            })
        end)
    end,
}

Multi-file Generators

If generator.multiple_files is true, the generator can return results for more than one file at a time. At the moment, null-ls supports multi-file generators for diagnostics (see below).

Generator Return Types

Generators must return nil or a list containing their results. The structure of each item in the list varies depending on the null-ls method that invoked the generator.

All return values are required unless specified as optional.

Code Actions

return {
    {
        title, -- string
        action, -- function (callback with no arguments)
    },
}

Once generated, null-ls stores code action results in its internal state and calls them if selected by the user. It clears and re-generates non-selected actions on the next request.

Like generator functions, code action callbacks are schedule-wrapped, making it safe to call any API function.

Diagnostics

-- null-ls assumes ranges are 1-indexed, so sources should offset if not
return {
    {
        row, -- number, optional (defaults to first line)
        col, -- number, optional (defaults to beginning of line)
        end_row, -- number, optional (defaults to row)
        end_col, -- number, optional (defaults to end of line),
        source, -- string, optional (defaults to "null-ls")
        code, -- number, optional
        message, -- string
        severity, -- 1 (error), 2 (warning), 3 (information), 4 (hint)
        filename, -- string, optional (requires generator.multiple_files)
        bufnr, -- number, optional (requires generator.multiple_files)
    },
}

null-ls generates diagnostics in response to LSP notifications and publishes them via the vim.diagnostic API.

When generator.multiple_files is true, specifying filename or bufnr publishes diagnostics to the specified buffer. Otherwise, null-ls publishes diagnostics to the buffer that triggered the LSP diagnostic (in most cases the active buffer).

Note that filename (if specified) should be an absolute path to the file.

severity should be one of the values described in :help diagnostic-severity. If unspecified, severity falls back to the config option fallback_severity, described in CONFIG.

Formatting

return {
    {
        row, -- number, optional (see diagnostics for defaults)
        col, -- number, optional
        end_row, -- number, optional
        end_col, -- number, optional
        text, -- string
    },
}

null-ls applies formatting results to the matching buffer and, depending on the user's settings, will optionally write the buffer.

Users can register an arbitrary number of formatters for a single filetype. To match expected behavior, formatters run sequentially in the order in which the user has registered them (manually or via an integration).

Range Formatting

Same as formatting.

Note that getting range-formatted output requires converting the visually selected range into the required format and modifying the spawn arguments accordingly. See range_formatting_args_factory in HELPERS for an example of how null-ls handles this for built-in sources.

Hover

return { "First line", "Second line", "And so on" }

Hover sources should return a list of plaintext strings, where each element represents a single line.

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

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

Completion sources must return a CompletionList. You can leverage the full attributes of CompletionItem from the LSP specification. Other plugins (e.g completion plugins) can then use these attributes to provide more context about the highlighted completion item.