merman-lsp 0.8.0-alpha.3

Language server for Merman diagnostics and completion foundations.
Documentation
use crate::completion::{completion_for_snapshot, resolve_completion_item};
use crate::document_store::DocumentStore;
use tower_lsp::lsp_types::{
    CompletionTextEdit, Documentation, InsertTextFormat, MarkupKind, Position, Url,
};

#[test]
fn completion_items_carry_resolve_data() {
    let mut store = DocumentStore::new();
    let uri = Url::parse("file:///tmp/example.mmd").unwrap();
    let snapshot = store.upsert(uri, 1, "flow".to_string());
    let list = completion_for_snapshot(&snapshot, Position::new(0, 4));

    let item = list
        .items
        .iter()
        .find(|item| item.label == "flowchart TD")
        .unwrap();
    let data = item.data.as_ref().expect("completion resolve data");

    assert_eq!(data["kind"], "diagram_header");
    assert_eq!(data["label"], "flowchart TD");
}

#[test]
fn completion_resolve_adds_documentation_without_changing_insert_fields() {
    let mut store = DocumentStore::new();
    let uri = Url::parse("file:///tmp/example.mmd").unwrap();
    let snapshot = store.upsert(uri, 1, "flow".to_string());
    let list = completion_for_snapshot(&snapshot, Position::new(0, 4));

    let item = list
        .items
        .into_iter()
        .find(|item| item.label == "flowchart TD")
        .unwrap();
    let original_text_edit = item.text_edit.clone();
    let original_insert_text = item.insert_text.clone();

    let resolved = resolve_completion_item(item);

    assert_eq!(resolved.text_edit, original_text_edit);
    assert_eq!(resolved.insert_text, original_insert_text);
    match resolved.documentation.as_ref().unwrap() {
        Documentation::MarkupContent(markup) => {
            assert_eq!(markup.kind, MarkupKind::Markdown);
            assert!(markup.value.contains("Starts a Mermaid"));
            assert!(markup.value.contains("flowchart TD"));
        }
        other => panic!("unexpected completion documentation: {other:?}"),
    }
}

#[test]
fn completion_projects_header_edit_ranges() {
    let mut store = DocumentStore::new();
    let uri = Url::parse("file:///tmp/example.mmd").unwrap();
    let snapshot = store.upsert(uri, 1, "flow".to_string());
    let list = completion_for_snapshot(&snapshot, Position::new(0, 4));

    let item = list
        .items
        .iter()
        .find(|item| item.label == "flowchart TD")
        .unwrap();
    match item.text_edit.as_ref().unwrap() {
        CompletionTextEdit::Edit(edit) => {
            assert_eq!(edit.new_text, "flowchart TD");
            assert_eq!(edit.range.start.character, 0);
            assert_eq!(edit.range.end.character, 4);
        }
        other => panic!("unexpected text edit: {other:?}"),
    }
}

#[test]
fn completion_projects_core_snippet_items_to_lsp_snippets() {
    let mut store = DocumentStore::new();
    let uri = Url::parse("file:///tmp/example.mmd").unwrap();
    let snapshot = store.upsert(uri, 1, "flow".to_string());
    let list = completion_for_snapshot(&snapshot, Position::new(0, 4));

    let item = list
        .items
        .iter()
        .find(|item| item.label == "flowchart template")
        .expect("flowchart template completion");

    assert_eq!(item.insert_text_format, Some(InsertTextFormat::SNIPPET));
    assert_eq!(
        item.kind,
        Some(tower_lsp::lsp_types::CompletionItemKind::SNIPPET)
    );
    match item.text_edit.as_ref().unwrap() {
        CompletionTextEdit::Edit(edit) => {
            assert!(edit.new_text.contains("${1|TD,TB,BT,LR,RL|}"));
            assert_eq!(edit.range.start.character, 0);
            assert_eq!(edit.range.end.character, 4);
        }
        other => panic!("unexpected text edit: {other:?}"),
    }
}

#[test]
fn completion_projects_class_name_items_to_lsp_classes() {
    let mut store = DocumentStore::new();
    let uri = Url::parse("file:///tmp/example.mmd").unwrap();
    let snapshot = store.upsert(
        uri,
        1,
        "flowchart TD\nA-->B\nclassDef hot fill:#f00\nclass A h\n".to_string(),
    );
    let list = completion_for_snapshot(&snapshot, Position::new(3, 9));

    let item = list
        .items
        .iter()
        .find(|item| item.label == "hot")
        .expect("class name completion");

    assert_eq!(
        item.kind,
        Some(tower_lsp::lsp_types::CompletionItemKind::CLASS)
    );
    match item.text_edit.as_ref().unwrap() {
        CompletionTextEdit::Edit(edit) => {
            assert_eq!(edit.new_text, "hot");
            assert_eq!(edit.range.start.character, 8);
            assert_eq!(edit.range.end.character, 9);
        }
        other => panic!("unexpected text edit: {other:?}"),
    }
}