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 goto_definition(
    index: &WorldIndex,
    path: &Path,
    pos: Position,
) -> Option<GotoDefinitionResponse> {
    let fa = index.files.get(path)?;
    let offset = fa.line_index.offset(pos.line, pos.character);
    let word = word_at_offset(&fa.source, offset)?;

    let defs = index.get_definitions(&word);
    if defs.is_empty() {
        return None;
    }

    let locations: Vec<Location> = defs
        .iter()
        .filter_map(|d| {
            let target_fa = index.files.get(&d.file)?;
            let (line, col) = target_fa.line_index.line_col(d.name_span.start);
            let (end_line, end_col) = target_fa.line_index.line_col(d.name_span.end);
            let uri = Url::from_file_path(&d.file).ok()?;
            Some(Location {
                uri,
                range: Range {
                    start: Position::new(line, col),
                    end: Position::new(end_line, end_col),
                },
            })
        })
        .collect();

    if locations.is_empty() {
        None
    } else if locations.len() == 1 {
        Some(GotoDefinitionResponse::Scalar(
            locations.into_iter().next().unwrap(),
        ))
    } else {
        Some(GotoDefinitionResponse::Array(locations))
    }
}

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'_'
}