1use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use mant_ir::{DefinitionCase, DefinitionRole, DocumentMeta, DocumentSource, NodeId, SourceSpan};
7
8use crate::NodePath;
9
10pub const DEFAULT_SEARCH_LIMIT: u32 = 100;
12
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
15#[serde(rename_all = "kebab-case")]
16pub enum SearchSyntax {
17 #[default]
19 Literal,
20 Regex,
22}
23
24#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
26#[serde(rename_all = "kebab-case")]
27pub enum SearchCase {
28 #[default]
30 Insensitive,
31 Sensitive,
33 Smart,
35}
36
37#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
39#[serde(rename_all = "kebab-case")]
40pub enum SearchScope {
41 #[default]
43 Visible,
44 Markdown,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
50#[serde(rename_all = "camelCase", deny_unknown_fields)]
51pub struct SearchQuery {
52 #[schemars(length(min = 1, max = 4096))]
54 pub pattern: String,
55 #[serde(default)]
57 pub syntax: SearchSyntax,
58 #[serde(default)]
60 pub case: SearchCase,
61 #[serde(default)]
63 pub scope: SearchScope,
64 #[serde(default)]
66 pub word: bool,
67 #[serde(default)]
69 #[schemars(range(max = 100))]
70 pub context_lines: u16,
71 #[serde(default = "default_search_limit")]
73 #[schemars(range(min = 1, max = 10000))]
74 pub limit: u32,
75 #[serde(default)]
77 pub offset: u32,
78}
79
80#[must_use]
81pub const fn default_search_limit() -> u32 {
83 DEFAULT_SEARCH_LIMIT
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
88pub enum SearchSchema {
89 #[serde(rename = "mant.search/v7")]
91 V7,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
96pub enum MarkdownSchema {
97 #[serde(rename = "mant.markdown/v1")]
99 V1,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
104#[serde(rename_all = "kebab-case")]
105pub enum SearchRenderFormat {
106 Markdown,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
112#[serde(rename_all = "kebab-case")]
113pub enum SearchRenderScope {
114 Full,
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
120#[serde(rename_all = "camelCase")]
121pub struct SearchRender {
122 pub schema: MarkdownSchema,
124 pub format: SearchRenderFormat,
126 pub scope: SearchRenderScope,
128 pub line_base: u8,
130 pub column_base: u8,
132 pub line_count: u32,
134}
135
136#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
138#[serde(rename_all = "camelCase")]
139#[schemars(extend("$id" = "urn:mant:search:v7"))]
140pub struct QuerySearch {
141 pub schema: SearchSchema,
143 pub label: String,
145 #[serde(skip_serializing_if = "Option::is_none")]
147 pub source: Option<DocumentSource>,
148 #[serde(skip_serializing_if = "Option::is_none")]
150 pub meta: Option<DocumentMeta>,
151 pub query: SearchQuery,
153 pub render: SearchRender,
155 pub total: u32,
157 pub returned: u32,
159 pub offset: u32,
161 pub truncated: bool,
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub next_offset: Option<u32>,
166 pub matches: Vec<SearchMatch>,
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
172#[serde(rename_all = "camelCase")]
173pub struct SearchMatch {
174 pub ordinal: u32,
176 pub node: SearchNode,
178 #[serde(skip_serializing_if = "Option::is_none")]
180 pub section: Option<SearchSectionReference>,
181 pub matched_text: String,
183 pub markdown: SearchMarkdownRange,
185 #[serde(skip_serializing_if = "Option::is_none")]
187 pub source: Option<SourceSpan>,
188 pub preview: String,
190 #[serde(default, skip_serializing_if = "Vec::is_empty")]
192 pub context: Vec<SearchContextLine>,
193}
194
195#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
197#[serde(
198 tag = "kind",
199 rename_all = "kebab-case",
200 rename_all_fields = "camelCase"
201)]
202pub enum SearchNode {
203 Tldr {
205 path: NodePath,
207 id: NodeId,
209 title: String,
211 },
212 DocumentRoot {
214 path: NodePath,
216 id: NodeId,
218 title: String,
220 },
221 DocumentSection {
223 path: NodePath,
225 id: NodeId,
227 title: String,
229 },
230 DocumentEntry {
232 path: NodePath,
234 id: NodeId,
236 title: String,
238 role: DefinitionRole,
240 case: DefinitionCase,
242 names: Vec<String>,
244 },
245}
246
247impl SearchNode {
248 #[must_use]
250 pub fn path(&self) -> &str {
251 match self {
252 Self::Tldr { path, .. }
253 | Self::DocumentRoot { path, .. }
254 | Self::DocumentSection { path, .. }
255 | Self::DocumentEntry { path, .. } => path,
256 }
257 }
258
259 #[must_use]
261 pub fn title(&self) -> &str {
262 match self {
263 Self::Tldr { title, .. }
264 | Self::DocumentRoot { title, .. }
265 | Self::DocumentSection { title, .. }
266 | Self::DocumentEntry { title, .. } => title,
267 }
268 }
269}
270
271#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
273#[serde(rename_all = "camelCase")]
274pub struct SearchSectionReference {
275 pub path: NodePath,
277 pub id: NodeId,
279 pub title: String,
281}
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
285#[serde(rename_all = "camelCase")]
286pub struct SearchMarkdownRange {
287 pub start_byte: u64,
289 pub end_byte: u64,
291 pub start_line: u32,
293 pub start_column: u32,
295 pub end_line: u32,
297 pub end_column: u32,
299}
300
301#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
303#[serde(rename_all = "camelCase")]
304pub struct SearchContextLine {
305 pub line: u32,
307 pub text: String,
309 pub matched: bool,
311}