~rrc/pbls

c182ed0ea2dcc97c7b0491f82d445fefc7b754cd — Ryan Roden-Corrent a month ago 3e2d097 1.0.3
Respect query for workspace symbols.
5 files changed, 89 insertions(+), 38 deletions(-)

M Cargo.lock
M Cargo.toml
M src/lib.rs
M src/workspace.rs
M tests/integration_test.rs
M Cargo.lock => Cargo.lock +1 -0
@@ 193,6 193,7 @@ dependencies = [
 "lsp-server",
 "lsp-types",
 "pretty_assertions",
 "regex",
 "serde",
 "serde_json",
 "tempfile",

M Cargo.toml => Cargo.toml +2 -1
@@ 1,6 1,6 @@
[package]
name = "pbls"
version = "1.0.2"
version = "1.0.3"
edition = "2021"

[dependencies]


@@ 14,6 14,7 @@ tree-sitter = "~0.20.0"
log = "0.4.20"
env_logger = { version = "0.10.1", default-features = false }
walkdir = "2.4.0"
regex = "1"

[dev-dependencies]
pretty_assertions = "1.4.0"

M src/lib.rs => src/lib.rs +2 -2
@@ 98,10 98,10 @@ fn handle_document_symbols(

fn handle_workspace_symbols(
    workspace: &mut workspace::Workspace,
    _: WorkspaceSymbolParams,
    params: WorkspaceSymbolParams,
) -> Result<Option<lsp_types::WorkspaceSymbolResponse>> {
    Ok(Some(lsp_types::WorkspaceSymbolResponse::Flat(
        workspace.all_symbols()?,
        workspace.all_symbols(&params.query)?,
    )))
}


M src/workspace.rs => src/workspace.rs +23 -3
@@ 1,9 1,10 @@
use std::collections::hash_map;

use crate::file;
use crate::file::{self};

use super::protoc;
use lsp_types::{SymbolInformation, Url};
use regex::RegexBuilder;
use tree_sitter::QueryCursor;

pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;


@@ 124,7 125,7 @@ impl Workspace {
            .collect())
    }

    pub fn all_symbols(&mut self) -> Result<Vec<SymbolInformation>> {
    pub fn all_symbols(&mut self, query: &str) -> Result<Vec<SymbolInformation>> {
        let paths = self
            .proto_paths
            .iter()


@@ 136,6 137,23 @@ impl Workspace {
            .map(|p| std::fs::canonicalize(p));
        let mut res = vec![];
        let mut qc = tree_sitter::QueryCursor::new();

        let regexes: std::result::Result<Vec<_>, _> = query
            .split_whitespace()
            .map(|s| {
                RegexBuilder::new(
                    &s.chars()
                        .map(|c| c.to_string())
                        .collect::<Vec<_>>()
                        .join(".*"),
                )
                .case_insensitive(query.chars().all(|c| !c.is_uppercase()))
                .build()
            })
            .collect();
        let regexes = regexes?;
        log::debug!("Searching workspace symbols with patterns: {regexes:?}");

        for path in paths {
            let path = path?;
            let uri = Url::from_file_path(&path).or(Err(format!("Invalid path: {path:?}")))?;


@@ 148,7 166,9 @@ impl Workspace {
                self.files.get(&uri).unwrap()
            };
            let symbols = file.symbols(&mut qc);
            let syms = symbols.map(|s| to_lsp_symbol(uri.clone(), s));
            let syms = symbols
                .filter(|s| regexes.iter().all(|r| r.is_match(&s.name)))
                .map(|s| to_lsp_symbol(uri.clone(), s));
            res.extend(syms);
        }
        Ok(res)

M tests/integration_test.rs => tests/integration_test.rs +61 -32
@@ 32,10 32,6 @@ fn dep_uri() -> Url {
    Url::from_file_path(std::fs::canonicalize("./testdata/dep.proto").unwrap()).unwrap()
}

fn stuff_uri() -> Url {
    Url::from_file_path(std::fs::canonicalize("./testdata/folder/stuff.proto").unwrap()).unwrap()
}

fn error_uri() -> Url {
    Url::from_file_path(std::fs::canonicalize("./testdata/error.proto").unwrap()).unwrap()
}


@@ 486,35 482,68 @@ fn test_workspace_symbols() -> pbls::Result<()> {
    let mut client = TestClient::new()?;
    client.open(base_uri())?;

    let Some(WorkspaceSymbolResponse::Flat(actual)) =
        client.request::<WorkspaceSymbolRequest>(WorkspaceSymbolParams {
            query: "".into(),
            work_done_progress_params: lsp_types::WorkDoneProgressParams {
                work_done_token: None,
            },
            partial_result_params: lsp_types::PartialResultParams {
                partial_result_token: None,
            },
        })?
    else {
        panic!("Symbols response is not Flat")
    let mut test = |query: &str, expected| {
        let Some(WorkspaceSymbolResponse::Flat(actual)) = client
            .request::<WorkspaceSymbolRequest>(WorkspaceSymbolParams {
                query: query.into(),
                work_done_progress_params: lsp_types::WorkDoneProgressParams {
                    work_done_token: None,
                },
                partial_result_params: lsp_types::PartialResultParams {
                    partial_result_token: None,
                },
            })
            .unwrap()
        else {
            panic!("Symbols response is not Flat")
        };
        assert_elements_equal(actual, expected, |s| s.name.clone());
    };
    let expected = vec![
        sym(other_uri(), "Other", "message Other"),
        sym(dep_uri(), "Dep", "message Dep"),
        sym(dep_uri(), "Dep2", "enum Dep2"),
        sym(base_uri(), "Thing", "enum Thing"),
        sym(base_uri(), "Foo", "message Foo"),
        sym(base_uri(), "Foo.Buz", "message Buz"),
        sym(base_uri(), "Bar", "message Bar"),
        sym(base_uri(), "Empty", "message Empty"),
        sym(other_uri(), "Other.Nested", "message Nested"),
        sym(error_uri(), "Nope", "enum Nope"),
        sym(error_uri(), "Nah", "message Nah"),
        sym(error_uri(), "Noo", "message Noo"),
        // sym(stuff_uri(), "Stuff", "message Stuff"), BUG: should find nested symbols
    ];
    assert_elements_equal(actual, expected, |s| s.name.clone());

    // No filter, all symbols
    test(
        "",
        vec![
            sym(other_uri(), "Other", "message Other"),
            sym(dep_uri(), "Dep", "message Dep"),
            sym(dep_uri(), "Dep2", "enum Dep2"),
            sym(base_uri(), "Thing", "enum Thing"),
            sym(base_uri(), "Foo", "message Foo"),
            sym(base_uri(), "Foo.Buz", "message Buz"),
            sym(base_uri(), "Bar", "message Bar"),
            sym(base_uri(), "Empty", "message Empty"),
            sym(other_uri(), "Other.Nested", "message Nested"),
            sym(error_uri(), "Nope", "enum Nope"),
            sym(error_uri(), "Nah", "message Nah"),
            sym(error_uri(), "Noo", "message Noo"),
            // sym(stuff_uri(), "Stuff", "message Stuff"), BUG: should find nested symbols
        ],
    );

    test(
        "oo",
        vec![
            sym(base_uri(), "Foo", "message Foo"),
            sym(base_uri(), "Foo.Buz", "message Buz"),
            sym(error_uri(), "Noo", "message Noo"),
        ],
    );

    test(
        "fo",
        vec![
            sym(base_uri(), "Foo", "message Foo"),
            sym(base_uri(), "Foo.Buz", "message Buz"),
        ],
    );

    test("np", vec![sym(error_uri(), "Nope", "enum Nope")]);

    test(
        "nst oth",
        vec![sym(other_uri(), "Other.Nested", "message Nested")],
    );

    Ok(())
}