use crate::assistant_settings::AssistantSettings;use crate::{ humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent, RequestType, DEFAULT_CONTEXT_LINES,};use anyhow::{Context as _, Result};use client::telemetry::Telemetry;use collections::{HashMap, VecDeque};use editor::{ actions::{MoveDown, MoveUp, SelectAll}, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer,};use fs::Fs;use futures::{channel::mpsc, SinkExt, StreamExt};use gpui::{ AppContext, Context, EventEmitter, FocusHandle, FocusableView, Global, Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View, WeakView,};use language::Buffer;use language_model::{ LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,};use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu};use language_models::report_assistant_event;use settings::{update_settings_file, Settings};use std::{ cmp, sync::Arc, time::{Duration, Instant},};use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};use terminal::Terminal;use terminal_view::TerminalView;use theme::ThemeSettings;use ui::{prelude::*, text_for_action, IconButtonShape, Tooltip};use util::ResultExt;use workspace::{notifications::NotificationId, Toast, Workspace}; pub fn init( fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, telemetry: Arc<Telemetry>, cx: &mut AppContext,) { cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry));} const PROMPT_HISTORY_MAX_LEN: usize = 20; #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]struct TerminalInlineAssistId(usize); impl TerminalInlineAssistId { fn post_inc(&mut self) -> TerminalInlineAssistId { let id = *self; self.0 += 1; id }} pub struct TerminalInlineAssistant { next_assist_id: TerminalInlineAssistId, assists: HashMap<TerminalInlineAssistId, TerminalInlineAssist>, prompt_history: VecDeque<String>, telemetry: Option<Arc<Telemetry>>, fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>,} impl Global for TerminalInlineAssistant {} impl TerminalInlineAssistant { pub fn new( fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, telemetry: Arc<Telemetry>, ) -> Self { Self { next_assist_id: TerminalInlineAssistId::default(), assists: HashMap::default(), prompt_history: VecDeque::default(), telemetry: Some(telemetry), fs, prompt_builder, } } pub fn assist( &mut self, terminal_view: &View<TerminalView>, workspace: Option<WeakView<Workspace>>, assistant_panel: Option<&View<AssistantPanel>>, initial_prompt: Option<String>, cx: &mut WindowContext, ) { let terminal = terminal_view.read(cx).terminal().clone(); let assist_id = self.next_assist_id.post_inc(); let prompt_buffer = cx.new_model(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx)); let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx)); let codegen = cx.new_model(|_| Codegen::new(terminal, self.telemetry.clone())); let prompt_editor = cx.new_view(|cx| { PromptEditor::new( assist_id, self.prompt_history.clone(), prompt_buffer.clone(), codegen, assistant_panel, workspace.clone(), self.fs.clone(), cx, ) }); let prompt_editor_render = prompt_editor.clone(); let block = terminal_view::BlockProperties { height: 2, render: Box::new(move |_| prompt_editor_render.clone().into_any_element()), }; terminal_view.update(cx, |terminal_view, cx| { terminal_view.set_block_below_cursor(block, cx); }); let terminal_assistant = TerminalInlineAssist::new( assist_id, terminal_view, assistant_panel.is_some(), prompt_editor, workspace.clone(), cx, ); self.assists.insert(assist_id, terminal_assistant); self.focus_assist(assist_id, cx); } fn focus_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) { let assist = &self.assists[&assist_id]; if let Some(prompt_editor) = assist.prompt_editor.as_ref() { prompt_editor.update(cx, |this, cx| { this.editor.update(cx, |editor, cx| { editor.focus(cx); editor.select_all(&SelectAll, cx); }); }); } } fn handle_prompt_editor_event( &mut self, prompt_editor: View<PromptEditor>, event: &PromptEditorEvent, cx: &mut WindowContext, ) { let assist_id = prompt_editor.read(cx).id; match event { PromptEditorEvent::StartRequested => { self.start_assist(assist_id, cx); } PromptEditorEvent::StopRequested => { self.stop_assist(assist_id, cx); } PromptEditorEvent::ConfirmRequested { execute } => { self.finish_assist(assist_id, false, *execute, cx); } PromptEditorEvent::CancelRequested => { self.finish_assist(assist_id, true, false, cx); } PromptEditorEvent::DismissRequested => { self.dismiss_assist(assist_id, cx); } PromptEditorEvent::Resized { height_in_lines } => { self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, cx); } } } fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) { let assist = if let Some(assist) = self.assists.get_mut(&assist_id) { assist } else { return; }; let Some(user_prompt) = assist .prompt_editor .as_ref() .map(|editor| editor.read(cx).prompt(cx)) else { return; }; self.prompt_history.retain(|prompt| *prompt != user_prompt); self.prompt_history.push_back(user_prompt.clone()); if self.prompt_history.len() > PROMPT_HISTORY_MAX_LEN { self.prompt_history.pop_front(); } assist .terminal .update(cx, |terminal, cx| { terminal .terminal() .update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string())); }) .log_err(); let codegen = assist.codegen.clone(); let Some(request) = self.request_for_inline_assist(assist_id, cx).log_err() else { return; }; codegen.update(cx, |codegen, cx| codegen.start(request, cx)); } fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) { let assist = if let Some(assist) = self.assists.get_mut(&assist_id) { assist } else { return; }; assist.codegen.update(cx, |codegen, cx| codegen.stop(cx)); } fn request_for_inline_assist( &self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext, ) -> Result<LanguageModelRequest> { let assist = self.assists.get(&assist_id).context("invalid assist")?; let shell = std::env::var("SHELL").ok(); let (latest_output, working_directory) = assist .terminal .update(cx, |terminal, cx| { let terminal = terminal.model().read(cx); let latest_output = terminal.last_n_non_empty_lines(DEFAULT_CONTEXT_LINES); let working_directory = terminal .working_directory() .map(|path| path.to_string_lossy().to_string()); (latest_output, working_directory) }) .ok() .unwrap_or_default(); let context_request = if assist.include_context { assist.workspace.as_ref().and_then(|workspace| { let workspace = workspace.upgrade()?.read(cx); let assistant_panel = workspace.panel::<AssistantPanel>(cx)?; Some( assistant_panel .read(cx) .active_context(cx)? .read(cx) .to_completion_request(RequestType::Chat, cx), ) }) } else { None }; let prompt = self.prompt_builder.generate_terminal_assistant_prompt( &assist .prompt_editor .clone() .context("invalid assist")? .read(cx) .prompt(cx), shell.as_deref(), working_directory.as_deref(), &latest_output, )?; let mut messages = Vec::new(); if let Some(context_request) = context_request { messages = context_request.messages; } messages.push(LanguageModelRequestMessage { role: Role::User, content: vec![prompt.into()], cache: false, }); Ok(LanguageModelRequest { messages, tools: Vec::new(), stop: Vec::new(), temperature: None, }) } fn finish_assist( &mut self, assist_id: TerminalInlineAssistId, undo: bool, execute: bool, cx: &mut WindowContext, ) { self.dismiss_assist(assist_id, cx); if let Some(assist) = self.assists.remove(&assist_id) { assist .terminal .update(cx, |this, cx| { this.clear_block_below_cursor(cx); this.focus_handle(cx).focus(cx); }) .log_err(); if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() { let codegen = assist.codegen.read(cx); let executor = cx.background_executor().clone(); report_assistant_event( AssistantEvent { conversation_id: None, kind: AssistantKind::InlineTerminal, message_id: codegen.message_id.clone(), phase: if undo { AssistantPhase::Rejected } else { AssistantPhase::Accepted }, model: model.telemetry_id(), model_provider: model.provider_id().to_string(), response_latency: None, error_message: None, language_name: None, }, codegen.telemetry.clone(), cx.http_client(), model.api_key(cx), &executor, ); } assist.codegen.update(cx, |codegen, cx| { if undo { codegen.undo(cx); } else if execute { codegen.complete(cx); } }); } } fn dismiss_assist( &mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext, ) -> bool { let Some(assist) = self.assists.get_mut(&assist_id) else { return false; }; if assist.prompt_editor.is_none() { return false; } assist.prompt_editor = None; assist .terminal .update(cx, |this, cx| { this.clear_block_below_cursor(cx); this.focus_handle(cx).focus(cx); }) .is_ok() } fn insert_prompt_editor_into_terminal( &mut self, assist_id: TerminalInlineAssistId, height: u8, cx: &mut WindowContext, ) { if let Some(assist) = self.assists.get_mut(&assist_id) { if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() { assist .terminal .update(cx, |terminal, cx| { terminal.clear_block_below_cursor(cx); let block = terminal_view::BlockProperties { height, render: Box::new(move |_| prompt_editor.clone().into_any_element()), }; terminal.set_block_below_cursor(block, cx); }) .log_err(); } } }} struct TerminalInlineAssist { terminal: WeakView<TerminalView>, prompt_editor: Option<View<PromptEditor>>, codegen: Model<Codegen>, workspace: Option<WeakView<Workspace>>, include_context: bool, _subscriptions: Vec<Subscription>,} impl TerminalInlineAssist { pub fn new( assist_id: TerminalInlineAssistId, terminal: &View<TerminalView>, include_context: bool, prompt_editor: View<PromptEditor>, workspace: Option<WeakView<Workspace>>, cx: &mut WindowContext, ) -> Self { let codegen = prompt_editor.read(cx).codegen.clone(); Self { terminal: terminal.downgrade(), prompt_editor: Some(prompt_editor.clone()), codegen: codegen.clone(), workspace: workspace.clone(), include_context, _subscriptions: vec![ cx.subscribe(&prompt_editor, |prompt_editor, event, cx| { TerminalInlineAssistant::update_global(cx, |this, cx| { this.handle_prompt_editor_event(prompt_editor, event, cx) }) }), cx.subscribe(&codegen, move |codegen, event, cx| { TerminalInlineAssistant::update_global(cx, |this, cx| match event { CodegenEvent::Finished => { let assist = if let Some(assist) = this.assists.get(&assist_id) { assist } else { return; }; if let CodegenStatus::Error(error) = &codegen.read(cx).status { if assist.prompt_editor.is_none() { if let Some(workspace) = assist .workspace .as_ref() .and_then(|workspace| workspace.upgrade()) { let error = format!("Terminal inline assistant error: {}", error); workspace.update(cx, |workspace, cx| { struct InlineAssistantError; let id = NotificationId::composite::<InlineAssistantError>( assist_id.0, ); workspace.show_toast(Toast::new(id, error), cx); }) } } } if assist.prompt_editor.is_none() { this.finish_assist(assist_id, false, false, cx); } } }) }), ], } }} enum PromptEditorEvent { StartRequested, StopRequested, ConfirmRequested { execute: bool }, CancelRequested, DismissRequested, Resized { height_in_lines: u8 },} struct PromptEditor { id: TerminalInlineAssistId, height_in_lines: u8, editor: View<Editor>, language_model_selector: View<LanguageModelSelector>, edited_since_done: bool, prompt_history: VecDeque<String>, prompt_history_ix: Option<usize>, pending_prompt: String, codegen: Model<Codegen>, _codegen_subscription: Subscription, editor_subscriptions: Vec<Subscription>, pending_token_count: Task<Result<()>>, token_count: Option<usize>, _token_count_subscriptions: Vec<Subscription>, workspace: Option<WeakView<Workspace>>,} impl EventEmitter<PromptEditorEvent> for PromptEditor {} impl Render for PromptEditor { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { let status = &self.codegen.read(cx).status; let buttons = match status {Lines 1–500 of 1210. Load more
use crate::assistant_settings::AssistantSettings; use crate::{ humanize_token_count, prompts::PromptBuilder, AssistantPanel, AssistantPanelEvent, RequestType, DEFAULT_CONTEXT_LINES, }; use anyhow::{Context as _, Result}; use client::telemetry::Telemetry; use collections::{HashMap, VecDeque}; use editor::{ actions::{MoveDown, MoveUp, SelectAll}, Editor, EditorElement, EditorEvent, EditorMode, EditorStyle, MultiBuffer, }; use fs::Fs; use futures::{channel::mpsc, SinkExt, StreamExt}; use gpui::{ AppContext, Context, EventEmitter, FocusHandle, FocusableView, Global, Model, ModelContext, Subscription, Task, TextStyle, UpdateGlobal, View, WeakView, }; use language::Buffer; use language_model::{ LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role, }; use language_model_selector::{LanguageModelSelector, LanguageModelSelectorPopoverMenu}; use language_models::report_assistant_event; use settings::{update_settings_file, Settings}; use std::{ cmp, sync::Arc, time::{Duration, Instant}, }; use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase}; use terminal::Terminal; use terminal_view::TerminalView; use theme::ThemeSettings; use ui::{prelude::*, text_for_action, IconButtonShape, Tooltip}; use util::ResultExt; use workspace::{notifications::NotificationId, Toast, Workspace}; pub fn init( fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, telemetry: Arc<Telemetry>, cx: &mut AppContext, ) { cx.set_global(TerminalInlineAssistant::new(fs, prompt_builder, telemetry)); } const PROMPT_HISTORY_MAX_LEN: usize = 20; #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)] struct TerminalInlineAssistId(usize); impl TerminalInlineAssistId { fn post_inc(&mut self) -> TerminalInlineAssistId { let id = *self; self.0 += 1; id } } pub struct TerminalInlineAssistant { next_assist_id: TerminalInlineAssistId, assists: HashMap<TerminalInlineAssistId, TerminalInlineAssist>, prompt_history: VecDeque<String>, telemetry: Option<Arc<Telemetry>>, fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, } impl Global for TerminalInlineAssistant {} impl TerminalInlineAssistant { pub fn new( fs: Arc<dyn Fs>, prompt_builder: Arc<PromptBuilder>, telemetry: Arc<Telemetry>, ) -> Self { Self { next_assist_id: TerminalInlineAssistId::default(), assists: HashMap::default(), prompt_history: VecDeque::default(), telemetry: Some(telemetry), fs, prompt_builder, } } pub fn assist( &mut self, terminal_view: &View<TerminalView>, workspace: Option<WeakView<Workspace>>, assistant_panel: Option<&View<AssistantPanel>>, initial_prompt: Option<String>, cx: &mut WindowContext, ) { let terminal = terminal_view.read(cx).terminal().clone(); let assist_id = self.next_assist_id.post_inc(); let prompt_buffer = cx.new_model(|cx| Buffer::local(initial_prompt.unwrap_or_default(), cx)); let prompt_buffer = cx.new_model(|cx| MultiBuffer::singleton(prompt_buffer, cx)); let codegen = cx.new_model(|_| Codegen::new(terminal, self.telemetry.clone())); let prompt_editor = cx.new_view(|cx| { PromptEditor::new( assist_id, self.prompt_history.clone(), prompt_buffer.clone(), codegen, assistant_panel, workspace.clone(), self.fs.clone(), cx, ) }); let prompt_editor_render = prompt_editor.clone(); let block = terminal_view::BlockProperties { height: 2, render: Box::new(move |_| prompt_editor_render.clone().into_any_element()), }; terminal_view.update(cx, |terminal_view, cx| { terminal_view.set_block_below_cursor(block, cx); }); let terminal_assistant = TerminalInlineAssist::new( assist_id, terminal_view, assistant_panel.is_some(), prompt_editor, workspace.clone(), cx, ); self.assists.insert(assist_id, terminal_assistant); self.focus_assist(assist_id, cx); } fn focus_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) { let assist = &self.assists[&assist_id]; if let Some(prompt_editor) = assist.prompt_editor.as_ref() { prompt_editor.update(cx, |this, cx| { this.editor.update(cx, |editor, cx| { editor.focus(cx); editor.select_all(&SelectAll, cx); }); }); } } fn handle_prompt_editor_event( &mut self, prompt_editor: View<PromptEditor>, event: &PromptEditorEvent, cx: &mut WindowContext, ) { let assist_id = prompt_editor.read(cx).id; match event { PromptEditorEvent::StartRequested => { self.start_assist(assist_id, cx); } PromptEditorEvent::StopRequested => { self.stop_assist(assist_id, cx); } PromptEditorEvent::ConfirmRequested { execute } => { self.finish_assist(assist_id, false, *execute, cx); } PromptEditorEvent::CancelRequested => { self.finish_assist(assist_id, true, false, cx); } PromptEditorEvent::DismissRequested => { self.dismiss_assist(assist_id, cx); } PromptEditorEvent::Resized { height_in_lines } => { self.insert_prompt_editor_into_terminal(assist_id, *height_in_lines, cx); } } } fn start_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) { let assist = if let Some(assist) = self.assists.get_mut(&assist_id) { assist } else { return; }; let Some(user_prompt) = assist .prompt_editor .as_ref() .map(|editor| editor.read(cx).prompt(cx)) else { return; }; self.prompt_history.retain(|prompt| *prompt != user_prompt); self.prompt_history.push_back(user_prompt.clone()); if self.prompt_history.len() > PROMPT_HISTORY_MAX_LEN { self.prompt_history.pop_front(); } assist .terminal .update(cx, |terminal, cx| { terminal .terminal() .update(cx, |terminal, _| terminal.input(CLEAR_INPUT.to_string())); }) .log_err(); let codegen = assist.codegen.clone(); let Some(request) = self.request_for_inline_assist(assist_id, cx).log_err() else { return; }; codegen.update(cx, |codegen, cx| codegen.start(request, cx)); } fn stop_assist(&mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext) { let assist = if let Some(assist) = self.assists.get_mut(&assist_id) { assist } else { return; }; assist.codegen.update(cx, |codegen, cx| codegen.stop(cx)); } fn request_for_inline_assist( &self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext, ) -> Result<LanguageModelRequest> { let assist = self.assists.get(&assist_id).context("invalid assist")?; let shell = std::env::var("SHELL").ok(); let (latest_output, working_directory) = assist .terminal .update(cx, |terminal, cx| { let terminal = terminal.model().read(cx); let latest_output = terminal.last_n_non_empty_lines(DEFAULT_CONTEXT_LINES); let working_directory = terminal .working_directory() .map(|path| path.to_string_lossy().to_string()); (latest_output, working_directory) }) .ok() .unwrap_or_default(); let context_request = if assist.include_context { assist.workspace.as_ref().and_then(|workspace| { let workspace = workspace.upgrade()?.read(cx); let assistant_panel = workspace.panel::<AssistantPanel>(cx)?; Some( assistant_panel .read(cx) .active_context(cx)? .read(cx) .to_completion_request(RequestType::Chat, cx), ) }) } else { None }; let prompt = self.prompt_builder.generate_terminal_assistant_prompt( &assist .prompt_editor .clone() .context("invalid assist")? .read(cx) .prompt(cx), shell.as_deref(), working_directory.as_deref(), &latest_output, )?; let mut messages = Vec::new(); if let Some(context_request) = context_request { messages = context_request.messages; } messages.push(LanguageModelRequestMessage { role: Role::User, content: vec![prompt.into()], cache: false, }); Ok(LanguageModelRequest { messages, tools: Vec::new(), stop: Vec::new(), temperature: None, }) } fn finish_assist( &mut self, assist_id: TerminalInlineAssistId, undo: bool, execute: bool, cx: &mut WindowContext, ) { self.dismiss_assist(assist_id, cx); if let Some(assist) = self.assists.remove(&assist_id) { assist .terminal .update(cx, |this, cx| { this.clear_block_below_cursor(cx); this.focus_handle(cx).focus(cx); }) .log_err(); if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() { let codegen = assist.codegen.read(cx); let executor = cx.background_executor().clone(); report_assistant_event( AssistantEvent { conversation_id: None, kind: AssistantKind::InlineTerminal, message_id: codegen.message_id.clone(), phase: if undo { AssistantPhase::Rejected } else { AssistantPhase::Accepted }, model: model.telemetry_id(), model_provider: model.provider_id().to_string(), response_latency: None, error_message: None, language_name: None, }, codegen.telemetry.clone(), cx.http_client(), model.api_key(cx), &executor, ); } assist.codegen.update(cx, |codegen, cx| { if undo { codegen.undo(cx); } else if execute { codegen.complete(cx); } }); } } fn dismiss_assist( &mut self, assist_id: TerminalInlineAssistId, cx: &mut WindowContext, ) -> bool { let Some(assist) = self.assists.get_mut(&assist_id) else { return false; }; if assist.prompt_editor.is_none() { return false; } assist.prompt_editor = None; assist .terminal .update(cx, |this, cx| { this.clear_block_below_cursor(cx); this.focus_handle(cx).focus(cx); }) .is_ok() } fn insert_prompt_editor_into_terminal( &mut self, assist_id: TerminalInlineAssistId, height: u8, cx: &mut WindowContext, ) { if let Some(assist) = self.assists.get_mut(&assist_id) { if let Some(prompt_editor) = assist.prompt_editor.as_ref().cloned() { assist .terminal .update(cx, |terminal, cx| { terminal.clear_block_below_cursor(cx); let block = terminal_view::BlockProperties { height, render: Box::new(move |_| prompt_editor.clone().into_any_element()), }; terminal.set_block_below_cursor(block, cx); }) .log_err(); } } } } struct TerminalInlineAssist { terminal: WeakView<TerminalView>, prompt_editor: Option<View<PromptEditor>>, codegen: Model<Codegen>, workspace: Option<WeakView<Workspace>>, include_context: bool, _subscriptions: Vec<Subscription>, } impl TerminalInlineAssist { pub fn new( assist_id: TerminalInlineAssistId, terminal: &View<TerminalView>, include_context: bool, prompt_editor: View<PromptEditor>, workspace: Option<WeakView<Workspace>>, cx: &mut WindowContext, ) -> Self { let codegen = prompt_editor.read(cx).codegen.clone(); Self { terminal: terminal.downgrade(), prompt_editor: Some(prompt_editor.clone()), codegen: codegen.clone(), workspace: workspace.clone(), include_context, _subscriptions: vec![ cx.subscribe(&prompt_editor, |prompt_editor, event, cx| { TerminalInlineAssistant::update_global(cx, |this, cx| { this.handle_prompt_editor_event(prompt_editor, event, cx) }) }), cx.subscribe(&codegen, move |codegen, event, cx| { TerminalInlineAssistant::update_global(cx, |this, cx| match event { CodegenEvent::Finished => { let assist = if let Some(assist) = this.assists.get(&assist_id) { assist } else { return; }; if let CodegenStatus::Error(error) = &codegen.read(cx).status { if assist.prompt_editor.is_none() { if let Some(workspace) = assist .workspace .as_ref() .and_then(|workspace| workspace.upgrade()) { let error = format!("Terminal inline assistant error: {}", error); workspace.update(cx, |workspace, cx| { struct InlineAssistantError; let id = NotificationId::composite::<InlineAssistantError>( assist_id.0, ); workspace.show_toast(Toast::new(id, error), cx); }) } } } if assist.prompt_editor.is_none() { this.finish_assist(assist_id, false, false, cx); } } }) }), ], } } } enum PromptEditorEvent { StartRequested, StopRequested, ConfirmRequested { execute: bool }, CancelRequested, DismissRequested, Resized { height_in_lines: u8 }, } struct PromptEditor { id: TerminalInlineAssistId, height_in_lines: u8, editor: View<Editor>, language_model_selector: View<LanguageModelSelector>, edited_since_done: bool, prompt_history: VecDeque<String>, prompt_history_ix: Option<usize>, pending_prompt: String, codegen: Model<Codegen>, _codegen_subscription: Subscription, editor_subscriptions: Vec<Subscription>, pending_token_count: Task<Result<()>>, token_count: Option<usize>, _token_count_subscriptions: Vec<Subscription>, workspace: Option<WeakView<Workspace>>, } impl EventEmitter<PromptEditorEvent> for PromptEditor {} impl Render for PromptEditor { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { let status = &self.codegen.read(cx).status; let buttons = match status {