Allow using the inline assistant in prompt library (#12680)

Release Notes:

- N/A
This commit is contained in:
Antonio Scandurra 2024-06-05 14:46:33 +02:00 committed by GitHub
parent 0289c312c9
commit a96782cc6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 1589 additions and 1470 deletions

View file

@ -1,31 +1,27 @@
use crate::{
assistant_settings::{AssistantDockPosition, AssistantSettings},
codegen::{self, Codegen, CodegenKind},
prompt_library::open_prompt_library,
prompts::generate_content_prompt,
search::*,
slash_command::{
default_command::DefaultSlashCommand, SlashCommandCompletionProvider, SlashCommandLine,
SlashCommandRegistry,
},
ApplyEdit, Assist, CompletionProvider, ConfirmCommand, CycleMessageRole, InlineAssist,
LanguageModelRequest, LanguageModelRequestMessage, MessageId, MessageMetadata, MessageStatus,
ModelSelector, QuoteSelection, ResetKey, Role, SavedConversation, SavedConversationMetadata,
SavedMessage, Split, ToggleFocus, ToggleHistory, ToggleModelSelector,
InlineAssistant, LanguageModelRequest, LanguageModelRequestMessage, MessageId, MessageMetadata,
MessageStatus, ModelSelector, QuoteSelection, ResetKey, Role, SavedConversation,
SavedConversationMetadata, SavedMessage, Split, ToggleFocus, ToggleHistory,
ToggleModelSelector,
};
use anyhow::{anyhow, Result};
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
use client::telemetry::Telemetry;
use collections::{hash_map, BTreeSet, HashMap, HashSet, VecDeque};
use editor::{actions::ShowCompletions, GutterDimensions};
use collections::{BTreeSet, HashMap, HashSet};
use editor::actions::ShowCompletions;
use editor::{
actions::{FoldAt, MoveDown, MoveToEndOfLine, MoveUp, Newline, UnfoldAt},
display_map::{
BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, Flap, ToDisplayPoint,
},
actions::{FoldAt, MoveToEndOfLine, Newline, UnfoldAt},
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, Flap, ToDisplayPoint},
scroll::{Autoscroll, AutoscrollStrategy},
Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MultiBufferSnapshot, RowExt,
ToOffset as _, ToPoint,
Anchor, Editor, EditorEvent, RowExt, ToOffset as _, ToPoint,
};
use editor::{display_map::FlapId, FoldPlaceholder};
use file_icons::FileIcons;
@ -33,19 +29,17 @@ use fs::Fs;
use futures::future::Shared;
use futures::{FutureExt, StreamExt};
use gpui::{
div, point, relative, rems, uniform_list, Action, AnyElement, AnyView, AppContext,
AsyncAppContext, AsyncWindowContext, ClipboardItem, Context, Empty, EventEmitter, FocusHandle,
FocusableView, FontStyle, FontWeight, HighlightStyle, InteractiveElement, IntoElement, Model,
ModelContext, ParentElement, Pixels, Render, SharedString, StatefulInteractiveElement, Styled,
Subscription, Task, TextStyle, UniformListScrollHandle, View, ViewContext, VisualContext,
WeakModel, WeakView, WhiteSpace, WindowContext,
div, point, rems, uniform_list, Action, AnyElement, AnyView, AppContext, AsyncAppContext,
AsyncWindowContext, ClipboardItem, Context, Empty, EventEmitter, FocusHandle, FocusableView,
InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels, Render,
SharedString, StatefulInteractiveElement, Styled, Subscription, Task, UniformListScrollHandle,
UpdateGlobal, View, ViewContext, VisualContext, WeakView, WindowContext,
};
use language::{
language_settings::SoftWrap, AnchorRangeExt, AutoindentMode, Buffer, LanguageRegistry,
LspAdapterDelegate, OffsetRangeExt as _, Point, ToOffset as _,
};
use multi_buffer::MultiBufferRow;
use parking_lot::Mutex;
use project::{Project, ProjectLspAdapterDelegate, ProjectTransaction};
use search::{buffer_search::DivRegistrar, BufferSearchBar};
use settings::Settings;
@ -59,19 +53,18 @@ use std::{
time::{Duration, Instant},
};
use telemetry_events::AssistantKind;
use theme::ThemeSettings;
use ui::{
popover_menu, prelude::*, ButtonLike, ContextMenu, ElevationIndex, KeyBinding,
PopoverMenuHandle, Tab, TabBar, Tooltip,
};
use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
use uuid::Uuid;
use workspace::NewFile;
use workspace::{
dock::{DockPosition, Panel, PanelEvent},
searchable::Direction,
Save, Toast, ToggleZoom, Toolbar, Workspace,
Save, ToggleZoom, Toolbar, Workspace,
};
use workspace::{notifications::NotificationId, NewFile};
pub fn init(cx: &mut AppContext) {
cx.observe_new_views(
@ -110,10 +103,6 @@ pub struct AssistantPanel {
fs: Arc<dyn Fs>,
telemetry: Arc<Telemetry>,
_subscriptions: Vec<Subscription>,
next_inline_assist_id: usize,
pending_inline_assists: HashMap<usize, PendingInlineAssist>,
pending_inline_assist_ids_by_editor: HashMap<WeakView<Editor>, Vec<usize>>,
inline_prompt_history: VecDeque<String>,
_watch_saved_conversations: Task<Result<()>>,
authentication_prompt: Option<AnyView>,
model_menu_handle: PopoverMenuHandle<ContextMenu>,
@ -125,8 +114,6 @@ struct ActiveConversationEditor {
}
impl AssistantPanel {
const INLINE_PROMPT_HISTORY_MAX_LEN: usize = 20;
pub fn load(
workspace: WeakView<Workspace>,
cx: AsyncWindowContext,
@ -205,10 +192,6 @@ impl AssistantPanel {
width: None,
height: None,
_subscriptions: subscriptions,
next_inline_assist_id: 0,
pending_inline_assists: Default::default(),
pending_inline_assist_ids_by_editor: Default::default(),
inline_prompt_history: Default::default(),
_watch_saved_conversations,
authentication_prompt: None,
model_menu_handle: PopoverMenuHandle::default(),
@ -295,26 +278,30 @@ impl AssistantPanel {
}
});
let show_include_conversation;
let include_conversation;
let active_editor;
if let Some(conversation_editor) = conversation_editor {
active_editor = conversation_editor;
show_include_conversation = false;
include_conversation = false;
} else if let Some(workspace_editor) = workspace
.active_item(cx)
.and_then(|item| item.act_as::<Editor>(cx))
{
active_editor = workspace_editor;
show_include_conversation = true;
include_conversation = true;
} else {
return;
};
let project = workspace.project().clone();
if assistant.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
assistant.update(cx, |assistant, cx| {
assistant.new_inline_assist(&active_editor, &project, show_include_conversation, cx)
});
InlineAssistant::update_global(cx, |assistant, cx| {
assistant.assist(
&active_editor,
Some(cx.view().downgrade()),
include_conversation,
cx,
)
})
} else {
let assistant = assistant.downgrade();
cx.spawn(|workspace, mut cx| async move {
@ -322,14 +309,16 @@ impl AssistantPanel {
.update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
.await?;
if assistant.update(&mut cx, |assistant, cx| assistant.is_authenticated(cx))? {
assistant.update(&mut cx, |assistant, cx| {
assistant.new_inline_assist(
&active_editor,
&project,
show_include_conversation,
cx,
)
})?;
cx.update(|cx| {
InlineAssistant::update_global(cx, |assistant, cx| {
assistant.assist(
&active_editor,
Some(workspace),
include_conversation,
cx,
)
})
})?
} else {
workspace.update(&mut cx, |workspace, cx| {
workspace.focus_panel::<AssistantPanel>(cx)
@ -342,439 +331,17 @@ impl AssistantPanel {
}
}
fn new_inline_assist(
&mut self,
editor: &View<Editor>,
project: &Model<Project>,
include_conversation: bool,
cx: &mut ViewContext<Self>,
) {
let selection = editor.read(cx).selections.newest_anchor().clone();
if selection.start.excerpt_id != selection.end.excerpt_id {
return;
}
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
// Extend the selection to the start and the end of the line.
let mut point_selection = selection.map(|selection| selection.to_point(&snapshot));
if point_selection.end > point_selection.start {
point_selection.start.column = 0;
// If the selection ends at the start of the line, we don't want to include it.
if point_selection.end.column == 0 {
point_selection.end.row -= 1;
}
point_selection.end.column = snapshot.line_len(MultiBufferRow(point_selection.end.row));
}
let codegen_kind = if point_selection.start == point_selection.end {
CodegenKind::Generate {
position: snapshot.anchor_after(point_selection.start),
}
} else {
CodegenKind::Transform {
range: snapshot.anchor_before(point_selection.start)
..snapshot.anchor_after(point_selection.end),
}
};
let inline_assist_id = post_inc(&mut self.next_inline_assist_id);
let telemetry = self.telemetry.clone();
let codegen = cx.new_model(|cx| {
Codegen::new(
editor.read(cx).buffer().clone(),
codegen_kind,
Some(telemetry),
cx,
)
});
let measurements = Arc::new(Mutex::new(GutterDimensions::default()));
let inline_assistant = cx.new_view(|cx| {
InlineAssistant::new(
inline_assist_id,
measurements.clone(),
include_conversation,
self.inline_prompt_history.clone(),
codegen.clone(),
cx,
)
});
let block_id = editor.update(cx, |editor, cx| {
editor.change_selections(None, cx, |selections| {
selections.select_anchor_ranges([selection.head()..selection.head()])
});
editor.insert_blocks(
[BlockProperties {
style: BlockStyle::Flex,
position: snapshot.anchor_before(Point::new(point_selection.head().row, 0)),
height: 2,
render: Box::new({
let inline_assistant = inline_assistant.clone();
move |cx: &mut BlockContext| {
*measurements.lock() = *cx.gutter_dimensions;
inline_assistant.clone().into_any_element()
}
}),
disposition: if selection.reversed {
BlockDisposition::Above
} else {
BlockDisposition::Below
},
}],
Some(Autoscroll::Strategy(AutoscrollStrategy::Newest)),
cx,
)[0]
});
self.pending_inline_assists.insert(
inline_assist_id,
PendingInlineAssist {
editor: editor.downgrade(),
inline_assistant: Some((block_id, inline_assistant.clone())),
codegen: codegen.clone(),
project: project.downgrade(),
_subscriptions: vec![
cx.subscribe(&inline_assistant, Self::handle_inline_assistant_event),
cx.subscribe(editor, {
let inline_assistant = inline_assistant.downgrade();
move |_, editor, event, cx| {
if let Some(inline_assistant) = inline_assistant.upgrade() {
if let EditorEvent::SelectionsChanged { local } = event {
if *local
&& inline_assistant.focus_handle(cx).contains_focused(cx)
{
cx.focus_view(&editor);
}
}
}
}
}),
cx.observe(&codegen, {
let editor = editor.downgrade();
move |this, _, cx| {
if let Some(editor) = editor.upgrade() {
this.update_highlights_for_editor(&editor, cx);
}
}
}),
cx.subscribe(&codegen, move |this, codegen, event, cx| match event {
codegen::Event::Undone => {
this.finish_inline_assist(inline_assist_id, false, cx)
}
codegen::Event::Finished => {
let pending_assist = if let Some(pending_assist) =
this.pending_inline_assists.get(&inline_assist_id)
{
pending_assist
} else {
return;
};
let error = codegen
.read(cx)
.error()
.map(|error| format!("Inline assistant error: {}", error));
if let Some(error) = error {
if pending_assist.inline_assistant.is_none() {
if let Some(workspace) = this.workspace.upgrade() {
workspace.update(cx, |workspace, cx| {
struct InlineAssistantError;
let id =
NotificationId::identified::<InlineAssistantError>(
inline_assist_id,
);
workspace.show_toast(Toast::new(id, error), cx);
})
}
this.finish_inline_assist(inline_assist_id, false, cx);
}
} else {
this.finish_inline_assist(inline_assist_id, false, cx);
}
}
}),
],
},
);
self.pending_inline_assist_ids_by_editor
.entry(editor.downgrade())
.or_default()
.push(inline_assist_id);
self.update_highlights_for_editor(editor, cx);
}
fn handle_inline_assistant_event(
&mut self,
inline_assistant: View<InlineAssistant>,
event: &InlineAssistantEvent,
cx: &mut ViewContext<Self>,
) {
let assist_id = inline_assistant.read(cx).id;
match event {
InlineAssistantEvent::Confirmed {
prompt,
include_conversation,
} => {
self.confirm_inline_assist(assist_id, prompt, *include_conversation, cx);
}
InlineAssistantEvent::Canceled => {
self.finish_inline_assist(assist_id, true, cx);
}
InlineAssistantEvent::Dismissed => {
self.hide_inline_assist(assist_id, cx);
}
}
}
fn cancel_last_inline_assist(
workspace: &mut Workspace,
_workspace: &mut Workspace,
_: &editor::actions::Cancel,
cx: &mut ViewContext<Workspace>,
) {
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
if let Some(editor) = workspace
.active_item(cx)
.and_then(|item| item.downcast::<Editor>())
{
let handled = panel.update(cx, |panel, cx| {
if let Some(assist_id) = panel
.pending_inline_assist_ids_by_editor
.get(&editor.downgrade())
.and_then(|assist_ids| assist_ids.last().copied())
{
panel.finish_inline_assist(assist_id, true, cx);
true
} else {
false
}
});
if handled {
return;
}
}
}
cx.propagate();
}
fn finish_inline_assist(&mut self, assist_id: usize, undo: bool, cx: &mut ViewContext<Self>) {
self.hide_inline_assist(assist_id, cx);
if let Some(pending_assist) = self.pending_inline_assists.remove(&assist_id) {
if let hash_map::Entry::Occupied(mut entry) = self
.pending_inline_assist_ids_by_editor
.entry(pending_assist.editor.clone())
{
entry.get_mut().retain(|id| *id != assist_id);
if entry.get().is_empty() {
entry.remove();
}
}
if let Some(editor) = pending_assist.editor.upgrade() {
self.update_highlights_for_editor(&editor, cx);
if undo {
pending_assist
.codegen
.update(cx, |codegen, cx| codegen.undo(cx));
}
}
}
}
fn hide_inline_assist(&mut self, assist_id: usize, cx: &mut ViewContext<Self>) {
if let Some(pending_assist) = self.pending_inline_assists.get_mut(&assist_id) {
if let Some(editor) = pending_assist.editor.upgrade() {
if let Some((block_id, inline_assistant)) = pending_assist.inline_assistant.take() {
editor.update(cx, |editor, cx| {
editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
if inline_assistant.focus_handle(cx).contains_focused(cx) {
editor.focus(cx);
}
});
}
}
}
}
fn confirm_inline_assist(
&mut self,
inline_assist_id: usize,
user_prompt: &str,
include_conversation: bool,
cx: &mut ViewContext<Self>,
) {
let conversation = if include_conversation {
self.active_conversation_editor()
.map(|editor| editor.read(cx).conversation.clone())
} else {
None
};
let pending_assist =
if let Some(pending_assist) = self.pending_inline_assists.get_mut(&inline_assist_id) {
pending_assist
} else {
return;
};
let editor = if let Some(editor) = pending_assist.editor.upgrade() {
editor
} else {
return;
};
let project = pending_assist.project.clone();
let project_name = project.upgrade().map(|project| {
project
.read(cx)
.worktree_root_names(cx)
.collect::<Vec<&str>>()
.join("/")
let canceled = InlineAssistant::update_global(cx, |assistant, cx| {
assistant.cancel_last_inline_assist(cx)
});
self.inline_prompt_history
.retain(|prompt| prompt != user_prompt);
self.inline_prompt_history.push_back(user_prompt.into());
if self.inline_prompt_history.len() > Self::INLINE_PROMPT_HISTORY_MAX_LEN {
self.inline_prompt_history.pop_front();
if !canceled {
cx.propagate();
}
let codegen = pending_assist.codegen.clone();
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
let range = codegen.read(cx).range();
let start = snapshot.point_to_buffer_offset(range.start);
let end = snapshot.point_to_buffer_offset(range.end);
let (buffer, range) = if let Some((start, end)) = start.zip(end) {
let (start_buffer, start_buffer_offset) = start;
let (end_buffer, end_buffer_offset) = end;
if start_buffer.remote_id() == end_buffer.remote_id() {
(start_buffer.clone(), start_buffer_offset..end_buffer_offset)
} else {
self.finish_inline_assist(inline_assist_id, false, cx);
return;
}
} else {
self.finish_inline_assist(inline_assist_id, false, cx);
return;
};
let language = buffer.language_at(range.start);
let language_name = if let Some(language) = language.as_ref() {
if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
None
} else {
Some(language.name())
}
} else {
None
};
// Higher Temperature increases the randomness of model outputs.
// If Markdown or No Language is Known, increase the randomness for more creative output
// If Code, decrease temperature to get more deterministic outputs
let temperature = if let Some(language) = language_name.clone() {
if language.as_ref() == "Markdown" {
1.0
} else {
0.5
}
} else {
1.0
};
let user_prompt = user_prompt.to_string();
let prompt = cx.background_executor().spawn(async move {
let language_name = language_name.as_deref();
generate_content_prompt(user_prompt, language_name, buffer, range, project_name)
});
let mut messages = Vec::new();
if let Some(conversation) = conversation {
let conversation = conversation.read(cx);
let buffer = conversation.buffer.read(cx);
messages.extend(
conversation
.messages(cx)
.map(|message| message.to_request_message(buffer)),
);
}
let model = CompletionProvider::global(cx).model();
cx.spawn(|_, mut cx| async move {
// I Don't know if we want to return a ? here.
let prompt = prompt.await?;
messages.push(LanguageModelRequestMessage {
role: Role::User,
content: prompt,
});
let request = LanguageModelRequest {
model,
messages,
stop: vec!["|END|>".to_string()],
temperature,
};
codegen.update(&mut cx, |codegen, cx| codegen.start(request, cx))?;
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
fn update_highlights_for_editor(&self, editor: &View<Editor>, cx: &mut ViewContext<Self>) {
let mut background_ranges = Vec::new();
let mut foreground_ranges = Vec::new();
let empty_inline_assist_ids = Vec::new();
let inline_assist_ids = self
.pending_inline_assist_ids_by_editor
.get(&editor.downgrade())
.unwrap_or(&empty_inline_assist_ids);
for inline_assist_id in inline_assist_ids {
if let Some(pending_assist) = self.pending_inline_assists.get(inline_assist_id) {
let codegen = pending_assist.codegen.read(cx);
background_ranges.push(codegen.range());
foreground_ranges.extend(codegen.last_equal_ranges().iter().cloned());
}
}
let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
merge_ranges(&mut background_ranges, &snapshot);
merge_ranges(&mut foreground_ranges, &snapshot);
editor.update(cx, |editor, cx| {
if background_ranges.is_empty() {
editor.clear_background_highlights::<PendingInlineAssist>(cx);
} else {
editor.highlight_background::<PendingInlineAssist>(
&background_ranges,
|theme| theme.editor_active_line_background, // TODO use the appropriate color
cx,
);
}
if foreground_ranges.is_empty() {
editor.clear_highlights::<PendingInlineAssist>(cx);
} else {
editor.highlight_text::<PendingInlineAssist>(
foreground_ranges,
HighlightStyle {
fade_out: Some(0.6),
..Default::default()
},
cx,
);
}
});
}
fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ConversationEditor>> {
@ -919,6 +486,15 @@ impl AssistantPanel {
Some(&self.active_conversation_editor.as_ref()?.editor)
}
pub fn active_conversation(&self, cx: &AppContext) -> Option<Model<Conversation>> {
Some(
self.active_conversation_editor()?
.read(cx)
.conversation
.clone(),
)
}
fn render_popover_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let assistant = cx.view().clone();
let zoomed = self.zoomed;
@ -2045,7 +1621,7 @@ impl Conversation {
user_messages
}
fn to_completion_request(&self, cx: &mut ModelContext<Conversation>) -> LanguageModelRequest {
pub fn to_completion_request(&self, cx: &AppContext) -> LanguageModelRequest {
let messages = self
.messages(cx)
.filter(|message| matches!(message.status, MessageStatus::Done))
@ -2919,16 +2495,8 @@ impl ConversationEditor {
)
}
};
let render_trailer = {
let confirm_command = confirm_command.clone();
move |row, _, cx: &mut WindowContext| {
render_pending_slash_command_trailer(
row,
confirm_command.clone(),
cx,
)
}
};
let render_trailer =
|_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
let start = buffer
.anchor_in_excerpt(excerpt_id, command.source_range.start)
@ -3517,239 +3085,6 @@ impl Message {
}
}
enum InlineAssistantEvent {
Confirmed {
prompt: String,
include_conversation: bool,
},
Canceled,
Dismissed,
}
struct InlineAssistant {
id: usize,
prompt_editor: View<Editor>,
confirmed: bool,
include_conversation: bool,
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
prompt_history: VecDeque<String>,
prompt_history_ix: Option<usize>,
pending_prompt: String,
codegen: Model<Codegen>,
_subscriptions: Vec<Subscription>,
}
impl EventEmitter<InlineAssistantEvent> for InlineAssistant {}
impl Render for InlineAssistant {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let gutter_dimensions = *self.gutter_dimensions.lock();
let icon_size = IconSize::default();
h_flex()
.w_full()
.py_2()
.border_y_1()
.border_color(cx.theme().colors().border)
.bg(cx.theme().colors().editor_background)
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::move_up))
.on_action(cx.listener(Self::move_down))
.child(
h_flex()
.w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0))
.pr(gutter_dimensions.fold_area_width())
.justify_end()
.children(if let Some(error) = self.codegen.read(cx).error() {
let error_message = SharedString::from(error.to_string());
Some(
div()
.id("error")
.tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
.child(
Icon::new(IconName::XCircle)
.size(icon_size)
.color(Color::Error),
),
)
} else {
None
}),
)
.child(h_flex().flex_1().child(self.render_prompt_editor(cx)))
}
}
impl FocusableView for InlineAssistant {
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
self.prompt_editor.focus_handle(cx)
}
}
impl InlineAssistant {
#[allow(clippy::too_many_arguments)]
fn new(
id: usize,
gutter_dimensions: Arc<Mutex<GutterDimensions>>,
include_conversation: bool,
prompt_history: VecDeque<String>,
codegen: Model<Codegen>,
cx: &mut ViewContext<Self>,
) -> Self {
let prompt_editor = cx.new_view(|cx| {
let mut editor = Editor::single_line(cx);
let placeholder = match codegen.read(cx).kind() {
CodegenKind::Transform { .. } => "Enter transformation prompt…",
CodegenKind::Generate { .. } => "Enter generation prompt…",
};
editor.set_placeholder_text(placeholder, cx);
editor
});
cx.focus_view(&prompt_editor);
let subscriptions = vec![
cx.observe(&codegen, Self::handle_codegen_changed),
cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events),
];
Self {
id,
prompt_editor,
confirmed: false,
include_conversation,
gutter_dimensions,
prompt_history,
prompt_history_ix: None,
pending_prompt: String::new(),
codegen,
_subscriptions: subscriptions,
}
}
fn handle_prompt_editor_events(
&mut self,
_: View<Editor>,
event: &EditorEvent,
cx: &mut ViewContext<Self>,
) {
if let EditorEvent::Edited = event {
self.pending_prompt = self.prompt_editor.read(cx).text(cx);
cx.notify();
}
}
fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
let is_read_only = !self.codegen.read(cx).idle();
self.prompt_editor.update(cx, |editor, cx| {
let was_read_only = editor.read_only(cx);
if was_read_only != is_read_only {
if is_read_only {
editor.set_read_only(true);
} else {
self.confirmed = false;
editor.set_read_only(false);
}
}
});
cx.notify();
}
fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
cx.emit(InlineAssistantEvent::Canceled);
}
fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
if self.confirmed {
cx.emit(InlineAssistantEvent::Dismissed);
} else {
let prompt = self.prompt_editor.read(cx).text(cx);
self.prompt_editor
.update(cx, |editor, _cx| editor.set_read_only(true));
cx.emit(InlineAssistantEvent::Confirmed {
prompt,
include_conversation: self.include_conversation,
});
self.confirmed = true;
cx.notify();
}
}
fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.prompt_history_ix {
if ix > 0 {
self.prompt_history_ix = Some(ix - 1);
let prompt = self.prompt_history[ix - 1].clone();
self.set_prompt(&prompt, cx);
}
} else if !self.prompt_history.is_empty() {
self.prompt_history_ix = Some(self.prompt_history.len() - 1);
let prompt = self.prompt_history[self.prompt_history.len() - 1].clone();
self.set_prompt(&prompt, cx);
}
}
fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
if let Some(ix) = self.prompt_history_ix {
if ix < self.prompt_history.len() - 1 {
self.prompt_history_ix = Some(ix + 1);
let prompt = self.prompt_history[ix + 1].clone();
self.set_prompt(&prompt, cx);
} else {
self.prompt_history_ix = None;
let pending_prompt = self.pending_prompt.clone();
self.set_prompt(&pending_prompt, cx);
}
}
}
fn set_prompt(&mut self, prompt: &str, cx: &mut ViewContext<Self>) {
self.prompt_editor.update(cx, |editor, cx| {
editor.buffer().update(cx, |buffer, cx| {
let len = buffer.len(cx);
buffer.edit([(0..len, prompt)], None, cx);
});
});
}
fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let text_style = TextStyle {
color: if self.prompt_editor.read(cx).read_only(cx) {
cx.theme().colors().text_disabled
} else {
cx.theme().colors().text
},
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_size: rems(0.875).into(),
font_weight: FontWeight::NORMAL,
font_style: FontStyle::Normal,
line_height: relative(1.3),
background_color: None,
underline: None,
strikethrough: None,
white_space: WhiteSpace::Normal,
};
EditorElement::new(
&self.prompt_editor,
EditorStyle {
background: cx.theme().colors().editor_background,
local_player: cx.theme().players().local(),
text: text_style,
..Default::default()
},
)
}
}
struct PendingInlineAssist {
editor: WeakView<Editor>,
inline_assistant: Option<(BlockId, View<InlineAssistant>)>,
codegen: Model<Codegen>,
_subscriptions: Vec<Subscription>,
project: WeakModel<Project>,
}
type ToggleFold = Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>;
fn render_slash_command_output_toggle(
@ -3801,47 +3136,6 @@ fn render_pending_slash_command_gutter_decoration(
icon.into_any_element()
}
fn render_pending_slash_command_trailer(
_row: MultiBufferRow,
_confirm_command: Arc<dyn Fn(&mut WindowContext)>,
_cx: &mut WindowContext,
) -> AnyElement {
Empty.into_any()
// ButtonLike::new(("run_button", row.0))
// .style(ButtonStyle::Filled)
// .size(ButtonSize::Compact)
// .layer(ElevationIndex::ModalSurface)
// .children(
// KeyBinding::for_action(&Confirm, cx)
// .map(|binding| binding.icon_size(IconSize::XSmall).into_any_element()),
// )
// .child(Label::new("Run").size(LabelSize::XSmall))
// .on_click(move |_, cx| confirm_command(cx))
// .into_any_element()
}
fn merge_ranges(ranges: &mut Vec<Range<Anchor>>, buffer: &MultiBufferSnapshot) {
ranges.sort_unstable_by(|a, b| {
a.start
.cmp(&b.start, buffer)
.then_with(|| b.end.cmp(&a.end, buffer))
});
let mut ix = 0;
while ix + 1 < ranges.len() {
let b = ranges[ix + 1].clone();
let a = &mut ranges[ix];
if a.end.cmp(&b.start, buffer).is_gt() {
if a.end.cmp(&b.end, buffer).is_lt() {
a.end = b.end;
}
ranges.remove(ix + 1);
} else {
ix += 1;
}
}
}
fn make_lsp_adapter_delegate(
project: &Model<Project>,
cx: &mut AppContext,