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