1use crate::context_editor::{
2 ContextEditor, ContextEditorToolbarItem, ContextEditorToolbarItemEvent, DEFAULT_TAB_TITLE,
3};
4use crate::context_history::ContextHistory;
5use crate::{
6 slash_command::SlashCommandCompletionProvider,
7 terminal_inline_assistant::TerminalInlineAssistant, Context, ContextId, ContextStore,
8 ContextStoreEvent, DeployHistory, DeployPromptLibrary, InlineAssistant, InsertDraggedFiles,
9 NewContext, ToggleFocus, ToggleModelSelector,
10};
11use anyhow::Result;
12use assistant_settings::{AssistantDockPosition, AssistantSettings};
13use assistant_slash_command::SlashCommandWorkingSet;
14use assistant_tool::ToolWorkingSet;
15use client::{proto, Client, Status};
16use collections::HashMap;
17use editor::{Editor, EditorEvent};
18use fs::Fs;
19use gpui::{
20 canvas, div, prelude::*, Action, AnyView, AppContext, AsyncWindowContext, EventEmitter,
21 ExternalPaths, FocusHandle, FocusableView, InteractiveElement, IntoElement, Model,
22 ParentElement, Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Subscription,
23 Task, UpdateGlobal, View, WeakView,
24};
25use language::{LanguageRegistry, LspAdapterDelegate};
26use language_model::{
27 LanguageModelProvider, LanguageModelProviderId, LanguageModelRegistry, ZED_CLOUD_PROVIDER_ID,
28};
29use language_model_selector::LanguageModelSelector;
30use project::lsp_store::LocalLspAdapterDelegate;
31use project::Project;
32use prompt_library::{open_prompt_library, PromptBuilder, PromptLibrary};
33use search::{buffer_search::DivRegistrar, BufferSearchBar};
34use settings::{update_settings_file, Settings};
35use smol::stream::StreamExt;
36use std::{ops::ControlFlow, path::PathBuf, sync::Arc};
37use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
38use ui::{prelude::*, ContextMenu, ElevationIndex, PopoverMenu, PopoverMenuHandle, Tooltip};
39use util::{maybe, ResultExt};
40use workspace::DraggedTab;
41use workspace::{
42 dock::{DockPosition, Panel, PanelEvent},
43 item::Item,
44 pane, DraggedSelection, Pane, ShowConfiguration, ToggleZoom, Workspace,
45};
46use zed_actions::InlineAssist;
47
48pub fn init(cx: &mut AppContext) {
49 workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
50 cx.observe_new_views(
51 |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
52 workspace
53 .register_action(|workspace, _: &ToggleFocus, cx| {
54 let settings = AssistantSettings::get_global(cx);
55 if !settings.enabled {
56 return;
57 }
58
59 workspace.toggle_panel_focus::<AssistantPanel>(cx);
60 })
61 .register_action(ContextEditor::quote_selection)
62 .register_action(ContextEditor::insert_selection)
63 .register_action(ContextEditor::copy_code)
64 .register_action(ContextEditor::insert_dragged_files)
65 .register_action(AssistantPanel::show_configuration)
66 .register_action(AssistantPanel::create_new_context)
67 .register_action(AssistantPanel::restart_context_servers);
68 },
69 )
70 .detach();
71
72 cx.observe_new_views(
73 |terminal_panel: &mut TerminalPanel, cx: &mut ViewContext<TerminalPanel>| {
74 let settings = AssistantSettings::get_global(cx);
75 terminal_panel.set_assistant_enabled(settings.enabled, cx);
76 },
77 )
78 .detach();
79}
80
81pub enum AssistantPanelEvent {
82 ContextEdited,
83}
84
85pub struct AssistantPanel {
86 pane: View<Pane>,
87 workspace: WeakView<Workspace>,
88 width: Option<Pixels>,
89 height: Option<Pixels>,
90 project: Model<Project>,
91 context_store: Model<ContextStore>,
92 languages: Arc<LanguageRegistry>,
93 fs: Arc<dyn Fs>,
94 subscriptions: Vec<Subscription>,
95 model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
96 model_summary_editor: View<Editor>,
97 authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
98 configuration_subscription: Option<Subscription>,
99 client_status: Option<client::Status>,
100 watch_client_status: Option<Task<()>>,
101 pub(crate) show_zed_ai_notice: bool,
102}
103
104enum InlineAssistTarget {
105 Editor(View<Editor>, bool),
106 Terminal(View<TerminalView>),
107}
108
109impl AssistantPanel {
110 pub fn load(
111 workspace: WeakView<Workspace>,
112 prompt_builder: Arc<PromptBuilder>,
113 cx: AsyncWindowContext,
114 ) -> Task<Result<View<Self>>> {
115 cx.spawn(|mut cx| async move {
116 let slash_commands = Arc::new(SlashCommandWorkingSet::default());
117 let tools = Arc::new(ToolWorkingSet::default());
118 let context_store = workspace
119 .update(&mut cx, |workspace, cx| {
120 let project = workspace.project().clone();
121 ContextStore::new(project, prompt_builder.clone(), slash_commands, tools, cx)
122 })?
123 .await?;
124
125 workspace.update(&mut cx, |workspace, cx| {
126 // TODO: deserialize state.
127 cx.new_view(|cx| Self::new(workspace, context_store, cx))
128 })
129 })
130 }
131
132 fn new(
133 workspace: &Workspace,
134 context_store: Model<ContextStore>,
135 cx: &mut ViewContext<Self>,
136 ) -> Self {
137 let model_selector_menu_handle = PopoverMenuHandle::default();
138 let model_summary_editor = cx.new_view(Editor::single_line);
139 let context_editor_toolbar = cx.new_view(|cx| {
140 ContextEditorToolbarItem::new(
141 workspace,
142 model_selector_menu_handle.clone(),
143 model_summary_editor.clone(),
144 cx,
145 )
146 });
147
148 let pane = cx.new_view(|cx| {
149 let mut pane = Pane::new(
150 workspace.weak_handle(),
151 workspace.project().clone(),
152 Default::default(),
153 None,
154 NewContext.boxed_clone(),
155 cx,
156 );
157
158 let project = workspace.project().clone();
159 pane.set_custom_drop_handle(cx, move |_, dropped_item, cx| {
160 let action = maybe!({
161 if project.read(cx).is_local() {
162 if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() {
163 return Some(InsertDraggedFiles::ExternalFiles(paths.paths().to_vec()));
164 }
165 }
166
167 let project_paths = if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>()
168 {
169 if &tab.pane == cx.view() {
170 return None;
171 }
172 let item = tab.pane.read(cx).item_for_index(tab.ix);
173 Some(
174 item.and_then(|item| item.project_path(cx))
175 .into_iter()
176 .collect::<Vec<_>>(),
177 )
178 } else if let Some(selection) = dropped_item.downcast_ref::<DraggedSelection>()
179 {
180 Some(
181 selection
182 .items()
183 .filter_map(|item| {
184 project.read(cx).path_for_entry(item.entry_id, cx)
185 })
186 .collect::<Vec<_>>(),
187 )
188 } else {
189 None
190 }?;
191
192 let paths = project_paths
193 .into_iter()
194 .filter_map(|project_path| {
195 let worktree = project
196 .read(cx)
197 .worktree_for_id(project_path.worktree_id, cx)?;
198
199 let mut full_path = PathBuf::from(worktree.read(cx).root_name());
200 full_path.push(&project_path.path);
201 Some(full_path)
202 })
203 .collect::<Vec<_>>();
204
205 Some(InsertDraggedFiles::ProjectPaths(paths))
206 });
207
208 if let Some(action) = action {
209 cx.dispatch_action(action.boxed_clone());
210 }
211
212 ControlFlow::Break(())
213 });
214
215 pane.set_can_navigate(true, cx);
216 pane.display_nav_history_buttons(None);
217 pane.set_should_display_tab_bar(|_| true);
218 pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
219 let focus_handle = pane.focus_handle(cx);
220 let left_children = IconButton::new("history", IconName::HistoryRerun)
221 .icon_size(IconSize::Small)
222 .on_click(cx.listener({
223 let focus_handle = focus_handle.clone();
224 move |_, _, cx| {
225 focus_handle.focus(cx);
226 cx.dispatch_action(DeployHistory.boxed_clone())
227 }
228 }))
229 .tooltip({
230 let focus_handle = focus_handle.clone();
231 move |cx| {
232 Tooltip::for_action_in(
233 "Open History",
234 &DeployHistory,
235 &focus_handle,
236 cx,
237 )
238 }
239 })
240 .toggle_state(
241 pane.active_item()
242 .map_or(false, |item| item.downcast::<ContextHistory>().is_some()),
243 );
244 let _pane = cx.view().clone();
245 let right_children = h_flex()
246 .gap(DynamicSpacing::Base02.rems(cx))
247 .child(
248 IconButton::new("new-chat", IconName::Plus)
249 .icon_size(IconSize::Small)
250 .on_click(
251 cx.listener(|_, _, cx| {
252 cx.dispatch_action(NewContext.boxed_clone())
253 }),
254 )
255 .tooltip(move |cx| {
256 Tooltip::for_action_in("New Chat", &NewContext, &focus_handle, cx)
257 }),
258 )
259 .child(
260 PopoverMenu::new("assistant-panel-popover-menu")
261 .trigger(
262 IconButton::new("menu", IconName::EllipsisVertical)
263 .icon_size(IconSize::Small)
264 .tooltip(|cx| Tooltip::text("Toggle Assistant Menu", cx)),
265 )
266 .menu(move |cx| {
267 let zoom_label = if _pane.read(cx).is_zoomed() {
268 "Zoom Out"
269 } else {
270 "Zoom In"
271 };
272 let focus_handle = _pane.focus_handle(cx);
273 Some(ContextMenu::build(cx, move |menu, _| {
274 menu.context(focus_handle.clone())
275 .action("New Chat", Box::new(NewContext))
276 .action("History", Box::new(DeployHistory))
277 .action("Prompt Library", Box::new(DeployPromptLibrary))
278 .action("Configure", Box::new(ShowConfiguration))
279 .action(zoom_label, Box::new(ToggleZoom))
280 }))
281 }),
282 )
283 .into_any_element()
284 .into();
285
286 (Some(left_children.into_any_element()), right_children)
287 });
288 pane.toolbar().update(cx, |toolbar, cx| {
289 toolbar.add_item(context_editor_toolbar.clone(), cx);
290 toolbar.add_item(cx.new_view(BufferSearchBar::new), cx)
291 });
292 pane
293 });
294
295 let subscriptions = vec![
296 cx.observe(&pane, |_, _, cx| cx.notify()),
297 cx.subscribe(&pane, Self::handle_pane_event),
298 cx.subscribe(&context_editor_toolbar, Self::handle_toolbar_event),
299 cx.subscribe(&model_summary_editor, Self::handle_summary_editor_event),
300 cx.subscribe(&context_store, Self::handle_context_store_event),
301 cx.subscribe(
302 &LanguageModelRegistry::global(cx),
303 |this, _, event: &language_model::Event, cx| match event {
304 language_model::Event::ActiveModelChanged => {
305 this.completion_provider_changed(cx);
306 }
307 language_model::Event::ProviderStateChanged => {
308 this.ensure_authenticated(cx);
309 cx.notify()
310 }
311 language_model::Event::AddedProvider(_)
312 | language_model::Event::RemovedProvider(_) => {
313 this.ensure_authenticated(cx);
314 }
315 },
316 ),
317 ];
318
319 let watch_client_status = Self::watch_client_status(workspace.client().clone(), cx);
320
321 let mut this = Self {
322 pane,
323 workspace: workspace.weak_handle(),
324 width: None,
325 height: None,
326 project: workspace.project().clone(),
327 context_store,
328 languages: workspace.app_state().languages.clone(),
329 fs: workspace.app_state().fs.clone(),
330 subscriptions,
331 model_selector_menu_handle,
332 model_summary_editor,
333 authenticate_provider_task: None,
334 configuration_subscription: None,
335 client_status: None,
336 watch_client_status: Some(watch_client_status),
337 show_zed_ai_notice: false,
338 };
339 this.new_context(cx);
340 this
341 }
342
343 fn watch_client_status(client: Arc<Client>, cx: &mut ViewContext<Self>) -> Task<()> {
344 let mut status_rx = client.status();
345
346 cx.spawn(|this, mut cx| async move {
347 while let Some(status) = status_rx.next().await {
348 this.update(&mut cx, |this, cx| {
349 if this.client_status.is_none()
350 || this
351 .client_status
352 .map_or(false, |old_status| old_status != status)
353 {
354 this.update_zed_ai_notice_visibility(status, cx);
355 }
356 this.client_status = Some(status);
357 })
358 .log_err();
359 }
360 this.update(&mut cx, |this, _cx| this.watch_client_status = None)
361 .log_err();
362 })
363 }
364
365 fn handle_pane_event(
366 &mut self,
367 pane: View<Pane>,
368 event: &pane::Event,
369 cx: &mut ViewContext<Self>,
370 ) {
371 let update_model_summary = match event {
372 pane::Event::Remove { .. } => {
373 cx.emit(PanelEvent::Close);
374 false
375 }
376 pane::Event::ZoomIn => {
377 cx.emit(PanelEvent::ZoomIn);
378 false
379 }
380 pane::Event::ZoomOut => {
381 cx.emit(PanelEvent::ZoomOut);
382 false
383 }
384
385 pane::Event::AddItem { item } => {
386 self.workspace
387 .update(cx, |workspace, cx| {
388 item.added_to_pane(workspace, self.pane.clone(), cx)
389 })
390 .ok();
391 true
392 }
393
394 pane::Event::ActivateItem { local, .. } => {
395 if *local {
396 self.workspace
397 .update(cx, |workspace, cx| {
398 workspace.unfollow_in_pane(&pane, cx);
399 })
400 .ok();
401 }
402 cx.emit(AssistantPanelEvent::ContextEdited);
403 true
404 }
405 pane::Event::RemovedItem { .. } => {
406 let has_configuration_view = self
407 .pane
408 .read(cx)
409 .items_of_type::<ConfigurationView>()
410 .next()
411 .is_some();
412
413 if !has_configuration_view {
414 self.configuration_subscription = None;
415 }
416
417 cx.emit(AssistantPanelEvent::ContextEdited);
418 true
419 }
420
421 _ => false,
422 };
423
424 if update_model_summary {
425 if let Some(editor) = self.active_context_editor(cx) {
426 self.show_updated_summary(&editor, cx)
427 }
428 }
429 }
430
431 fn handle_summary_editor_event(
432 &mut self,
433 model_summary_editor: View<Editor>,
434 event: &EditorEvent,
435 cx: &mut ViewContext<Self>,
436 ) {
437 if matches!(event, EditorEvent::Edited { .. }) {
438 if let Some(context_editor) = self.active_context_editor(cx) {
439 let new_summary = model_summary_editor.read(cx).text(cx);
440 context_editor.update(cx, |context_editor, cx| {
441 context_editor.context.update(cx, |context, cx| {
442 if context.summary().is_none()
443 && (new_summary == DEFAULT_TAB_TITLE || new_summary.trim().is_empty())
444 {
445 return;
446 }
447 context.custom_summary(new_summary, cx)
448 });
449 });
450 }
451 }
452 }
453
454 fn update_zed_ai_notice_visibility(
455 &mut self,
456 client_status: Status,
457 cx: &mut ViewContext<Self>,
458 ) {
459 let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
460
461 // If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is
462 // the provider, we want to show a nudge to sign in.
463 let show_zed_ai_notice = client_status.is_signed_out()
464 && active_provider.map_or(true, |provider| provider.id().0 == ZED_CLOUD_PROVIDER_ID);
465
466 self.show_zed_ai_notice = show_zed_ai_notice;
467 cx.notify();
468 }
469
470 fn handle_toolbar_event(
471 &mut self,
472 _: View<ContextEditorToolbarItem>,
473 _: &ContextEditorToolbarItemEvent,
474 cx: &mut ViewContext<Self>,
475 ) {
476 if let Some(context_editor) = self.active_context_editor(cx) {
477 context_editor.update(cx, |context_editor, cx| {
478 context_editor.context.update(cx, |context, cx| {
479 context.summarize(true, cx);
480 })
481 })
482 }
483 }
484
485 fn handle_context_store_event(
486 &mut self,
487 _context_store: Model<ContextStore>,
488 event: &ContextStoreEvent,
489 cx: &mut ViewContext<Self>,
490 ) {
491 let ContextStoreEvent::ContextCreated(context_id) = event;
492 let Some(context) = self
493 .context_store
494 .read(cx)
495 .loaded_context_for_id(&context_id, cx)
496 else {
497 log::error!("no context found with ID: {}", context_id.to_proto());
498 return;
499 };
500 let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
501 .log_err()
502 .flatten();
503
504 let assistant_panel = cx.view().downgrade();
505 let editor = cx.new_view(|cx| {
506 let mut editor = ContextEditor::for_context(
507 context,
508 self.fs.clone(),
509 self.workspace.clone(),
510 self.project.clone(),
511 lsp_adapter_delegate,
512 assistant_panel,
513 cx,
514 );
515 editor.insert_default_prompt(cx);
516 editor
517 });
518
519 self.show_context(editor.clone(), cx);
520 }
521
522 fn completion_provider_changed(&mut self, cx: &mut ViewContext<Self>) {
523 if let Some(editor) = self.active_context_editor(cx) {
524 editor.update(cx, |active_context, cx| {
525 active_context
526 .context
527 .update(cx, |context, cx| context.completion_provider_changed(cx))
528 })
529 }
530
531 let Some(new_provider_id) = LanguageModelRegistry::read_global(cx)
532 .active_provider()
533 .map(|p| p.id())
534 else {
535 return;
536 };
537
538 if self
539 .authenticate_provider_task
540 .as_ref()
541 .map_or(true, |(old_provider_id, _)| {
542 *old_provider_id != new_provider_id
543 })
544 {
545 self.authenticate_provider_task = None;
546 self.ensure_authenticated(cx);
547 }
548
549 if let Some(status) = self.client_status {
550 self.update_zed_ai_notice_visibility(status, cx);
551 }
552 }
553
554 fn ensure_authenticated(&mut self, cx: &mut ViewContext<Self>) {
555 if self.is_authenticated(cx) {
556 return;
557 }
558
559 let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
560 return;
561 };
562
563 let load_credentials = self.authenticate(cx);
564
565 if self.authenticate_provider_task.is_none() {
566 self.authenticate_provider_task = Some((
567 provider.id(),
568 cx.spawn(|this, mut cx| async move {
569 if let Some(future) = load_credentials {
570 let _ = future.await;
571 }
572 this.update(&mut cx, |this, _cx| {
573 this.authenticate_provider_task = None;
574 })
575 .log_err();
576 }),
577 ));
578 }
579 }
580
581 pub fn inline_assist(
582 workspace: &mut Workspace,
583 action: &InlineAssist,
584 cx: &mut ViewContext<Workspace>,
585 ) {
586 let settings = AssistantSettings::get_global(cx);
587 if !settings.enabled {
588 return;
589 }
590
591 let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
592 return;
593 };
594
595 let Some(inline_assist_target) =
596 Self::resolve_inline_assist_target(workspace, &assistant_panel, cx)
597 else {
598 return;
599 };
600
601 let initial_prompt = action.prompt.clone();
602
603 if assistant_panel.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
604 match inline_assist_target {
605 InlineAssistTarget::Editor(active_editor, include_context) => {
606 InlineAssistant::update_global(cx, |assistant, cx| {
607 assistant.assist(
608 &active_editor,
609 Some(cx.view().downgrade()),
610 include_context.then_some(&assistant_panel),
611 initial_prompt,
612 cx,
613 )
614 })
615 }
616 InlineAssistTarget::Terminal(active_terminal) => {
617 TerminalInlineAssistant::update_global(cx, |assistant, cx| {
618 assistant.assist(
619 &active_terminal,
620 Some(cx.view().downgrade()),
621 Some(&assistant_panel),
622 initial_prompt,
623 cx,
624 )
625 })
626 }
627 }
628 } else {
629 let assistant_panel = assistant_panel.downgrade();
630 cx.spawn(|workspace, mut cx| async move {
631 let Some(task) =
632 assistant_panel.update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
633 else {
634 let answer = cx
635 .prompt(
636 gpui::PromptLevel::Warning,
637 "No language model provider configured",
638 None,
639 &["Configure", "Cancel"],
640 )
641 .await
642 .ok();
643 if let Some(answer) = answer {
644 if answer == 0 {
645 cx.update(|cx| cx.dispatch_action(Box::new(ShowConfiguration)))
646 .ok();
647 }
648 }
649 return Ok(());
650 };
651 task.await?;
652 if assistant_panel.update(&mut cx, |panel, cx| panel.is_authenticated(cx))? {
653 cx.update(|cx| match inline_assist_target {
654 InlineAssistTarget::Editor(active_editor, include_context) => {
655 let assistant_panel = if include_context {
656 assistant_panel.upgrade()
657 } else {
658 None
659 };
660 InlineAssistant::update_global(cx, |assistant, cx| {
661 assistant.assist(
662 &active_editor,
663 Some(workspace),
664 assistant_panel.as_ref(),
665 initial_prompt,
666 cx,
667 )
668 })
669 }
670 InlineAssistTarget::Terminal(active_terminal) => {
671 TerminalInlineAssistant::update_global(cx, |assistant, cx| {
672 assistant.assist(
673 &active_terminal,
674 Some(workspace),
675 assistant_panel.upgrade().as_ref(),
676 initial_prompt,
677 cx,
678 )
679 })
680 }
681 })?
682 } else {
683 workspace.update(&mut cx, |workspace, cx| {
684 workspace.focus_panel::<AssistantPanel>(cx)
685 })?;
686 }
687
688 anyhow::Ok(())
689 })
690 .detach_and_log_err(cx)
691 }
692 }
693
694 fn resolve_inline_assist_target(
695 workspace: &mut Workspace,
696 assistant_panel: &View<AssistantPanel>,
697 cx: &mut WindowContext,
698 ) -> Option<InlineAssistTarget> {
699 if let Some(terminal_panel) = workspace.panel::<TerminalPanel>(cx) {
700 if terminal_panel
701 .read(cx)
702 .focus_handle(cx)
703 .contains_focused(cx)
704 {
705 if let Some(terminal_view) = terminal_panel.read(cx).pane().and_then(|pane| {
706 pane.read(cx)
707 .active_item()
708 .and_then(|t| t.downcast::<TerminalView>())
709 }) {
710 return Some(InlineAssistTarget::Terminal(terminal_view));
711 }
712 }
713 }
714 let context_editor =
715 assistant_panel
716 .read(cx)
717 .active_context_editor(cx)
718 .and_then(|editor| {
719 let editor = &editor.read(cx).editor;
720 if editor.read(cx).is_focused(cx) {
721 Some(editor.clone())
722 } else {
723 None
724 }
725 });
726
727 if let Some(context_editor) = context_editor {
728 Some(InlineAssistTarget::Editor(context_editor, false))
729 } else if let Some(workspace_editor) = workspace
730 .active_item(cx)
731 .and_then(|item| item.act_as::<Editor>(cx))
732 {
733 Some(InlineAssistTarget::Editor(workspace_editor, true))
734 } else if let Some(terminal_view) = workspace
735 .active_item(cx)
736 .and_then(|item| item.act_as::<TerminalView>(cx))
737 {
738 Some(InlineAssistTarget::Terminal(terminal_view))
739 } else {
740 None
741 }
742 }
743
744 pub fn create_new_context(
745 workspace: &mut Workspace,
746 _: &NewContext,
747 cx: &mut ViewContext<Workspace>,
748 ) {
749 if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
750 let did_create_context = panel
751 .update(cx, |panel, cx| {
752 panel.new_context(cx)?;
753
754 Some(())
755 })
756 .is_some();
757 if did_create_context {
758 ContextEditor::quote_selection(workspace, &Default::default(), cx);
759 }
760 }
761 }
762
763 pub fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
764 let project = self.project.read(cx);
765 if project.is_via_collab() {
766 let task = self
767 .context_store
768 .update(cx, |store, cx| store.create_remote_context(cx));
769
770 cx.spawn(|this, mut cx| async move {
771 let context = task.await?;
772
773 this.update(&mut cx, |this, cx| {
774 let workspace = this.workspace.clone();
775 let project = this.project.clone();
776 let lsp_adapter_delegate =
777 make_lsp_adapter_delegate(&project, cx).log_err().flatten();
778
779 let fs = this.fs.clone();
780 let project = this.project.clone();
781 let weak_assistant_panel = cx.view().downgrade();
782
783 let editor = cx.new_view(|cx| {
784 ContextEditor::for_context(
785 context,
786 fs,
787 workspace,
788 project,
789 lsp_adapter_delegate,
790 weak_assistant_panel,
791 cx,
792 )
793 });
794
795 this.show_context(editor, cx);
796
797 anyhow::Ok(())
798 })??;
799
800 anyhow::Ok(())
801 })
802 .detach_and_log_err(cx);
803
804 None
805 } else {
806 let context = self.context_store.update(cx, |store, cx| store.create(cx));
807 let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
808 .log_err()
809 .flatten();
810
811 let assistant_panel = cx.view().downgrade();
812 let editor = cx.new_view(|cx| {
813 let mut editor = ContextEditor::for_context(
814 context,
815 self.fs.clone(),
816 self.workspace.clone(),
817 self.project.clone(),
818 lsp_adapter_delegate,
819 assistant_panel,
820 cx,
821 );
822 editor.insert_default_prompt(cx);
823 editor
824 });
825
826 self.show_context(editor.clone(), cx);
827 let workspace = self.workspace.clone();
828 cx.spawn(move |_, mut cx| async move {
829 workspace
830 .update(&mut cx, |workspace, cx| {
831 workspace.focus_panel::<AssistantPanel>(cx);
832 })
833 .ok();
834 })
835 .detach();
836 Some(editor)
837 }
838 }
839
840 fn show_context(&mut self, context_editor: View<ContextEditor>, cx: &mut ViewContext<Self>) {
841 let focus = self.focus_handle(cx).contains_focused(cx);
842 let prev_len = self.pane.read(cx).items_len();
843 self.pane.update(cx, |pane, cx| {
844 pane.add_item(Box::new(context_editor.clone()), focus, focus, None, cx)
845 });
846
847 if prev_len != self.pane.read(cx).items_len() {
848 self.subscriptions
849 .push(cx.subscribe(&context_editor, Self::handle_context_editor_event));
850 }
851
852 self.show_updated_summary(&context_editor, cx);
853
854 cx.emit(AssistantPanelEvent::ContextEdited);
855 cx.notify();
856 }
857
858 fn show_updated_summary(
859 &self,
860 context_editor: &View<ContextEditor>,
861 cx: &mut ViewContext<Self>,
862 ) {
863 context_editor.update(cx, |context_editor, cx| {
864 let new_summary = context_editor.title(cx).to_string();
865 self.model_summary_editor.update(cx, |summary_editor, cx| {
866 if summary_editor.text(cx) != new_summary {
867 summary_editor.set_text(new_summary, cx);
868 }
869 });
870 });
871 }
872
873 fn handle_context_editor_event(
874 &mut self,
875 context_editor: View<ContextEditor>,
876 event: &EditorEvent,
877 cx: &mut ViewContext<Self>,
878 ) {
879 match event {
880 EditorEvent::TitleChanged => {
881 self.show_updated_summary(&context_editor, cx);
882 cx.notify()
883 }
884 EditorEvent::Edited { .. } => {
885 self.workspace
886 .update(cx, |workspace, cx| {
887 let is_via_ssh = workspace
888 .project()
889 .update(cx, |project, _| project.is_via_ssh());
890
891 workspace
892 .client()
893 .telemetry()
894 .log_edit_event("assistant panel", is_via_ssh);
895 })
896 .log_err();
897 cx.emit(AssistantPanelEvent::ContextEdited)
898 }
899 _ => {}
900 }
901 }
902
903 fn show_configuration(
904 workspace: &mut Workspace,
905 _: &ShowConfiguration,
906 cx: &mut ViewContext<Workspace>,
907 ) {
908 let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
909 return;
910 };
911
912 if !panel.focus_handle(cx).contains_focused(cx) {
913 workspace.toggle_panel_focus::<AssistantPanel>(cx);
914 }
915
916 panel.update(cx, |this, cx| {
917 this.show_configuration_tab(cx);
918 })
919 }
920
921 fn show_configuration_tab(&mut self, cx: &mut ViewContext<Self>) {
922 let configuration_item_ix = self
923 .pane
924 .read(cx)
925 .items()
926 .position(|item| item.downcast::<ConfigurationView>().is_some());
927
928 if let Some(configuration_item_ix) = configuration_item_ix {
929 self.pane.update(cx, |pane, cx| {
930 pane.activate_item(configuration_item_ix, true, true, cx);
931 });
932 } else {
933 let configuration = cx.new_view(ConfigurationView::new);
934 self.configuration_subscription = Some(cx.subscribe(
935 &configuration,
936 |this, _, event: &ConfigurationViewEvent, cx| match event {
937 ConfigurationViewEvent::NewProviderContextEditor(provider) => {
938 if LanguageModelRegistry::read_global(cx)
939 .active_provider()
940 .map_or(true, |p| p.id() != provider.id())
941 {
942 if let Some(model) = provider.provided_models(cx).first().cloned() {
943 update_settings_file::<AssistantSettings>(
944 this.fs.clone(),
945 cx,
946 move |settings, _| settings.set_model(model),
947 );
948 }
949 }
950
951 this.new_context(cx);
952 }
953 },
954 ));
955 self.pane.update(cx, |pane, cx| {
956 pane.add_item(Box::new(configuration), true, true, None, cx);
957 });
958 }
959 }
960
961 fn deploy_history(&mut self, _: &DeployHistory, cx: &mut ViewContext<Self>) {
962 let history_item_ix = self
963 .pane
964 .read(cx)
965 .items()
966 .position(|item| item.downcast::<ContextHistory>().is_some());
967
968 if let Some(history_item_ix) = history_item_ix {
969 self.pane.update(cx, |pane, cx| {
970 pane.activate_item(history_item_ix, true, true, cx);
971 });
972 } else {
973 let assistant_panel = cx.view().downgrade();
974 let history = cx.new_view(|cx| {
975 ContextHistory::new(
976 self.project.clone(),
977 self.context_store.clone(),
978 assistant_panel,
979 cx,
980 )
981 });
982 self.pane.update(cx, |pane, cx| {
983 pane.add_item(Box::new(history), true, true, None, cx);
984 });
985 }
986 }
987
988 fn deploy_prompt_library(&mut self, _: &DeployPromptLibrary, cx: &mut ViewContext<Self>) {
989 open_prompt_library(
990 self.languages.clone(),
991 Box::new(PromptLibraryInlineAssist),
992 Arc::new(|| {
993 Box::new(SlashCommandCompletionProvider::new(
994 Arc::new(SlashCommandWorkingSet::default()),
995 None,
996 None,
997 ))
998 }),
999 cx,
1000 )
1001 .detach_and_log_err(cx);
1002 }
1003
1004 fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
1005 self.model_selector_menu_handle.toggle(cx);
1006 }
1007
1008 pub(crate) fn active_context_editor(&self, cx: &AppContext) -> Option<View<ContextEditor>> {
1009 self.pane
1010 .read(cx)
1011 .active_item()?
1012 .downcast::<ContextEditor>()
1013 }
1014
1015 pub fn active_context(&self, cx: &AppContext) -> Option<Model<Context>> {
1016 Some(self.active_context_editor(cx)?.read(cx).context.clone())
1017 }
1018
1019 pub fn open_saved_context(
1020 &mut self,
1021 path: PathBuf,
1022 cx: &mut ViewContext<Self>,
1023 ) -> Task<Result<()>> {
1024 let existing_context = self.pane.read(cx).items().find_map(|item| {
1025 item.downcast::<ContextEditor>()
1026 .filter(|editor| editor.read(cx).context.read(cx).path() == Some(&path))
1027 });
1028 if let Some(existing_context) = existing_context {
1029 return cx.spawn(|this, mut cx| async move {
1030 this.update(&mut cx, |this, cx| this.show_context(existing_context, cx))
1031 });
1032 }
1033
1034 let context = self
1035 .context_store
1036 .update(cx, |store, cx| store.open_local_context(path.clone(), cx));
1037 let fs = self.fs.clone();
1038 let project = self.project.clone();
1039 let workspace = self.workspace.clone();
1040
1041 let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
1042
1043 cx.spawn(|this, mut cx| async move {
1044 let context = context.await?;
1045 let assistant_panel = this.clone();
1046 this.update(&mut cx, |this, cx| {
1047 let editor = cx.new_view(|cx| {
1048 ContextEditor::for_context(
1049 context,
1050 fs,
1051 workspace,
1052 project,
1053 lsp_adapter_delegate,
1054 assistant_panel,
1055 cx,
1056 )
1057 });
1058 this.show_context(editor, cx);
1059 anyhow::Ok(())
1060 })??;
1061 Ok(())
1062 })
1063 }
1064
1065 pub fn open_remote_context(
1066 &mut self,
1067 id: ContextId,
1068 cx: &mut ViewContext<Self>,
1069 ) -> Task<Result<View<ContextEditor>>> {
1070 let existing_context = self.pane.read(cx).items().find_map(|item| {
1071 item.downcast::<ContextEditor>()
1072 .filter(|editor| *editor.read(cx).context.read(cx).id() == id)
1073 });
1074 if let Some(existing_context) = existing_context {
1075 return cx.spawn(|this, mut cx| async move {
1076 this.update(&mut cx, |this, cx| {
1077 this.show_context(existing_context.clone(), cx)
1078 })?;
1079 Ok(existing_context)
1080 });
1081 }
1082
1083 let context = self
1084 .context_store
1085 .update(cx, |store, cx| store.open_remote_context(id, cx));
1086 let fs = self.fs.clone();
1087 let workspace = self.workspace.clone();
1088 let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
1089 .log_err()
1090 .flatten();
1091
1092 cx.spawn(|this, mut cx| async move {
1093 let context = context.await?;
1094 let assistant_panel = this.clone();
1095 this.update(&mut cx, |this, cx| {
1096 let editor = cx.new_view(|cx| {
1097 ContextEditor::for_context(
1098 context,
1099 fs,
1100 workspace,
1101 this.project.clone(),
1102 lsp_adapter_delegate,
1103 assistant_panel,
1104 cx,
1105 )
1106 });
1107 this.show_context(editor.clone(), cx);
1108 anyhow::Ok(editor)
1109 })?
1110 })
1111 }
1112
1113 fn is_authenticated(&mut self, cx: &mut ViewContext<Self>) -> bool {
1114 LanguageModelRegistry::read_global(cx)
1115 .active_provider()
1116 .map_or(false, |provider| provider.is_authenticated(cx))
1117 }
1118
1119 fn authenticate(&mut self, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1120 LanguageModelRegistry::read_global(cx)
1121 .active_provider()
1122 .map_or(None, |provider| Some(provider.authenticate(cx)))
1123 }
1124
1125 fn restart_context_servers(
1126 workspace: &mut Workspace,
1127 _action: &context_server::Restart,
1128 cx: &mut ViewContext<Workspace>,
1129 ) {
1130 let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
1131 return;
1132 };
1133
1134 assistant_panel.update(cx, |assistant_panel, cx| {
1135 assistant_panel
1136 .context_store
1137 .update(cx, |context_store, cx| {
1138 context_store.restart_context_servers(cx);
1139 });
1140 });
1141 }
1142}
1143
1144impl Render for AssistantPanel {
1145 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1146 let mut registrar = DivRegistrar::new(
1147 |panel, cx| {
1148 panel
1149 .pane
1150 .read(cx)
1151 .toolbar()
1152 .read(cx)
1153 .item_of_type::<BufferSearchBar>()
1154 },
1155 cx,
1156 );
1157 BufferSearchBar::register(&mut registrar);
1158 let registrar = registrar.into_div();
1159
1160 v_flex()
1161 .key_context("AssistantPanel")
1162 .size_full()
1163 .on_action(cx.listener(|this, _: &NewContext, cx| {
1164 this.new_context(cx);
1165 }))
1166 .on_action(
1167 cx.listener(|this, _: &ShowConfiguration, cx| this.show_configuration_tab(cx)),
1168 )
1169 .on_action(cx.listener(AssistantPanel::deploy_history))
1170 .on_action(cx.listener(AssistantPanel::deploy_prompt_library))
1171 .on_action(cx.listener(AssistantPanel::toggle_model_selector))
1172 .child(registrar.size_full().child(self.pane.clone()))
1173 .into_any_element()
1174 }
1175}
1176
1177impl Panel for AssistantPanel {
1178 fn persistent_name() -> &'static str {
1179 "AssistantPanel"
1180 }
1181
1182 fn position(&self, cx: &WindowContext) -> DockPosition {
1183 match AssistantSettings::get_global(cx).dock {
1184 AssistantDockPosition::Left => DockPosition::Left,
1185 AssistantDockPosition::Bottom => DockPosition::Bottom,
1186 AssistantDockPosition::Right => DockPosition::Right,
1187 }
1188 }
1189
1190 fn position_is_valid(&self, _: DockPosition) -> bool {
1191 true
1192 }
1193
1194 fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
1195 settings::update_settings_file::<AssistantSettings>(
1196 self.fs.clone(),
1197 cx,
1198 move |settings, _| {
1199 let dock = match position {
1200 DockPosition::Left => AssistantDockPosition::Left,
1201 DockPosition::Bottom => AssistantDockPosition::Bottom,
1202 DockPosition::Right => AssistantDockPosition::Right,
1203 };
1204 settings.set_dock(dock);
1205 },
1206 );
1207 }
1208
1209 fn size(&self, cx: &WindowContext) -> Pixels {
1210 let settings = AssistantSettings::get_global(cx);
1211 match self.position(cx) {
1212 DockPosition::Left | DockPosition::Right => {
1213 self.width.unwrap_or(settings.default_width)
1214 }
1215 DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1216 }
1217 }
1218
1219 fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
1220 match self.position(cx) {
1221 DockPosition::Left | DockPosition::Right => self.width = size,
1222 DockPosition::Bottom => self.height = size,
1223 }
1224 cx.notify();
1225 }
1226
1227 fn is_zoomed(&self, cx: &WindowContext) -> bool {
1228 self.pane.read(cx).is_zoomed()
1229 }
1230
1231 fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
1232 self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx));
1233 }
1234
1235 fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
1236 if active {
1237 if self.pane.read(cx).items_len() == 0 {
1238 self.new_context(cx);
1239 }
1240
1241 self.ensure_authenticated(cx);
1242 }
1243 }
1244
1245 fn pane(&self) -> Option<View<Pane>> {
1246 Some(self.pane.clone())
1247 }
1248
1249 fn remote_id() -> Option<proto::PanelId> {
1250 Some(proto::PanelId::AssistantPanel)
1251 }
1252
1253 fn icon(&self, cx: &WindowContext) -> Option<IconName> {
1254 let settings = AssistantSettings::get_global(cx);
1255 if !settings.enabled || !settings.button {
1256 return None;
1257 }
1258
1259 Some(IconName::ZedAssistant)
1260 }
1261
1262 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
1263 Some("Assistant Panel")
1264 }
1265
1266 fn toggle_action(&self) -> Box<dyn Action> {
1267 Box::new(ToggleFocus)
1268 }
1269
1270 fn activation_priority(&self) -> u32 {
1271 4
1272 }
1273}
1274
1275impl EventEmitter<PanelEvent> for AssistantPanel {}
1276impl EventEmitter<AssistantPanelEvent> for AssistantPanel {}
1277
1278impl FocusableView for AssistantPanel {
1279 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
1280 self.pane.focus_handle(cx)
1281 }
1282}
1283
1284struct PromptLibraryInlineAssist;
1285
1286impl prompt_library::InlineAssistDelegate for PromptLibraryInlineAssist {
1287 fn assist(
1288 &self,
1289 prompt_editor: &View<Editor>,
1290 initial_prompt: Option<String>,
1291 cx: &mut ViewContext<PromptLibrary>,
1292 ) {
1293 InlineAssistant::update_global(cx, |assistant, cx| {
1294 assistant.assist(&prompt_editor, None, None, initial_prompt, cx)
1295 })
1296 }
1297
1298 fn focus_assistant_panel(
1299 &self,
1300 workspace: &mut Workspace,
1301 cx: &mut ViewContext<Workspace>,
1302 ) -> bool {
1303 workspace.focus_panel::<AssistantPanel>(cx).is_some()
1304 }
1305}
1306
1307#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1308pub enum WorkflowAssistStatus {
1309 Pending,
1310 Confirmed,
1311 Done,
1312 Idle,
1313}
1314
1315pub struct ConfigurationView {
1316 focus_handle: FocusHandle,
1317 configuration_views: HashMap<LanguageModelProviderId, AnyView>,
1318 _registry_subscription: Subscription,
1319}
1320
1321impl ConfigurationView {
1322 fn new(cx: &mut ViewContext<Self>) -> Self {
1323 let focus_handle = cx.focus_handle();
1324
1325 let registry_subscription = cx.subscribe(
1326 &LanguageModelRegistry::global(cx),
1327 |this, _, event: &language_model::Event, cx| match event {
1328 language_model::Event::AddedProvider(provider_id) => {
1329 let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
1330 if let Some(provider) = provider {
1331 this.add_configuration_view(&provider, cx);
1332 }
1333 }
1334 language_model::Event::RemovedProvider(provider_id) => {
1335 this.remove_configuration_view(provider_id);
1336 }
1337 _ => {}
1338 },
1339 );
1340
1341 let mut this = Self {
1342 focus_handle,
1343 configuration_views: HashMap::default(),
1344 _registry_subscription: registry_subscription,
1345 };
1346 this.build_configuration_views(cx);
1347 this
1348 }
1349
1350 fn build_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
1351 let providers = LanguageModelRegistry::read_global(cx).providers();
1352 for provider in providers {
1353 self.add_configuration_view(&provider, cx);
1354 }
1355 }
1356
1357 fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
1358 self.configuration_views.remove(provider_id);
1359 }
1360
1361 fn add_configuration_view(
1362 &mut self,
1363 provider: &Arc<dyn LanguageModelProvider>,
1364 cx: &mut ViewContext<Self>,
1365 ) {
1366 let configuration_view = provider.configuration_view(cx);
1367 self.configuration_views
1368 .insert(provider.id(), configuration_view);
1369 }
1370
1371 fn render_provider_view(
1372 &mut self,
1373 provider: &Arc<dyn LanguageModelProvider>,
1374 cx: &mut ViewContext<Self>,
1375 ) -> Div {
1376 let provider_id = provider.id().0.clone();
1377 let provider_name = provider.name().0.clone();
1378 let configuration_view = self.configuration_views.get(&provider.id()).cloned();
1379
1380 let open_new_context = cx.listener({
1381 let provider = provider.clone();
1382 move |_, _, cx| {
1383 cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
1384 provider.clone(),
1385 ))
1386 }
1387 });
1388
1389 v_flex()
1390 .gap_2()
1391 .child(
1392 h_flex()
1393 .justify_between()
1394 .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
1395 .when(provider.is_authenticated(cx), move |this| {
1396 this.child(
1397 h_flex().justify_end().child(
1398 Button::new(
1399 SharedString::from(format!("new-context-{provider_id}")),
1400 "Open New Chat",
1401 )
1402 .icon_position(IconPosition::Start)
1403 .icon(IconName::Plus)
1404 .style(ButtonStyle::Filled)
1405 .layer(ElevationIndex::ModalSurface)
1406 .on_click(open_new_context),
1407 ),
1408 )
1409 }),
1410 )
1411 .child(
1412 div()
1413 .p(DynamicSpacing::Base08.rems(cx))
1414 .bg(cx.theme().colors().surface_background)
1415 .border_1()
1416 .border_color(cx.theme().colors().border_variant)
1417 .rounded_md()
1418 .when(configuration_view.is_none(), |this| {
1419 this.child(div().child(Label::new(format!(
1420 "No configuration view for {}",
1421 provider_name
1422 ))))
1423 })
1424 .when_some(configuration_view, |this, configuration_view| {
1425 this.child(configuration_view)
1426 }),
1427 )
1428 }
1429}
1430
1431impl Render for ConfigurationView {
1432 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1433 let providers = LanguageModelRegistry::read_global(cx).providers();
1434 let provider_views = providers
1435 .into_iter()
1436 .map(|provider| self.render_provider_view(&provider, cx))
1437 .collect::<Vec<_>>();
1438
1439 let mut element = v_flex()
1440 .id("assistant-configuration-view")
1441 .track_focus(&self.focus_handle(cx))
1442 .bg(cx.theme().colors().editor_background)
1443 .size_full()
1444 .overflow_y_scroll()
1445 .child(
1446 v_flex()
1447 .p(DynamicSpacing::Base16.rems(cx))
1448 .border_b_1()
1449 .border_color(cx.theme().colors().border)
1450 .gap_1()
1451 .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
1452 .child(
1453 Label::new(
1454 "At least one LLM provider must be configured to use the Assistant.",
1455 )
1456 .color(Color::Muted),
1457 ),
1458 )
1459 .child(
1460 v_flex()
1461 .p(DynamicSpacing::Base16.rems(cx))
1462 .mt_1()
1463 .gap_6()
1464 .flex_1()
1465 .children(provider_views),
1466 )
1467 .into_any();
1468
1469 // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
1470 // because we couldn't the element to take up the size of the parent.
1471 canvas(
1472 move |bounds, cx| {
1473 element.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
1474 element
1475 },
1476 |_, mut element, cx| {
1477 element.paint(cx);
1478 },
1479 )
1480 .flex_1()
1481 .w_full()
1482 }
1483}
1484
1485pub enum ConfigurationViewEvent {
1486 NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
1487}
1488
1489impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
1490
1491impl FocusableView for ConfigurationView {
1492 fn focus_handle(&self, _: &AppContext) -> FocusHandle {
1493 self.focus_handle.clone()
1494 }
1495}
1496
1497impl Item for ConfigurationView {
1498 type Event = ConfigurationViewEvent;
1499
1500 fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
1501 Some("Configuration".into())
1502 }
1503}
1504
1505fn make_lsp_adapter_delegate(
1506 project: &Model<Project>,
1507 cx: &mut AppContext,
1508) -> Result<Option<Arc<dyn LspAdapterDelegate>>> {
1509 project.update(cx, |project, cx| {
1510 // TODO: Find the right worktree.
1511 let Some(worktree) = project.worktrees(cx).next() else {
1512 return Ok(None::<Arc<dyn LspAdapterDelegate>>);
1513 };
1514 let http_client = project.client().http_client().clone();
1515 project.lsp_store().update(cx, |_, cx| {
1516 Ok(Some(LocalLspAdapterDelegate::new(
1517 project.languages().clone(),
1518 project.environment(),
1519 cx.weak_model(),
1520 &worktree,
1521 http_client,
1522 project.fs().clone(),
1523 cx,
1524 ) as Arc<dyn LspAdapterDelegate>))
1525 })
1526 })
1527}