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 find_references(index: &WorldIndex, path: &Path, pos: Position) -> Option<Vec<Location>> {
    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 mut locations: Vec<Location> = Vec::new();

    for d in index.get_definitions(&word) {
        if let Some(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);
            if let Ok(uri) = Url::from_file_path(&d.file) {
                locations.push(Location {
                    uri,
                    range: Range {
                        start: Position::new(line, col),
                        end: Position::new(end_line, end_col),
                    },
                });
            }
        }
    }

    for r in index.get_references(&word) {
        if let Some(target_fa) = index.files.get(&r.file) {
            let (line, col) = target_fa.line_index.line_col(r.span.start);
            let (end_line, end_col) = target_fa.line_index.line_col(r.span.end);
            if let Ok(uri) = Url::from_file_path(&r.file) {
                locations.push(Location {
                    uri,
                    range: Range {
                        start: Position::new(line, col),
                        end: Position::new(end_line, end_col),
                    },
                });
            }
        }
    }

    if locations.is_empty() {
        None
    } else {
        Some(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'_'
}