Skip to main content

bynk_lsp/
documentation_request.rs

1//! #847: `bynk/documentationModel` — the documentation-view custom LSP request.
2//!
3//! The second custom request in this server (after #846's `bynk/sequenceModel`),
4//! and the same posture: no `workspace/*/refresh` nudge exists for a custom
5//! method and none is needed — Tier 1 is on-demand, the client re-issues the
6//! request each time "Bynk: Show Documentation" fires (Decision D).
7//!
8//! Unlike `bynk/sequenceModel`, the request carries **no cursor position**: a
9//! documentation page is the *whole file's* declarations (Decision A,
10//! file-scoped), so the params are a bare `TextDocumentIdentifier`. The wire
11//! shape is a plain serde mirror of [`bynk_ide::documentation::DocModel`], each
12//! `Span` lowered to an LSP `Range` against the committed snapshot text the
13//! caller already holds — the same convention `sequence_request`/`SerKey` use.
14
15use bynk_ide::documentation::{self, DocModel};
16
17/// Build the documentation model for `text` (a committed snapshot). `None` for
18/// a unit with no doc page — a `suite`, or a file with no recognisable header.
19pub fn documentation_model_at(text: &str) -> Option<DocModel> {
20    documentation::documentation_model(text)
21}
22
23/// The `bynk/documentationModel` request payload. A bare text-document
24/// identifier — no cursor position (the page is the whole file, Decision A).
25///
26/// `rename_all = "camelCase"` is load-bearing: the client sends the LSP wire
27/// name `textDocument`, so the field must deserialize from camelCase, not the
28/// Rust `text_document`. (Without it, every request fails with a missing-field
29/// error — a wire-shape bug no direct `documentation_model_at` test would catch,
30/// only a deserialize test; see `documentation_request.rs`'s params test.)
31#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct DocumentationModelParams {
34    pub text_document: tower_lsp::lsp_types::TextDocumentIdentifier,
35}
36
37// -- Wire shape: a plain serde mirror of `bynk_ide::documentation::DocModel`,
38// -- `Span` lowered to `Range` against the committed snapshot text.
39
40#[derive(Debug, Clone, serde::Serialize)]
41pub struct WireDocModel {
42    #[serde(rename = "unitName")]
43    pub unit_name: String,
44    #[serde(rename = "unitKind")]
45    pub unit_kind: &'static str,
46    #[serde(rename = "unitDoc")]
47    pub unit_doc: Option<String>,
48    #[serde(rename = "unitRange")]
49    pub unit_range: tower_lsp::lsp_types::Range,
50    pub entries: Vec<WireDocEntry>,
51}
52
53#[derive(Debug, Clone, serde::Serialize)]
54pub struct WireDocEntry {
55    pub name: String,
56    pub kind: &'static str,
57    pub depth: u32,
58    pub markdown: String,
59    pub documented: bool,
60    pub range: tower_lsp::lsp_types::Range,
61}
62
63pub fn to_wire(model: &DocModel, text: &str) -> WireDocModel {
64    WireDocModel {
65        unit_name: model.unit_name.clone(),
66        unit_kind: model.unit_kind,
67        unit_doc: model.unit_doc.clone(),
68        unit_range: crate::position::span_to_range(text, model.unit_span),
69        entries: model
70            .entries
71            .iter()
72            .map(|e| WireDocEntry {
73                name: e.name.clone(),
74                kind: e.kind,
75                depth: e.depth,
76                markdown: e.markdown.clone(),
77                documented: e.documented,
78                range: crate::position::span_to_range(text, e.span),
79            })
80            .collect(),
81    }
82}