kconfig-lsp 0.1.0

A language server for Kconfig files used in Linux, Zephyr, U-Boot, and coreboot
Documentation
use std::path::Path;

use tower_lsp::lsp_types::*;

use crate::analysis::WorldIndex;

pub fn hover(index: &WorldIndex, path: &Path, pos: Position) -> Option<Hover> {
    let fa = index.files.get(path)?;
    let offset = fa.line_index.offset(pos.line, pos.character);
    let word = word_at_offset(&fa.source, offset)?;

    if let Some(doc) = keyword_docs(&word) {
        return Some(Hover {
            contents: HoverContents::Markup(MarkupContent {
                kind: MarkupKind::Markdown,
                value: doc.to_string(),
            }),
            range: None,
        });
    }

    let defs = index.get_definitions(&word);
    if !defs.is_empty() {
        let mut parts: Vec<String> = Vec::new();
        for d in defs {
            let mut section = format!("**{}** ({})", d.name, def_kind_label(d.kind));
            if let Some(tk) = d.type_kind {
                section.push_str(&format!(" `{}`", tk.as_str()));
            }
            if let Some(prompt) = &d.prompt {
                section.push_str(&format!("\n\n*\"{}\"*", prompt));
            }
            section.push_str(&format!("\n\nDefined in `{}`", d.file.display()));
            if let Some(help) = &d.help {
                section.push_str(&format!("\n\n---\n\n{}", help));
            }
            parts.push(section);
        }
        return Some(Hover {
            contents: HoverContents::Markup(MarkupContent {
                kind: MarkupKind::Markdown,
                value: parts.join("\n\n---\n\n"),
            }),
            range: None,
        });
    }

    None
}

fn word_at_offset(source: &str, offset: usize) -> Option<String> {
    let bytes = source.as_bytes();
    if offset >= bytes.len() {
        return None;
    }

    let mut start = offset;
    while start > 0 && is_word_char(bytes[start - 1]) {
        start -= 1;
    }
    let mut end = offset;
    while end < bytes.len() && is_word_char(bytes[end]) {
        end += 1;
    }
    if start == end {
        return None;
    }
    Some(source[start..end].to_string())
}

fn is_word_char(b: u8) -> bool {
    b.is_ascii_alphanumeric() || b == b'_'
}

fn def_kind_label(kind: crate::analysis::DefKind) -> &'static str {
    match kind {
        crate::analysis::DefKind::Config => "config",
        crate::analysis::DefKind::MenuConfig => "menuconfig",
        crate::analysis::DefKind::Choice => "choice",
    }
}

