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