1use oxc_allocator::Allocator;
12use oxc_codegen::Codegen;
13use oxc_parser::Parser;
14use oxc_span::SourceType;
15use ts_rs::{Config as TsConfig, TS};
16
17use fresh_core::api::{
18 ActionPopupAction, ActionPopupOptions, ActionSpec, BackgroundProcessResult, BufferInfo,
19 BufferSavedDiff, CompositeHunk, CompositeLayoutConfig, CompositePaneStyle,
20 CompositeSourceConfig, CreateCompositeBufferOptions, CreateTerminalOptions,
21 CreateVirtualBufferInExistingSplitOptions, CreateVirtualBufferInSplitOptions,
22 CreateVirtualBufferOptions, CursorInfo, DirEntry, FormatterPackConfig, JsDiagnostic,
23 JsPosition, JsRange, JsTextPropertyEntry, LanguagePackConfig, LayoutHints, LspServerPackConfig,
24 OverlayColorSpec, OverlayOptions, ProcessLimitsPackConfig, SpawnResult, TerminalResult,
25 TextPropertiesAtCursor, TsHighlightSpan, ViewTokenStyle, ViewTokenWire, ViewTokenWireKind,
26 ViewportInfo, VirtualBufferResult,
27};
28use fresh_core::command::Suggestion;
29use fresh_core::file_explorer::FileExplorerDecoration;
30use fresh_core::text_property::InlineOverlay;
31
32fn get_type_decl(type_name: &str) -> Option<String> {
37 let cfg = TsConfig::default();
38 match type_name {
41 "BufferInfo" => Some(BufferInfo::decl(&cfg)),
43 "CursorInfo" => Some(CursorInfo::decl(&cfg)),
44 "ViewportInfo" => Some(ViewportInfo::decl(&cfg)),
45 "ActionSpec" => Some(ActionSpec::decl(&cfg)),
46 "BufferSavedDiff" => Some(BufferSavedDiff::decl(&cfg)),
47 "LayoutHints" => Some(LayoutHints::decl(&cfg)),
48
49 "SpawnResult" => Some(SpawnResult::decl(&cfg)),
51 "BackgroundProcessResult" => Some(BackgroundProcessResult::decl(&cfg)),
52
53 "TerminalResult" => Some(TerminalResult::decl(&cfg)),
55 "CreateTerminalOptions" => Some(CreateTerminalOptions::decl(&cfg)),
56
57 "TsCompositeLayoutConfig" | "CompositeLayoutConfig" => {
59 Some(CompositeLayoutConfig::decl(&cfg))
60 }
61 "TsCompositeSourceConfig" | "CompositeSourceConfig" => {
62 Some(CompositeSourceConfig::decl(&cfg))
63 }
64 "TsCompositePaneStyle" | "CompositePaneStyle" => Some(CompositePaneStyle::decl(&cfg)),
65 "TsCompositeHunk" | "CompositeHunk" => Some(CompositeHunk::decl(&cfg)),
66 "TsCreateCompositeBufferOptions" | "CreateCompositeBufferOptions" => {
67 Some(CreateCompositeBufferOptions::decl(&cfg))
68 }
69
70 "ViewTokenWireKind" => Some(ViewTokenWireKind::decl(&cfg)),
72 "ViewTokenStyle" => Some(ViewTokenStyle::decl(&cfg)),
73 "ViewTokenWire" => Some(ViewTokenWire::decl(&cfg)),
74
75 "TsActionPopupAction" | "ActionPopupAction" => Some(ActionPopupAction::decl(&cfg)),
77 "ActionPopupOptions" => Some(ActionPopupOptions::decl(&cfg)),
78 "TsHighlightSpan" => Some(TsHighlightSpan::decl(&cfg)),
79 "FileExplorerDecoration" => Some(FileExplorerDecoration::decl(&cfg)),
80
81 "TextPropertyEntry" | "JsTextPropertyEntry" => Some(JsTextPropertyEntry::decl(&cfg)),
83 "CreateVirtualBufferOptions" => Some(CreateVirtualBufferOptions::decl(&cfg)),
84 "CreateVirtualBufferInSplitOptions" => Some(CreateVirtualBufferInSplitOptions::decl(&cfg)),
85 "CreateVirtualBufferInExistingSplitOptions" => {
86 Some(CreateVirtualBufferInExistingSplitOptions::decl(&cfg))
87 }
88
89 "TextPropertiesAtCursor" => Some(TextPropertiesAtCursor::decl(&cfg)),
91 "VirtualBufferResult" => Some(VirtualBufferResult::decl(&cfg)),
92
93 "PromptSuggestion" | "Suggestion" => Some(Suggestion::decl(&cfg)),
95 "DirEntry" => Some(DirEntry::decl(&cfg)),
96
97 "JsDiagnostic" => Some(JsDiagnostic::decl(&cfg)),
99 "JsRange" => Some(JsRange::decl(&cfg)),
100 "JsPosition" => Some(JsPosition::decl(&cfg)),
101
102 "LanguagePackConfig" => Some(LanguagePackConfig::decl(&cfg)),
104 "LspServerPackConfig" => Some(LspServerPackConfig::decl(&cfg)),
105 "ProcessLimitsPackConfig" => Some(ProcessLimitsPackConfig::decl(&cfg)),
106 "FormatterPackConfig" => Some(FormatterPackConfig::decl(&cfg)),
107
108 "OverlayOptions" => Some(OverlayOptions::decl(&cfg)),
110 "OverlayColorSpec" => Some(OverlayColorSpec::decl(&cfg)),
111 "InlineOverlay" => Some(InlineOverlay::decl(&cfg)),
112
113 _ => None,
114 }
115}
116
117const DEPENDENCY_TYPES: &[&str] = &[
121 "TextPropertyEntry", "TsCompositeLayoutConfig", "TsCompositeSourceConfig", "TsCompositePaneStyle", "TsCompositeHunk", "TsCreateCompositeBufferOptions", "ViewportInfo", "LayoutHints", "ViewTokenWire", "ViewTokenWireKind", "ViewTokenStyle", "PromptSuggestion", "DirEntry", "BufferInfo", "JsDiagnostic", "JsRange", "JsPosition", "ActionSpec", "TsActionPopupAction", "ActionPopupOptions", "FileExplorerDecoration", "FormatterPackConfig", "ProcessLimitsPackConfig", "TerminalResult", "CreateTerminalOptions", "CursorInfo", "OverlayOptions", "OverlayColorSpec", "InlineOverlay", ];
151
152pub fn collect_ts_types() -> String {
157 use crate::backend::quickjs_backend::JSEDITORAPI_REFERENCED_TYPES;
158
159 let mut types = Vec::new();
160 let mut included_decls = std::collections::HashSet::new();
163
164 for type_name in DEPENDENCY_TYPES {
166 if let Some(decl) = get_type_decl(type_name) {
167 if included_decls.insert(decl.clone()) {
168 types.push(decl);
169 }
170 }
171 }
172
173 for type_name in JSEDITORAPI_REFERENCED_TYPES {
175 if let Some(decl) = get_type_decl(type_name) {
176 if included_decls.insert(decl.clone()) {
177 types.push(decl);
178 }
179 } else {
180 eprintln!(
182 "Warning: Type '{}' is referenced in API but not registered in get_type_decl()",
183 type_name
184 );
185 }
186 }
187
188 types.join("\n\n")
189}
190
191pub fn validate_typescript(source: &str) -> Result<(), String> {
195 let allocator = Allocator::default();
196 let source_type = SourceType::d_ts();
197
198 let parser_ret = Parser::new(&allocator, source, source_type).parse();
199
200 if parser_ret.errors.is_empty() {
201 Ok(())
202 } else {
203 let errors: Vec<String> = parser_ret
204 .errors
205 .iter()
206 .map(|e: &oxc_diagnostics::OxcDiagnostic| e.to_string())
207 .collect();
208 Err(format!("TypeScript parse errors:\n{}", errors.join("\n")))
209 }
210}
211
212pub fn format_typescript(source: &str) -> String {
217 let allocator = Allocator::default();
218 let source_type = SourceType::d_ts();
219
220 let parser_ret = Parser::new(&allocator, source, source_type).parse();
221
222 if !parser_ret.errors.is_empty() {
223 return source.to_string();
225 }
226
227 Codegen::new().build(&parser_ret.program).code
229}
230
231pub fn write_fresh_dts() -> Result<(), String> {
236 use crate::backend::quickjs_backend::{JSEDITORAPI_TS_EDITOR_API, JSEDITORAPI_TS_PREAMBLE};
237
238 let ts_types = collect_ts_types();
239
240 let content = format!(
241 "{}\n{}\n{}",
242 JSEDITORAPI_TS_PREAMBLE, ts_types, JSEDITORAPI_TS_EDITOR_API
243 );
244
245 validate_typescript(&content)?;
247
248 let formatted = format_typescript(&content);
250
251 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
253 let output_path = std::path::Path::new(&manifest_dir)
254 .parent() .and_then(|p| p.parent()) .map(|p| p.join("crates/fresh-editor/plugins/lib/fresh.d.ts"))
257 .unwrap_or_else(|| std::path::PathBuf::from("plugins/lib/fresh.d.ts"));
258
259 let should_write = match std::fs::read_to_string(&output_path) {
261 Ok(existing) => existing != formatted,
262 Err(_) => true,
263 };
264
265 if should_write {
266 if let Some(parent) = output_path.parent() {
267 std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
268 }
269 std::fs::write(&output_path, &formatted).map_err(|e| e.to_string())?;
270 }
271
272 Ok(())
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[test]
282 #[ignore]
283 fn write_fresh_dts_file() {
284 write_fresh_dts().expect("Failed to write fresh.d.ts");
286 println!("Successfully generated, validated, and formatted fresh.d.ts");
287 }
288
289 #[test]
293 #[ignore]
294 fn type_check_plugins() {
295 let tsc_check = std::process::Command::new("tsc").arg("--version").output();
297
298 match tsc_check {
299 Ok(output) if output.status.success() => {
300 println!(
301 "Found tsc: {}",
302 String::from_utf8_lossy(&output.stdout).trim()
303 );
304 }
305 _ => {
306 println!("tsc not found in PATH, skipping type check test");
307 return;
308 }
309 }
310
311 let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string());
313 let script_path = std::path::Path::new(&manifest_dir)
314 .parent()
315 .and_then(|p| p.parent())
316 .map(|p| p.join("crates/fresh-editor/plugins/check-types.sh"))
317 .expect("Failed to find check-types.sh");
318
319 println!("Running type check script: {}", script_path.display());
320
321 let output = std::process::Command::new("bash")
323 .arg(&script_path)
324 .output()
325 .expect("Failed to run check-types.sh");
326
327 let stdout = String::from_utf8_lossy(&output.stdout);
328 let stderr = String::from_utf8_lossy(&output.stderr);
329
330 println!("stdout:\n{}", stdout);
331 if !stderr.is_empty() {
332 println!("stderr:\n{}", stderr);
333 }
334
335 if stdout.contains("had type errors") || !output.status.success() {
337 panic!(
338 "TypeScript type check failed. Run 'crates/fresh-editor/plugins/check-types.sh' to see details."
339 );
340 }
341
342 println!("All plugins type check successfully!");
343 }
344
345 #[test]
350 fn test_get_type_decl_returns_all_expected_types() {
351 let expected_types = vec![
352 "BufferInfo",
353 "CursorInfo",
354 "ViewportInfo",
355 "ActionSpec",
356 "BufferSavedDiff",
357 "LayoutHints",
358 "SpawnResult",
359 "BackgroundProcessResult",
360 "TerminalResult",
361 "CreateTerminalOptions",
362 "TsCompositeLayoutConfig",
363 "TsCompositeSourceConfig",
364 "TsCompositePaneStyle",
365 "TsCompositeHunk",
366 "TsCreateCompositeBufferOptions",
367 "ViewTokenWireKind",
368 "ViewTokenStyle",
369 "ViewTokenWire",
370 "TsActionPopupAction",
371 "ActionPopupOptions",
372 "TsHighlightSpan",
373 "FileExplorerDecoration",
374 "TextPropertyEntry",
375 "CreateVirtualBufferOptions",
376 "CreateVirtualBufferInSplitOptions",
377 "CreateVirtualBufferInExistingSplitOptions",
378 "TextPropertiesAtCursor",
379 "VirtualBufferResult",
380 "PromptSuggestion",
381 "DirEntry",
382 "JsDiagnostic",
383 "JsRange",
384 "JsPosition",
385 "LanguagePackConfig",
386 "LspServerPackConfig",
387 "ProcessLimitsPackConfig",
388 "FormatterPackConfig",
389 ];
390
391 for type_name in &expected_types {
392 assert!(
393 get_type_decl(type_name).is_some(),
394 "get_type_decl should return a declaration for '{}'",
395 type_name
396 );
397 }
398 }
399
400 #[test]
401 fn test_get_type_decl_aliases_resolve_same() {
402 let alias_pairs = vec![
404 ("CompositeHunk", "TsCompositeHunk"),
405 ("CompositeLayoutConfig", "TsCompositeLayoutConfig"),
406 ("CompositeSourceConfig", "TsCompositeSourceConfig"),
407 ("CompositePaneStyle", "TsCompositePaneStyle"),
408 (
409 "CreateCompositeBufferOptions",
410 "TsCreateCompositeBufferOptions",
411 ),
412 ("ActionPopupAction", "TsActionPopupAction"),
413 ("Suggestion", "PromptSuggestion"),
414 ("JsTextPropertyEntry", "TextPropertyEntry"),
415 ];
416
417 for (rust_name, ts_name) in &alias_pairs {
418 let rust_decl = get_type_decl(rust_name);
419 let ts_decl = get_type_decl(ts_name);
420 assert!(
421 rust_decl.is_some(),
422 "get_type_decl should handle Rust name '{}'",
423 rust_name
424 );
425 assert_eq!(
426 rust_decl, ts_decl,
427 "Alias '{}' and '{}' should produce identical declarations",
428 rust_name, ts_name
429 );
430 }
431 }
432
433 #[test]
434 fn test_terminal_types_exist() {
435 let terminal_result = get_type_decl("TerminalResult");
436 assert!(
437 terminal_result.is_some(),
438 "TerminalResult should be defined"
439 );
440 let decl = terminal_result.unwrap();
441 assert!(
442 decl.contains("bufferId"),
443 "TerminalResult should have bufferId field"
444 );
445 assert!(
446 decl.contains("terminalId"),
447 "TerminalResult should have terminalId field"
448 );
449 assert!(
450 decl.contains("splitId"),
451 "TerminalResult should have splitId field"
452 );
453
454 let terminal_opts = get_type_decl("CreateTerminalOptions");
455 assert!(
456 terminal_opts.is_some(),
457 "CreateTerminalOptions should be defined"
458 );
459 }
460
461 #[test]
462 fn test_cursor_info_type_exists() {
463 let cursor_info = get_type_decl("CursorInfo");
464 assert!(cursor_info.is_some(), "CursorInfo should be defined");
465 let decl = cursor_info.unwrap();
466 assert!(
467 decl.contains("position"),
468 "CursorInfo should have position field"
469 );
470 assert!(
471 decl.contains("selection"),
472 "CursorInfo should have selection field"
473 );
474 }
475
476 #[test]
477 fn test_collect_ts_types_no_duplicates() {
478 let output = collect_ts_types();
479 let lines: Vec<&str> = output.lines().collect();
480
481 let mut declarations = std::collections::HashSet::new();
483 for line in &lines {
484 let trimmed = line.trim();
485 if trimmed.starts_with("type ") && trimmed.contains('=') {
487 let name = trimmed
488 .strip_prefix("type ")
489 .unwrap()
490 .split(|c: char| c == '=' || c.is_whitespace())
491 .next()
492 .unwrap();
493 assert!(
494 declarations.insert(name.to_string()),
495 "Duplicate type declaration found: '{}'",
496 name
497 );
498 }
499 }
500 }
501
502 #[test]
503 fn test_collect_ts_types_includes_dependency_types() {
504 let output = collect_ts_types();
505 let required_types = [
506 "TextPropertyEntry",
507 "TsCompositeLayoutConfig",
508 "TsCompositeSourceConfig",
509 "TsCompositePaneStyle",
510 "TsCompositeHunk",
511 "TsCreateCompositeBufferOptions",
512 "PromptSuggestion",
513 "BufferInfo",
514 "CursorInfo",
515 "TerminalResult",
516 "CreateTerminalOptions",
517 ];
518
519 for type_name in &required_types {
520 assert!(
521 output.contains(type_name),
522 "collect_ts_types output should contain type '{}'",
523 type_name
524 );
525 }
526 }
527
528 #[test]
529 fn test_generated_dts_validates_as_typescript() {
530 use crate::backend::quickjs_backend::{JSEDITORAPI_TS_EDITOR_API, JSEDITORAPI_TS_PREAMBLE};
531
532 let ts_types = collect_ts_types();
533 let content = format!(
534 "{}\n{}\n{}",
535 JSEDITORAPI_TS_PREAMBLE, ts_types, JSEDITORAPI_TS_EDITOR_API
536 );
537
538 validate_typescript(&content).expect("Generated TypeScript should be syntactically valid");
539 }
540
541 #[test]
542 fn test_generated_dts_no_undefined_type_references() {
543 use crate::backend::quickjs_backend::{JSEDITORAPI_TS_EDITOR_API, JSEDITORAPI_TS_PREAMBLE};
544
545 let ts_types = collect_ts_types();
546 let content = format!(
547 "{}\n{}\n{}",
548 JSEDITORAPI_TS_PREAMBLE, ts_types, JSEDITORAPI_TS_EDITOR_API
549 );
550
551 let mut defined_types = std::collections::HashSet::new();
553 for builtin in &[
555 "number",
556 "string",
557 "boolean",
558 "void",
559 "unknown",
560 "null",
561 "undefined",
562 "Record",
563 "Array",
564 "Promise",
565 "ProcessHandle",
566 "PromiseLike",
567 "BufferId",
568 "SplitId",
569 "EditorAPI",
570 ] {
571 defined_types.insert(builtin.to_string());
572 }
573
574 for line in content.lines() {
576 let trimmed = line.trim();
577 if trimmed.starts_with("type ") && trimmed.contains('=') {
578 if let Some(name) = trimmed
579 .strip_prefix("type ")
580 .unwrap()
581 .split(|c: char| c == '=' || c.is_whitespace())
582 .next()
583 {
584 defined_types.insert(name.to_string());
585 }
586 }
587 if trimmed.starts_with("interface ") {
588 if let Some(name) = trimmed
589 .strip_prefix("interface ")
590 .unwrap()
591 .split(|c: char| !c.is_alphanumeric() && c != '_')
592 .next()
593 {
594 defined_types.insert(name.to_string());
595 }
596 }
597 }
598
599 let interface_section = JSEDITORAPI_TS_EDITOR_API;
602 let mut undefined_refs = Vec::new();
603
604 for line in interface_section.lines() {
605 let trimmed = line.trim();
606
607 if trimmed.starts_with('*')
609 || trimmed.starts_with("/*")
610 || trimmed.starts_with("//")
611 || trimmed.is_empty()
612 || trimmed == "{"
613 || trimmed == "}"
614 {
615 continue;
616 }
617
618 for word in trimmed.split(|c: char| !c.is_alphanumeric() && c != '_') {
620 if word.is_empty() {
621 continue;
622 }
623 if word.chars().next().is_some_and(|c| c.is_uppercase())
625 && !defined_types.contains(word)
626 {
627 undefined_refs.push(word.to_string());
628 }
629 }
630 }
631
632 undefined_refs.sort();
634 undefined_refs.dedup();
635
636 assert!(
637 undefined_refs.is_empty(),
638 "Found undefined type references in EditorAPI interface: {:?}",
639 undefined_refs
640 );
641 }
642
643 #[test]
644 fn test_editor_api_cursor_methods_have_typed_returns() {
645 use crate::backend::quickjs_backend::JSEDITORAPI_TS_EDITOR_API;
646
647 let api = JSEDITORAPI_TS_EDITOR_API;
648
649 assert!(
651 api.contains("getPrimaryCursor(): CursorInfo | null;"),
652 "getPrimaryCursor should return CursorInfo | null, got: {}",
653 api.lines()
654 .find(|l| l.contains("getPrimaryCursor"))
655 .unwrap_or("not found")
656 );
657
658 assert!(
660 api.contains("getAllCursors(): CursorInfo[];"),
661 "getAllCursors should return CursorInfo[], got: {}",
662 api.lines()
663 .find(|l| l.contains("getAllCursors"))
664 .unwrap_or("not found")
665 );
666
667 assert!(
669 api.contains("getAllCursorPositions(): number[];"),
670 "getAllCursorPositions should return number[], got: {}",
671 api.lines()
672 .find(|l| l.contains("getAllCursorPositions"))
673 .unwrap_or("not found")
674 );
675 }
676
677 #[test]
678 fn test_editor_api_terminal_methods_use_defined_types() {
679 use crate::backend::quickjs_backend::JSEDITORAPI_TS_EDITOR_API;
680
681 let api = JSEDITORAPI_TS_EDITOR_API;
682
683 assert!(
685 api.contains("CreateTerminalOptions"),
686 "createTerminal should reference CreateTerminalOptions"
687 );
688 assert!(
689 api.contains("TerminalResult"),
690 "createTerminal should reference TerminalResult"
691 );
692 }
693
694 #[test]
695 fn test_editor_api_composite_methods_use_ts_prefix_types() {
696 use crate::backend::quickjs_backend::JSEDITORAPI_TS_EDITOR_API;
697
698 let api = JSEDITORAPI_TS_EDITOR_API;
699
700 assert!(
702 api.contains("TsCompositeHunk[]"),
703 "updateCompositeAlignment should use TsCompositeHunk[], not CompositeHunk[]"
704 );
705
706 assert!(
708 api.contains("TsCreateCompositeBufferOptions"),
709 "createCompositeBuffer should use TsCreateCompositeBufferOptions"
710 );
711 }
712
713 #[test]
714 fn test_editor_api_prompt_suggestions_use_prompt_suggestion() {
715 use crate::backend::quickjs_backend::JSEDITORAPI_TS_EDITOR_API;
716
717 let api = JSEDITORAPI_TS_EDITOR_API;
718
719 assert!(
721 api.contains("PromptSuggestion[]"),
722 "setPromptSuggestions should use PromptSuggestion[], not Suggestion[]"
723 );
724 }
725
726 #[test]
727 fn test_all_editor_api_methods_present() {
728 use crate::backend::quickjs_backend::JSEDITORAPI_TS_EDITOR_API;
729
730 let api = JSEDITORAPI_TS_EDITOR_API;
731
732 let expected_methods = vec![
734 "apiVersion",
735 "getActiveBufferId",
736 "getActiveSplitId",
737 "listBuffers",
738 "debug",
739 "info",
740 "warn",
741 "error",
742 "setStatus",
743 "copyToClipboard",
744 "setClipboard",
745 "registerCommand",
746 "unregisterCommand",
747 "setContext",
748 "executeAction",
749 "getCursorPosition",
750 "getBufferPath",
751 "getBufferLength",
752 "isBufferModified",
753 "saveBufferToPath",
754 "getBufferInfo",
755 "getPrimaryCursor",
756 "getAllCursors",
757 "getAllCursorPositions",
758 "getViewport",
759 "getCursorLine",
760 "getLineStartPosition",
761 "getLineEndPosition",
762 "getBufferLineCount",
763 "scrollToLineCenter",
764 "findBufferByPath",
765 "getBufferSavedDiff",
766 "insertText",
767 "deleteRange",
768 "insertAtCursor",
769 "openFile",
770 "openFileInSplit",
771 "showBuffer",
772 "closeBuffer",
773 "on",
774 "off",
775 "getEnv",
776 "getCwd",
777 "pathJoin",
778 "pathDirname",
779 "pathBasename",
780 "pathExtname",
781 "pathIsAbsolute",
782 "utf8ByteLength",
783 "fileExists",
784 "readFile",
785 "writeFile",
786 "readDir",
787 "getConfig",
788 "getUserConfig",
789 "reloadConfig",
790 "reloadThemes",
791 "reloadAndApplyTheme",
792 "registerGrammar",
793 "registerLanguageConfig",
794 "registerLspServer",
795 "reloadGrammars",
796 "getConfigDir",
797 "getThemesDir",
798 "applyTheme",
799 "getThemeSchema",
800 "getBuiltinThemes",
801 "getThemeData",
802 "saveThemeFile",
803 "themeFileExists",
804 "deleteTheme",
805 "fileStat",
806 "isProcessRunning",
807 "killProcess",
808 "pluginTranslate",
809 "createCompositeBuffer",
810 "updateCompositeAlignment",
811 "closeCompositeBuffer",
812 "getHighlights",
813 "addOverlay",
814 "clearNamespace",
815 "clearAllOverlays",
816 "clearOverlaysInRange",
817 "removeOverlay",
818 "addConceal",
819 "clearConcealNamespace",
820 "clearConcealsInRange",
821 "addSoftBreak",
822 "clearSoftBreakNamespace",
823 "clearSoftBreaksInRange",
824 "submitViewTransform",
825 "clearViewTransform",
826 "setLayoutHints",
827 "setFileExplorerDecorations",
828 "clearFileExplorerDecorations",
829 "addVirtualText",
830 "removeVirtualText",
831 "removeVirtualTextsByPrefix",
832 "clearVirtualTexts",
833 "clearVirtualTextNamespace",
834 "addVirtualLine",
835 "prompt",
836 "startPrompt",
837 "startPromptWithInitial",
838 "setPromptSuggestions",
839 "setPromptInputSync",
840 "defineMode",
841 "setEditorMode",
842 "getEditorMode",
843 "closeSplit",
844 "setSplitBuffer",
845 "focusSplit",
846 "setSplitScroll",
847 "setSplitRatio",
848 "setSplitLabel",
849 "clearSplitLabel",
850 "getSplitByLabel",
851 "distributeSplitsEvenly",
852 "setBufferCursor",
853 "setLineIndicator",
854 "clearLineIndicators",
855 "setLineNumbers",
856 "setViewMode",
857 "setViewState",
858 "getViewState",
859 "setLineWrap",
860 "createScrollSyncGroup",
861 "setScrollSyncAnchors",
862 "removeScrollSyncGroup",
863 "executeActions",
864 "showActionPopup",
865 "disableLspForLanguage",
866 "setLspRootUri",
867 "getAllDiagnostics",
868 "getHandlers",
869 "createVirtualBuffer",
870 "createVirtualBufferInSplit",
871 "createVirtualBufferInExistingSplit",
872 "setVirtualBufferContent",
873 "getTextPropertiesAtCursor",
874 "spawnProcess",
875 "spawnProcessWait",
876 "getBufferText",
877 "delay",
878 "sendLspRequest",
879 "spawnBackgroundProcess",
880 "killBackgroundProcess",
881 "createTerminal",
882 "sendTerminalInput",
883 "closeTerminal",
884 "refreshLines",
885 "getCurrentLocale",
886 "loadPlugin",
887 "unloadPlugin",
888 "reloadPlugin",
889 "listPlugins",
890 ];
891
892 let mut missing = Vec::new();
893 for method in &expected_methods {
894 let pattern = format!("{}(", method);
896 if !api.contains(&pattern) {
897 missing.push(*method);
898 }
899 }
900
901 assert!(
902 missing.is_empty(),
903 "Missing methods in EditorAPI interface: {:?}",
904 missing
905 );
906 }
907}