1use super::*;
13
14impl Editor {
15 pub fn new(
18 config: Config,
19 width: u16,
20 height: u16,
21 dir_context: DirectoryContext,
22 color_capability: crate::view::color_support::ColorCapability,
23 filesystem: Arc<dyn FileSystem + Send + Sync>,
24 ) -> AnyhowResult<Self> {
25 Self::with_working_dir(
26 config,
27 width,
28 height,
29 None,
30 dir_context,
31 true,
32 color_capability,
33 filesystem,
34 )
35 }
36
37 #[allow(clippy::too_many_arguments)]
40 pub fn with_working_dir(
41 config: Config,
42 width: u16,
43 height: u16,
44 working_dir: Option<PathBuf>,
45 dir_context: DirectoryContext,
46 plugins_enabled: bool,
47 color_capability: crate::view::color_support::ColorCapability,
48 filesystem: Arc<dyn FileSystem + Send + Sync>,
49 ) -> AnyhowResult<Self> {
50 tracing::info!("Building default grammar registry...");
51 let start = std::time::Instant::now();
52 let mut grammar_registry = crate::primitives::grammar::GrammarRegistry::defaults_only();
53 std::sync::Arc::get_mut(&mut grammar_registry)
59 .expect("defaults_only returned a shared Arc")
60 .apply_language_config(&config.languages);
61 tracing::info!("Default grammar registry built in {:?}", start.elapsed());
62 Self::with_options(
66 config,
67 width,
68 height,
69 working_dir,
70 filesystem,
71 plugins_enabled,
72 dir_context,
73 None,
74 color_capability,
75 grammar_registry,
76 )
77 }
78
79 #[allow(clippy::too_many_arguments)]
84 pub fn for_test(
85 config: Config,
86 width: u16,
87 height: u16,
88 working_dir: Option<PathBuf>,
89 dir_context: DirectoryContext,
90 color_capability: crate::view::color_support::ColorCapability,
91 filesystem: Arc<dyn FileSystem + Send + Sync>,
92 time_source: Option<SharedTimeSource>,
93 grammar_registry: Option<Arc<crate::primitives::grammar::GrammarRegistry>>,
94 ) -> AnyhowResult<Self> {
95 let mut grammar_registry =
96 grammar_registry.unwrap_or_else(crate::primitives::grammar::GrammarRegistry::empty);
97 std::sync::Arc::get_mut(&mut grammar_registry)
104 .expect("grammar registry Arc must be uniquely owned at for_test entry")
105 .apply_language_config(&config.languages);
106 let mut editor = Self::with_options(
107 config,
108 width,
109 height,
110 working_dir,
111 filesystem,
112 true,
113 dir_context,
114 time_source,
115 color_capability,
116 grammar_registry,
117 )?;
118 editor.needs_full_grammar_build = false;
121 Ok(editor)
122 }
123
124 #[allow(clippy::too_many_arguments)]
128 fn with_options(
129 mut config: Config,
130 width: u16,
131 height: u16,
132 working_dir: Option<PathBuf>,
133 filesystem: Arc<dyn FileSystem + Send + Sync>,
134 enable_plugins: bool,
135 dir_context: DirectoryContext,
136 time_source: Option<SharedTimeSource>,
137 color_capability: crate::view::color_support::ColorCapability,
138 grammar_registry: Arc<crate::primitives::grammar::GrammarRegistry>,
139 ) -> AnyhowResult<Self> {
140 let time_source = time_source.unwrap_or_else(RealTimeSource::shared);
142 tracing::info!("Editor::new called with width={}, height={}", width, height);
143
144 let working_dir = working_dir
146 .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
147
148 let working_dir = working_dir.canonicalize().unwrap_or(working_dir);
151
152 tracing::info!("Loading themes...");
154 let theme_loader = crate::view::theme::ThemeLoader::new(dir_context.themes_dir());
155 let scan_result =
159 crate::services::packages::scan_installed_packages(&dir_context.config_dir);
160
161 for (lang_id, lang_config) in &scan_result.language_configs {
163 config
164 .languages
165 .entry(lang_id.clone())
166 .or_insert_with(|| lang_config.clone());
167 }
168
169 for (lang_id, lsp_config) in &scan_result.lsp_configs {
171 config
172 .lsp
173 .entry(lang_id.clone())
174 .or_insert_with(|| LspLanguageConfig::Multi(vec![lsp_config.clone()]));
175 }
176
177 let theme_registry = theme_loader.load_all(&scan_result.bundle_theme_dirs);
178 tracing::info!("Themes loaded");
179
180 let theme = theme_registry.get_cloned(&config.theme).unwrap_or_else(|| {
182 tracing::warn!(
183 "Theme '{}' not found, falling back to default theme",
184 config.theme.0
185 );
186 theme_registry
187 .get_cloned(&crate::config::ThemeName(
188 crate::view::theme::THEME_HIGH_CONTRAST.to_string(),
189 ))
190 .expect("Default theme must exist")
191 });
192
193 theme.set_terminal_cursor_color();
195
196 let keybindings = Arc::new(RwLock::new(KeybindingResolver::new(&config)));
197
198 let mut buffers = HashMap::new();
200 let mut event_logs = HashMap::new();
201
202 let buffer_id = BufferId(1);
207 let mut state = EditorState::new(
208 width,
209 height,
210 config.editor.large_file_threshold_bytes as usize,
211 Arc::clone(&filesystem),
212 );
213 state
215 .margins
216 .configure_for_line_numbers(config.editor.line_numbers);
217 state.buffer_settings.tab_size = config.editor.tab_size;
218 state.buffer_settings.auto_close = config.editor.auto_close;
219 tracing::info!("EditorState created for buffer {:?}", buffer_id);
221 buffers.insert(buffer_id, state);
222 event_logs.insert(buffer_id, EventLog::new());
223
224 let mut buffer_metadata = HashMap::new();
226 buffer_metadata.insert(buffer_id, BufferMetadata::new());
227
228 let root_uri = types::file_path_to_lsp_uri(&working_dir);
230
231 let tokio_runtime = tokio::runtime::Builder::new_multi_thread()
233 .worker_threads(2) .thread_name("editor-async")
235 .enable_all()
236 .build()
237 .ok();
238
239 let async_bridge = AsyncBridge::new();
241
242 if tokio_runtime.is_none() {
243 tracing::warn!("Failed to create Tokio runtime - async features disabled");
244 }
245
246 let mut lsp = LspManager::new(root_uri);
248
249 if let Some(ref runtime) = tokio_runtime {
251 lsp.set_runtime(runtime.handle().clone(), async_bridge.clone());
252 }
253
254 for (language, lsp_configs) in &config.lsp {
256 lsp.set_language_configs(language.clone(), lsp_configs.as_slice().to_vec());
257 }
258
259 let universal_servers: Vec<LspServerConfig> = config
261 .universal_lsp
262 .values()
263 .flat_map(|lc| lc.as_slice().to_vec())
264 .filter(|c| c.enabled)
265 .collect();
266 lsp.set_universal_configs(universal_servers);
267
268 if working_dir.join("deno.json").exists() || working_dir.join("deno.jsonc").exists() {
271 tracing::info!("Detected Deno project (deno.json found), using deno lsp for JS/TS");
272 let deno_config = LspServerConfig {
273 command: "deno".to_string(),
274 args: vec!["lsp".to_string()],
275 enabled: true,
276 auto_start: false,
277 process_limits: ProcessLimits::default(),
278 initialization_options: Some(serde_json::json!({"enable": true})),
279 ..Default::default()
280 };
281 lsp.set_language_config("javascript".to_string(), deno_config.clone());
282 lsp.set_language_config("typescript".to_string(), deno_config);
283 }
284
285 let split_manager = SplitManager::new(buffer_id);
287
288 let mut split_view_states = HashMap::new();
290 let initial_split_id = split_manager.active_split();
291 let mut initial_view_state = SplitViewState::with_buffer(width, height, buffer_id);
292 initial_view_state.apply_config_defaults(
293 config.editor.line_numbers,
294 config.editor.highlight_current_line,
295 config.editor.line_wrap,
296 config.editor.wrap_indent,
297 config.editor.wrap_column,
298 config.editor.rulers.clone(),
299 );
300 split_view_states.insert(initial_split_id, initial_view_state);
301
302 let fs_manager = Arc::new(FsManager::new(Arc::clone(&filesystem)));
304
305 let command_registry = Arc::new(RwLock::new(CommandRegistry::new()));
307
308 let mut quick_open_registry = QuickOpenRegistry::new();
310 let process_spawner: Arc<dyn crate::services::remote::ProcessSpawner> =
311 Arc::new(crate::services::remote::LocalProcessSpawner);
312 quick_open_registry.register(Box::new(FileProvider::new(
313 Arc::clone(&filesystem),
314 Arc::clone(&process_spawner),
315 tokio_runtime.as_ref().map(|rt| rt.handle().clone()),
316 Some(async_bridge.sender()),
317 )));
318 quick_open_registry.register(Box::new(CommandProvider::new(
319 Arc::clone(&command_registry),
320 Arc::clone(&keybindings),
321 )));
322 quick_open_registry.register(Box::new(BufferProvider::new()));
323 quick_open_registry.register(Box::new(GotoLineProvider::new()));
324
325 let theme_cache = Arc::new(RwLock::new(theme_registry.to_json_map()));
327
328 let plugin_manager = PluginManager::new(
330 enable_plugins,
331 Arc::clone(&command_registry),
332 dir_context.clone(),
333 Arc::clone(&theme_cache),
334 );
335
336 #[cfg(feature = "plugins")]
339 if let Some(snapshot_handle) = plugin_manager.state_snapshot_handle() {
340 let mut snapshot = snapshot_handle.write().unwrap();
341 snapshot.working_dir = working_dir.clone();
342 }
343
344 if plugin_manager.is_active() {
351 let mut plugin_dirs: Vec<std::path::PathBuf> = vec![];
352
353 if let Ok(exe_path) = std::env::current_exe() {
355 if let Some(exe_dir) = exe_path.parent() {
356 let exe_plugin_dir = exe_dir.join("plugins");
357 if exe_plugin_dir.exists() {
358 plugin_dirs.push(exe_plugin_dir);
359 }
360 }
361 }
362
363 let working_plugin_dir = working_dir.join("plugins");
365 if working_plugin_dir.exists() && !plugin_dirs.contains(&working_plugin_dir) {
366 plugin_dirs.push(working_plugin_dir);
367 }
368
369 #[cfg(feature = "embed-plugins")]
371 if plugin_dirs.is_empty() {
372 if let Some(embedded_dir) =
373 crate::services::plugins::embedded::get_embedded_plugins_dir()
374 {
375 tracing::info!("Using embedded plugins from: {:?}", embedded_dir);
376 plugin_dirs.push(embedded_dir.clone());
377 }
378 }
379
380 let user_plugins_dir = dir_context.config_dir.join("plugins");
382 if user_plugins_dir.exists() && !plugin_dirs.contains(&user_plugins_dir) {
383 tracing::info!("Found user plugins directory: {:?}", user_plugins_dir);
384 plugin_dirs.push(user_plugins_dir.clone());
385 }
386
387 let packages_dir = dir_context.config_dir.join("plugins").join("packages");
389 if packages_dir.exists() {
390 if let Ok(entries) = std::fs::read_dir(&packages_dir) {
391 for entry in entries.flatten() {
392 let path = entry.path();
393 if path.is_dir() {
395 if let Some(name) = path.file_name().and_then(|n| n.to_str()) {
396 if !name.starts_with('.') {
397 tracing::info!("Found package manager plugin: {:?}", path);
398 plugin_dirs.push(path);
399 }
400 }
401 }
402 }
403 }
404 }
405
406 for dir in &scan_result.bundle_plugin_dirs {
408 tracing::info!("Found bundle plugin directory: {:?}", dir);
409 plugin_dirs.push(dir.clone());
410 }
411
412 if plugin_dirs.is_empty() {
413 tracing::debug!(
414 "No plugins directory found next to executable or in working dir: {:?}",
415 working_dir
416 );
417 }
418
419 for plugin_dir in plugin_dirs {
421 tracing::info!("Loading TypeScript plugins from: {:?}", plugin_dir);
422 let (errors, discovered_plugins) =
423 plugin_manager.load_plugins_from_dir_with_config(&plugin_dir, &config.plugins);
424
425 for (name, plugin_config) in discovered_plugins {
428 config.plugins.insert(name, plugin_config);
429 }
430
431 if !errors.is_empty() {
432 for err in &errors {
433 tracing::error!("TypeScript plugin load error: {}", err);
434 }
435 #[cfg(debug_assertions)]
437 panic!(
438 "TypeScript plugin loading failed with {} error(s): {}",
439 errors.len(),
440 errors.join("; ")
441 );
442 }
443 }
444 }
445
446 let file_explorer_width = config.file_explorer.width;
448 let recovery_enabled = config.editor.recovery_enabled;
449 let check_for_updates = config.check_for_updates;
450 let show_menu_bar = config.editor.show_menu_bar;
451 let show_tab_bar = config.editor.show_tab_bar;
452 let show_status_bar = config.editor.show_status_bar;
453 let show_prompt_line = config.editor.show_prompt_line;
454
455 let update_checker = if check_for_updates {
457 tracing::debug!("Update checking enabled, starting periodic checker");
458 Some(
459 crate::services::release_checker::start_periodic_update_check(
460 crate::services::release_checker::DEFAULT_RELEASES_URL,
461 time_source.clone(),
462 dir_context.data_dir.clone(),
463 ),
464 )
465 } else {
466 tracing::debug!("Update checking disabled by config");
467 None
468 };
469
470 let user_config_raw = Config::read_user_config_raw(&working_dir);
472
473 let config_arc = Arc::new(config);
480 let config_cached_json =
481 Arc::new(serde_json::to_value(&*config_arc).unwrap_or(serde_json::Value::Null));
482 let config_snapshot_anchor = Arc::clone(&config_arc);
483
484 let mut editor = Editor {
485 buffers,
486 event_logs,
487 next_buffer_id: 2,
488 config: config_arc,
489 config_snapshot_anchor,
490 config_cached_json,
491 user_config_raw: Arc::new(user_config_raw),
492 dir_context: dir_context.clone(),
493 grammar_registry,
494 pending_grammars: scan_result
495 .additional_grammars
496 .iter()
497 .map(|g| PendingGrammar {
498 language: g.language.clone(),
499 grammar_path: g.path.to_string_lossy().to_string(),
500 extensions: g.extensions.clone(),
501 })
502 .collect(),
503 grammar_reload_pending: false,
504 grammar_build_in_progress: false,
505 needs_full_grammar_build: true,
506 streaming_grep_cancellation: None,
507 pending_grammar_callbacks: Vec::new(),
508 theme,
509 theme_registry,
510 theme_cache,
511 ansi_background: None,
512 ansi_background_path: None,
513 background_fade: crate::primitives::ansi_background::DEFAULT_BACKGROUND_FADE,
514 keybindings,
515 clipboard: crate::services::clipboard::Clipboard::new(),
516 should_quit: false,
517 should_detach: false,
518 session_mode: false,
519 software_cursor_only: false,
520 session_name: None,
521 pending_escape_sequences: Vec::new(),
522 restart_with_dir: None,
523 status_message: None,
524 plugin_status_message: None,
525 plugin_errors: Vec::new(),
526 prompt: None,
527 terminal_width: width,
528 terminal_height: height,
529 lsp: Some(lsp),
530 buffer_metadata,
531 mode_registry: ModeRegistry::new(),
532 tokio_runtime,
533 async_bridge: Some(async_bridge),
534 split_manager,
535 split_view_states,
536 previous_viewports: HashMap::new(),
537 scroll_sync_manager: ScrollSyncManager::new(),
538 file_explorer: None,
539 preview: None,
540 suppress_position_history_once: false,
541 fs_manager,
542 filesystem,
543 local_filesystem: Arc::new(crate::model::filesystem::StdFileSystem),
544 process_spawner,
545 file_explorer_visible: false,
546 file_explorer_sync_in_progress: false,
547 file_explorer_width_percent: file_explorer_width,
548 pending_file_explorer_show_hidden: None,
549 pending_file_explorer_show_gitignored: None,
550 menu_bar_visible: show_menu_bar,
551 file_explorer_decorations: HashMap::new(),
552 file_explorer_decoration_cache:
553 crate::view::file_tree::FileExplorerDecorationCache::default(),
554 menu_bar_auto_shown: false,
555 tab_bar_visible: show_tab_bar,
556 status_bar_visible: show_status_bar,
557 prompt_line_visible: show_prompt_line,
558 mouse_enabled: true,
559 same_buffer_scroll_sync: false,
560 mouse_cursor_position: None,
561 gpm_active: false,
562 key_context: KeyContext::Normal,
563 menu_state: crate::view::ui::MenuState::new(dir_context.themes_dir()),
564 menus: crate::config::MenuConfig::translated(),
565 working_dir,
566 position_history: PositionHistory::new(),
567 in_navigation: false,
568 next_lsp_request_id: 0,
569 pending_completion_requests: HashSet::new(),
570 completion_items: None,
571 scheduled_completion_trigger: None,
572 completion_service: crate::services::completion::CompletionService::new(),
573 dabbrev_state: None,
574 pending_goto_definition_request: None,
575 hover: hover::HoverState::default(),
576 pending_references_request: None,
577 pending_references_symbol: String::new(),
578 pending_signature_help_request: None,
579 pending_code_actions_requests: HashSet::new(),
580 pending_code_actions_server_names: HashMap::new(),
581 pending_code_actions: None,
582 pending_inlay_hints_requests: HashMap::new(),
583 pending_folding_range_requests: HashMap::new(),
584 folding_ranges_in_flight: HashMap::new(),
585 folding_ranges_debounce: HashMap::new(),
586 pending_semantic_token_requests: HashMap::new(),
587 semantic_tokens_in_flight: HashMap::new(),
588 pending_semantic_token_range_requests: HashMap::new(),
589 semantic_tokens_range_in_flight: HashMap::new(),
590 semantic_tokens_range_last_request: HashMap::new(),
591 semantic_tokens_range_applied: HashMap::new(),
592 semantic_tokens_full_debounce: HashMap::new(),
593 search_state: None,
594 search_namespace: crate::view::overlay::OverlayNamespace::from_string(
595 "search".to_string(),
596 ),
597 lsp_diagnostic_namespace: crate::view::overlay::OverlayNamespace::from_string(
598 "lsp-diagnostic".to_string(),
599 ),
600 pending_search_range: None,
601 interactive_replace_state: None,
602 mouse_state: MouseState::default(),
603 tab_context_menu: None,
604 theme_info_popup: None,
605 cached_layout: CachedLayout::default(),
606 command_registry,
607 quick_open_registry,
608 plugin_manager,
609 plugin_dev_workspaces: HashMap::new(),
610 seen_byte_ranges: HashMap::new(),
611 panel_ids: HashMap::new(),
612 buffer_groups: HashMap::new(),
613 buffer_to_group: HashMap::new(),
614 next_buffer_group_id: 0,
615 grouped_subtrees: HashMap::new(),
616 background_process_handles: HashMap::new(),
617 prompt_histories: {
618 let mut histories = HashMap::new();
620 for history_name in ["search", "replace", "goto_line"] {
621 let path = dir_context.prompt_history_path(history_name);
622 let history = crate::input::input_history::InputHistory::load_from_file(&path)
623 .unwrap_or_else(|e| {
624 tracing::warn!("Failed to load {} history: {}", history_name, e);
625 crate::input::input_history::InputHistory::new()
626 });
627 histories.insert(history_name.to_string(), history);
628 }
629 histories
630 },
631 pending_async_prompt_callback: None,
632 lsp_progress: std::collections::HashMap::new(),
633 lsp_server_statuses: std::collections::HashMap::new(),
634 lsp_window_messages: Vec::new(),
635 lsp_log_messages: Vec::new(),
636 diagnostic_result_ids: HashMap::new(),
637 scheduled_diagnostic_pull: None,
638 scheduled_inlay_hints_request: None,
639 stored_push_diagnostics: HashMap::new(),
640 stored_pull_diagnostics: HashMap::new(),
641 stored_diagnostics: Arc::new(HashMap::new()),
642 stored_folding_ranges: Arc::new(HashMap::new()),
643 event_broadcaster: crate::model::control_event::EventBroadcaster::default(),
644 bookmarks: bookmarks::BookmarkState::default(),
645 search_case_sensitive: true,
646 search_whole_word: false,
647 search_use_regex: false,
648 search_confirm_each: false,
649 macros: macros::MacroState::default(),
650 #[cfg(feature = "plugins")]
651 pending_plugin_actions: Vec::new(),
652 #[cfg(feature = "plugins")]
653 plugin_render_requested: false,
654 chord_state: Vec::new(),
655 pending_lsp_confirmation: None,
656 pending_lsp_status_popup: None,
657 user_dismissed_lsp_languages: std::collections::HashSet::new(),
658 pending_close_buffer: None,
659 auto_revert_enabled: true,
660 last_auto_revert_poll: time_source.now(),
661 last_file_tree_poll: time_source.now(),
662 git_index_resolved: false,
663 file_mod_times: HashMap::new(),
664 dir_mod_times: HashMap::new(),
665 pending_file_poll_rx: None,
666 pending_dir_poll_rx: None,
667 file_rapid_change_counts: HashMap::new(),
668 file_open_state: None,
669 file_browser_layout: None,
670 recovery_service: {
671 let recovery_config = RecoveryConfig {
672 enabled: recovery_enabled,
673 ..RecoveryConfig::default()
674 };
675 RecoveryService::with_config_and_dir(recovery_config, dir_context.recovery_dir())
676 },
677 full_redraw_requested: false,
678 time_source: time_source.clone(),
679 last_auto_recovery_save: time_source.now(),
680 last_persistent_auto_save: time_source.now(),
681 active_custom_contexts: HashSet::new(),
682 plugin_global_state: HashMap::new(),
683 editor_mode: None,
684 warning_log: None,
685 status_log_path: None,
686 warning_domains: WarningDomainRegistry::new(),
687 update_checker,
688 terminal_manager: crate::services::terminal::TerminalManager::new(),
689 terminal_buffers: HashMap::new(),
690 terminal_backing_files: HashMap::new(),
691 terminal_log_files: HashMap::new(),
692 terminal_mode: false,
693 keyboard_capture: false,
694 terminal_mode_resume: std::collections::HashSet::new(),
695 previous_click_time: None,
696 previous_click_position: None,
697 click_count: 0,
698 settings_state: None,
699 calibration_wizard: None,
700 event_debug: None,
701 keybinding_editor: None,
702 key_translator: crate::input::key_translator::KeyTranslator::load_from_config_dir(
703 &dir_context.config_dir,
704 )
705 .unwrap_or_default(),
706 color_capability,
707 pending_file_opens: Vec::new(),
708 pending_hot_exit_recovery: false,
709 wait_tracking: HashMap::new(),
710 completed_waits: Vec::new(),
711 stdin_stream: stdin_stream::StdinStream::default(),
712 line_scan: line_scan::LineScan::default(),
713 search_scan: search_scan::SearchScan::default(),
714 search_overlay_top_byte: None,
715 review_hunks: Vec::new(),
716 active_action_popup: None,
717 composite_buffers: HashMap::new(),
718 composite_view_states: HashMap::new(),
719 };
720
721 editor.clipboard.apply_config(&editor.config.clipboard);
723
724 #[cfg(feature = "plugins")]
725 {
726 editor.update_plugin_state_snapshot();
727 if editor.plugin_manager.is_active() {
728 editor.plugin_manager.run_hook(
729 "editor_initialized",
730 crate::services::plugins::hooks::HookArgs::EditorInitialized,
731 );
732 }
733 }
734
735 Ok(editor)
736 }
737
738 pub fn event_broadcaster(&self) -> &crate::model::control_event::EventBroadcaster {
740 &self.event_broadcaster
741 }
742
743 pub(super) fn start_background_grammar_build(
748 &mut self,
749 additional: Vec<crate::primitives::grammar::GrammarSpec>,
750 callback_ids: Vec<fresh_core::api::JsCallbackId>,
751 ) {
752 let Some(bridge) = &self.async_bridge else {
753 return;
754 };
755 self.grammar_build_in_progress = true;
756 let sender = bridge.sender();
757 let config_dir = self.dir_context.config_dir.clone();
758 tracing::info!(
759 "Spawning background grammar build thread ({} plugin grammars)...",
760 additional.len()
761 );
762 std::thread::Builder::new()
763 .name("grammar-build".to_string())
764 .spawn(move || {
765 tracing::info!("[grammar-build] Thread started");
766 let start = std::time::Instant::now();
767 let registry = if additional.is_empty() {
768 crate::primitives::grammar::GrammarRegistry::for_editor(config_dir)
769 } else {
770 crate::primitives::grammar::GrammarRegistry::for_editor_with_additional(
771 config_dir,
772 &additional,
773 )
774 };
775 tracing::info!("[grammar-build] Complete in {:?}", start.elapsed());
776 drop(sender.send(
777 crate::services::async_bridge::AsyncMessage::GrammarRegistryBuilt {
778 registry,
779 callback_ids,
780 },
781 ));
782 })
783 .ok();
784 }
785}