fn keyword_docs(word: &str) -> Option<&'static str> {
    Some(match word {
        "config" => {
            "\
**config** `<symbol>`

Starts a new config entry. The following lines define attributes for \
this config option. Attributes can be the type of the config option, \
input prompt, dependencies, help text and default values. A config \
option can be defined multiple times with the same name, but every \
definition can have only a single input prompt and the type must not \
conflict."
        }

        "menuconfig" => {
            "\
**menuconfig** `<symbol>`

Similar to `config`, but also gives a hint to front ends that all \
sub-options should be displayed as a separate list of options. To make \
sure all the sub-options will really show up under the menuconfig entry \
and not outside of it, every item from the options list must depend on \
the menuconfig symbol.

```kconfig
menuconfig M
if M
    config C1
    config C2
endif
```"
        }

        "choice" | "endchoice" => {
            "\
**choice** / **endchoice**

Defines a choice group. A choice allows only a single config entry \
to be selected. Accepts `prompt`, `default`, `depends on`, and `help` \
attributes.

```kconfig
choice
    prompt \"Choose one\"
    default OPT_A

config OPT_A
    bool \"Option A\"

config OPT_B
    bool \"Option B\"

endchoice
```"
        }

        "comment" => {
            "\
**comment** `<prompt>`

Defines a comment which is displayed to the user during the \
configuration process and is also echoed to the output files. \
The only possible options are dependencies."
        }

        "menu" | "endmenu" => {
            "\
**menu** `<prompt>` / **endmenu**

Defines a menu block. All entries within the `menu` ... `endmenu` \
block become a submenu. All sub-entries inherit the dependencies \
from the menu entry. The only possible options are dependencies \
and `visible` attributes."
        }

        "if" | "endif" => {
            "\
**if** `<expr>` / **endif**

Defines an if block. The dependency expression is appended to all \
enclosed menu entries."
        }

        "source" => {
            "\
**source** `<path>`

Reads the specified configuration file. This file is always parsed."
        }

        "mainmenu" => {
            "\
**mainmenu** `<prompt>`

Sets the config program's title bar. It should be placed at the top \
of the configuration, before any other statement."
        }

        "bool" => {
            "\
**bool** [`<prompt>`]

Boolean type. The config option can be `y` (built-in) or `n` (disabled)."
        }

        "tristate" => {
            "\
**tristate** [`<prompt>`]

Tristate type. The config option can be `y` (built-in), `m` (module), \
or `n` (disabled)."
        }

        "string" => {
            "\
**string** [`<prompt>`]

String type. The config option holds a free-form string value."
        }

        "hex" => {
            "\
**hex** [`<prompt>`]

Hexadecimal type. The config option holds a hex value (e.g. `0x1234`)."
        }

        "int" => {
            "\
**int** [`<prompt>`]

Integer type. The config option holds a decimal integer value."
        }

        "prompt" => {
            "\
**prompt** `<prompt>` [`if` `<expr>`]

Sets the input prompt displayed to the user. Every menu entry can have \
at most one prompt. Optionally, a dependency for this prompt can be \
added with `if`."
        }

        "default" => {
            "\
**default** `<expr>` [`if` `<expr>`]

Sets a default value. If multiple default values are visible, only the \
first defined one is active. Default values are not limited to the menu \
entry where they are defined.

The default value deliberately defaults to `n` in order to avoid \
bloating the build. With few exceptions, new config options should not \
change this."
        }

        "def_bool" => {
            "\
**def_bool** `<expr>` [`if` `<expr>`]

Shorthand for a `bool` type definition plus a default value."
        }

        "def_tristate" => {
            "\
**def_tristate** `<expr>` [`if` `<expr>`]

Shorthand for a `tristate` type definition plus a default value."
        }

        "depends" => {
            "\
**depends on** `<expr>`

Defines a dependency for this menu entry. If multiple dependencies \
are defined, they are connected with `&&`. Dependencies are applied \
to all other options within this menu entry."
        }

        "select" => {
            "\
**select** `<symbol>` [`if` `<expr>`]

Reverse dependency. Forces a lower limit on another symbol. The value \
of the current menu symbol is used as the minimal value the selected \
symbol can be set to.

**Note:** `select` should be used with care. It will force a symbol \
to a value without visiting the dependencies. In general use `select` \
only for non-visible symbols (no prompts) and for symbols with no \
dependencies."
        }

        "imply" => {
            "\
**imply** `<symbol>` [`if` `<expr>`]

Weak reverse dependency. Similar to `select` but the implied symbol's \
value may still be set to `n` from a direct dependency or with a \
visible prompt."
        }

        "visible" => {
            "\
**visible if** `<expr>`

Only applicable to menu blocks. If the condition is false, the menu \
block is not displayed to the user (the symbols contained there can \
still be selected by other symbols, though). Default value is `true`."
        }

        "range" => {
            "\
**range** `<symbol>` `<symbol>` [`if` `<expr>`]

Limits the range of possible input values for `int` and `hex` symbols. \
The user can only input a value which is `>=` the first symbol and \
`<=` the second symbol."
        }

        "help" => {
            "\
**help**

Defines a help text. The end of the help text is determined by the \
indentation level — it ends at the first line which has a smaller \
indentation than the first line of the help text.

Per kernel coding style: help text is indented with one tab plus two \
additional spaces."
        }

        "modules" => {
            "\
**modules**

Declares the symbol to be used as the `MODULES` symbol, which enables \
the third modular state for all config symbols. At most one symbol may \
have the `modules` option set."
        }

        "transitional" => {
            "\
**transitional**

Declares the symbol as transitional, meaning it should be processed \
during configuration but omitted from newly written `.config` files. \
Useful for backward compatibility during config option migrations.

A transitional symbol has no prompt, is not written to new `.config` \
files, and cannot have any other properties."
        }

        "optional" => {
            "\
**optional**

Marks a choice as optional — the user may leave all options unselected."
        }

        "on" => {
            "\
Part of the **depends on** syntax. See `depends`."
        }

        _ => return None,
    })
}