1use crate::{
2 assistant_settings::{AssistantDockPosition, AssistantSettings},
3 prompt_library::open_prompt_library,
4 search::*,
5 slash_command::{
6 default_command::DefaultSlashCommand, SlashCommandCompletionProvider, SlashCommandLine,
7 SlashCommandRegistry,
8 },
9 ApplyEdit, Assist, CompletionProvider, ConfirmCommand, ContextStore, CycleMessageRole,
10 InlineAssist, InlineAssistant, LanguageModelRequest, LanguageModelRequestMessage, MessageId,
11 MessageMetadata, MessageStatus, ModelSelector, QuoteSelection, ResetKey, Role, SavedContext,
12 SavedContextMetadata, SavedMessage, Split, ToggleFocus, ToggleHistory, ToggleModelSelector,
13};
14use anyhow::{anyhow, Result};
15use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
16use client::telemetry::Telemetry;
17use collections::{BTreeSet, HashMap, HashSet};
18use editor::actions::ShowCompletions;
19use editor::{
20 actions::{FoldAt, MoveToEndOfLine, Newline, UnfoldAt},
21 display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, Flap, ToDisplayPoint},
22 scroll::{Autoscroll, AutoscrollStrategy},
23 Anchor, Editor, EditorEvent, RowExt, ToOffset as _, ToPoint,
24};
25use editor::{display_map::FlapId, FoldPlaceholder};
26use file_icons::FileIcons;
27use fs::Fs;
28use futures::future::Shared;
29use futures::{FutureExt, StreamExt};
30use gpui::{
31 div, point, rems, Action, AnyElement, AnyView, AppContext, AsyncAppContext, AsyncWindowContext,
32 ClipboardItem, Context as _, Empty, EventEmitter, FocusHandle, FocusableView,
33 InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels, Render,
34 SharedString, StatefulInteractiveElement, Styled, Subscription, Task, UpdateGlobal, View,
35 ViewContext, VisualContext, WeakView, WindowContext,
36};
37use language::{
38 language_settings::SoftWrap, AnchorRangeExt, AutoindentMode, Buffer, LanguageRegistry,
39 LspAdapterDelegate, OffsetRangeExt as _, Point, ToOffset as _,
40};
41use multi_buffer::MultiBufferRow;
42use picker::{Picker, PickerDelegate};
43use project::{Project, ProjectLspAdapterDelegate, ProjectTransaction};
44use search::{buffer_search::DivRegistrar, BufferSearchBar};
45use settings::Settings;
46use std::{
47 cmp::{self, Ordering},
48 fmt::Write,
49 iter,
50 ops::Range,
51 path::PathBuf,
52 sync::Arc,
53 time::{Duration, Instant},
54};
55use telemetry_events::AssistantKind;
56use ui::{
57 popover_menu, prelude::*, ButtonLike, ContextMenu, ElevationIndex, KeyBinding, ListItem,
58 ListItemSpacing, PopoverMenuHandle, Tab, TabBar, Tooltip,
59};
60use util::{paths::CONTEXTS_DIR, post_inc, ResultExt, TryFutureExt};
61use uuid::Uuid;
62use workspace::NewFile;
63use workspace::{
64 dock::{DockPosition, Panel, PanelEvent},
65 searchable::Direction,
66 Save, ToggleZoom, Toolbar, Workspace,
67};
68
69pub fn init(cx: &mut AppContext) {
70 cx.observe_new_views(
71 |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
72 workspace
73 .register_action(|workspace, _: &ToggleFocus, cx| {
74 let settings = AssistantSettings::get_global(cx);
75 if !settings.enabled {
76 return;
77 }
78
79 workspace.toggle_panel_focus::<AssistantPanel>(cx);
80 })
81 .register_action(AssistantPanel::inline_assist)
82 .register_action(ContextEditor::quote_selection);
83 },
84 )
85 .detach();
86}
87
88pub struct AssistantPanel {
89 workspace: WeakView<Workspace>,
90 width: Option<Pixels>,
91 height: Option<Pixels>,
92 active_context_editor: Option<ActiveContextEditor>,
93 show_saved_contexts: bool,
94 context_store: Model<ContextStore>,
95 saved_context_picker: View<Picker<SavedContextPickerDelegate>>,
96 zoomed: bool,
97 focus_handle: FocusHandle,
98 toolbar: View<Toolbar>,
99 languages: Arc<LanguageRegistry>,
100 slash_commands: Arc<SlashCommandRegistry>,
101 fs: Arc<dyn Fs>,
102 telemetry: Arc<Telemetry>,
103 _subscriptions: Vec<Subscription>,
104 authentication_prompt: Option<AnyView>,
105 model_menu_handle: PopoverMenuHandle<ContextMenu>,
106}
107
108struct SavedContextPickerDelegate {
109 store: Model<ContextStore>,
110 matches: Vec<SavedContextMetadata>,
111 selected_index: usize,
112}
113
114enum SavedContextPickerEvent {
115 Confirmed { path: PathBuf },
116}
117
118impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {}
119
120impl SavedContextPickerDelegate {
121 fn new(store: Model<ContextStore>) -> Self {
122 Self {
123 store,
124 matches: Vec::new(),
125 selected_index: 0,
126 }
127 }
128}
129
130impl PickerDelegate for SavedContextPickerDelegate {
131 type ListItem = ListItem;
132
133 fn match_count(&self) -> usize {
134 self.matches.len()
135 }
136
137 fn selected_index(&self) -> usize {
138 self.selected_index
139 }
140
141 fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
142 self.selected_index = ix;
143 }
144
145 fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
146 "Search...".into()
147 }
148
149 fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
150 let search = self.store.read(cx).search(query, cx);
151 cx.spawn(|this, mut cx| async move {
152 let matches = search.await;
153 this.update(&mut cx, |this, cx| {
154 this.delegate.matches = matches;
155 this.delegate.selected_index = 0;
156 cx.notify();
157 })
158 .ok();
159 })
160 }
161
162 fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
163 if let Some(metadata) = self.matches.get(self.selected_index) {
164 cx.emit(SavedContextPickerEvent::Confirmed {
165 path: metadata.path.clone(),
166 })
167 }
168 }
169
170 fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
171
172 fn render_match(
173 &self,
174 ix: usize,
175 selected: bool,
176 _cx: &mut ViewContext<Picker<Self>>,
177 ) -> Option<Self::ListItem> {
178 let context = self.matches.get(ix)?;
179 Some(
180 ListItem::new(ix)
181 .inset(true)
182 .spacing(ListItemSpacing::Sparse)
183 .selected(selected)
184 .child(
185 div()
186 .flex()
187 .w_full()
188 .gap_2()
189 .child(
190 Label::new(context.mtime.format("%F %I:%M%p").to_string())
191 .color(Color::Muted)
192 .size(LabelSize::Small),
193 )
194 .child(Label::new(context.title.clone()).size(LabelSize::Small)),
195 ),
196 )
197 }
198}
199
200struct ActiveContextEditor {
201 editor: View<ContextEditor>,
202 _subscriptions: Vec<Subscription>,
203}
204
205impl AssistantPanel {
206 pub fn load(
207 workspace: WeakView<Workspace>,
208 cx: AsyncWindowContext,
209 ) -> Task<Result<View<Self>>> {
210 cx.spawn(|mut cx| async move {
211 let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
212 let context_store = cx.update(|cx| ContextStore::new(fs.clone(), cx))?.await?;
213
214 // TODO: deserialize state.
215 let workspace_handle = workspace.clone();
216 workspace.update(&mut cx, |workspace, cx| {
217 cx.new_view::<Self>(|cx| {
218 let toolbar = cx.new_view(|cx| {
219 let mut toolbar = Toolbar::new();
220 toolbar.set_can_navigate(false, cx);
221 toolbar.add_item(cx.new_view(BufferSearchBar::new), cx);
222 toolbar
223 });
224
225 let saved_context_picker = cx.new_view(|cx| {
226 Picker::uniform_list(
227 SavedContextPickerDelegate::new(context_store.clone()),
228 cx,
229 )
230 .modal(false)
231 .max_height(None)
232 });
233
234 let focus_handle = cx.focus_handle();
235 let subscriptions = vec![
236 cx.on_focus_in(&focus_handle, Self::focus_in),
237 cx.on_focus_out(&focus_handle, Self::focus_out),
238 cx.observe_global::<CompletionProvider>({
239 let mut prev_settings_version =
240 CompletionProvider::global(cx).settings_version();
241 move |this, cx| {
242 this.completion_provider_changed(prev_settings_version, cx);
243 prev_settings_version =
244 CompletionProvider::global(cx).settings_version();
245 }
246 }),
247 cx.observe(&context_store, |this, _, cx| {
248 this.saved_context_picker
249 .update(cx, |picker, cx| picker.refresh(cx));
250 }),
251 cx.subscribe(
252 &saved_context_picker,
253 Self::handle_saved_context_picker_event,
254 ),
255 ];
256
257 cx.observe_global::<FileIcons>(|_, cx| {
258 cx.notify();
259 })
260 .detach();
261
262 Self {
263 workspace: workspace_handle,
264 active_context_editor: None,
265 show_saved_contexts: false,
266 saved_context_picker,
267 context_store,
268 zoomed: false,
269 focus_handle,
270 toolbar,
271 languages: workspace.app_state().languages.clone(),
272 slash_commands: SlashCommandRegistry::global(cx),
273 fs: workspace.app_state().fs.clone(),
274 telemetry: workspace.client().telemetry().clone(),
275 width: None,
276 height: None,
277 _subscriptions: subscriptions,
278 authentication_prompt: None,
279 model_menu_handle: PopoverMenuHandle::default(),
280 }
281 })
282 })
283 })
284 }
285
286 fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
287 self.toolbar
288 .update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
289 cx.notify();
290 if self.focus_handle.is_focused(cx) {
291 if self.show_saved_contexts {
292 cx.focus_view(&self.saved_context_picker);
293 } else if let Some(context) = self.active_context_editor() {
294 cx.focus_view(context);
295 }
296 }
297 }
298
299 fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
300 self.toolbar
301 .update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
302 cx.notify();
303 }
304
305 fn completion_provider_changed(
306 &mut self,
307 prev_settings_version: usize,
308 cx: &mut ViewContext<Self>,
309 ) {
310 if self.is_authenticated(cx) {
311 self.authentication_prompt = None;
312
313 if let Some(editor) = self.active_context_editor() {
314 editor.update(cx, |active_context, cx| {
315 active_context
316 .context
317 .update(cx, |context, cx| context.completion_provider_changed(cx))
318 })
319 }
320
321 if self.active_context_editor().is_none() {
322 self.new_context(cx);
323 }
324 cx.notify();
325 } else if self.authentication_prompt.is_none()
326 || prev_settings_version != CompletionProvider::global(cx).settings_version()
327 {
328 self.authentication_prompt =
329 Some(cx.update_global::<CompletionProvider, _>(|provider, cx| {
330 provider.authentication_prompt(cx)
331 }));
332 cx.notify();
333 }
334 }
335
336 fn handle_saved_context_picker_event(
337 &mut self,
338 _picker: View<Picker<SavedContextPickerDelegate>>,
339 event: &SavedContextPickerEvent,
340 cx: &mut ViewContext<Self>,
341 ) {
342 match event {
343 SavedContextPickerEvent::Confirmed { path } => {
344 self.open_context(path.clone(), cx).detach_and_log_err(cx);
345 }
346 }
347 }
348
349 pub fn inline_assist(
350 workspace: &mut Workspace,
351 _: &InlineAssist,
352 cx: &mut ViewContext<Workspace>,
353 ) {
354 let settings = AssistantSettings::get_global(cx);
355 if !settings.enabled {
356 return;
357 }
358
359 let Some(assistant) = workspace.panel::<AssistantPanel>(cx) else {
360 return;
361 };
362
363 let context_editor = assistant
364 .read(cx)
365 .active_context_editor()
366 .and_then(|editor| {
367 let editor = &editor.read(cx).editor;
368 if editor.read(cx).is_focused(cx) {
369 Some(editor.clone())
370 } else {
371 None
372 }
373 });
374
375 let include_context;
376 let active_editor;
377 if let Some(context_editor) = context_editor {
378 active_editor = context_editor;
379 include_context = false;
380 } else if let Some(workspace_editor) = workspace
381 .active_item(cx)
382 .and_then(|item| item.act_as::<Editor>(cx))
383 {
384 active_editor = workspace_editor;
385 include_context = true;
386 } else {
387 return;
388 };
389
390 if assistant.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
391 InlineAssistant::update_global(cx, |assistant, cx| {
392 assistant.assist(
393 &active_editor,
394 Some(cx.view().downgrade()),
395 include_context,
396 cx,
397 )
398 })
399 } else {
400 let assistant = assistant.downgrade();
401 cx.spawn(|workspace, mut cx| async move {
402 assistant
403 .update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
404 .await?;
405 if assistant.update(&mut cx, |assistant, cx| assistant.is_authenticated(cx))? {
406 cx.update(|cx| {
407 InlineAssistant::update_global(cx, |assistant, cx| {
408 assistant.assist(&active_editor, Some(workspace), include_context, cx)
409 })
410 })?
411 } else {
412 workspace.update(&mut cx, |workspace, cx| {
413 workspace.focus_panel::<AssistantPanel>(cx)
414 })?;
415 }
416
417 anyhow::Ok(())
418 })
419 .detach_and_log_err(cx)
420 }
421 }
422
423 fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
424 let workspace = self.workspace.upgrade()?;
425
426 let editor = cx.new_view(|cx| {
427 ContextEditor::new(
428 self.languages.clone(),
429 self.slash_commands.clone(),
430 self.fs.clone(),
431 workspace,
432 cx,
433 )
434 });
435
436 self.show_context(editor.clone(), cx);
437 Some(editor)
438 }
439
440 fn show_context(&mut self, context_editor: View<ContextEditor>, cx: &mut ViewContext<Self>) {
441 let mut subscriptions = Vec::new();
442 subscriptions.push(cx.subscribe(&context_editor, Self::handle_context_editor_event));
443
444 let context = context_editor.read(cx).context.clone();
445 subscriptions.push(cx.observe(&context, |_, _, cx| cx.notify()));
446
447 let editor = context_editor.read(cx).editor.clone();
448 self.toolbar.update(cx, |toolbar, cx| {
449 toolbar.set_active_item(Some(&editor), cx);
450 });
451 if self.focus_handle.contains_focused(cx) {
452 cx.focus_view(&editor);
453 }
454 self.active_context_editor = Some(ActiveContextEditor {
455 editor: context_editor,
456 _subscriptions: subscriptions,
457 });
458 self.show_saved_contexts = false;
459
460 cx.notify();
461 }
462
463 fn handle_context_editor_event(
464 &mut self,
465 _: View<ContextEditor>,
466 event: &ContextEditorEvent,
467 cx: &mut ViewContext<Self>,
468 ) {
469 match event {
470 ContextEditorEvent::TabContentChanged => cx.notify(),
471 }
472 }
473
474 fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext<Self>) {
475 if self.zoomed {
476 cx.emit(PanelEvent::ZoomOut)
477 } else {
478 cx.emit(PanelEvent::ZoomIn)
479 }
480 }
481
482 fn toggle_history(&mut self, _: &ToggleHistory, cx: &mut ViewContext<Self>) {
483 if self.show_saved_contexts {
484 self.hide_history(cx);
485 } else {
486 self.show_history(cx);
487 }
488 }
489
490 fn show_history(&mut self, cx: &mut ViewContext<Self>) {
491 cx.focus_view(&self.saved_context_picker);
492 if !self.show_saved_contexts {
493 self.show_saved_contexts = true;
494 cx.notify();
495 }
496 }
497
498 fn hide_history(&mut self, cx: &mut ViewContext<Self>) {
499 if let Some(editor) = self.active_context_editor() {
500 cx.focus_view(&editor);
501 self.show_saved_contexts = false;
502 cx.notify();
503 }
504 }
505
506 fn deploy(&mut self, action: &search::buffer_search::Deploy, cx: &mut ViewContext<Self>) {
507 let mut propagate = true;
508 if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
509 search_bar.update(cx, |search_bar, cx| {
510 if search_bar.show(cx) {
511 search_bar.search_suggested(cx);
512 if action.focus {
513 let focus_handle = search_bar.focus_handle(cx);
514 search_bar.select_query(cx);
515 cx.focus(&focus_handle);
516 }
517 propagate = false
518 }
519 });
520 }
521 if propagate {
522 cx.propagate();
523 }
524 }
525
526 fn handle_editor_cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
527 if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
528 if !search_bar.read(cx).is_dismissed() {
529 search_bar.update(cx, |search_bar, cx| {
530 search_bar.dismiss(&Default::default(), cx)
531 });
532 return;
533 }
534 }
535 cx.propagate();
536 }
537
538 fn select_next_match(&mut self, _: &search::SelectNextMatch, cx: &mut ViewContext<Self>) {
539 if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
540 search_bar.update(cx, |bar, cx| bar.select_match(Direction::Next, 1, cx));
541 }
542 }
543
544 fn select_prev_match(&mut self, _: &search::SelectPrevMatch, cx: &mut ViewContext<Self>) {
545 if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
546 search_bar.update(cx, |bar, cx| bar.select_match(Direction::Prev, 1, cx));
547 }
548 }
549
550 fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) {
551 CompletionProvider::global(cx)
552 .reset_credentials(cx)
553 .detach_and_log_err(cx);
554 }
555
556 fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
557 self.model_menu_handle.toggle(cx);
558 }
559
560 fn insert_command(&mut self, name: &str, cx: &mut ViewContext<Self>) {
561 if let Some(context_editor) = self.active_context_editor() {
562 context_editor.update(cx, |context_editor, cx| {
563 context_editor.insert_command(name, cx)
564 });
565 }
566 }
567
568 fn active_context_editor(&self) -> Option<&View<ContextEditor>> {
569 Some(&self.active_context_editor.as_ref()?.editor)
570 }
571
572 pub fn active_context(&self, cx: &AppContext) -> Option<Model<Context>> {
573 Some(self.active_context_editor()?.read(cx).context.clone())
574 }
575
576 fn render_popover_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
577 let assistant = cx.view().clone();
578 let zoomed = self.zoomed;
579 popover_menu("assistant-popover")
580 .trigger(IconButton::new("trigger", IconName::Menu))
581 .menu(move |cx| {
582 let assistant = assistant.clone();
583 ContextMenu::build(cx, |menu, _cx| {
584 menu.entry(
585 if zoomed { "Zoom Out" } else { "Zoom In" },
586 Some(Box::new(ToggleZoom)),
587 {
588 let assistant = assistant.clone();
589 move |cx| {
590 assistant.focus_handle(cx).dispatch_action(&ToggleZoom, cx);
591 }
592 },
593 )
594 .entry("New Context", Some(Box::new(NewFile)), {
595 let assistant = assistant.clone();
596 move |cx| {
597 assistant.focus_handle(cx).dispatch_action(&NewFile, cx);
598 }
599 })
600 .entry("History", Some(Box::new(ToggleHistory)), {
601 let assistant = assistant.clone();
602 move |cx| assistant.update(cx, |assistant, cx| assistant.show_history(cx))
603 })
604 })
605 .into()
606 })
607 }
608
609 fn render_inject_context_menu(&self, cx: &mut ViewContext<Self>) -> impl Element {
610 let commands = self.slash_commands.clone();
611 let assistant_panel = cx.view().downgrade();
612 let active_editor_focus_handle = self.workspace.upgrade().and_then(|workspace| {
613 Some(
614 workspace
615 .read(cx)
616 .active_item_as::<Editor>(cx)?
617 .focus_handle(cx),
618 )
619 });
620
621 popover_menu("inject-context-menu")
622 .trigger(IconButton::new("trigger", IconName::Quote).tooltip(|cx| {
623 Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
624 }))
625 .menu(move |cx| {
626 ContextMenu::build(cx, |mut menu, _cx| {
627 for command_name in commands.featured_command_names() {
628 if let Some(command) = commands.command(&command_name) {
629 let menu_text = SharedString::from(Arc::from(command.menu_text()));
630 menu = menu.custom_entry(
631 {
632 let command_name = command_name.clone();
633 move |_cx| {
634 h_flex()
635 .w_full()
636 .justify_between()
637 .child(Label::new(menu_text.clone()))
638 .child(
639 div().ml_4().child(
640 Label::new(format!("/{command_name}"))
641 .color(Color::Muted),
642 ),
643 )
644 .into_any()
645 }
646 },
647 {
648 let assistant_panel = assistant_panel.clone();
649 move |cx| {
650 assistant_panel
651 .update(cx, |assistant_panel, cx| {
652 assistant_panel.insert_command(&command_name, cx)
653 })
654 .ok();
655 }
656 },
657 )
658 }
659 }
660
661 if let Some(active_editor_focus_handle) = active_editor_focus_handle.clone() {
662 menu = menu
663 .context(active_editor_focus_handle)
664 .action("Quote Selection", Box::new(QuoteSelection));
665 }
666
667 menu
668 })
669 .into()
670 })
671 }
672
673 fn render_send_button(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
674 self.active_context_editor.as_ref().map(|context| {
675 let focus_handle = context.editor.focus_handle(cx);
676 ButtonLike::new("send_button")
677 .style(ButtonStyle::Filled)
678 .layer(ElevationIndex::ModalSurface)
679 .children(
680 KeyBinding::for_action_in(&Assist, &focus_handle, cx)
681 .map(|binding| binding.into_any_element()),
682 )
683 .child(Label::new("Send"))
684 .on_click(cx.listener(|this, _event, cx| {
685 if let Some(active_editor) = this.active_context_editor() {
686 active_editor.update(cx, |editor, cx| editor.assist(&Assist, cx));
687 }
688 }))
689 })
690 }
691
692 fn open_context(&mut self, path: PathBuf, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
693 cx.focus(&self.focus_handle);
694
695 let saved_context = self.context_store.read(cx).load(path.clone(), cx);
696 let fs = self.fs.clone();
697 let workspace = self.workspace.clone();
698 let slash_commands = self.slash_commands.clone();
699 let languages = self.languages.clone();
700 let telemetry = self.telemetry.clone();
701
702 let lsp_adapter_delegate = workspace
703 .update(cx, |workspace, cx| {
704 make_lsp_adapter_delegate(workspace.project(), cx).log_err()
705 })
706 .log_err()
707 .flatten();
708
709 cx.spawn(|this, mut cx| async move {
710 let saved_context = saved_context.await?;
711 let context = Context::deserialize(
712 saved_context,
713 path.clone(),
714 languages,
715 slash_commands,
716 Some(telemetry),
717 &mut cx,
718 )
719 .await?;
720
721 this.update(&mut cx, |this, cx| {
722 let workspace = workspace
723 .upgrade()
724 .ok_or_else(|| anyhow!("workspace dropped"))?;
725 let editor = cx.new_view(|cx| {
726 ContextEditor::for_context(context, fs, workspace, lsp_adapter_delegate, cx)
727 });
728 this.show_context(editor, cx);
729 anyhow::Ok(())
730 })??;
731 Ok(())
732 })
733 }
734
735 fn is_authenticated(&mut self, cx: &mut ViewContext<Self>) -> bool {
736 CompletionProvider::global(cx).is_authenticated()
737 }
738
739 fn authenticate(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
740 cx.update_global::<CompletionProvider, _>(|provider, cx| provider.authenticate(cx))
741 }
742
743 fn render_signed_in(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
744 let header = TabBar::new("assistant_header")
745 .start_child(h_flex().gap_1().child(self.render_popover_button(cx)))
746 .children(self.active_context_editor().map(|editor| {
747 h_flex()
748 .h(rems(Tab::CONTAINER_HEIGHT_IN_REMS))
749 .flex_1()
750 .px_2()
751 .child(
752 div()
753 .id("title")
754 .cursor_pointer()
755 .on_click(cx.listener(|this, _, cx| this.hide_history(cx)))
756 .child(Label::new(editor.read(cx).title(cx))),
757 )
758 }))
759 .end_child(
760 h_flex()
761 .gap_2()
762 .when_some(self.active_context_editor(), |this, editor| {
763 let context = editor.read(cx).context.clone();
764 this.child(
765 h_flex()
766 .gap_1()
767 .child(ModelSelector::new(
768 self.model_menu_handle.clone(),
769 self.fs.clone(),
770 ))
771 .children(self.render_remaining_tokens(&context, cx)),
772 )
773 .child(
774 ui::Divider::vertical()
775 .inset()
776 .color(ui::DividerColor::Border),
777 )
778 })
779 .child(
780 h_flex()
781 .gap_1()
782 .child(self.render_inject_context_menu(cx))
783 .child(
784 IconButton::new("show-prompt-library", IconName::Library)
785 .icon_size(IconSize::Small)
786 .on_click({
787 let language_registry = self.languages.clone();
788 cx.listener(move |_this, _event, cx| {
789 open_prompt_library(language_registry.clone(), cx)
790 .detach_and_log_err(cx);
791 })
792 })
793 .tooltip(|cx| Tooltip::text("Prompt Library…", cx)),
794 ),
795 ),
796 );
797
798 let contents = if self.active_context_editor().is_some() {
799 let mut registrar = DivRegistrar::new(
800 |panel, cx| panel.toolbar.read(cx).item_of_type::<BufferSearchBar>(),
801 cx,
802 );
803 BufferSearchBar::register(&mut registrar);
804 registrar.into_div()
805 } else {
806 div()
807 };
808
809 v_flex()
810 .key_context("AssistantPanel")
811 .size_full()
812 .on_action(cx.listener(|this, _: &workspace::NewFile, cx| {
813 this.new_context(cx);
814 }))
815 .on_action(cx.listener(AssistantPanel::toggle_zoom))
816 .on_action(cx.listener(AssistantPanel::toggle_history))
817 .on_action(cx.listener(AssistantPanel::deploy))
818 .on_action(cx.listener(AssistantPanel::select_next_match))
819 .on_action(cx.listener(AssistantPanel::select_prev_match))
820 .on_action(cx.listener(AssistantPanel::handle_editor_cancel))
821 .on_action(cx.listener(AssistantPanel::reset_credentials))
822 .on_action(cx.listener(AssistantPanel::toggle_model_selector))
823 .track_focus(&self.focus_handle)
824 .child(header)
825 .children(if self.toolbar.read(cx).hidden() {
826 None
827 } else {
828 Some(self.toolbar.clone())
829 })
830 .child(contents.flex_1().child(
831 if self.show_saved_contexts || self.active_context_editor().is_none() {
832 div()
833 .size_full()
834 .child(self.saved_context_picker.clone())
835 .into_any_element()
836 } else if let Some(editor) = self.active_context_editor() {
837 let editor = editor.clone();
838 div()
839 .size_full()
840 .child(editor.clone())
841 .child(
842 h_flex()
843 .w_full()
844 .absolute()
845 .bottom_0()
846 .p_4()
847 .justify_end()
848 .children(self.render_send_button(cx)),
849 )
850 .into_any_element()
851 } else {
852 div().into_any_element()
853 },
854 ))
855 }
856
857 fn render_remaining_tokens(
858 &self,
859 context: &Model<Context>,
860 cx: &mut ViewContext<Self>,
861 ) -> Option<impl IntoElement> {
862 let remaining_tokens = context.read(cx).remaining_tokens(cx)?;
863 let remaining_tokens_color = if remaining_tokens <= 0 {
864 Color::Error
865 } else if remaining_tokens <= 500 {
866 Color::Warning
867 } else {
868 Color::Muted
869 };
870 Some(
871 Label::new(remaining_tokens.to_string())
872 .size(LabelSize::Small)
873 .color(remaining_tokens_color),
874 )
875 }
876}
877
878impl Render for AssistantPanel {
879 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
880 if let Some(authentication_prompt) = self.authentication_prompt.as_ref() {
881 authentication_prompt.clone().into_any()
882 } else {
883 self.render_signed_in(cx).into_any_element()
884 }
885 }
886}
887
888impl Panel for AssistantPanel {
889 fn persistent_name() -> &'static str {
890 "AssistantPanel"
891 }
892
893 fn position(&self, cx: &WindowContext) -> DockPosition {
894 match AssistantSettings::get_global(cx).dock {
895 AssistantDockPosition::Left => DockPosition::Left,
896 AssistantDockPosition::Bottom => DockPosition::Bottom,
897 AssistantDockPosition::Right => DockPosition::Right,
898 }
899 }
900
901 fn position_is_valid(&self, _: DockPosition) -> bool {
902 true
903 }
904
905 fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
906 settings::update_settings_file::<AssistantSettings>(self.fs.clone(), cx, move |settings| {
907 let dock = match position {
908 DockPosition::Left => AssistantDockPosition::Left,
909 DockPosition::Bottom => AssistantDockPosition::Bottom,
910 DockPosition::Right => AssistantDockPosition::Right,
911 };
912 settings.set_dock(dock);
913 });
914 }
915
916 fn size(&self, cx: &WindowContext) -> Pixels {
917 let settings = AssistantSettings::get_global(cx);
918 match self.position(cx) {
919 DockPosition::Left | DockPosition::Right => {
920 self.width.unwrap_or(settings.default_width)
921 }
922 DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
923 }
924 }
925
926 fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
927 match self.position(cx) {
928 DockPosition::Left | DockPosition::Right => self.width = size,
929 DockPosition::Bottom => self.height = size,
930 }
931 cx.notify();
932 }
933
934 fn is_zoomed(&self, _: &WindowContext) -> bool {
935 self.zoomed
936 }
937
938 fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
939 self.zoomed = zoomed;
940 cx.notify();
941 }
942
943 fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
944 if active {
945 let load_credentials = self.authenticate(cx);
946 cx.spawn(|this, mut cx| async move {
947 load_credentials.await?;
948 this.update(&mut cx, |this, cx| {
949 if this.is_authenticated(cx) && this.active_context_editor().is_none() {
950 this.new_context(cx);
951 }
952 })
953 })
954 .detach_and_log_err(cx);
955 }
956 }
957
958 fn icon(&self, cx: &WindowContext) -> Option<IconName> {
959 let settings = AssistantSettings::get_global(cx);
960 if !settings.enabled || !settings.button {
961 return None;
962 }
963
964 Some(IconName::ZedAssistant)
965 }
966
967 fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
968 Some("Assistant Panel")
969 }
970
971 fn toggle_action(&self) -> Box<dyn Action> {
972 Box::new(ToggleFocus)
973 }
974}
975
976impl EventEmitter<PanelEvent> for AssistantPanel {}
977
978impl FocusableView for AssistantPanel {
979 fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
980 self.focus_handle.clone()
981 }
982}
983
984#[derive(Clone)]
985enum ContextEvent {
986 MessagesEdited,
987 SummaryChanged,
988 EditSuggestionsChanged,
989 StreamedCompletion,
990 PendingSlashCommandsUpdated {
991 removed: Vec<Range<language::Anchor>>,
992 updated: Vec<PendingSlashCommand>,
993 },
994 SlashCommandFinished {
995 output_range: Range<language::Anchor>,
996 sections: Vec<SlashCommandOutputSection<language::Anchor>>,
997 run_commands_in_output: bool,
998 },
999}
1000
1001#[derive(Default)]
1002struct Summary {
1003 text: String,
1004 done: bool,
1005}
1006
1007pub struct Context {
1008 id: Option<String>,
1009 buffer: Model<Buffer>,
1010 edit_suggestions: Vec<EditSuggestion>,
1011 pending_slash_commands: Vec<PendingSlashCommand>,
1012 edits_since_last_slash_command_parse: language::Subscription,
1013 message_anchors: Vec<MessageAnchor>,
1014 messages_metadata: HashMap<MessageId, MessageMetadata>,
1015 next_message_id: MessageId,
1016 summary: Option<Summary>,
1017 pending_summary: Task<Option<()>>,
1018 completion_count: usize,
1019 pending_completions: Vec<PendingCompletion>,
1020 token_count: Option<usize>,
1021 pending_token_count: Task<Option<()>>,
1022 pending_edit_suggestion_parse: Option<Task<()>>,
1023 pending_save: Task<Result<()>>,
1024 path: Option<PathBuf>,
1025 _subscriptions: Vec<Subscription>,
1026 telemetry: Option<Arc<Telemetry>>,
1027 slash_command_registry: Arc<SlashCommandRegistry>,
1028 language_registry: Arc<LanguageRegistry>,
1029}
1030
1031impl EventEmitter<ContextEvent> for Context {}
1032
1033impl Context {
1034 fn new(
1035 language_registry: Arc<LanguageRegistry>,
1036 slash_command_registry: Arc<SlashCommandRegistry>,
1037 telemetry: Option<Arc<Telemetry>>,
1038 cx: &mut ModelContext<Self>,
1039 ) -> Self {
1040 let buffer = cx.new_model(|cx| {
1041 let mut buffer = Buffer::local("", cx);
1042 buffer.set_language_registry(language_registry.clone());
1043 buffer
1044 });
1045 let edits_since_last_slash_command_parse =
1046 buffer.update(cx, |buffer, _| buffer.subscribe());
1047 let mut this = Self {
1048 id: Some(Uuid::new_v4().to_string()),
1049 message_anchors: Default::default(),
1050 messages_metadata: Default::default(),
1051 next_message_id: Default::default(),
1052 edit_suggestions: Vec::new(),
1053 pending_slash_commands: Vec::new(),
1054 edits_since_last_slash_command_parse,
1055 summary: None,
1056 pending_summary: Task::ready(None),
1057 completion_count: Default::default(),
1058 pending_completions: Default::default(),
1059 token_count: None,
1060 pending_token_count: Task::ready(None),
1061 pending_edit_suggestion_parse: None,
1062 _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
1063 pending_save: Task::ready(Ok(())),
1064 path: None,
1065 buffer,
1066 telemetry,
1067 language_registry,
1068 slash_command_registry,
1069 };
1070
1071 let message = MessageAnchor {
1072 id: MessageId(post_inc(&mut this.next_message_id.0)),
1073 start: language::Anchor::MIN,
1074 };
1075 this.message_anchors.push(message.clone());
1076 this.messages_metadata.insert(
1077 message.id,
1078 MessageMetadata {
1079 role: Role::User,
1080 status: MessageStatus::Done,
1081 },
1082 );
1083
1084 this.set_language(cx);
1085 this.count_remaining_tokens(cx);
1086 this
1087 }
1088
1089 fn serialize(&self, cx: &AppContext) -> SavedContext {
1090 SavedContext {
1091 id: self.id.clone(),
1092 zed: "context".into(),
1093 version: SavedContext::VERSION.into(),
1094 text: self.buffer.read(cx).text(),
1095 message_metadata: self.messages_metadata.clone(),
1096 messages: self
1097 .messages(cx)
1098 .map(|message| SavedMessage {
1099 id: message.id,
1100 start: message.offset_range.start,
1101 })
1102 .collect(),
1103 summary: self
1104 .summary
1105 .as_ref()
1106 .map(|summary| summary.text.clone())
1107 .unwrap_or_default(),
1108 }
1109 }
1110
1111 #[allow(clippy::too_many_arguments)]
1112 async fn deserialize(
1113 saved_context: SavedContext,
1114 path: PathBuf,
1115 language_registry: Arc<LanguageRegistry>,
1116 slash_command_registry: Arc<SlashCommandRegistry>,
1117 telemetry: Option<Arc<Telemetry>>,
1118 cx: &mut AsyncAppContext,
1119 ) -> Result<Model<Self>> {
1120 let id = match saved_context.id {
1121 Some(id) => Some(id),
1122 None => Some(Uuid::new_v4().to_string()),
1123 };
1124
1125 let markdown = language_registry.language_for_name("Markdown");
1126 let mut message_anchors = Vec::new();
1127 let mut next_message_id = MessageId(0);
1128 let buffer = cx.new_model(|cx| {
1129 let mut buffer = Buffer::local(saved_context.text, cx);
1130 for message in saved_context.messages {
1131 message_anchors.push(MessageAnchor {
1132 id: message.id,
1133 start: buffer.anchor_before(message.start),
1134 });
1135 next_message_id = cmp::max(next_message_id, MessageId(message.id.0 + 1));
1136 }
1137 buffer.set_language_registry(language_registry.clone());
1138 cx.spawn(|buffer, mut cx| async move {
1139 let markdown = markdown.await?;
1140 buffer.update(&mut cx, |buffer: &mut Buffer, cx| {
1141 buffer.set_language(Some(markdown), cx)
1142 })?;
1143 anyhow::Ok(())
1144 })
1145 .detach_and_log_err(cx);
1146 buffer
1147 })?;
1148
1149 cx.new_model(move |cx| {
1150 let edits_since_last_slash_command_parse =
1151 buffer.update(cx, |buffer, _| buffer.subscribe());
1152 let mut this = Self {
1153 id,
1154 message_anchors,
1155 messages_metadata: saved_context.message_metadata,
1156 next_message_id,
1157 edit_suggestions: Vec::new(),
1158 pending_slash_commands: Vec::new(),
1159 edits_since_last_slash_command_parse,
1160 summary: Some(Summary {
1161 text: saved_context.summary,
1162 done: true,
1163 }),
1164 pending_summary: Task::ready(None),
1165 completion_count: Default::default(),
1166 pending_completions: Default::default(),
1167 token_count: None,
1168 pending_edit_suggestion_parse: None,
1169 pending_token_count: Task::ready(None),
1170 _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
1171 pending_save: Task::ready(Ok(())),
1172 path: Some(path),
1173 buffer,
1174 telemetry,
1175 language_registry,
1176 slash_command_registry,
1177 };
1178 this.set_language(cx);
1179 this.reparse_edit_suggestions(cx);
1180 this.count_remaining_tokens(cx);
1181 this
1182 })
1183 }
1184
1185 fn set_language(&mut self, cx: &mut ModelContext<Self>) {
1186 let markdown = self.language_registry.language_for_name("Markdown");
1187 cx.spawn(|this, mut cx| async move {
1188 let markdown = markdown.await?;
1189 this.update(&mut cx, |this, cx| {
1190 this.buffer
1191 .update(cx, |buffer, cx| buffer.set_language(Some(markdown), cx));
1192 })
1193 })
1194 .detach_and_log_err(cx);
1195 }
1196
1197 fn handle_buffer_event(
1198 &mut self,
1199 _: Model<Buffer>,
1200 event: &language::Event,
1201 cx: &mut ModelContext<Self>,
1202 ) {
1203 if *event == language::Event::Edited {
1204 self.count_remaining_tokens(cx);
1205 self.reparse_edit_suggestions(cx);
1206 self.reparse_slash_commands(cx);
1207 cx.emit(ContextEvent::MessagesEdited);
1208 }
1209 }
1210
1211 pub(crate) fn token_count(&self) -> Option<usize> {
1212 self.token_count
1213 }
1214
1215 pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
1216 let request = self.to_completion_request(cx);
1217 self.pending_token_count = cx.spawn(|this, mut cx| {
1218 async move {
1219 cx.background_executor()
1220 .timer(Duration::from_millis(200))
1221 .await;
1222
1223 let token_count = cx
1224 .update(|cx| CompletionProvider::global(cx).count_tokens(request, cx))?
1225 .await?;
1226
1227 this.update(&mut cx, |this, cx| {
1228 this.token_count = Some(token_count);
1229 cx.notify()
1230 })?;
1231 anyhow::Ok(())
1232 }
1233 .log_err()
1234 });
1235 }
1236
1237 fn reparse_slash_commands(&mut self, cx: &mut ModelContext<Self>) {
1238 let buffer = self.buffer.read(cx);
1239 let mut row_ranges = self
1240 .edits_since_last_slash_command_parse
1241 .consume()
1242 .into_iter()
1243 .map(|edit| {
1244 let start_row = buffer.offset_to_point(edit.new.start).row;
1245 let end_row = buffer.offset_to_point(edit.new.end).row + 1;
1246 start_row..end_row
1247 })
1248 .peekable();
1249
1250 let mut removed = Vec::new();
1251 let mut updated = Vec::new();
1252 while let Some(mut row_range) = row_ranges.next() {
1253 while let Some(next_row_range) = row_ranges.peek() {
1254 if row_range.end >= next_row_range.start {
1255 row_range.end = next_row_range.end;
1256 row_ranges.next();
1257 } else {
1258 break;
1259 }
1260 }
1261
1262 let start = buffer.anchor_before(Point::new(row_range.start, 0));
1263 let end = buffer.anchor_after(Point::new(
1264 row_range.end - 1,
1265 buffer.line_len(row_range.end - 1),
1266 ));
1267
1268 let old_range = self.pending_command_indices_for_range(start..end, cx);
1269
1270 let mut new_commands = Vec::new();
1271 let mut lines = buffer.text_for_range(start..end).lines();
1272 let mut offset = lines.offset();
1273 while let Some(line) = lines.next() {
1274 if let Some(command_line) = SlashCommandLine::parse(line) {
1275 let name = &line[command_line.name.clone()];
1276 let argument = command_line.argument.as_ref().and_then(|argument| {
1277 (!argument.is_empty()).then_some(&line[argument.clone()])
1278 });
1279 if let Some(command) = self.slash_command_registry.command(name) {
1280 if !command.requires_argument() || argument.is_some() {
1281 let start_ix = offset + command_line.name.start - 1;
1282 let end_ix = offset
1283 + command_line
1284 .argument
1285 .map_or(command_line.name.end, |argument| argument.end);
1286 let source_range =
1287 buffer.anchor_after(start_ix)..buffer.anchor_after(end_ix);
1288 let pending_command = PendingSlashCommand {
1289 name: name.to_string(),
1290 argument: argument.map(ToString::to_string),
1291 source_range,
1292 status: PendingSlashCommandStatus::Idle,
1293 };
1294 updated.push(pending_command.clone());
1295 new_commands.push(pending_command);
1296 }
1297 }
1298 }
1299
1300 offset = lines.offset();
1301 }
1302
1303 let removed_commands = self.pending_slash_commands.splice(old_range, new_commands);
1304 removed.extend(removed_commands.map(|command| command.source_range));
1305 }
1306
1307 if !updated.is_empty() || !removed.is_empty() {
1308 cx.emit(ContextEvent::PendingSlashCommandsUpdated { removed, updated });
1309 }
1310 }
1311
1312 fn reparse_edit_suggestions(&mut self, cx: &mut ModelContext<Self>) {
1313 self.pending_edit_suggestion_parse = Some(cx.spawn(|this, mut cx| async move {
1314 cx.background_executor()
1315 .timer(Duration::from_millis(200))
1316 .await;
1317
1318 this.update(&mut cx, |this, cx| {
1319 this.reparse_edit_suggestions_in_range(0..this.buffer.read(cx).len(), cx);
1320 })
1321 .ok();
1322 }));
1323 }
1324
1325 fn reparse_edit_suggestions_in_range(
1326 &mut self,
1327 range: Range<usize>,
1328 cx: &mut ModelContext<Self>,
1329 ) {
1330 self.buffer.update(cx, |buffer, _| {
1331 let range_start = buffer.anchor_before(range.start);
1332 let range_end = buffer.anchor_after(range.end);
1333 let start_ix = self
1334 .edit_suggestions
1335 .binary_search_by(|probe| {
1336 probe
1337 .source_range
1338 .end
1339 .cmp(&range_start, buffer)
1340 .then(Ordering::Greater)
1341 })
1342 .unwrap_err();
1343 let end_ix = self
1344 .edit_suggestions
1345 .binary_search_by(|probe| {
1346 probe
1347 .source_range
1348 .start
1349 .cmp(&range_end, buffer)
1350 .then(Ordering::Less)
1351 })
1352 .unwrap_err();
1353
1354 let mut new_edit_suggestions = Vec::new();
1355 let mut message_lines = buffer.as_rope().chunks_in_range(range).lines();
1356 while let Some(suggestion) = parse_next_edit_suggestion(&mut message_lines) {
1357 let start_anchor = buffer.anchor_after(suggestion.outer_range.start);
1358 let end_anchor = buffer.anchor_before(suggestion.outer_range.end);
1359 new_edit_suggestions.push(EditSuggestion {
1360 source_range: start_anchor..end_anchor,
1361 full_path: suggestion.path,
1362 });
1363 }
1364 self.edit_suggestions
1365 .splice(start_ix..end_ix, new_edit_suggestions);
1366 });
1367 cx.emit(ContextEvent::EditSuggestionsChanged);
1368 cx.notify();
1369 }
1370
1371 fn pending_command_for_position(
1372 &mut self,
1373 position: language::Anchor,
1374 cx: &mut ModelContext<Self>,
1375 ) -> Option<&mut PendingSlashCommand> {
1376 let buffer = self.buffer.read(cx);
1377 match self
1378 .pending_slash_commands
1379 .binary_search_by(|probe| probe.source_range.end.cmp(&position, buffer))
1380 {
1381 Ok(ix) => Some(&mut self.pending_slash_commands[ix]),
1382 Err(ix) => {
1383 let cmd = self.pending_slash_commands.get_mut(ix)?;
1384 if position.cmp(&cmd.source_range.start, buffer).is_ge()
1385 && position.cmp(&cmd.source_range.end, buffer).is_le()
1386 {
1387 Some(cmd)
1388 } else {
1389 None
1390 }
1391 }
1392 }
1393 }
1394
1395 fn pending_commands_for_range(
1396 &self,
1397 range: Range<language::Anchor>,
1398 cx: &AppContext,
1399 ) -> &[PendingSlashCommand] {
1400 let range = self.pending_command_indices_for_range(range, cx);
1401 &self.pending_slash_commands[range]
1402 }
1403
1404 fn pending_command_indices_for_range(
1405 &self,
1406 range: Range<language::Anchor>,
1407 cx: &AppContext,
1408 ) -> Range<usize> {
1409 let buffer = self.buffer.read(cx);
1410 let start_ix = match self
1411 .pending_slash_commands
1412 .binary_search_by(|probe| probe.source_range.end.cmp(&range.start, &buffer))
1413 {
1414 Ok(ix) | Err(ix) => ix,
1415 };
1416 let end_ix = match self
1417 .pending_slash_commands
1418 .binary_search_by(|probe| probe.source_range.start.cmp(&range.end, &buffer))
1419 {
1420 Ok(ix) => ix + 1,
1421 Err(ix) => ix,
1422 };
1423 start_ix..end_ix
1424 }
1425
1426 fn insert_command_output(
1427 &mut self,
1428 command_range: Range<language::Anchor>,
1429 output: Task<Result<SlashCommandOutput>>,
1430 insert_trailing_newline: bool,
1431 cx: &mut ModelContext<Self>,
1432 ) {
1433 self.reparse_slash_commands(cx);
1434
1435 let insert_output_task = cx.spawn(|this, mut cx| {
1436 let command_range = command_range.clone();
1437 async move {
1438 let output = output.await;
1439 this.update(&mut cx, |this, cx| match output {
1440 Ok(mut output) => {
1441 if insert_trailing_newline {
1442 output.text.push('\n');
1443 }
1444
1445 let event = this.buffer.update(cx, |buffer, cx| {
1446 let start = command_range.start.to_offset(buffer);
1447 let old_end = command_range.end.to_offset(buffer);
1448 let new_end = start + output.text.len();
1449 buffer.edit([(start..old_end, output.text)], None, cx);
1450
1451 let mut sections = output
1452 .sections
1453 .into_iter()
1454 .map(|section| SlashCommandOutputSection {
1455 range: buffer.anchor_after(start + section.range.start)
1456 ..buffer.anchor_before(start + section.range.end),
1457 render_placeholder: section.render_placeholder,
1458 })
1459 .collect::<Vec<_>>();
1460 sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
1461 ContextEvent::SlashCommandFinished {
1462 output_range: buffer.anchor_after(start)
1463 ..buffer.anchor_before(new_end),
1464 sections,
1465 run_commands_in_output: output.run_commands_in_text,
1466 }
1467 });
1468 cx.emit(event);
1469 }
1470 Err(error) => {
1471 if let Some(pending_command) =
1472 this.pending_command_for_position(command_range.start, cx)
1473 {
1474 pending_command.status =
1475 PendingSlashCommandStatus::Error(error.to_string());
1476 cx.emit(ContextEvent::PendingSlashCommandsUpdated {
1477 removed: vec![pending_command.source_range.clone()],
1478 updated: vec![pending_command.clone()],
1479 });
1480 }
1481 }
1482 })
1483 .ok();
1484 }
1485 });
1486
1487 if let Some(pending_command) = self.pending_command_for_position(command_range.start, cx) {
1488 pending_command.status = PendingSlashCommandStatus::Running {
1489 _task: insert_output_task.shared(),
1490 };
1491 cx.emit(ContextEvent::PendingSlashCommandsUpdated {
1492 removed: vec![pending_command.source_range.clone()],
1493 updated: vec![pending_command.clone()],
1494 });
1495 }
1496 }
1497
1498 fn remaining_tokens(&self, cx: &AppContext) -> Option<isize> {
1499 let model = CompletionProvider::global(cx).model();
1500 Some(model.max_token_count() as isize - self.token_count? as isize)
1501 }
1502
1503 fn completion_provider_changed(&mut self, cx: &mut ModelContext<Self>) {
1504 self.count_remaining_tokens(cx);
1505 }
1506
1507 fn assist(
1508 &mut self,
1509 selected_messages: HashSet<MessageId>,
1510 cx: &mut ModelContext<Self>,
1511 ) -> Vec<MessageAnchor> {
1512 let mut user_messages = Vec::new();
1513
1514 let last_message_id = if let Some(last_message_id) =
1515 self.message_anchors.iter().rev().find_map(|message| {
1516 message
1517 .start
1518 .is_valid(self.buffer.read(cx))
1519 .then_some(message.id)
1520 }) {
1521 last_message_id
1522 } else {
1523 return Default::default();
1524 };
1525
1526 let mut should_assist = false;
1527 for selected_message_id in selected_messages {
1528 let selected_message_role =
1529 if let Some(metadata) = self.messages_metadata.get(&selected_message_id) {
1530 metadata.role
1531 } else {
1532 continue;
1533 };
1534
1535 if selected_message_role == Role::Assistant {
1536 if let Some(user_message) = self.insert_message_after(
1537 selected_message_id,
1538 Role::User,
1539 MessageStatus::Done,
1540 cx,
1541 ) {
1542 user_messages.push(user_message);
1543 }
1544 } else {
1545 should_assist = true;
1546 }
1547 }
1548
1549 if should_assist {
1550 if !CompletionProvider::global(cx).is_authenticated() {
1551 log::info!("completion provider has no credentials");
1552 return Default::default();
1553 }
1554
1555 let request = self.to_completion_request(cx);
1556 let stream = CompletionProvider::global(cx).complete(request);
1557 let assistant_message = self
1558 .insert_message_after(last_message_id, Role::Assistant, MessageStatus::Pending, cx)
1559 .unwrap();
1560
1561 // Queue up the user's next reply.
1562 let user_message = self
1563 .insert_message_after(assistant_message.id, Role::User, MessageStatus::Done, cx)
1564 .unwrap();
1565 user_messages.push(user_message);
1566
1567 let task = cx.spawn({
1568 |this, mut cx| async move {
1569 let assistant_message_id = assistant_message.id;
1570 let mut response_latency = None;
1571 let stream_completion = async {
1572 let request_start = Instant::now();
1573 let mut messages = stream.await?;
1574
1575 while let Some(message) = messages.next().await {
1576 if response_latency.is_none() {
1577 response_latency = Some(request_start.elapsed());
1578 }
1579 let text = message?;
1580
1581 this.update(&mut cx, |this, cx| {
1582 let message_ix = this
1583 .message_anchors
1584 .iter()
1585 .position(|message| message.id == assistant_message_id)?;
1586 let message_range = this.buffer.update(cx, |buffer, cx| {
1587 let message_start_offset =
1588 this.message_anchors[message_ix].start.to_offset(buffer);
1589 let message_old_end_offset = this.message_anchors
1590 [message_ix + 1..]
1591 .iter()
1592 .find(|message| message.start.is_valid(buffer))
1593 .map_or(buffer.len(), |message| {
1594 message.start.to_offset(buffer).saturating_sub(1)
1595 });
1596 let message_new_end_offset =
1597 message_old_end_offset + text.len();
1598 buffer.edit(
1599 [(message_old_end_offset..message_old_end_offset, text)],
1600 None,
1601 cx,
1602 );
1603 message_start_offset..message_new_end_offset
1604 });
1605 this.reparse_edit_suggestions_in_range(message_range, cx);
1606 cx.emit(ContextEvent::StreamedCompletion);
1607
1608 Some(())
1609 })?;
1610 smol::future::yield_now().await;
1611 }
1612
1613 this.update(&mut cx, |this, cx| {
1614 this.pending_completions
1615 .retain(|completion| completion.id != this.completion_count);
1616 this.summarize(cx);
1617 })?;
1618
1619 anyhow::Ok(())
1620 };
1621
1622 let result = stream_completion.await;
1623
1624 this.update(&mut cx, |this, cx| {
1625 if let Some(metadata) =
1626 this.messages_metadata.get_mut(&assistant_message.id)
1627 {
1628 let error_message = result
1629 .err()
1630 .map(|error| error.to_string().trim().to_string());
1631 if let Some(error_message) = error_message.as_ref() {
1632 metadata.status =
1633 MessageStatus::Error(SharedString::from(error_message.clone()));
1634 } else {
1635 metadata.status = MessageStatus::Done;
1636 }
1637
1638 if let Some(telemetry) = this.telemetry.as_ref() {
1639 let model = CompletionProvider::global(cx).model();
1640 telemetry.report_assistant_event(
1641 this.id.clone(),
1642 AssistantKind::Panel,
1643 model.telemetry_id(),
1644 response_latency,
1645 error_message,
1646 );
1647 }
1648
1649 cx.emit(ContextEvent::MessagesEdited);
1650 }
1651 })
1652 .ok();
1653 }
1654 });
1655
1656 self.pending_completions.push(PendingCompletion {
1657 id: post_inc(&mut self.completion_count),
1658 _task: task,
1659 });
1660 }
1661
1662 user_messages
1663 }
1664
1665 pub fn to_completion_request(&self, cx: &AppContext) -> LanguageModelRequest {
1666 let messages = self
1667 .messages(cx)
1668 .filter(|message| matches!(message.status, MessageStatus::Done))
1669 .map(|message| message.to_request_message(self.buffer.read(cx)));
1670
1671 LanguageModelRequest {
1672 model: CompletionProvider::global(cx).model(),
1673 messages: messages.collect(),
1674 stop: vec![],
1675 temperature: 1.0,
1676 }
1677 }
1678
1679 fn cancel_last_assist(&mut self) -> bool {
1680 self.pending_completions.pop().is_some()
1681 }
1682
1683 fn cycle_message_roles(&mut self, ids: HashSet<MessageId>, cx: &mut ModelContext<Self>) {
1684 for id in ids {
1685 if let Some(metadata) = self.messages_metadata.get_mut(&id) {
1686 metadata.role.cycle();
1687 cx.emit(ContextEvent::MessagesEdited);
1688 cx.notify();
1689 }
1690 }
1691 }
1692
1693 fn insert_message_after(
1694 &mut self,
1695 message_id: MessageId,
1696 role: Role,
1697 status: MessageStatus,
1698 cx: &mut ModelContext<Self>,
1699 ) -> Option<MessageAnchor> {
1700 if let Some(prev_message_ix) = self
1701 .message_anchors
1702 .iter()
1703 .position(|message| message.id == message_id)
1704 {
1705 // Find the next valid message after the one we were given.
1706 let mut next_message_ix = prev_message_ix + 1;
1707 while let Some(next_message) = self.message_anchors.get(next_message_ix) {
1708 if next_message.start.is_valid(self.buffer.read(cx)) {
1709 break;
1710 }
1711 next_message_ix += 1;
1712 }
1713
1714 let start = self.buffer.update(cx, |buffer, cx| {
1715 let offset = self
1716 .message_anchors
1717 .get(next_message_ix)
1718 .map_or(buffer.len(), |message| message.start.to_offset(buffer) - 1);
1719 buffer.edit([(offset..offset, "\n")], None, cx);
1720 buffer.anchor_before(offset + 1)
1721 });
1722 let message = MessageAnchor {
1723 id: MessageId(post_inc(&mut self.next_message_id.0)),
1724 start,
1725 };
1726 self.message_anchors
1727 .insert(next_message_ix, message.clone());
1728 self.messages_metadata
1729 .insert(message.id, MessageMetadata { role, status });
1730 cx.emit(ContextEvent::MessagesEdited);
1731 Some(message)
1732 } else {
1733 None
1734 }
1735 }
1736
1737 fn split_message(
1738 &mut self,
1739 range: Range<usize>,
1740 cx: &mut ModelContext<Self>,
1741 ) -> (Option<MessageAnchor>, Option<MessageAnchor>) {
1742 let start_message = self.message_for_offset(range.start, cx);
1743 let end_message = self.message_for_offset(range.end, cx);
1744 if let Some((start_message, end_message)) = start_message.zip(end_message) {
1745 // Prevent splitting when range spans multiple messages.
1746 if start_message.id != end_message.id {
1747 return (None, None);
1748 }
1749
1750 let message = start_message;
1751 let role = message.role;
1752 let mut edited_buffer = false;
1753
1754 let mut suffix_start = None;
1755 if range.start > message.offset_range.start && range.end < message.offset_range.end - 1
1756 {
1757 if self.buffer.read(cx).chars_at(range.end).next() == Some('\n') {
1758 suffix_start = Some(range.end + 1);
1759 } else if self.buffer.read(cx).reversed_chars_at(range.end).next() == Some('\n') {
1760 suffix_start = Some(range.end);
1761 }
1762 }
1763
1764 let suffix = if let Some(suffix_start) = suffix_start {
1765 MessageAnchor {
1766 id: MessageId(post_inc(&mut self.next_message_id.0)),
1767 start: self.buffer.read(cx).anchor_before(suffix_start),
1768 }
1769 } else {
1770 self.buffer.update(cx, |buffer, cx| {
1771 buffer.edit([(range.end..range.end, "\n")], None, cx);
1772 });
1773 edited_buffer = true;
1774 MessageAnchor {
1775 id: MessageId(post_inc(&mut self.next_message_id.0)),
1776 start: self.buffer.read(cx).anchor_before(range.end + 1),
1777 }
1778 };
1779
1780 self.message_anchors
1781 .insert(message.index_range.end + 1, suffix.clone());
1782 self.messages_metadata.insert(
1783 suffix.id,
1784 MessageMetadata {
1785 role,
1786 status: MessageStatus::Done,
1787 },
1788 );
1789
1790 let new_messages =
1791 if range.start == range.end || range.start == message.offset_range.start {
1792 (None, Some(suffix))
1793 } else {
1794 let mut prefix_end = None;
1795 if range.start > message.offset_range.start
1796 && range.end < message.offset_range.end - 1
1797 {
1798 if self.buffer.read(cx).chars_at(range.start).next() == Some('\n') {
1799 prefix_end = Some(range.start + 1);
1800 } else if self.buffer.read(cx).reversed_chars_at(range.start).next()
1801 == Some('\n')
1802 {
1803 prefix_end = Some(range.start);
1804 }
1805 }
1806
1807 let selection = if let Some(prefix_end) = prefix_end {
1808 cx.emit(ContextEvent::MessagesEdited);
1809 MessageAnchor {
1810 id: MessageId(post_inc(&mut self.next_message_id.0)),
1811 start: self.buffer.read(cx).anchor_before(prefix_end),
1812 }
1813 } else {
1814 self.buffer.update(cx, |buffer, cx| {
1815 buffer.edit([(range.start..range.start, "\n")], None, cx)
1816 });
1817 edited_buffer = true;
1818 MessageAnchor {
1819 id: MessageId(post_inc(&mut self.next_message_id.0)),
1820 start: self.buffer.read(cx).anchor_before(range.end + 1),
1821 }
1822 };
1823
1824 self.message_anchors
1825 .insert(message.index_range.end + 1, selection.clone());
1826 self.messages_metadata.insert(
1827 selection.id,
1828 MessageMetadata {
1829 role,
1830 status: MessageStatus::Done,
1831 },
1832 );
1833 (Some(selection), Some(suffix))
1834 };
1835
1836 if !edited_buffer {
1837 cx.emit(ContextEvent::MessagesEdited);
1838 }
1839 new_messages
1840 } else {
1841 (None, None)
1842 }
1843 }
1844
1845 fn summarize(&mut self, cx: &mut ModelContext<Self>) {
1846 if self.message_anchors.len() >= 2 && self.summary.is_none() {
1847 if !CompletionProvider::global(cx).is_authenticated() {
1848 return;
1849 }
1850
1851 let messages = self
1852 .messages(cx)
1853 .map(|message| message.to_request_message(self.buffer.read(cx)))
1854 .chain(Some(LanguageModelRequestMessage {
1855 role: Role::User,
1856 content: "Summarize the context into a short title without punctuation.".into(),
1857 }));
1858 let request = LanguageModelRequest {
1859 model: CompletionProvider::global(cx).model(),
1860 messages: messages.collect(),
1861 stop: vec![],
1862 temperature: 1.0,
1863 };
1864
1865 let stream = CompletionProvider::global(cx).complete(request);
1866 self.pending_summary = cx.spawn(|this, mut cx| {
1867 async move {
1868 let mut messages = stream.await?;
1869
1870 while let Some(message) = messages.next().await {
1871 let text = message?;
1872 let mut lines = text.lines();
1873 this.update(&mut cx, |this, cx| {
1874 let summary = this.summary.get_or_insert(Default::default());
1875 summary.text.extend(lines.next());
1876 cx.emit(ContextEvent::SummaryChanged);
1877 })?;
1878
1879 // Stop if the LLM generated multiple lines.
1880 if lines.next().is_some() {
1881 break;
1882 }
1883 }
1884
1885 this.update(&mut cx, |this, cx| {
1886 if let Some(summary) = this.summary.as_mut() {
1887 summary.done = true;
1888 cx.emit(ContextEvent::SummaryChanged);
1889 }
1890 })?;
1891
1892 anyhow::Ok(())
1893 }
1894 .log_err()
1895 });
1896 }
1897 }
1898
1899 fn message_for_offset(&self, offset: usize, cx: &AppContext) -> Option<Message> {
1900 self.messages_for_offsets([offset], cx).pop()
1901 }
1902
1903 fn messages_for_offsets(
1904 &self,
1905 offsets: impl IntoIterator<Item = usize>,
1906 cx: &AppContext,
1907 ) -> Vec<Message> {
1908 let mut result = Vec::new();
1909
1910 let mut messages = self.messages(cx).peekable();
1911 let mut offsets = offsets.into_iter().peekable();
1912 let mut current_message = messages.next();
1913 while let Some(offset) = offsets.next() {
1914 // Locate the message that contains the offset.
1915 while current_message.as_ref().map_or(false, |message| {
1916 !message.offset_range.contains(&offset) && messages.peek().is_some()
1917 }) {
1918 current_message = messages.next();
1919 }
1920 let Some(message) = current_message.as_ref() else {
1921 break;
1922 };
1923
1924 // Skip offsets that are in the same message.
1925 while offsets.peek().map_or(false, |offset| {
1926 message.offset_range.contains(offset) || messages.peek().is_none()
1927 }) {
1928 offsets.next();
1929 }
1930
1931 result.push(message.clone());
1932 }
1933 result
1934 }
1935
1936 fn messages<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Message> {
1937 let buffer = self.buffer.read(cx);
1938 let mut message_anchors = self.message_anchors.iter().enumerate().peekable();
1939 iter::from_fn(move || {
1940 if let Some((start_ix, message_anchor)) = message_anchors.next() {
1941 let metadata = self.messages_metadata.get(&message_anchor.id)?;
1942 let message_start = message_anchor.start.to_offset(buffer);
1943 let mut message_end = None;
1944 let mut end_ix = start_ix;
1945 while let Some((_, next_message)) = message_anchors.peek() {
1946 if next_message.start.is_valid(buffer) {
1947 message_end = Some(next_message.start);
1948 break;
1949 } else {
1950 end_ix += 1;
1951 message_anchors.next();
1952 }
1953 }
1954 let message_end = message_end
1955 .unwrap_or(language::Anchor::MAX)
1956 .to_offset(buffer);
1957
1958 return Some(Message {
1959 index_range: start_ix..end_ix,
1960 offset_range: message_start..message_end,
1961 id: message_anchor.id,
1962 anchor: message_anchor.start,
1963 role: metadata.role,
1964 status: metadata.status.clone(),
1965 });
1966 }
1967 None
1968 })
1969 }
1970
1971 fn save(
1972 &mut self,
1973 debounce: Option<Duration>,
1974 fs: Arc<dyn Fs>,
1975 cx: &mut ModelContext<Context>,
1976 ) {
1977 self.pending_save = cx.spawn(|this, mut cx| async move {
1978 if let Some(debounce) = debounce {
1979 cx.background_executor().timer(debounce).await;
1980 }
1981
1982 let (old_path, summary) = this.read_with(&cx, |this, _| {
1983 let path = this.path.clone();
1984 let summary = if let Some(summary) = this.summary.as_ref() {
1985 if summary.done {
1986 Some(summary.text.clone())
1987 } else {
1988 None
1989 }
1990 } else {
1991 None
1992 };
1993 (path, summary)
1994 })?;
1995
1996 if let Some(summary) = summary {
1997 let context = this.read_with(&cx, |this, cx| this.serialize(cx))?;
1998 let path = if let Some(old_path) = old_path {
1999 old_path
2000 } else {
2001 let mut discriminant = 1;
2002 let mut new_path;
2003 loop {
2004 new_path = CONTEXTS_DIR.join(&format!(
2005 "{} - {}.zed.json",
2006 summary.trim(),
2007 discriminant
2008 ));
2009 if fs.is_file(&new_path).await {
2010 discriminant += 1;
2011 } else {
2012 break;
2013 }
2014 }
2015 new_path
2016 };
2017
2018 fs.create_dir(CONTEXTS_DIR.as_ref()).await?;
2019 fs.atomic_write(path.clone(), serde_json::to_string(&context).unwrap())
2020 .await?;
2021 this.update(&mut cx, |this, _| this.path = Some(path))?;
2022 }
2023
2024 Ok(())
2025 });
2026 }
2027}
2028
2029#[derive(Debug)]
2030enum EditParsingState {
2031 None,
2032 InOldText {
2033 path: PathBuf,
2034 start_offset: usize,
2035 old_text_start_offset: usize,
2036 },
2037 InNewText {
2038 path: PathBuf,
2039 start_offset: usize,
2040 old_text_range: Range<usize>,
2041 new_text_start_offset: usize,
2042 },
2043}
2044
2045#[derive(Clone, Debug, PartialEq)]
2046struct EditSuggestion {
2047 source_range: Range<language::Anchor>,
2048 full_path: PathBuf,
2049}
2050
2051struct ParsedEditSuggestion {
2052 path: PathBuf,
2053 outer_range: Range<usize>,
2054 old_text_range: Range<usize>,
2055 new_text_range: Range<usize>,
2056}
2057
2058fn parse_next_edit_suggestion(lines: &mut rope::Lines) -> Option<ParsedEditSuggestion> {
2059 let mut state = EditParsingState::None;
2060 loop {
2061 let offset = lines.offset();
2062 let message_line = lines.next()?;
2063 match state {
2064 EditParsingState::None => {
2065 if let Some(rest) = message_line.strip_prefix("```edit ") {
2066 let path = rest.trim();
2067 if !path.is_empty() {
2068 state = EditParsingState::InOldText {
2069 path: PathBuf::from(path),
2070 start_offset: offset,
2071 old_text_start_offset: lines.offset(),
2072 };
2073 }
2074 }
2075 }
2076 EditParsingState::InOldText {
2077 path,
2078 start_offset,
2079 old_text_start_offset,
2080 } => {
2081 if message_line == "---" {
2082 state = EditParsingState::InNewText {
2083 path,
2084 start_offset,
2085 old_text_range: old_text_start_offset..offset,
2086 new_text_start_offset: lines.offset(),
2087 };
2088 } else {
2089 state = EditParsingState::InOldText {
2090 path,
2091 start_offset,
2092 old_text_start_offset,
2093 };
2094 }
2095 }
2096 EditParsingState::InNewText {
2097 path,
2098 start_offset,
2099 old_text_range,
2100 new_text_start_offset,
2101 } => {
2102 if message_line == "```" {
2103 return Some(ParsedEditSuggestion {
2104 path,
2105 outer_range: start_offset..offset + "```".len(),
2106 old_text_range,
2107 new_text_range: new_text_start_offset..offset,
2108 });
2109 } else {
2110 state = EditParsingState::InNewText {
2111 path,
2112 start_offset,
2113 old_text_range,
2114 new_text_start_offset,
2115 };
2116 }
2117 }
2118 }
2119 }
2120}
2121
2122#[derive(Clone)]
2123struct PendingSlashCommand {
2124 name: String,
2125 argument: Option<String>,
2126 status: PendingSlashCommandStatus,
2127 source_range: Range<language::Anchor>,
2128}
2129
2130#[derive(Clone)]
2131enum PendingSlashCommandStatus {
2132 Idle,
2133 Running { _task: Shared<Task<()>> },
2134 Error(String),
2135}
2136
2137struct PendingCompletion {
2138 id: usize,
2139 _task: Task<()>,
2140}
2141
2142enum ContextEditorEvent {
2143 TabContentChanged,
2144}
2145
2146#[derive(Copy, Clone, Debug, PartialEq)]
2147struct ScrollPosition {
2148 offset_before_cursor: gpui::Point<f32>,
2149 cursor: Anchor,
2150}
2151
2152pub struct ContextEditor {
2153 context: Model<Context>,
2154 fs: Arc<dyn Fs>,
2155 workspace: WeakView<Workspace>,
2156 slash_command_registry: Arc<SlashCommandRegistry>,
2157 lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
2158 editor: View<Editor>,
2159 blocks: HashSet<BlockId>,
2160 scroll_position: Option<ScrollPosition>,
2161 pending_slash_command_flaps: HashMap<Range<language::Anchor>, FlapId>,
2162 _subscriptions: Vec<Subscription>,
2163}
2164
2165impl ContextEditor {
2166 fn new(
2167 language_registry: Arc<LanguageRegistry>,
2168 slash_command_registry: Arc<SlashCommandRegistry>,
2169 fs: Arc<dyn Fs>,
2170 workspace: View<Workspace>,
2171 cx: &mut ViewContext<Self>,
2172 ) -> Self {
2173 let telemetry = workspace.read(cx).client().telemetry().clone();
2174 let project = workspace.read(cx).project().clone();
2175 let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err();
2176
2177 let context = cx.new_model(|cx| {
2178 Context::new(
2179 language_registry,
2180 slash_command_registry,
2181 Some(telemetry),
2182 cx,
2183 )
2184 });
2185
2186 let mut this = Self::for_context(context, fs, workspace, lsp_adapter_delegate, cx);
2187 this.insert_default_prompt(cx);
2188 this
2189 }
2190
2191 fn for_context(
2192 context: Model<Context>,
2193 fs: Arc<dyn Fs>,
2194 workspace: View<Workspace>,
2195 lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
2196 cx: &mut ViewContext<Self>,
2197 ) -> Self {
2198 let slash_command_registry = context.read(cx).slash_command_registry.clone();
2199
2200 let completion_provider = SlashCommandCompletionProvider::new(
2201 slash_command_registry.clone(),
2202 Some(cx.view().downgrade()),
2203 Some(workspace.downgrade()),
2204 );
2205
2206 let editor = cx.new_view(|cx| {
2207 let mut editor = Editor::for_buffer(context.read(cx).buffer.clone(), None, cx);
2208 editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
2209 editor.set_show_line_numbers(false, cx);
2210 editor.set_show_git_diff_gutter(false, cx);
2211 editor.set_show_code_actions(false, cx);
2212 editor.set_show_wrap_guides(false, cx);
2213 editor.set_show_indent_guides(false, cx);
2214 editor.set_completion_provider(Box::new(completion_provider));
2215 editor
2216 });
2217
2218 let _subscriptions = vec![
2219 cx.observe(&context, |_, _, cx| cx.notify()),
2220 cx.subscribe(&context, Self::handle_context_event),
2221 cx.subscribe(&editor, Self::handle_editor_event),
2222 ];
2223
2224 let mut this = Self {
2225 context,
2226 editor,
2227 slash_command_registry,
2228 lsp_adapter_delegate,
2229 blocks: Default::default(),
2230 scroll_position: None,
2231 fs,
2232 workspace: workspace.downgrade(),
2233 pending_slash_command_flaps: HashMap::default(),
2234 _subscriptions,
2235 };
2236 this.update_message_headers(cx);
2237 this
2238 }
2239
2240 fn insert_default_prompt(&mut self, cx: &mut ViewContext<Self>) {
2241 let command_name = DefaultSlashCommand.name();
2242 self.editor.update(cx, |editor, cx| {
2243 editor.insert(&format!("/{command_name}"), cx)
2244 });
2245 self.split(&Split, cx);
2246 let command = self.context.update(cx, |context, cx| {
2247 context
2248 .messages_metadata
2249 .get_mut(&MessageId::default())
2250 .unwrap()
2251 .role = Role::System;
2252 context.reparse_slash_commands(cx);
2253 context.pending_slash_commands[0].clone()
2254 });
2255
2256 self.run_command(
2257 command.source_range,
2258 &command.name,
2259 command.argument.as_deref(),
2260 false,
2261 self.workspace.clone(),
2262 cx,
2263 );
2264 }
2265
2266 fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
2267 let cursors = self.cursors(cx);
2268
2269 let user_messages = self.context.update(cx, |context, cx| {
2270 let selected_messages = context
2271 .messages_for_offsets(cursors, cx)
2272 .into_iter()
2273 .map(|message| message.id)
2274 .collect();
2275 context.assist(selected_messages, cx)
2276 });
2277 let new_selections = user_messages
2278 .iter()
2279 .map(|message| {
2280 let cursor = message
2281 .start
2282 .to_offset(self.context.read(cx).buffer.read(cx));
2283 cursor..cursor
2284 })
2285 .collect::<Vec<_>>();
2286 if !new_selections.is_empty() {
2287 self.editor.update(cx, |editor, cx| {
2288 editor.change_selections(
2289 Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
2290 cx,
2291 |selections| selections.select_ranges(new_selections),
2292 );
2293 });
2294 // Avoid scrolling to the new cursor position so the assistant's output is stable.
2295 cx.defer(|this, _| this.scroll_position = None);
2296 }
2297 }
2298
2299 fn cancel_last_assist(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
2300 if !self
2301 .context
2302 .update(cx, |context, _| context.cancel_last_assist())
2303 {
2304 cx.propagate();
2305 }
2306 }
2307
2308 fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext<Self>) {
2309 let cursors = self.cursors(cx);
2310 self.context.update(cx, |context, cx| {
2311 let messages = context
2312 .messages_for_offsets(cursors, cx)
2313 .into_iter()
2314 .map(|message| message.id)
2315 .collect();
2316 context.cycle_message_roles(messages, cx)
2317 });
2318 }
2319
2320 fn cursors(&self, cx: &AppContext) -> Vec<usize> {
2321 let selections = self.editor.read(cx).selections.all::<usize>(cx);
2322 selections
2323 .into_iter()
2324 .map(|selection| selection.head())
2325 .collect()
2326 }
2327
2328 fn insert_command(&mut self, name: &str, cx: &mut ViewContext<Self>) {
2329 if let Some(command) = self.slash_command_registry.command(name) {
2330 self.editor.update(cx, |editor, cx| {
2331 editor.transact(cx, |editor, cx| {
2332 editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel());
2333 let snapshot = editor.buffer().read(cx).snapshot(cx);
2334 let newest_cursor = editor.selections.newest::<Point>(cx).head();
2335 if newest_cursor.column > 0
2336 || snapshot
2337 .chars_at(newest_cursor)
2338 .next()
2339 .map_or(false, |ch| ch != '\n')
2340 {
2341 editor.move_to_end_of_line(
2342 &MoveToEndOfLine {
2343 stop_at_soft_wraps: false,
2344 },
2345 cx,
2346 );
2347 editor.newline(&Newline, cx);
2348 }
2349
2350 editor.insert(&format!("/{name}"), cx);
2351 if command.requires_argument() {
2352 editor.insert(" ", cx);
2353 editor.show_completions(&ShowCompletions, cx);
2354 }
2355 });
2356 });
2357 if !command.requires_argument() {
2358 self.confirm_command(&ConfirmCommand, cx);
2359 }
2360 }
2361 }
2362
2363 pub fn confirm_command(&mut self, _: &ConfirmCommand, cx: &mut ViewContext<Self>) {
2364 let selections = self.editor.read(cx).selections.disjoint_anchors();
2365 let mut commands_by_range = HashMap::default();
2366 let workspace = self.workspace.clone();
2367 self.context.update(cx, |context, cx| {
2368 context.reparse_slash_commands(cx);
2369 for selection in selections.iter() {
2370 if let Some(command) =
2371 context.pending_command_for_position(selection.head().text_anchor, cx)
2372 {
2373 commands_by_range
2374 .entry(command.source_range.clone())
2375 .or_insert_with(|| command.clone());
2376 }
2377 }
2378 });
2379
2380 if commands_by_range.is_empty() {
2381 cx.propagate();
2382 } else {
2383 for command in commands_by_range.into_values() {
2384 self.run_command(
2385 command.source_range,
2386 &command.name,
2387 command.argument.as_deref(),
2388 true,
2389 workspace.clone(),
2390 cx,
2391 );
2392 }
2393 cx.stop_propagation();
2394 }
2395 }
2396
2397 pub fn run_command(
2398 &mut self,
2399 command_range: Range<language::Anchor>,
2400 name: &str,
2401 argument: Option<&str>,
2402 insert_trailing_newline: bool,
2403 workspace: WeakView<Workspace>,
2404 cx: &mut ViewContext<Self>,
2405 ) {
2406 if let Some(command) = self.slash_command_registry.command(name) {
2407 if let Some(lsp_adapter_delegate) = self.lsp_adapter_delegate.clone() {
2408 let argument = argument.map(ToString::to_string);
2409 let output = command.run(argument.as_deref(), workspace, lsp_adapter_delegate, cx);
2410 self.context.update(cx, |context, cx| {
2411 context.insert_command_output(
2412 command_range,
2413 output,
2414 insert_trailing_newline,
2415 cx,
2416 )
2417 });
2418 }
2419 }
2420 }
2421
2422 fn handle_context_event(
2423 &mut self,
2424 _: Model<Context>,
2425 event: &ContextEvent,
2426 cx: &mut ViewContext<Self>,
2427 ) {
2428 let context_editor = cx.view().downgrade();
2429
2430 match event {
2431 ContextEvent::MessagesEdited => {
2432 self.update_message_headers(cx);
2433 self.context.update(cx, |context, cx| {
2434 context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
2435 });
2436 }
2437 ContextEvent::EditSuggestionsChanged => {
2438 self.editor.update(cx, |editor, cx| {
2439 let buffer = editor.buffer().read(cx).snapshot(cx);
2440 let excerpt_id = *buffer.as_singleton().unwrap().0;
2441 let context = self.context.read(cx);
2442 let highlighted_rows = context
2443 .edit_suggestions
2444 .iter()
2445 .map(|suggestion| {
2446 let start = buffer
2447 .anchor_in_excerpt(excerpt_id, suggestion.source_range.start)
2448 .unwrap();
2449 let end = buffer
2450 .anchor_in_excerpt(excerpt_id, suggestion.source_range.end)
2451 .unwrap();
2452 start..=end
2453 })
2454 .collect::<Vec<_>>();
2455
2456 editor.clear_row_highlights::<EditSuggestion>();
2457 for range in highlighted_rows {
2458 editor.highlight_rows::<EditSuggestion>(
2459 range,
2460 Some(
2461 cx.theme()
2462 .colors()
2463 .editor_document_highlight_read_background,
2464 ),
2465 false,
2466 cx,
2467 );
2468 }
2469 });
2470 }
2471 ContextEvent::SummaryChanged => {
2472 cx.emit(ContextEditorEvent::TabContentChanged);
2473 self.context.update(cx, |context, cx| {
2474 context.save(None, self.fs.clone(), cx);
2475 });
2476 }
2477 ContextEvent::StreamedCompletion => {
2478 self.editor.update(cx, |editor, cx| {
2479 if let Some(scroll_position) = self.scroll_position {
2480 let snapshot = editor.snapshot(cx);
2481 let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
2482 let scroll_top =
2483 cursor_point.row().as_f32() - scroll_position.offset_before_cursor.y;
2484 editor.set_scroll_position(
2485 point(scroll_position.offset_before_cursor.x, scroll_top),
2486 cx,
2487 );
2488 }
2489 });
2490 }
2491 ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
2492 self.editor.update(cx, |editor, cx| {
2493 let buffer = editor.buffer().read(cx).snapshot(cx);
2494 let excerpt_id = *buffer.as_singleton().unwrap().0;
2495
2496 editor.remove_flaps(
2497 removed
2498 .iter()
2499 .filter_map(|range| self.pending_slash_command_flaps.remove(range)),
2500 cx,
2501 );
2502
2503 let flap_ids = editor.insert_flaps(
2504 updated.iter().map(|command| {
2505 let workspace = self.workspace.clone();
2506 let confirm_command = Arc::new({
2507 let context_editor = context_editor.clone();
2508 let command = command.clone();
2509 move |cx: &mut WindowContext| {
2510 context_editor
2511 .update(cx, |context_editor, cx| {
2512 context_editor.run_command(
2513 command.source_range.clone(),
2514 &command.name,
2515 command.argument.as_deref(),
2516 false,
2517 workspace.clone(),
2518 cx,
2519 );
2520 })
2521 .ok();
2522 }
2523 });
2524 let placeholder = FoldPlaceholder {
2525 render: Arc::new(move |_, _, _| Empty.into_any()),
2526 constrain_width: false,
2527 merge_adjacent: false,
2528 };
2529 let render_toggle = {
2530 let confirm_command = confirm_command.clone();
2531 let command = command.clone();
2532 move |row, _, _, _cx: &mut WindowContext| {
2533 render_pending_slash_command_gutter_decoration(
2534 row,
2535 command.status.clone(),
2536 confirm_command.clone(),
2537 )
2538 }
2539 };
2540 let render_trailer =
2541 |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
2542
2543 let start = buffer
2544 .anchor_in_excerpt(excerpt_id, command.source_range.start)
2545 .unwrap();
2546 let end = buffer
2547 .anchor_in_excerpt(excerpt_id, command.source_range.end)
2548 .unwrap();
2549 Flap::new(start..end, placeholder, render_toggle, render_trailer)
2550 }),
2551 cx,
2552 );
2553
2554 self.pending_slash_command_flaps.extend(
2555 updated
2556 .iter()
2557 .map(|command| command.source_range.clone())
2558 .zip(flap_ids),
2559 );
2560 })
2561 }
2562 ContextEvent::SlashCommandFinished {
2563 output_range,
2564 sections,
2565 run_commands_in_output,
2566 } => {
2567 self.insert_slash_command_output_sections(sections.iter().cloned(), cx);
2568
2569 if *run_commands_in_output {
2570 let commands = self.context.update(cx, |context, cx| {
2571 context.reparse_slash_commands(cx);
2572 context
2573 .pending_commands_for_range(output_range.clone(), cx)
2574 .to_vec()
2575 });
2576
2577 for command in commands {
2578 self.run_command(
2579 command.source_range,
2580 &command.name,
2581 command.argument.as_deref(),
2582 false,
2583 self.workspace.clone(),
2584 cx,
2585 );
2586 }
2587 }
2588 }
2589 }
2590 }
2591
2592 fn insert_slash_command_output_sections(
2593 &mut self,
2594 sections: impl IntoIterator<Item = SlashCommandOutputSection<language::Anchor>>,
2595 cx: &mut ViewContext<Self>,
2596 ) {
2597 self.editor.update(cx, |editor, cx| {
2598 let buffer = editor.buffer().read(cx).snapshot(cx);
2599 let excerpt_id = *buffer.as_singleton().unwrap().0;
2600 let mut buffer_rows_to_fold = BTreeSet::new();
2601 let mut flaps = Vec::new();
2602 for section in sections {
2603 let start = buffer
2604 .anchor_in_excerpt(excerpt_id, section.range.start)
2605 .unwrap();
2606 let end = buffer
2607 .anchor_in_excerpt(excerpt_id, section.range.end)
2608 .unwrap();
2609 let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
2610 buffer_rows_to_fold.insert(buffer_row);
2611 flaps.push(Flap::new(
2612 start..end,
2613 FoldPlaceholder {
2614 render: Arc::new({
2615 let editor = cx.view().downgrade();
2616 let render_placeholder = section.render_placeholder.clone();
2617 move |fold_id, fold_range, cx| {
2618 let editor = editor.clone();
2619 let unfold = Arc::new(move |cx: &mut WindowContext| {
2620 editor
2621 .update(cx, |editor, cx| {
2622 let buffer_start = fold_range
2623 .start
2624 .to_point(&editor.buffer().read(cx).read(cx));
2625 let buffer_row = MultiBufferRow(buffer_start.row);
2626 editor.unfold_at(&UnfoldAt { buffer_row }, cx);
2627 })
2628 .ok();
2629 });
2630 render_placeholder(fold_id.into(), unfold, cx)
2631 }
2632 }),
2633 constrain_width: false,
2634 merge_adjacent: false,
2635 },
2636 render_slash_command_output_toggle,
2637 |_, _, _| Empty.into_any_element(),
2638 ));
2639 }
2640
2641 editor.insert_flaps(flaps, cx);
2642
2643 for buffer_row in buffer_rows_to_fold.into_iter().rev() {
2644 editor.fold_at(&FoldAt { buffer_row }, cx);
2645 }
2646 });
2647 }
2648
2649 fn handle_editor_event(
2650 &mut self,
2651 _: View<Editor>,
2652 event: &EditorEvent,
2653 cx: &mut ViewContext<Self>,
2654 ) {
2655 match event {
2656 EditorEvent::ScrollPositionChanged { autoscroll, .. } => {
2657 let cursor_scroll_position = self.cursor_scroll_position(cx);
2658 if *autoscroll {
2659 self.scroll_position = cursor_scroll_position;
2660 } else if self.scroll_position != cursor_scroll_position {
2661 self.scroll_position = None;
2662 }
2663 }
2664 EditorEvent::SelectionsChanged { .. } => {
2665 self.scroll_position = self.cursor_scroll_position(cx);
2666 }
2667 _ => {}
2668 }
2669 }
2670
2671 fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> {
2672 self.editor.update(cx, |editor, cx| {
2673 let snapshot = editor.snapshot(cx);
2674 let cursor = editor.selections.newest_anchor().head();
2675 let cursor_row = cursor
2676 .to_display_point(&snapshot.display_snapshot)
2677 .row()
2678 .as_f32();
2679 let scroll_position = editor
2680 .scroll_manager
2681 .anchor()
2682 .scroll_position(&snapshot.display_snapshot);
2683
2684 let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
2685 if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
2686 Some(ScrollPosition {
2687 cursor,
2688 offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
2689 })
2690 } else {
2691 None
2692 }
2693 })
2694 }
2695
2696 fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
2697 self.editor.update(cx, |editor, cx| {
2698 let buffer = editor.buffer().read(cx).snapshot(cx);
2699 let excerpt_id = *buffer.as_singleton().unwrap().0;
2700 let old_blocks = std::mem::take(&mut self.blocks);
2701 let new_blocks = self
2702 .context
2703 .read(cx)
2704 .messages(cx)
2705 .map(|message| BlockProperties {
2706 position: buffer
2707 .anchor_in_excerpt(excerpt_id, message.anchor)
2708 .unwrap(),
2709 height: 2,
2710 style: BlockStyle::Sticky,
2711 render: Box::new({
2712 let context = self.context.clone();
2713 move |cx| {
2714 let message_id = message.id;
2715 let sender = ButtonLike::new("role")
2716 .style(ButtonStyle::Filled)
2717 .child(match message.role {
2718 Role::User => Label::new("You").color(Color::Default),
2719 Role::Assistant => Label::new("Assistant").color(Color::Info),
2720 Role::System => Label::new("System").color(Color::Warning),
2721 })
2722 .tooltip(|cx| {
2723 Tooltip::with_meta(
2724 "Toggle message role",
2725 None,
2726 "Available roles: You (User), Assistant, System",
2727 cx,
2728 )
2729 })
2730 .on_click({
2731 let context = context.clone();
2732 move |_, cx| {
2733 context.update(cx, |context, cx| {
2734 context.cycle_message_roles(
2735 HashSet::from_iter(Some(message_id)),
2736 cx,
2737 )
2738 })
2739 }
2740 });
2741
2742 h_flex()
2743 .id(("message_header", message_id.0))
2744 .pl(cx.gutter_dimensions.full_width())
2745 .h_11()
2746 .w_full()
2747 .relative()
2748 .gap_1()
2749 .child(sender)
2750 .children(
2751 if let MessageStatus::Error(error) = message.status.clone() {
2752 Some(
2753 div()
2754 .id("error")
2755 .tooltip(move |cx| Tooltip::text(error.clone(), cx))
2756 .child(Icon::new(IconName::XCircle)),
2757 )
2758 } else {
2759 None
2760 },
2761 )
2762 .into_any_element()
2763 }
2764 }),
2765 disposition: BlockDisposition::Above,
2766 })
2767 .collect::<Vec<_>>();
2768
2769 editor.remove_blocks(old_blocks, None, cx);
2770 let ids = editor.insert_blocks(new_blocks, None, cx);
2771 self.blocks = HashSet::from_iter(ids);
2772 });
2773 }
2774
2775 fn quote_selection(
2776 workspace: &mut Workspace,
2777 _: &QuoteSelection,
2778 cx: &mut ViewContext<Workspace>,
2779 ) {
2780 let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2781 return;
2782 };
2783 let Some(editor) = workspace
2784 .active_item(cx)
2785 .and_then(|item| item.act_as::<Editor>(cx))
2786 else {
2787 return;
2788 };
2789
2790 let editor = editor.read(cx);
2791 let range = editor.selections.newest::<usize>(cx).range();
2792 let buffer = editor.buffer().read(cx).snapshot(cx);
2793 let start_language = buffer.language_at(range.start);
2794 let end_language = buffer.language_at(range.end);
2795 let language_name = if start_language == end_language {
2796 start_language.map(|language| language.code_fence_block_name())
2797 } else {
2798 None
2799 };
2800 let language_name = language_name.as_deref().unwrap_or("");
2801
2802 let selected_text = buffer.text_for_range(range).collect::<String>();
2803 let text = if selected_text.is_empty() {
2804 None
2805 } else {
2806 Some(if language_name == "markdown" {
2807 selected_text
2808 .lines()
2809 .map(|line| format!("> {}", line))
2810 .collect::<Vec<_>>()
2811 .join("\n")
2812 } else {
2813 format!("```{language_name}\n{selected_text}\n```")
2814 })
2815 };
2816
2817 // Activate the panel
2818 if !panel.focus_handle(cx).contains_focused(cx) {
2819 workspace.toggle_panel_focus::<AssistantPanel>(cx);
2820 }
2821
2822 if let Some(text) = text {
2823 panel.update(cx, |_, cx| {
2824 // Wait to create a new context until the workspace is no longer
2825 // being updated.
2826 cx.defer(move |panel, cx| {
2827 if let Some(context) = panel
2828 .active_context_editor()
2829 .cloned()
2830 .or_else(|| panel.new_context(cx))
2831 {
2832 context.update(cx, |context, cx| {
2833 context
2834 .editor
2835 .update(cx, |editor, cx| editor.insert(&text, cx))
2836 });
2837 };
2838 });
2839 });
2840 }
2841 }
2842
2843 fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
2844 let editor = self.editor.read(cx);
2845 let context = self.context.read(cx);
2846 if editor.selections.count() == 1 {
2847 let selection = editor.selections.newest::<usize>(cx);
2848 let mut copied_text = String::new();
2849 let mut spanned_messages = 0;
2850 for message in context.messages(cx) {
2851 if message.offset_range.start >= selection.range().end {
2852 break;
2853 } else if message.offset_range.end >= selection.range().start {
2854 let range = cmp::max(message.offset_range.start, selection.range().start)
2855 ..cmp::min(message.offset_range.end, selection.range().end);
2856 if !range.is_empty() {
2857 spanned_messages += 1;
2858 write!(&mut copied_text, "## {}\n\n", message.role).unwrap();
2859 for chunk in context.buffer.read(cx).text_for_range(range) {
2860 copied_text.push_str(chunk);
2861 }
2862 copied_text.push('\n');
2863 }
2864 }
2865 }
2866
2867 if spanned_messages > 1 {
2868 cx.write_to_clipboard(ClipboardItem::new(copied_text));
2869 return;
2870 }
2871 }
2872
2873 cx.propagate();
2874 }
2875
2876 fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
2877 self.context.update(cx, |context, cx| {
2878 let selections = self.editor.read(cx).selections.disjoint_anchors();
2879 for selection in selections.as_ref() {
2880 let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
2881 let range = selection
2882 .map(|endpoint| endpoint.to_offset(&buffer))
2883 .range();
2884 context.split_message(range, cx);
2885 }
2886 });
2887 }
2888
2889 fn apply_edit(&mut self, _: &ApplyEdit, cx: &mut ViewContext<Self>) {
2890 let Some(workspace) = self.workspace.upgrade() else {
2891 return;
2892 };
2893 let project = workspace.read(cx).project().clone();
2894
2895 struct Edit {
2896 old_text: String,
2897 new_text: String,
2898 }
2899
2900 let context = self.context.read(cx);
2901 let context_buffer = context.buffer.read(cx);
2902 let context_buffer_snapshot = context_buffer.snapshot();
2903
2904 let selections = self.editor.read(cx).selections.disjoint_anchors();
2905 let mut selections = selections.iter().peekable();
2906 let selected_suggestions = context
2907 .edit_suggestions
2908 .iter()
2909 .filter(|suggestion| {
2910 while let Some(selection) = selections.peek() {
2911 if selection
2912 .end
2913 .text_anchor
2914 .cmp(&suggestion.source_range.start, context_buffer)
2915 .is_lt()
2916 {
2917 selections.next();
2918 continue;
2919 }
2920 if selection
2921 .start
2922 .text_anchor
2923 .cmp(&suggestion.source_range.end, context_buffer)
2924 .is_gt()
2925 {
2926 break;
2927 }
2928 return true;
2929 }
2930 false
2931 })
2932 .cloned()
2933 .collect::<Vec<_>>();
2934
2935 let mut opened_buffers: HashMap<PathBuf, Task<Result<Model<Buffer>>>> = HashMap::default();
2936 project.update(cx, |project, cx| {
2937 for suggestion in &selected_suggestions {
2938 opened_buffers
2939 .entry(suggestion.full_path.clone())
2940 .or_insert_with(|| {
2941 project.open_buffer_for_full_path(&suggestion.full_path, cx)
2942 });
2943 }
2944 });
2945
2946 cx.spawn(|this, mut cx| async move {
2947 let mut buffers_by_full_path = HashMap::default();
2948 for (full_path, buffer) in opened_buffers {
2949 if let Some(buffer) = buffer.await.log_err() {
2950 buffers_by_full_path.insert(full_path, buffer);
2951 }
2952 }
2953
2954 let mut suggestions_by_buffer = HashMap::default();
2955 cx.update(|cx| {
2956 for suggestion in selected_suggestions {
2957 if let Some(buffer) = buffers_by_full_path.get(&suggestion.full_path) {
2958 let (_, edits) = suggestions_by_buffer
2959 .entry(buffer.clone())
2960 .or_insert_with(|| (buffer.read(cx).snapshot(), Vec::new()));
2961
2962 let mut lines = context_buffer_snapshot
2963 .as_rope()
2964 .chunks_in_range(
2965 suggestion.source_range.to_offset(&context_buffer_snapshot),
2966 )
2967 .lines();
2968 if let Some(suggestion) = parse_next_edit_suggestion(&mut lines) {
2969 let old_text = context_buffer_snapshot
2970 .text_for_range(suggestion.old_text_range)
2971 .collect();
2972 let new_text = context_buffer_snapshot
2973 .text_for_range(suggestion.new_text_range)
2974 .collect();
2975 edits.push(Edit { old_text, new_text });
2976 }
2977 }
2978 }
2979 })?;
2980
2981 let edits_by_buffer = cx
2982 .background_executor()
2983 .spawn(async move {
2984 let mut result = HashMap::default();
2985 for (buffer, (snapshot, suggestions)) in suggestions_by_buffer {
2986 let edits =
2987 result
2988 .entry(buffer)
2989 .or_insert(Vec::<(Range<language::Anchor>, _)>::new());
2990 for suggestion in suggestions {
2991 if let Some(range) =
2992 fuzzy_search_lines(snapshot.as_rope(), &suggestion.old_text)
2993 {
2994 let edit_start = snapshot.anchor_after(range.start);
2995 let edit_end = snapshot.anchor_before(range.end);
2996 if let Err(ix) = edits.binary_search_by(|(range, _)| {
2997 range.start.cmp(&edit_start, &snapshot)
2998 }) {
2999 edits.insert(
3000 ix,
3001 (edit_start..edit_end, suggestion.new_text.clone()),
3002 );
3003 }
3004 } else {
3005 log::info!(
3006 "assistant edit did not match any text in buffer {:?}",
3007 &suggestion.old_text
3008 );
3009 }
3010 }
3011 }
3012 result
3013 })
3014 .await;
3015
3016 let mut project_transaction = ProjectTransaction::default();
3017 let (editor, workspace, title) = this.update(&mut cx, |this, cx| {
3018 for (buffer_handle, edits) in edits_by_buffer {
3019 buffer_handle.update(cx, |buffer, cx| {
3020 buffer.start_transaction();
3021 buffer.edit(
3022 edits,
3023 Some(AutoindentMode::Block {
3024 original_indent_columns: Vec::new(),
3025 }),
3026 cx,
3027 );
3028 buffer.end_transaction(cx);
3029 if let Some(transaction) = buffer.finalize_last_transaction() {
3030 project_transaction
3031 .0
3032 .insert(buffer_handle.clone(), transaction.clone());
3033 }
3034 });
3035 }
3036
3037 (
3038 this.editor.downgrade(),
3039 this.workspace.clone(),
3040 this.title(cx),
3041 )
3042 })?;
3043
3044 Editor::open_project_transaction(
3045 &editor,
3046 workspace,
3047 project_transaction,
3048 format!("Edits from {}", title),
3049 cx,
3050 )
3051 .await
3052 })
3053 .detach_and_log_err(cx);
3054 }
3055
3056 fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
3057 self.context
3058 .update(cx, |context, cx| context.save(None, self.fs.clone(), cx));
3059 }
3060
3061 fn title(&self, cx: &AppContext) -> String {
3062 self.context
3063 .read(cx)
3064 .summary
3065 .as_ref()
3066 .map(|summary| summary.text.clone())
3067 .unwrap_or_else(|| "New Context".into())
3068 }
3069}
3070
3071impl EventEmitter<ContextEditorEvent> for ContextEditor {}
3072
3073impl Render for ContextEditor {
3074 fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3075 div()
3076 .key_context("ContextEditor")
3077 .capture_action(cx.listener(ContextEditor::cancel_last_assist))
3078 .capture_action(cx.listener(ContextEditor::save))
3079 .capture_action(cx.listener(ContextEditor::copy))
3080 .capture_action(cx.listener(ContextEditor::cycle_message_role))
3081 .capture_action(cx.listener(ContextEditor::confirm_command))
3082 .on_action(cx.listener(ContextEditor::assist))
3083 .on_action(cx.listener(ContextEditor::split))
3084 .on_action(cx.listener(ContextEditor::apply_edit))
3085 .size_full()
3086 .v_flex()
3087 .child(
3088 div()
3089 .flex_grow()
3090 .bg(cx.theme().colors().editor_background)
3091 .child(self.editor.clone()),
3092 )
3093 }
3094}
3095
3096impl FocusableView for ContextEditor {
3097 fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3098 self.editor.focus_handle(cx)
3099 }
3100}
3101
3102#[derive(Clone, Debug)]
3103struct MessageAnchor {
3104 id: MessageId,
3105 start: language::Anchor,
3106}
3107
3108#[derive(Clone, Debug)]
3109pub struct Message {
3110 offset_range: Range<usize>,
3111 index_range: Range<usize>,
3112 id: MessageId,
3113 anchor: language::Anchor,
3114 role: Role,
3115 status: MessageStatus,
3116}
3117
3118impl Message {
3119 fn to_request_message(&self, buffer: &Buffer) -> LanguageModelRequestMessage {
3120 LanguageModelRequestMessage {
3121 role: self.role,
3122 content: buffer.text_for_range(self.offset_range.clone()).collect(),
3123 }
3124 }
3125}
3126
3127type ToggleFold = Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>;
3128
3129fn render_slash_command_output_toggle(
3130 row: MultiBufferRow,
3131 is_folded: bool,
3132 fold: ToggleFold,
3133 _cx: &mut WindowContext,
3134) -> AnyElement {
3135 IconButton::new(
3136 ("slash-command-output-fold-indicator", row.0),
3137 ui::IconName::ChevronDown,
3138 )
3139 .on_click(move |_e, cx| fold(!is_folded, cx))
3140 .icon_color(ui::Color::Muted)
3141 .icon_size(ui::IconSize::Small)
3142 .selected(is_folded)
3143 .selected_icon(ui::IconName::ChevronRight)
3144 .size(ui::ButtonSize::None)
3145 .into_any_element()
3146}
3147
3148fn render_pending_slash_command_gutter_decoration(
3149 row: MultiBufferRow,
3150 status: PendingSlashCommandStatus,
3151 confirm_command: Arc<dyn Fn(&mut WindowContext)>,
3152) -> AnyElement {
3153 let mut icon = IconButton::new(
3154 ("slash-command-gutter-decoration", row.0),
3155 ui::IconName::TriangleRight,
3156 )
3157 .on_click(move |_e, cx| confirm_command(cx))
3158 .icon_size(ui::IconSize::Small)
3159 .size(ui::ButtonSize::None);
3160
3161 match status {
3162 PendingSlashCommandStatus::Idle => {
3163 icon = icon.icon_color(Color::Muted);
3164 }
3165 PendingSlashCommandStatus::Running { .. } => {
3166 icon = icon.selected(true);
3167 }
3168 PendingSlashCommandStatus::Error(error) => {
3169 icon = icon
3170 .icon_color(Color::Error)
3171 .tooltip(move |cx| Tooltip::text(format!("error: {error}"), cx));
3172 }
3173 }
3174
3175 icon.into_any_element()
3176}
3177
3178fn make_lsp_adapter_delegate(
3179 project: &Model<Project>,
3180 cx: &mut AppContext,
3181) -> Result<Arc<dyn LspAdapterDelegate>> {
3182 project.update(cx, |project, cx| {
3183 // TODO: Find the right worktree.
3184 let worktree = project
3185 .worktrees()
3186 .next()
3187 .ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
3188 Ok(ProjectLspAdapterDelegate::new(project, &worktree, cx) as Arc<dyn LspAdapterDelegate>)
3189 })
3190}
3191
3192#[cfg(test)]
3193mod tests {
3194 use super::*;
3195 use crate::{
3196 slash_command::{active_command, file_command},
3197 FakeCompletionProvider, MessageId,
3198 };
3199 use fs::FakeFs;
3200 use gpui::{AppContext, TestAppContext};
3201 use rope::Rope;
3202 use serde_json::json;
3203 use settings::SettingsStore;
3204 use std::{cell::RefCell, path::Path, rc::Rc};
3205 use unindent::Unindent;
3206 use util::test::marked_text_ranges;
3207
3208 #[gpui::test]
3209 fn test_inserting_and_removing_messages(cx: &mut AppContext) {
3210 let settings_store = SettingsStore::test(cx);
3211 cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
3212 cx.set_global(settings_store);
3213 init(cx);
3214 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
3215
3216 let context = cx.new_model(|cx| Context::new(registry, Default::default(), None, cx));
3217 let buffer = context.read(cx).buffer.clone();
3218
3219 let message_1 = context.read(cx).message_anchors[0].clone();
3220 assert_eq!(
3221 messages(&context, cx),
3222 vec![(message_1.id, Role::User, 0..0)]
3223 );
3224
3225 let message_2 = context.update(cx, |context, cx| {
3226 context
3227 .insert_message_after(message_1.id, Role::Assistant, MessageStatus::Done, cx)
3228 .unwrap()
3229 });
3230 assert_eq!(
3231 messages(&context, cx),
3232 vec![
3233 (message_1.id, Role::User, 0..1),
3234 (message_2.id, Role::Assistant, 1..1)
3235 ]
3236 );
3237
3238 buffer.update(cx, |buffer, cx| {
3239 buffer.edit([(0..0, "1"), (1..1, "2")], None, cx)
3240 });
3241 assert_eq!(
3242 messages(&context, cx),
3243 vec![
3244 (message_1.id, Role::User, 0..2),
3245 (message_2.id, Role::Assistant, 2..3)
3246 ]
3247 );
3248
3249 let message_3 = context.update(cx, |context, cx| {
3250 context
3251 .insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
3252 .unwrap()
3253 });
3254 assert_eq!(
3255 messages(&context, cx),
3256 vec![
3257 (message_1.id, Role::User, 0..2),
3258 (message_2.id, Role::Assistant, 2..4),
3259 (message_3.id, Role::User, 4..4)
3260 ]
3261 );
3262
3263 let message_4 = context.update(cx, |context, cx| {
3264 context
3265 .insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
3266 .unwrap()
3267 });
3268 assert_eq!(
3269 messages(&context, cx),
3270 vec![
3271 (message_1.id, Role::User, 0..2),
3272 (message_2.id, Role::Assistant, 2..4),
3273 (message_4.id, Role::User, 4..5),
3274 (message_3.id, Role::User, 5..5),
3275 ]
3276 );
3277
3278 buffer.update(cx, |buffer, cx| {
3279 buffer.edit([(4..4, "C"), (5..5, "D")], None, cx)
3280 });
3281 assert_eq!(
3282 messages(&context, cx),
3283 vec![
3284 (message_1.id, Role::User, 0..2),
3285 (message_2.id, Role::Assistant, 2..4),
3286 (message_4.id, Role::User, 4..6),
3287 (message_3.id, Role::User, 6..7),
3288 ]
3289 );
3290
3291 // Deleting across message boundaries merges the messages.
3292 buffer.update(cx, |buffer, cx| buffer.edit([(1..4, "")], None, cx));
3293 assert_eq!(
3294 messages(&context, cx),
3295 vec![
3296 (message_1.id, Role::User, 0..3),
3297 (message_3.id, Role::User, 3..4),
3298 ]
3299 );
3300
3301 // Undoing the deletion should also undo the merge.
3302 buffer.update(cx, |buffer, cx| buffer.undo(cx));
3303 assert_eq!(
3304 messages(&context, cx),
3305 vec![
3306 (message_1.id, Role::User, 0..2),
3307 (message_2.id, Role::Assistant, 2..4),
3308 (message_4.id, Role::User, 4..6),
3309 (message_3.id, Role::User, 6..7),
3310 ]
3311 );
3312
3313 // Redoing the deletion should also redo the merge.
3314 buffer.update(cx, |buffer, cx| buffer.redo(cx));
3315 assert_eq!(
3316 messages(&context, cx),
3317 vec![
3318 (message_1.id, Role::User, 0..3),
3319 (message_3.id, Role::User, 3..4),
3320 ]
3321 );
3322
3323 // Ensure we can still insert after a merged message.
3324 let message_5 = context.update(cx, |context, cx| {
3325 context
3326 .insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
3327 .unwrap()
3328 });
3329 assert_eq!(
3330 messages(&context, cx),
3331 vec![
3332 (message_1.id, Role::User, 0..3),
3333 (message_5.id, Role::System, 3..4),
3334 (message_3.id, Role::User, 4..5)
3335 ]
3336 );
3337 }
3338
3339 #[gpui::test]
3340 fn test_message_splitting(cx: &mut AppContext) {
3341 let settings_store = SettingsStore::test(cx);
3342 cx.set_global(settings_store);
3343 cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
3344 init(cx);
3345 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
3346
3347 let context = cx.new_model(|cx| Context::new(registry, Default::default(), None, cx));
3348 let buffer = context.read(cx).buffer.clone();
3349
3350 let message_1 = context.read(cx).message_anchors[0].clone();
3351 assert_eq!(
3352 messages(&context, cx),
3353 vec![(message_1.id, Role::User, 0..0)]
3354 );
3355
3356 buffer.update(cx, |buffer, cx| {
3357 buffer.edit([(0..0, "aaa\nbbb\nccc\nddd\n")], None, cx)
3358 });
3359
3360 let (_, message_2) = context.update(cx, |context, cx| context.split_message(3..3, cx));
3361 let message_2 = message_2.unwrap();
3362
3363 // We recycle newlines in the middle of a split message
3364 assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\nddd\n");
3365 assert_eq!(
3366 messages(&context, cx),
3367 vec![
3368 (message_1.id, Role::User, 0..4),
3369 (message_2.id, Role::User, 4..16),
3370 ]
3371 );
3372
3373 let (_, message_3) = context.update(cx, |context, cx| context.split_message(3..3, cx));
3374 let message_3 = message_3.unwrap();
3375
3376 // We don't recycle newlines at the end of a split message
3377 assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
3378 assert_eq!(
3379 messages(&context, cx),
3380 vec![
3381 (message_1.id, Role::User, 0..4),
3382 (message_3.id, Role::User, 4..5),
3383 (message_2.id, Role::User, 5..17),
3384 ]
3385 );
3386
3387 let (_, message_4) = context.update(cx, |context, cx| context.split_message(9..9, cx));
3388 let message_4 = message_4.unwrap();
3389 assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
3390 assert_eq!(
3391 messages(&context, cx),
3392 vec![
3393 (message_1.id, Role::User, 0..4),
3394 (message_3.id, Role::User, 4..5),
3395 (message_2.id, Role::User, 5..9),
3396 (message_4.id, Role::User, 9..17),
3397 ]
3398 );
3399
3400 let (_, message_5) = context.update(cx, |context, cx| context.split_message(9..9, cx));
3401 let message_5 = message_5.unwrap();
3402 assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\nddd\n");
3403 assert_eq!(
3404 messages(&context, cx),
3405 vec![
3406 (message_1.id, Role::User, 0..4),
3407 (message_3.id, Role::User, 4..5),
3408 (message_2.id, Role::User, 5..9),
3409 (message_4.id, Role::User, 9..10),
3410 (message_5.id, Role::User, 10..18),
3411 ]
3412 );
3413
3414 let (message_6, message_7) =
3415 context.update(cx, |context, cx| context.split_message(14..16, cx));
3416 let message_6 = message_6.unwrap();
3417 let message_7 = message_7.unwrap();
3418 assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\ndd\nd\n");
3419 assert_eq!(
3420 messages(&context, cx),
3421 vec![
3422 (message_1.id, Role::User, 0..4),
3423 (message_3.id, Role::User, 4..5),
3424 (message_2.id, Role::User, 5..9),
3425 (message_4.id, Role::User, 9..10),
3426 (message_5.id, Role::User, 10..14),
3427 (message_6.id, Role::User, 14..17),
3428 (message_7.id, Role::User, 17..19),
3429 ]
3430 );
3431 }
3432
3433 #[gpui::test]
3434 fn test_messages_for_offsets(cx: &mut AppContext) {
3435 let settings_store = SettingsStore::test(cx);
3436 cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
3437 cx.set_global(settings_store);
3438 init(cx);
3439 let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
3440 let context = cx.new_model(|cx| Context::new(registry, Default::default(), None, cx));
3441 let buffer = context.read(cx).buffer.clone();
3442
3443 let message_1 = context.read(cx).message_anchors[0].clone();
3444 assert_eq!(
3445 messages(&context, cx),
3446 vec![(message_1.id, Role::User, 0..0)]
3447 );
3448
3449 buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "aaa")], None, cx));
3450 let message_2 = context
3451 .update(cx, |context, cx| {
3452 context.insert_message_after(message_1.id, Role::User, MessageStatus::Done, cx)
3453 })
3454 .unwrap();
3455 buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "bbb")], None, cx));
3456
3457 let message_3 = context
3458 .update(cx, |context, cx| {
3459 context.insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
3460 })
3461 .unwrap();
3462 buffer.update(cx, |buffer, cx| buffer.edit([(8..8, "ccc")], None, cx));
3463
3464 assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc");
3465 assert_eq!(
3466 messages(&context, cx),
3467 vec![
3468 (message_1.id, Role::User, 0..4),
3469 (message_2.id, Role::User, 4..8),
3470 (message_3.id, Role::User, 8..11)
3471 ]
3472 );
3473
3474 assert_eq!(
3475 message_ids_for_offsets(&context, &[0, 4, 9], cx),
3476 [message_1.id, message_2.id, message_3.id]
3477 );
3478 assert_eq!(
3479 message_ids_for_offsets(&context, &[0, 1, 11], cx),
3480 [message_1.id, message_3.id]
3481 );
3482
3483 let message_4 = context
3484 .update(cx, |context, cx| {
3485 context.insert_message_after(message_3.id, Role::User, MessageStatus::Done, cx)
3486 })
3487 .unwrap();
3488 assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\n");
3489 assert_eq!(
3490 messages(&context, cx),
3491 vec![
3492 (message_1.id, Role::User, 0..4),
3493 (message_2.id, Role::User, 4..8),
3494 (message_3.id, Role::User, 8..12),
3495 (message_4.id, Role::User, 12..12)
3496 ]
3497 );
3498 assert_eq!(
3499 message_ids_for_offsets(&context, &[0, 4, 8, 12], cx),
3500 [message_1.id, message_2.id, message_3.id, message_4.id]
3501 );
3502
3503 fn message_ids_for_offsets(
3504 context: &Model<Context>,
3505 offsets: &[usize],
3506 cx: &AppContext,
3507 ) -> Vec<MessageId> {
3508 context
3509 .read(cx)
3510 .messages_for_offsets(offsets.iter().copied(), cx)
3511 .into_iter()
3512 .map(|message| message.id)
3513 .collect()
3514 }
3515 }
3516
3517 #[gpui::test]
3518 async fn test_slash_commands(cx: &mut TestAppContext) {
3519 let settings_store = cx.update(SettingsStore::test);
3520 cx.set_global(settings_store);
3521 cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
3522 cx.update(Project::init_settings);
3523 cx.update(init);
3524 let fs = FakeFs::new(cx.background_executor.clone());
3525
3526 fs.insert_tree(
3527 "/test",
3528 json!({
3529 "src": {
3530 "lib.rs": "fn one() -> usize { 1 }",
3531 "main.rs": "
3532 use crate::one;
3533 fn main() { one(); }
3534 ".unindent(),
3535 }
3536 }),
3537 )
3538 .await;
3539
3540 let slash_command_registry = SlashCommandRegistry::new();
3541 slash_command_registry.register_command(file_command::FileSlashCommand, false);
3542 slash_command_registry.register_command(active_command::ActiveSlashCommand, false);
3543
3544 let registry = Arc::new(LanguageRegistry::test(cx.executor()));
3545 let context =
3546 cx.new_model(|cx| Context::new(registry.clone(), slash_command_registry, None, cx));
3547
3548 let output_ranges = Rc::new(RefCell::new(HashSet::default()));
3549 context.update(cx, |_, cx| {
3550 cx.subscribe(&context, {
3551 let ranges = output_ranges.clone();
3552 move |_, _, event, _| match event {
3553 ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
3554 for range in removed {
3555 ranges.borrow_mut().remove(range);
3556 }
3557 for command in updated {
3558 ranges.borrow_mut().insert(command.source_range.clone());
3559 }
3560 }
3561 _ => {}
3562 }
3563 })
3564 .detach();
3565 });
3566
3567 let buffer = context.read_with(cx, |context, _| context.buffer.clone());
3568
3569 // Insert a slash command
3570 buffer.update(cx, |buffer, cx| {
3571 buffer.edit([(0..0, "/file src/lib.rs")], None, cx);
3572 });
3573 assert_text_and_output_ranges(
3574 &buffer,
3575 &output_ranges.borrow(),
3576 "
3577 «/file src/lib.rs»
3578 "
3579 .unindent()
3580 .trim_end(),
3581 cx,
3582 );
3583
3584 // Edit the argument of the slash command.
3585 buffer.update(cx, |buffer, cx| {
3586 let edit_offset = buffer.text().find("lib.rs").unwrap();
3587 buffer.edit([(edit_offset..edit_offset + "lib".len(), "main")], None, cx);
3588 });
3589 assert_text_and_output_ranges(
3590 &buffer,
3591 &output_ranges.borrow(),
3592 "
3593 «/file src/main.rs»
3594 "
3595 .unindent()
3596 .trim_end(),
3597 cx,
3598 );
3599
3600 // Edit the name of the slash command, using one that doesn't exist.
3601 buffer.update(cx, |buffer, cx| {
3602 let edit_offset = buffer.text().find("/file").unwrap();
3603 buffer.edit(
3604 [(edit_offset..edit_offset + "/file".len(), "/unknown")],
3605 None,
3606 cx,
3607 );
3608 });
3609 assert_text_and_output_ranges(
3610 &buffer,
3611 &output_ranges.borrow(),
3612 "
3613 /unknown src/main.rs
3614 "
3615 .unindent()
3616 .trim_end(),
3617 cx,
3618 );
3619
3620 #[track_caller]
3621 fn assert_text_and_output_ranges(
3622 buffer: &Model<Buffer>,
3623 ranges: &HashSet<Range<language::Anchor>>,
3624 expected_marked_text: &str,
3625 cx: &mut TestAppContext,
3626 ) {
3627 let (expected_text, expected_ranges) = marked_text_ranges(expected_marked_text, false);
3628 let (actual_text, actual_ranges) = buffer.update(cx, |buffer, _| {
3629 let mut ranges = ranges
3630 .iter()
3631 .map(|range| range.to_offset(buffer))
3632 .collect::<Vec<_>>();
3633 ranges.sort_by_key(|a| a.start);
3634 (buffer.text(), ranges)
3635 });
3636
3637 assert_eq!(actual_text, expected_text);
3638 assert_eq!(actual_ranges, expected_ranges);
3639 }
3640 }
3641
3642 #[test]
3643 fn test_parse_next_edit_suggestion() {
3644 let text = "
3645 some output:
3646
3647 ```edit src/foo.rs
3648 let a = 1;
3649 let b = 2;
3650 ---
3651 let w = 1;
3652 let x = 2;
3653 let y = 3;
3654 let z = 4;
3655 ```
3656
3657 some more output:
3658
3659 ```edit src/foo.rs
3660 let c = 1;
3661 ---
3662 ```
3663
3664 and the conclusion.
3665 "
3666 .unindent();
3667
3668 let rope = Rope::from(text.as_str());
3669 let mut lines = rope.chunks().lines();
3670 let mut suggestions = vec![];
3671 while let Some(suggestion) = parse_next_edit_suggestion(&mut lines) {
3672 suggestions.push((
3673 suggestion.path.clone(),
3674 text[suggestion.old_text_range].to_string(),
3675 text[suggestion.new_text_range].to_string(),
3676 ));
3677 }
3678
3679 assert_eq!(
3680 suggestions,
3681 vec![
3682 (
3683 Path::new("src/foo.rs").into(),
3684 [
3685 " let a = 1;", //
3686 " let b = 2;",
3687 "",
3688 ]
3689 .join("\n"),
3690 [
3691 " let w = 1;",
3692 " let x = 2;",
3693 " let y = 3;",
3694 " let z = 4;",
3695 "",
3696 ]
3697 .join("\n"),
3698 ),
3699 (
3700 Path::new("src/foo.rs").into(),
3701 [
3702 " let c = 1;", //
3703 "",
3704 ]
3705 .join("\n"),
3706 String::new(),
3707 )
3708 ]
3709 );
3710 }
3711
3712 #[gpui::test]
3713 async fn test_serialization(cx: &mut TestAppContext) {
3714 let settings_store = cx.update(SettingsStore::test);
3715 cx.set_global(settings_store);
3716 cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
3717 cx.update(init);
3718 let registry = Arc::new(LanguageRegistry::test(cx.executor()));
3719 let context =
3720 cx.new_model(|cx| Context::new(registry.clone(), Default::default(), None, cx));
3721 let buffer = context.read_with(cx, |context, _| context.buffer.clone());
3722 let message_0 = context.read_with(cx, |context, _| context.message_anchors[0].id);
3723 let message_1 = context.update(cx, |context, cx| {
3724 context
3725 .insert_message_after(message_0, Role::Assistant, MessageStatus::Done, cx)
3726 .unwrap()
3727 });
3728 let message_2 = context.update(cx, |context, cx| {
3729 context
3730 .insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
3731 .unwrap()
3732 });
3733 buffer.update(cx, |buffer, cx| {
3734 buffer.edit([(0..0, "a"), (1..1, "b\nc")], None, cx);
3735 buffer.finalize_last_transaction();
3736 });
3737 let _message_3 = context.update(cx, |context, cx| {
3738 context
3739 .insert_message_after(message_2.id, Role::System, MessageStatus::Done, cx)
3740 .unwrap()
3741 });
3742 buffer.update(cx, |buffer, cx| buffer.undo(cx));
3743 assert_eq!(buffer.read_with(cx, |buffer, _| buffer.text()), "a\nb\nc\n");
3744 assert_eq!(
3745 cx.read(|cx| messages(&context, cx)),
3746 [
3747 (message_0, Role::User, 0..2),
3748 (message_1.id, Role::Assistant, 2..6),
3749 (message_2.id, Role::System, 6..6),
3750 ]
3751 );
3752
3753 let deserialized_context = Context::deserialize(
3754 context.read_with(cx, |context, cx| context.serialize(cx)),
3755 Default::default(),
3756 registry.clone(),
3757 Default::default(),
3758 None,
3759 &mut cx.to_async(),
3760 )
3761 .await
3762 .unwrap();
3763 let deserialized_buffer =
3764 deserialized_context.read_with(cx, |context, _| context.buffer.clone());
3765 assert_eq!(
3766 deserialized_buffer.read_with(cx, |buffer, _| buffer.text()),
3767 "a\nb\nc\n"
3768 );
3769 assert_eq!(
3770 cx.read(|cx| messages(&deserialized_context, cx)),
3771 [
3772 (message_0, Role::User, 0..2),
3773 (message_1.id, Role::Assistant, 2..6),
3774 (message_2.id, Role::System, 6..6),
3775 ]
3776 );
3777 }
3778
3779 fn messages(context: &Model<Context>, cx: &AppContext) -> Vec<(MessageId, Role, Range<usize>)> {
3780 context
3781 .read(cx)
3782 .messages(cx)
3783 .map(|message| (message.id, message.role, message.offset_range))
3784 .collect()
3785 }
3786}