1use lsp_types::{
4 CompletionItem, CompletionItemKind, Documentation, InsertTextFormat, MarkupContent, MarkupKind,
5 Position, Range,
6};
7
8use crate::analysis::rust::macro_analyzer::SummerMacro;
9use crate::analysis::toml::toml_analyzer::{TomlAnalyzer, TomlDocument};
10use crate::core::schema::SchemaProvider;
11
12#[derive(Debug, Clone)]
16pub enum CompletionContext {
17 Toml,
19 Macro,
21 Unknown,
23}
24
25pub struct CompletionEngine {
29 toml_analyzer: TomlAnalyzer,
31}
32
33impl CompletionEngine {
34 pub fn new(schema_provider: SchemaProvider) -> Self {
40 Self {
41 toml_analyzer: TomlAnalyzer::new(schema_provider),
42 }
43 }
44
45 pub fn complete(
60 &self,
61 context: CompletionContext,
62 position: Position,
63 toml_doc: Option<&TomlDocument>,
64 macro_info: Option<&SummerMacro>,
65 ) -> Vec<CompletionItem> {
66 match context {
67 CompletionContext::Toml => {
68 if let Some(doc) = toml_doc {
69 self.complete_toml(doc, position)
70 } else {
71 Vec::new()
72 }
73 }
74 CompletionContext::Macro => {
75 if let Some(macro_info) = macro_info {
76 self.complete_macro(macro_info, None)
77 } else {
78 Vec::new()
79 }
80 }
81 CompletionContext::Unknown => Vec::new(),
82 }
83 }
84
85 pub fn complete_toml_document(
102 &self,
103 doc: &TomlDocument,
104 position: Position,
105 ) -> Vec<CompletionItem> {
106 self.complete_toml(doc, position)
107 }
108
109 fn complete_toml(&self, doc: &TomlDocument, position: Position) -> Vec<CompletionItem> {
126 if self.is_prefix_position(doc, position) {
128 return self.complete_config_prefix();
129 }
130
131 if self.is_env_var_position(doc, position) {
133 return self.complete_env_var();
134 }
135
136 if let Some(section) = self.find_section_at_position(doc, position) {
138 if let Some(property_name) = self.find_property_at_position(section, position) {
140 let schema_provider = self.toml_analyzer.schema_provider();
142 if let Some(plugin_schema) = schema_provider.get_plugin(§ion.prefix) {
143 if let Some(property_schema) = plugin_schema.properties.get(&property_name) {
144 if let crate::schema::TypeInfo::String {
146 enum_values: Some(enum_vals),
147 ..
148 } = &property_schema.type_info
149 {
150 return self.complete_enum_values(enum_vals);
151 }
152 }
153 }
154 }
155
156 return self.complete_config_properties(section);
158 }
159
160 Vec::new()
161 }
162
163 fn is_prefix_position(&self, doc: &TomlDocument, position: Position) -> bool {
167 let lines: Vec<&str> = doc.content.lines().collect();
169
170 if position.line as usize >= lines.len() {
171 return false;
172 }
173
174 let line = lines[position.line as usize];
175 let char_pos = position.character as usize;
176
177 if char_pos > line.len() {
179 return false;
180 }
181
182 let before_cursor = if char_pos > 0 { &line[..char_pos] } else { "" };
184
185 let trimmed = before_cursor.trim_start();
188
189 trimmed.starts_with('[') && !trimmed.contains(']') && !line.contains(']')
192 }
193
194 fn is_env_var_position(&self, _doc: &TomlDocument, _position: Position) -> bool {
198 false
201 }
202
203 fn find_section_at_position<'a>(
207 &self,
208 doc: &'a TomlDocument,
209 position: Position,
210 ) -> Option<&'a crate::toml_analyzer::ConfigSection> {
211 doc.config_sections
212 .values()
213 .find(|§ion| self.position_in_range(position, section.range))
214 }
215
216 fn find_property_at_position(
220 &self,
221 section: &crate::toml_analyzer::ConfigSection,
222 position: Position,
223 ) -> Option<String> {
224 for (key, property) in §ion.properties {
225 if self.position_in_range(position, property.range) {
227 return Some(key.clone());
228 }
229 }
230 None
231 }
232
233 fn position_in_range(&self, position: Position, range: Range) -> bool {
235 if position.line < range.start.line || position.line > range.end.line {
236 return false;
237 }
238 if position.line == range.start.line && position.character < range.start.character {
239 return false;
240 }
241 if position.line == range.end.line && position.character > range.end.character {
242 return false;
243 }
244 true
245 }
246
247 fn complete_config_prefix(&self) -> Vec<CompletionItem> {
251 let prefixes = self.toml_analyzer.schema_provider().get_all_prefixes();
252
253 prefixes
254 .into_iter()
255 .map(|prefix: String| {
256 let description = format!("{} 插件配置", prefix);
257
258 CompletionItem {
259 label: prefix.clone(),
260 kind: Some(CompletionItemKind::MODULE),
261 detail: Some(format!("[{}] 配置节", prefix)),
262 documentation: Some(Documentation::MarkupContent(MarkupContent {
263 kind: MarkupKind::Markdown,
264 value: format!(
265 "**{}** 插件配置节\n\n{}\n\n\
266 **使用方式**:\n\
267 ```toml\n\
268 [{}]\n\
269 # 配置项...\n\
270 ```",
271 prefix, description, prefix
272 ),
273 })),
274 insert_text: Some(format!("[{}]\n", prefix)),
275 insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
276 ..Default::default()
277 }
278 })
279 .collect()
280 }
281
282 fn complete_config_properties(
289 &self,
290 section: &crate::toml_analyzer::ConfigSection,
291 ) -> Vec<CompletionItem> {
292 let prefix = §ion.prefix;
293
294 let schema_provider = self.toml_analyzer.schema_provider();
296 let plugin_schema = match schema_provider.get_plugin(prefix) {
297 Some(schema) => schema,
298 None => return Vec::new(),
299 };
300
301 let existing_keys: std::collections::HashSet<String> =
303 section.properties.keys().cloned().collect();
304
305 let mut completions = Vec::new();
307
308 for (key, property_schema) in &plugin_schema.properties {
309 if existing_keys.contains(key) {
311 continue;
312 }
313
314 let type_hint = self.type_info_to_hint(&property_schema.type_info);
316 let default_value = if let Some(default) = &property_schema.default {
317 self.value_to_string(default)
318 } else {
319 self.type_info_to_default(&property_schema.type_info)
320 };
321
322 let insert_text = format!("{} = {} # {}", key, default_value, type_hint);
324
325 let mut doc_parts = Vec::new();
327
328 if !property_schema.description.is_empty() {
329 doc_parts.push(property_schema.description.clone());
330 }
331
332 doc_parts.push(format!("**类型**: `{}`", type_hint));
333
334 if let Some(default) = &property_schema.default {
335 doc_parts.push(format!("**默认值**: `{}`", self.value_to_string(default)));
336 }
337
338 if property_schema.required {
339 doc_parts.push("**必需**: 是".to_string());
340 }
341
342 if let Some(deprecated_msg) = &property_schema.deprecated {
343 doc_parts.push(format!("**已废弃**: {}", deprecated_msg));
344 }
345
346 let documentation = Documentation::MarkupContent(MarkupContent {
347 kind: MarkupKind::Markdown,
348 value: doc_parts.join("\n\n"),
349 });
350
351 completions.push(CompletionItem {
352 label: key.clone(),
353 kind: Some(CompletionItemKind::PROPERTY),
354 detail: Some(format!("{}: {}", key, type_hint)),
355 documentation: Some(documentation),
356 insert_text: Some(insert_text),
357 insert_text_format: Some(lsp_types::InsertTextFormat::PLAIN_TEXT),
358 deprecated: property_schema.deprecated.is_some().then_some(true),
359 ..Default::default()
360 });
361 }
362
363 completions
364 }
365
366 #[allow(dead_code)]
368 fn json_value_to_toml_string(&self, value: &serde_json::Value) -> String {
369 match value {
370 serde_json::Value::String(s) => format!("\"{}\"", s),
371 serde_json::Value::Number(n) => n.to_string(),
372 serde_json::Value::Bool(b) => b.to_string(),
373 serde_json::Value::Array(_) => "[]".to_string(),
374 serde_json::Value::Object(_) => "{}".to_string(),
375 serde_json::Value::Null => "null".to_string(),
376 }
377 }
378
379 #[allow(dead_code)]
381 fn type_to_default_value(&self, type_name: &str) -> String {
382 match type_name {
383 "string" => "\"\"".to_string(),
384 "integer" | "number" => "0".to_string(),
385 "boolean" => "false".to_string(),
386 "array" => "[]".to_string(),
387 "object" => "{}".to_string(),
388 _ => "\"\"".to_string(),
389 }
390 }
391
392 fn complete_enum_values(&self, values: &[String]) -> Vec<CompletionItem> {
396 values
397 .iter()
398 .map(|value| CompletionItem {
399 label: value.clone(),
400 kind: Some(CompletionItemKind::ENUM_MEMBER),
401 detail: Some(format!("枚举值: {}", value)),
402 documentation: Some(Documentation::MarkupContent(MarkupContent {
403 kind: MarkupKind::Markdown,
404 value: format!("枚举值 `{}`", value),
405 })),
406 insert_text: Some(format!("\"{}\"", value)),
407 insert_text_format: Some(lsp_types::InsertTextFormat::PLAIN_TEXT),
408 ..Default::default()
409 })
410 .collect()
411 }
412
413 pub fn complete_env_var(&self) -> Vec<CompletionItem> {
417 let common_vars = vec![
418 ("HOST", "主机地址"),
419 ("PORT", "端口号"),
420 ("DATABASE_URL", "数据库连接 URL"),
421 ("REDIS_URL", "Redis 连接 URL"),
422 ("LOG_LEVEL", "日志级别"),
423 ("ENV", "运行环境"),
424 ("DEBUG", "调试模式"),
425 ];
426
427 common_vars
428 .into_iter()
429 .map(|(name, description)| CompletionItem {
430 label: name.to_string(),
431 kind: Some(CompletionItemKind::VARIABLE),
432 detail: Some(description.to_string()),
433 documentation: Some(Documentation::MarkupContent(MarkupContent {
434 kind: MarkupKind::Markdown,
435 value: format!(
436 "**{}**\n\n{}\n\n\
437 **使用方式**:\n\
438 ```toml\n\
439 value = \"${{{}:default}}\"\n\
440 ```",
441 name, description, name
442 ),
443 })),
444 insert_text: Some(format!("{}:${{1:default}}}}", name)),
445 insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
446 ..Default::default()
447 })
448 .collect()
449 }
450
451 fn type_info_to_hint(&self, type_info: &crate::schema::TypeInfo) -> String {
453 match type_info {
454 crate::schema::TypeInfo::String {
455 enum_values: Some(values),
456 ..
457 } => {
458 format!("enum: {:?}", values)
459 }
460 crate::schema::TypeInfo::String { .. } => "string".to_string(),
461 crate::schema::TypeInfo::Integer { min, max } => {
462 if let (Some(min), Some(max)) = (min, max) {
463 format!("integer ({} - {})", min, max)
464 } else {
465 "integer".to_string()
466 }
467 }
468 crate::schema::TypeInfo::Float { .. } => "float".to_string(),
469 crate::schema::TypeInfo::Boolean => "boolean".to_string(),
470 crate::schema::TypeInfo::Array { .. } => "array".to_string(),
471 crate::schema::TypeInfo::Object { .. } => "object".to_string(),
472 }
473 }
474
475 fn type_info_to_default(&self, type_info: &crate::schema::TypeInfo) -> String {
477 match type_info {
478 crate::schema::TypeInfo::String {
479 enum_values: Some(values),
480 ..
481 } => {
482 if let Some(first) = values.first() {
483 format!("\"{}\"", first)
484 } else {
485 "\"\"".to_string()
486 }
487 }
488 crate::schema::TypeInfo::String { .. } => "\"\"".to_string(),
489 crate::schema::TypeInfo::Integer { .. } => "0".to_string(),
490 crate::schema::TypeInfo::Float { .. } => "0.0".to_string(),
491 crate::schema::TypeInfo::Boolean => "false".to_string(),
492 crate::schema::TypeInfo::Array { .. } => "[]".to_string(),
493 crate::schema::TypeInfo::Object { .. } => "{}".to_string(),
494 }
495 }
496
497 fn value_to_string(&self, value: &crate::schema::Value) -> String {
499 match value {
500 crate::schema::Value::String(s) => format!("\"{}\"", s),
501 crate::schema::Value::Integer(i) => i.to_string(),
502 crate::schema::Value::Float(f) => f.to_string(),
503 crate::schema::Value::Boolean(b) => b.to_string(),
504 crate::schema::Value::Array(_) => "[]".to_string(),
505 crate::schema::Value::Table(_) => "{}".to_string(),
506 }
507 }
508
509 pub fn complete_macro(
522 &self,
523 macro_info: &SummerMacro,
524 _cursor_position: Option<&str>,
525 ) -> Vec<CompletionItem> {
526 match macro_info {
527 SummerMacro::DeriveService(_) => self.complete_service_macro(),
528 SummerMacro::Component(_) => self.complete_component_macro(),
529 SummerMacro::Inject(_) => self.complete_inject_macro(),
530 SummerMacro::AutoConfig(_) => self.complete_auto_config_macro(),
531 SummerMacro::Route(_) => self.complete_route_macro(),
532 SummerMacro::Job(_) => self.complete_job_macro(),
533 }
534 }
535
536 fn complete_component_macro(&self) -> Vec<CompletionItem> {
540 vec![CompletionItem {
541 label: "name".to_string(),
542 kind: Some(CompletionItemKind::PROPERTY),
543 detail: Some("插件名称".to_string()),
544 documentation: Some(Documentation::String(
545 "指定自定义的插件名称,默认使用组件类型名 + \"Plugin\"".to_string(),
546 )),
547 insert_text: Some("name = \"$1\"".to_string()),
548 insert_text_format: Some(InsertTextFormat::SNIPPET),
549 ..Default::default()
550 }]
551 }
552
553 fn complete_service_macro(&self) -> Vec<CompletionItem> {
557 vec![
558 CompletionItem {
560 label: "inject(component)".to_string(),
561 kind: Some(CompletionItemKind::PROPERTY),
562 detail: Some("注入组件".to_string()),
563 documentation: Some(Documentation::MarkupContent(MarkupContent {
564 kind: MarkupKind::Markdown,
565 value: "从应用上下文中注入已注册的组件实例。\n\n\
566 **示例**:\n\
567 ```rust\n\
568 #[inject(component)]\n\
569 db: ConnectPool,\n\
570 ```"
571 .to_string(),
572 })),
573 insert_text: Some("inject(component)".to_string()),
574 ..Default::default()
575 },
576 CompletionItem {
578 label: "inject(component = \"name\")".to_string(),
579 kind: Some(CompletionItemKind::PROPERTY),
580 detail: Some("注入指定名称的组件".to_string()),
581 documentation: Some(Documentation::MarkupContent(MarkupContent {
582 kind: MarkupKind::Markdown,
583 value: "使用指定名称从应用上下文中注入组件,适用于多实例场景(如多数据源)。\n\n\
584 **示例**:\n\
585 ```rust\n\
586 #[inject(component = \"primary\")]\n\
587 primary_db: ConnectPool,\n\
588 ```"
589 .to_string(),
590 })),
591 insert_text: Some("inject(component = \"$1\")".to_string()),
592 insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
593 ..Default::default()
594 },
595 CompletionItem {
597 label: "inject(config)".to_string(),
598 kind: Some(CompletionItemKind::PROPERTY),
599 detail: Some("注入配置".to_string()),
600 documentation: Some(Documentation::MarkupContent(MarkupContent {
601 kind: MarkupKind::Markdown,
602 value: "从配置文件中加载配置项。配置项通过 `#[config_prefix]` 指定的前缀从 `config/app.toml` 中读取。\n\n\
603 **示例**:\n\
604 ```rust\n\
605 #[inject(config)]\n\
606 config: MyConfig,\n\
607 ```"
608 .to_string(),
609 })),
610 insert_text: Some("inject(config)".to_string()),
611 ..Default::default()
612 },
613 ]
614 }
615
616 fn complete_inject_macro(&self) -> Vec<CompletionItem> {
620 vec![
621 CompletionItem {
623 label: "component".to_string(),
624 kind: Some(CompletionItemKind::KEYWORD),
625 detail: Some("注入组件".to_string()),
626 documentation: Some(Documentation::MarkupContent(MarkupContent {
627 kind: MarkupKind::Markdown,
628 value: "从应用上下文中注入已注册的组件实例。\n\n\
629 **使用方式**:\n\
630 - `#[inject(component)]` - 按类型自动查找\n\
631 - `#[inject(component = \"name\")]` - 按名称查找"
632 .to_string(),
633 })),
634 insert_text: Some("component".to_string()),
635 ..Default::default()
636 },
637 CompletionItem {
639 label: "config".to_string(),
640 kind: Some(CompletionItemKind::KEYWORD),
641 detail: Some("注入配置".to_string()),
642 documentation: Some(Documentation::MarkupContent(MarkupContent {
643 kind: MarkupKind::Markdown,
644 value: "从配置文件中加载配置项。\n\n\
645 **使用方式**:\n\
646 - `#[inject(config)]` - 从 config/app.toml 加载配置"
647 .to_string(),
648 })),
649 insert_text: Some("config".to_string()),
650 ..Default::default()
651 },
652 ]
653 }
654
655 fn complete_auto_config_macro(&self) -> Vec<CompletionItem> {
659 vec![
660 CompletionItem {
661 label: "WebConfigurator".to_string(),
662 kind: Some(CompletionItemKind::CLASS),
663 detail: Some("Web 路由配置器".to_string()),
664 documentation: Some(Documentation::MarkupContent(MarkupContent {
665 kind: MarkupKind::Markdown,
666 value: "自动注册 Web 路由处理器。\n\n\
667 **示例**:\n\
668 ```rust\n\
669 #[auto_config(WebConfigurator)]\n\
670 #[tokio::main]\n\
671 async fn main() {\n\
672 App::new().add_plugin(WebPlugin).run().await\n\
673 }\n\
674 ```"
675 .to_string(),
676 })),
677 insert_text: Some("WebConfigurator".to_string()),
678 ..Default::default()
679 },
680 CompletionItem {
681 label: "JobConfigurator".to_string(),
682 kind: Some(CompletionItemKind::CLASS),
683 detail: Some("任务调度配置器".to_string()),
684 documentation: Some(Documentation::MarkupContent(MarkupContent {
685 kind: MarkupKind::Markdown,
686 value: "自动注册定时任务。\n\n\
687 **示例**:\n\
688 ```rust\n\
689 #[auto_config(JobConfigurator)]\n\
690 #[tokio::main]\n\
691 async fn main() {\n\
692 App::new().add_plugin(JobPlugin).run().await\n\
693 }\n\
694 ```"
695 .to_string(),
696 })),
697 insert_text: Some("JobConfigurator".to_string()),
698 ..Default::default()
699 },
700 CompletionItem {
701 label: "StreamConfigurator".to_string(),
702 kind: Some(CompletionItemKind::CLASS),
703 detail: Some("流处理配置器".to_string()),
704 documentation: Some(Documentation::MarkupContent(MarkupContent {
705 kind: MarkupKind::Markdown,
706 value: "自动注册流消费者。\n\n\
707 **示例**:\n\
708 ```rust\n\
709 #[auto_config(StreamConfigurator)]\n\
710 #[tokio::main]\n\
711 async fn main() {\n\
712 App::new().add_plugin(StreamPlugin).run().await\n\
713 }\n\
714 ```"
715 .to_string(),
716 })),
717 insert_text: Some("StreamConfigurator".to_string()),
718 ..Default::default()
719 },
720 ]
721 }
722
723 fn complete_route_macro(&self) -> Vec<CompletionItem> {
727 let mut completions = Vec::new();
728
729 let methods = vec![
731 ("GET", "获取资源"),
732 ("POST", "创建资源"),
733 ("PUT", "更新资源(完整)"),
734 ("DELETE", "删除资源"),
735 ("PATCH", "更新资源(部分)"),
736 ("HEAD", "获取资源头信息"),
737 ("OPTIONS", "获取支持的方法"),
738 ];
739
740 for (method, description) in methods {
741 completions.push(CompletionItem {
742 label: method.to_string(),
743 kind: Some(CompletionItemKind::CONSTANT),
744 detail: Some(description.to_string()),
745 documentation: Some(Documentation::MarkupContent(MarkupContent {
746 kind: MarkupKind::Markdown,
747 value: format!(
748 "HTTP {} 方法\n\n\
749 **示例**:\n\
750 ```rust\n\
751 #[{}(\"/path\")]\n\
752 async fn handler() -> impl IntoResponse {{\n\
753 }}\n\
754 ```",
755 method,
756 method.to_lowercase()
757 ),
758 })),
759 insert_text: Some(method.to_string()),
760 ..Default::default()
761 });
762 }
763
764 completions.push(CompletionItem {
766 label: "{id}".to_string(),
767 kind: Some(CompletionItemKind::SNIPPET),
768 detail: Some("路径参数".to_string()),
769 documentation: Some(Documentation::MarkupContent(MarkupContent {
770 kind: MarkupKind::Markdown,
771 value: "路径参数占位符,用于捕获 URL 中的动态部分。\n\n\
772 **示例**:\n\
773 ```rust\n\
774 #[get(\"/users/{id}\")]\n\
775 async fn get_user(Path(id): Path<i64>) -> impl IntoResponse {\n\
776 }\n\
777 ```"
778 .to_string(),
779 })),
780 insert_text: Some("{${1:id}}".to_string()),
781 insert_text_format: Some(lsp_types::InsertTextFormat::SNIPPET),
782 ..Default::default()
783 });
784
785 completions
786 }
787
788 fn complete_job_macro(&self) -> Vec<CompletionItem> {
792 vec![
793 CompletionItem {
795 label: "0 0 * * * *".to_string(),
796 kind: Some(CompletionItemKind::SNIPPET),
797 detail: Some("每小时执行".to_string()),
798 documentation: Some(Documentation::MarkupContent(MarkupContent {
799 kind: MarkupKind::Markdown,
800 value: "Cron 表达式:每小时的第 0 分 0 秒执行\n\n\
801 **格式**: 秒 分 时 日 月 星期\n\n\
802 **示例**:\n\
803 ```rust\n\
804 #[cron(\"0 0 * * * *\")]\n\
805 async fn hourly_job() {\n\
806 }\n\
807 ```"
808 .to_string(),
809 })),
810 insert_text: Some("\"0 0 * * * *\"".to_string()),
811 ..Default::default()
812 },
813 CompletionItem {
814 label: "0 0 0 * * *".to_string(),
815 kind: Some(CompletionItemKind::SNIPPET),
816 detail: Some("每天午夜执行".to_string()),
817 documentation: Some(Documentation::MarkupContent(MarkupContent {
818 kind: MarkupKind::Markdown,
819 value: "Cron 表达式:每天午夜 00:00:00 执行\n\n\
820 **格式**: 秒 分 时 日 月 星期\n\n\
821 **示例**:\n\
822 ```rust\n\
823 #[cron(\"0 0 0 * * *\")]\n\
824 async fn daily_job() {\n\
825 }\n\
826 ```"
827 .to_string(),
828 })),
829 insert_text: Some("\"0 0 0 * * *\"".to_string()),
830 ..Default::default()
831 },
832 CompletionItem {
833 label: "0 */5 * * * *".to_string(),
834 kind: Some(CompletionItemKind::SNIPPET),
835 detail: Some("每 5 分钟执行".to_string()),
836 documentation: Some(Documentation::MarkupContent(MarkupContent {
837 kind: MarkupKind::Markdown,
838 value: "Cron 表达式:每 5 分钟执行一次\n\n\
839 **格式**: 秒 分 时 日 月 星期\n\n\
840 **示例**:\n\
841 ```rust\n\
842 #[cron(\"0 */5 * * * *\")]\n\
843 async fn every_five_minutes() {\n\
844 }\n\
845 ```"
846 .to_string(),
847 })),
848 insert_text: Some("\"0 */5 * * * *\"".to_string()),
849 ..Default::default()
850 },
851 CompletionItem {
853 label: "5".to_string(),
854 kind: Some(CompletionItemKind::VALUE),
855 detail: Some("延迟 5 秒".to_string()),
856 documentation: Some(Documentation::MarkupContent(MarkupContent {
857 kind: MarkupKind::Markdown,
858 value: "任务完成后延迟 5 秒再次执行\n\n\
859 **示例**:\n\
860 ```rust\n\
861 #[fix_delay(5)]\n\
862 async fn delayed_job() {\n\
863 }\n\
864 ```"
865 .to_string(),
866 })),
867 insert_text: Some("5".to_string()),
868 ..Default::default()
869 },
870 CompletionItem {
871 label: "10".to_string(),
872 kind: Some(CompletionItemKind::VALUE),
873 detail: Some("延迟/频率 10 秒".to_string()),
874 documentation: Some(Documentation::MarkupContent(MarkupContent {
875 kind: MarkupKind::Markdown,
876 value: "延迟或频率为 10 秒\n\n\
877 **fix_delay 示例**:\n\
878 ```rust\n\
879 #[fix_delay(10)]\n\
880 async fn delayed_job() {\n\
881 }\n\
882 ```\n\n\
883 **fix_rate 示例**:\n\
884 ```rust\n\
885 #[fix_rate(10)]\n\
886 async fn periodic_job() {\n\
887 }\n\
888 ```"
889 .to_string(),
890 })),
891 insert_text: Some("10".to_string()),
892 ..Default::default()
893 },
894 CompletionItem {
895 label: "60".to_string(),
896 kind: Some(CompletionItemKind::VALUE),
897 detail: Some("延迟/频率 60 秒(1 分钟)".to_string()),
898 documentation: Some(Documentation::MarkupContent(MarkupContent {
899 kind: MarkupKind::Markdown,
900 value: "延迟或频率为 60 秒(1 分钟)\n\n\
901 **fix_delay 示例**:\n\
902 ```rust\n\
903 #[fix_delay(60)]\n\
904 async fn delayed_job() {\n\
905 }\n\
906 ```\n\n\
907 **fix_rate 示例**:\n\
908 ```rust\n\
909 #[fix_rate(60)]\n\
910 async fn periodic_job() {\n\
911 }\n\
912 ```"
913 .to_string(),
914 })),
915 insert_text: Some("60".to_string()),
916 ..Default::default()
917 },
918 ]
919 }
920}
921
922impl Default for CompletionEngine {
923 fn default() -> Self {
924 Self::new(SchemaProvider::default())
925 }
926}
927
928#[cfg(test)]
929mod tests;