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}