mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
WIP
This commit is contained in:
parent
e534c5fdcd
commit
d86da04584
@ -104,7 +104,7 @@ pub struct OpenAIResponseStreamEvent {
|
||||
|
||||
pub async fn stream_completion(
|
||||
credential: ProviderCredential,
|
||||
executor: Arc<BackgroundExecutor>,
|
||||
executor: BackgroundExecutor,
|
||||
request: Box<dyn CompletionRequest>,
|
||||
) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
|
||||
let api_key = match credential {
|
||||
@ -197,11 +197,11 @@ pub async fn stream_completion(
|
||||
pub struct OpenAICompletionProvider {
|
||||
model: OpenAILanguageModel,
|
||||
credential: Arc<RwLock<ProviderCredential>>,
|
||||
executor: Arc<BackgroundExecutor>,
|
||||
executor: BackgroundExecutor,
|
||||
}
|
||||
|
||||
impl OpenAICompletionProvider {
|
||||
pub fn new(model_name: &str, executor: Arc<BackgroundExecutor>) -> Self {
|
||||
pub fn new(model_name: &str, executor: BackgroundExecutor) -> Self {
|
||||
let model = OpenAILanguageModel::load(model_name);
|
||||
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
|
||||
Self {
|
||||
|
@ -27,8 +27,8 @@ use editor::{
|
||||
use fs::Fs;
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
actions, div, point, uniform_list, Action, AnyElement, AppContext, AsyncAppContext,
|
||||
ClipboardItem, Div, Element, Entity, EventEmitter, FocusHandle, Focusable, FocusableView,
|
||||
actions, div, point, uniform_list, Action, AnyElement, AppContext, AsyncWindowContext,
|
||||
ClipboardItem, Context, Div, EventEmitter, FocusHandle, Focusable, FocusableView,
|
||||
HighlightStyle, InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels,
|
||||
PromptLevel, Render, StatefulInteractiveElement, Styled, Subscription, Task,
|
||||
UniformListScrollHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WindowContext,
|
||||
@ -51,7 +51,7 @@ use std::{
|
||||
};
|
||||
use ui::{
|
||||
h_stack, v_stack, Button, ButtonCommon, ButtonLike, Clickable, Color, Icon, IconButton,
|
||||
IconElement, Label, Selectable, StyledExt, Tooltip,
|
||||
IconElement, Label, Selectable, Tooltip,
|
||||
};
|
||||
use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
@ -76,49 +76,18 @@ actions!(
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
AssistantSettings::register(cx);
|
||||
cx.add_action(
|
||||
|this: &mut AssistantPanel,
|
||||
_: &workspace::NewFile,
|
||||
cx: &mut ViewContext<AssistantPanel>| {
|
||||
this.new_conversation(cx);
|
||||
cx.observe_new_views(
|
||||
|workspace: &mut Workspace, cx: &mut ViewContext<Workspace>| {
|
||||
workspace
|
||||
.register_action(|workspace, _: &ToggleFocus, cx| {
|
||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||
})
|
||||
.register_action(AssistantPanel::inline_assist)
|
||||
.register_action(AssistantPanel::cancel_last_inline_assist)
|
||||
.register_action(ConversationEditor::quote_selection);
|
||||
},
|
||||
);
|
||||
cx.add_action(ConversationEditor::assist);
|
||||
cx.capture_action(ConversationEditor::cancel_last_assist);
|
||||
cx.capture_action(ConversationEditor::save);
|
||||
cx.add_action(ConversationEditor::quote_selection);
|
||||
cx.capture_action(ConversationEditor::copy);
|
||||
cx.add_action(ConversationEditor::split);
|
||||
cx.capture_action(ConversationEditor::cycle_message_role);
|
||||
cx.add_action(AssistantPanel::save_credentials);
|
||||
cx.add_action(AssistantPanel::reset_credentials);
|
||||
cx.add_action(AssistantPanel::toggle_zoom);
|
||||
cx.add_action(AssistantPanel::deploy);
|
||||
cx.add_action(AssistantPanel::select_next_match);
|
||||
cx.add_action(AssistantPanel::select_prev_match);
|
||||
cx.add_action(AssistantPanel::handle_editor_cancel);
|
||||
cx.add_action(
|
||||
|workspace: &mut Workspace, _: &ToggleFocus, cx: &mut ViewContext<Workspace>| {
|
||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||
},
|
||||
);
|
||||
cx.add_action(AssistantPanel::inline_assist);
|
||||
cx.add_action(AssistantPanel::cancel_last_inline_assist);
|
||||
cx.add_action(InlineAssistant::confirm);
|
||||
cx.add_action(InlineAssistant::cancel);
|
||||
cx.add_action(InlineAssistant::toggle_include_conversation);
|
||||
cx.add_action(InlineAssistant::toggle_retrieve_context);
|
||||
cx.add_action(InlineAssistant::move_up);
|
||||
cx.add_action(InlineAssistant::move_down);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AssistantPanelEvent {
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
Focus,
|
||||
Close,
|
||||
DockPositionChanged,
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
pub struct AssistantPanel {
|
||||
@ -131,7 +100,6 @@ pub struct AssistantPanel {
|
||||
saved_conversations: Vec<SavedConversationMetadata>,
|
||||
saved_conversations_scroll_handle: UniformListScrollHandle,
|
||||
zoomed: bool,
|
||||
// todo!("remove has_focus field")
|
||||
focus_handle: FocusHandle,
|
||||
toolbar: View<Toolbar>,
|
||||
completion_provider: Arc<dyn CompletionProvider>,
|
||||
@ -152,9 +120,12 @@ pub struct AssistantPanel {
|
||||
impl AssistantPanel {
|
||||
const INLINE_PROMPT_HISTORY_MAX_LEN: usize = 20;
|
||||
|
||||
pub fn load(workspace: WeakView<Workspace>, cx: AsyncAppContext) -> Task<Result<View<Self>>> {
|
||||
pub fn load(
|
||||
workspace: WeakView<Workspace>,
|
||||
mut cx: AsyncWindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?;
|
||||
let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
|
||||
let saved_conversations = SavedConversationMetadata::list(fs.clone())
|
||||
.await
|
||||
.log_err()
|
||||
@ -163,7 +134,7 @@ impl AssistantPanel {
|
||||
// TODO: deserialize state.
|
||||
let workspace_handle = workspace.clone();
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
cx.add_view::<Self, _>(|cx| {
|
||||
cx.build_view::<Self>(|cx| {
|
||||
const CONVERSATION_WATCH_DURATION: Duration = Duration::from_millis(100);
|
||||
let _watch_saved_conversations = cx.spawn(move |this, mut cx| async move {
|
||||
let mut events = fs
|
||||
@ -184,10 +155,10 @@ impl AssistantPanel {
|
||||
anyhow::Ok(())
|
||||
});
|
||||
|
||||
let toolbar = cx.add_view(|cx| {
|
||||
let toolbar = cx.build_view(|cx| {
|
||||
let mut toolbar = Toolbar::new();
|
||||
toolbar.set_can_navigate(false, cx);
|
||||
toolbar.add_item(cx.add_view(|cx| BufferSearchBar::new(cx)), cx);
|
||||
toolbar.add_item(cx.build_view(|cx| BufferSearchBar::new(cx)), cx);
|
||||
toolbar
|
||||
});
|
||||
|
||||
@ -199,8 +170,8 @@ impl AssistantPanel {
|
||||
));
|
||||
|
||||
let focus_handle = cx.focus_handle();
|
||||
cx.on_focus_in(Self::focus_in).detach();
|
||||
cx.on_focus_out(Self::focus_out).detach();
|
||||
cx.on_focus_in(&focus_handle, Self::focus_in).detach();
|
||||
cx.on_focus_out(&focus_handle, Self::focus_out).detach();
|
||||
|
||||
let mut this = Self {
|
||||
workspace: workspace_handle,
|
||||
@ -231,11 +202,11 @@ impl AssistantPanel {
|
||||
|
||||
let mut old_dock_position = this.position(cx);
|
||||
this.subscriptions =
|
||||
vec![cx.observe_global::<SettingsStore, _>(move |this, cx| {
|
||||
vec![cx.observe_global::<SettingsStore>(move |this, cx| {
|
||||
let new_dock_position = this.position(cx);
|
||||
if new_dock_position != old_dock_position {
|
||||
old_dock_position = new_dock_position;
|
||||
cx.emit(AssistantPanelEvent::DockPositionChanged);
|
||||
cx.emit(PanelEvent::ChangePosition);
|
||||
}
|
||||
cx.notify();
|
||||
})];
|
||||
@ -343,7 +314,7 @@ impl AssistantPanel {
|
||||
// Retrieve Credentials Authenticates the Provider
|
||||
provider.retrieve_credentials(cx);
|
||||
|
||||
let codegen = cx.add_model(|cx| {
|
||||
let codegen = cx.build_model(|cx| {
|
||||
Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx)
|
||||
});
|
||||
|
||||
@ -353,14 +324,14 @@ impl AssistantPanel {
|
||||
let previously_indexed = semantic_index
|
||||
.update(&mut cx, |index, cx| {
|
||||
index.project_previously_indexed(&project, cx)
|
||||
})
|
||||
})?
|
||||
.await
|
||||
.unwrap_or(false);
|
||||
if previously_indexed {
|
||||
let _ = semantic_index
|
||||
.update(&mut cx, |index, cx| {
|
||||
index.index_project(project.clone(), cx)
|
||||
})
|
||||
})?
|
||||
.await;
|
||||
}
|
||||
anyhow::Ok(())
|
||||
@ -369,7 +340,7 @@ impl AssistantPanel {
|
||||
}
|
||||
|
||||
let measurements = Rc::new(Cell::new(BlockMeasurements::default()));
|
||||
let inline_assistant = cx.add_view(|cx| {
|
||||
let inline_assistant = cx.build_view(|cx| {
|
||||
let assistant = InlineAssistant::new(
|
||||
inline_assist_id,
|
||||
measurements.clone(),
|
||||
@ -382,7 +353,7 @@ impl AssistantPanel {
|
||||
self.semantic_index.clone(),
|
||||
project.clone(),
|
||||
);
|
||||
cx.focus_self();
|
||||
assistant.focus_handle.focus(cx);
|
||||
assistant
|
||||
});
|
||||
let block_id = editor.update(cx, |editor, cx| {
|
||||
@ -429,8 +400,13 @@ impl AssistantPanel {
|
||||
move |_, editor, event, cx| {
|
||||
if let Some(inline_assistant) = inline_assistant.upgrade() {
|
||||
if let EditorEvent::SelectionsChanged { local } = event {
|
||||
if *local && inline_assistant.read(cx).has_focus {
|
||||
cx.focus(&editor);
|
||||
if *local
|
||||
&& inline_assistant
|
||||
.read(cx)
|
||||
.focus_handle
|
||||
.contains_focused(cx)
|
||||
{
|
||||
cx.focus_view(&editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -555,7 +531,7 @@ impl AssistantPanel {
|
||||
}
|
||||
}
|
||||
|
||||
cx.propagate_action();
|
||||
cx.propagate();
|
||||
}
|
||||
|
||||
fn finish_inline_assist(&mut self, assist_id: usize, undo: bool, cx: &mut ViewContext<Self>) {
|
||||
@ -709,13 +685,17 @@ impl AssistantPanel {
|
||||
let snippets = cx.spawn(|_, mut cx| async move {
|
||||
let mut snippets = Vec::new();
|
||||
for result in search_results.await {
|
||||
snippets.push(PromptCodeSnippet::new(result.buffer, result.range, &mut cx));
|
||||
snippets.push(PromptCodeSnippet::new(
|
||||
result.buffer,
|
||||
result.range,
|
||||
&mut cx,
|
||||
)?);
|
||||
}
|
||||
snippets
|
||||
anyhow::Ok(snippets)
|
||||
});
|
||||
snippets
|
||||
} else {
|
||||
Task::ready(Vec::new())
|
||||
Task::ready(Ok(Vec::new()))
|
||||
};
|
||||
|
||||
let mut model = AssistantSettings::get_global(cx)
|
||||
@ -724,7 +704,7 @@ impl AssistantPanel {
|
||||
let model_name = model.full_name();
|
||||
|
||||
let prompt = cx.background_executor().spawn(async move {
|
||||
let snippets = snippets.await;
|
||||
let snippets = snippets.await?;
|
||||
|
||||
let language_name = language_name.as_deref();
|
||||
generate_content_prompt(
|
||||
@ -799,7 +779,7 @@ impl AssistantPanel {
|
||||
} else {
|
||||
editor.highlight_background::<PendingInlineAssist>(
|
||||
background_ranges,
|
||||
|theme| theme.assistant.inline.pending_edit_background,
|
||||
|theme| gpui::red(), // todo!("use the appropriate color")
|
||||
cx,
|
||||
);
|
||||
}
|
||||
@ -820,7 +800,7 @@ impl AssistantPanel {
|
||||
}
|
||||
|
||||
fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> View<ConversationEditor> {
|
||||
let editor = cx.add_view(|cx| {
|
||||
let editor = cx.build_view(|cx| {
|
||||
ConversationEditor::new(
|
||||
self.completion_provider.clone(),
|
||||
self.languages.clone(),
|
||||
@ -854,8 +834,8 @@ impl AssistantPanel {
|
||||
self.toolbar.update(cx, |toolbar, cx| {
|
||||
toolbar.set_active_item(Some(&editor), cx);
|
||||
});
|
||||
if self.has_focus(cx) {
|
||||
cx.focus(&editor);
|
||||
if self.focus_handle.contains_focused(cx) {
|
||||
cx.focus_view(&editor);
|
||||
}
|
||||
} else {
|
||||
self.toolbar.update(cx, |toolbar, cx| {
|
||||
@ -891,31 +871,31 @@ impl AssistantPanel {
|
||||
self.completion_provider.save_credentials(cx, credential);
|
||||
|
||||
self.api_key_editor.take();
|
||||
cx.focus_self();
|
||||
self.focus_handle.focus(cx);
|
||||
cx.notify();
|
||||
}
|
||||
} else {
|
||||
cx.propagate_action();
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
|
||||
fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) {
|
||||
self.completion_provider.delete_credentials(cx);
|
||||
self.api_key_editor = Some(build_api_key_editor(cx));
|
||||
cx.focus_self();
|
||||
self.focus_handle.focus(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext<Self>) {
|
||||
if self.zoomed {
|
||||
cx.emit(AssistantPanelEvent::ZoomOut)
|
||||
cx.emit(PanelEvent::ZoomOut)
|
||||
} else {
|
||||
cx.emit(AssistantPanelEvent::ZoomIn)
|
||||
cx.emit(PanelEvent::ZoomIn)
|
||||
}
|
||||
}
|
||||
|
||||
fn deploy(&mut self, action: &search::buffer_search::Deploy, cx: &mut ViewContext<Self>) {
|
||||
let mut propagate_action = true;
|
||||
let mut propagate = true;
|
||||
if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
|
||||
search_bar.update(cx, |search_bar, cx| {
|
||||
if search_bar.show(cx) {
|
||||
@ -924,12 +904,12 @@ impl AssistantPanel {
|
||||
search_bar.select_query(cx);
|
||||
cx.focus_self();
|
||||
}
|
||||
propagate_action = false
|
||||
propagate = false
|
||||
}
|
||||
});
|
||||
}
|
||||
if propagate_action {
|
||||
cx.propagate_action();
|
||||
if propagate {
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
|
||||
@ -942,7 +922,7 @@ impl AssistantPanel {
|
||||
return;
|
||||
}
|
||||
}
|
||||
cx.propagate_action();
|
||||
cx.propagate();
|
||||
}
|
||||
|
||||
fn select_next_match(&mut self, _: &search::SelectNextMatch, cx: &mut ViewContext<Self>) {
|
||||
@ -976,9 +956,9 @@ impl AssistantPanel {
|
||||
fn render_editor_tools(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement> {
|
||||
if self.active_editor().is_some() {
|
||||
vec![
|
||||
Self::render_split_button(cx).into_any(),
|
||||
Self::render_quote_button(cx).into_any(),
|
||||
Self::render_assist_button(cx).into_any(),
|
||||
Self::render_split_button(cx).into_any_element(),
|
||||
Self::render_quote_button(cx).into_any_element(),
|
||||
Self::render_assist_button(cx).into_any_element(),
|
||||
]
|
||||
} else {
|
||||
Default::default()
|
||||
@ -1028,16 +1008,13 @@ impl AssistantPanel {
|
||||
}
|
||||
|
||||
fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let zoomed = self.zoomed;
|
||||
IconButton::new("zoom_button", Icon::Menu)
|
||||
.on_click(cx.listener(|this, _event, cx| {
|
||||
this.toggle_zoom(&ToggleZoom, cx);
|
||||
}))
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action(
|
||||
if self.zoomed { "Zoom Out" } else { "Zoom In" },
|
||||
&ToggleZoom,
|
||||
cx,
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(if zoomed { "Zoom Out" } else { "Zoom In" }, &ToggleZoom, cx)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1072,16 +1049,16 @@ impl AssistantPanel {
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let saved_conversation = fs.load(&path).await?;
|
||||
let saved_conversation = serde_json::from_str(&saved_conversation)?;
|
||||
let conversation = cx.add_model(|cx| {
|
||||
let conversation = cx.build_model(|cx| {
|
||||
Conversation::deserialize(saved_conversation, path.clone(), languages, cx)
|
||||
});
|
||||
})?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
// If, by the time we've loaded the conversation, the user has already opened
|
||||
// the same conversation, we don't want to open it again.
|
||||
if let Some(ix) = this.editor_index_for_path(&path, cx) {
|
||||
this.set_active_editor_index(Some(ix), cx);
|
||||
} else {
|
||||
let editor = cx.add_view(|cx| {
|
||||
let editor = cx.build_view(|cx| {
|
||||
ConversationEditor::for_conversation(conversation, fs, workspace, cx)
|
||||
});
|
||||
this.add_conversation(editor, cx);
|
||||
@ -1120,6 +1097,7 @@ impl Render for AssistantPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
if let Some(api_key_editor) = self.api_key_editor.clone() {
|
||||
v_stack()
|
||||
.on_action(cx.listener(AssistantPanel::save_credentials))
|
||||
.track_focus(&self.focus_handle)
|
||||
.child(Label::new(
|
||||
"To use the assistant panel or inline assistant, you need to add your OpenAI api key.",
|
||||
@ -1159,6 +1137,15 @@ impl Render for AssistantPanel {
|
||||
}
|
||||
|
||||
v_stack()
|
||||
.on_action(cx.listener(|this, _: &workspace::NewFile, cx| {
|
||||
this.new_conversation(cx);
|
||||
}))
|
||||
.on_action(cx.listener(AssistantPanel::reset_credentials))
|
||||
.on_action(cx.listener(AssistantPanel::toggle_zoom))
|
||||
.on_action(cx.listener(AssistantPanel::deploy))
|
||||
.on_action(cx.listener(AssistantPanel::select_next_match))
|
||||
.on_action(cx.listener(AssistantPanel::select_prev_match))
|
||||
.on_action(cx.listener(AssistantPanel::handle_editor_cancel))
|
||||
.track_focus(&self.focus_handle)
|
||||
.child(header)
|
||||
.children(if self.toolbar.read(cx).hidden() {
|
||||
@ -1175,7 +1162,7 @@ impl Render for AssistantPanel {
|
||||
self.saved_conversations.len(),
|
||||
|this, range, cx| {
|
||||
range
|
||||
.map(|ix| this.render_saved_conversation(ix, cx).into_any())
|
||||
.map(|ix| this.render_saved_conversation(ix, cx))
|
||||
.collect()
|
||||
},
|
||||
)
|
||||
@ -1311,17 +1298,14 @@ impl Conversation {
|
||||
completion_provider: Arc<dyn CompletionProvider>,
|
||||
) -> Self {
|
||||
let markdown = language_registry.language_for_name("Markdown");
|
||||
let buffer = cx.add_model(|cx| {
|
||||
let mut buffer = Buffer::new(0, cx.model_id() as u64, "");
|
||||
let buffer = cx.build_model(|cx| {
|
||||
let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "");
|
||||
buffer.set_language_registry(language_registry);
|
||||
cx.spawn_weak(|buffer, mut cx| async move {
|
||||
cx.spawn(|buffer, mut cx| async move {
|
||||
let markdown = markdown.await?;
|
||||
let buffer = buffer
|
||||
.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("buffer was dropped"))?;
|
||||
buffer.update(&mut cx, |buffer: &mut Buffer, cx| {
|
||||
buffer.set_language(Some(markdown), cx)
|
||||
});
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
@ -1409,8 +1393,8 @@ impl Conversation {
|
||||
let markdown = language_registry.language_for_name("Markdown");
|
||||
let mut message_anchors = Vec::new();
|
||||
let mut next_message_id = MessageId(0);
|
||||
let buffer = cx.add_model(|cx| {
|
||||
let mut buffer = Buffer::new(0, cx.model_id() as u64, saved_conversation.text);
|
||||
let buffer = cx.build_model(|cx| {
|
||||
let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), saved_conversation.text);
|
||||
for message in saved_conversation.messages {
|
||||
message_anchors.push(MessageAnchor {
|
||||
id: message.id,
|
||||
@ -1419,14 +1403,11 @@ impl Conversation {
|
||||
next_message_id = cmp::max(next_message_id, MessageId(message.id.0 + 1));
|
||||
}
|
||||
buffer.set_language_registry(language_registry);
|
||||
cx.spawn_weak(|buffer, mut cx| async move {
|
||||
cx.spawn(|buffer, mut cx| async move {
|
||||
let markdown = markdown.await?;
|
||||
let buffer = buffer
|
||||
.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("buffer was dropped"))?;
|
||||
buffer.update(&mut cx, |buffer: &mut Buffer, cx| {
|
||||
buffer.set_language(Some(markdown), cx)
|
||||
});
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
@ -1497,26 +1478,24 @@ impl Conversation {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let model = self.model.clone();
|
||||
self.pending_token_count = cx.spawn_weak(|this, mut cx| {
|
||||
self.pending_token_count = cx.spawn(|this, mut cx| {
|
||||
async move {
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(200))
|
||||
.await;
|
||||
let token_count = cx
|
||||
.background()
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
tiktoken_rs::num_tokens_from_messages(&model.full_name(), &messages)
|
||||
})
|
||||
.await?;
|
||||
|
||||
this.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("conversation was dropped"))?
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.max_token_count =
|
||||
tiktoken_rs::model::get_context_size(&this.model.full_name());
|
||||
this.token_count = Some(token_count);
|
||||
cx.notify()
|
||||
});
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.max_token_count =
|
||||
tiktoken_rs::model::get_context_size(&this.model.full_name());
|
||||
this.token_count = Some(token_count);
|
||||
cx.notify()
|
||||
})?;
|
||||
anyhow::Ok(())
|
||||
}
|
||||
.log_err()
|
||||
@ -1603,7 +1582,7 @@ impl Conversation {
|
||||
.unwrap();
|
||||
user_messages.push(user_message);
|
||||
|
||||
let task = cx.spawn_weak({
|
||||
let task = cx.spawn({
|
||||
|this, mut cx| async move {
|
||||
let assistant_message_id = assistant_message.id;
|
||||
let stream_completion = async {
|
||||
@ -1612,59 +1591,55 @@ impl Conversation {
|
||||
while let Some(message) = messages.next().await {
|
||||
let text = message?;
|
||||
|
||||
this.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("conversation was dropped"))?
|
||||
.update(&mut cx, |this, cx| {
|
||||
let message_ix = this
|
||||
.message_anchors
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let message_ix = this
|
||||
.message_anchors
|
||||
.iter()
|
||||
.position(|message| message.id == assistant_message_id)?;
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
let offset = this.message_anchors[message_ix + 1..]
|
||||
.iter()
|
||||
.position(|message| message.id == assistant_message_id)?;
|
||||
this.buffer.update(cx, |buffer, cx| {
|
||||
let offset = this.message_anchors[message_ix + 1..]
|
||||
.iter()
|
||||
.find(|message| message.start.is_valid(buffer))
|
||||
.map_or(buffer.len(), |message| {
|
||||
message.start.to_offset(buffer).saturating_sub(1)
|
||||
});
|
||||
buffer.edit([(offset..offset, text)], None, cx);
|
||||
});
|
||||
cx.emit(ConversationEvent::StreamedCompletion);
|
||||
|
||||
Some(())
|
||||
.find(|message| message.start.is_valid(buffer))
|
||||
.map_or(buffer.len(), |message| {
|
||||
message.start.to_offset(buffer).saturating_sub(1)
|
||||
});
|
||||
buffer.edit([(offset..offset, text)], None, cx);
|
||||
});
|
||||
cx.emit(ConversationEvent::StreamedCompletion);
|
||||
|
||||
Some(())
|
||||
})?;
|
||||
smol::future::yield_now().await;
|
||||
}
|
||||
|
||||
this.upgrade(&cx)
|
||||
.ok_or_else(|| anyhow!("conversation was dropped"))?
|
||||
.update(&mut cx, |this, cx| {
|
||||
this.pending_completions
|
||||
.retain(|completion| completion.id != this.completion_count);
|
||||
this.summarize(cx);
|
||||
});
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.pending_completions
|
||||
.retain(|completion| completion.id != this.completion_count);
|
||||
this.summarize(cx);
|
||||
})?;
|
||||
|
||||
anyhow::Ok(())
|
||||
};
|
||||
|
||||
let result = stream_completion.await;
|
||||
if let Some(this) = this.upgrade(&cx) {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(metadata) =
|
||||
this.messages_metadata.get_mut(&assistant_message.id)
|
||||
{
|
||||
match result {
|
||||
Ok(_) => {
|
||||
metadata.status = MessageStatus::Done;
|
||||
}
|
||||
Err(error) => {
|
||||
metadata.status =
|
||||
MessageStatus::Error(error.to_string().trim().into());
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(metadata) =
|
||||
this.messages_metadata.get_mut(&assistant_message.id)
|
||||
{
|
||||
match result {
|
||||
Ok(_) => {
|
||||
metadata.status = MessageStatus::Done;
|
||||
}
|
||||
Err(error) => {
|
||||
metadata.status =
|
||||
MessageStatus::Error(error.to_string().trim().into());
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
});
|
||||
}
|
||||
cx.notify();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
});
|
||||
|
||||
@ -1999,10 +1974,10 @@ impl Conversation {
|
||||
None
|
||||
};
|
||||
(path, summary)
|
||||
});
|
||||
})?;
|
||||
|
||||
if let Some(summary) = summary {
|
||||
let conversation = this.read_with(&cx, |this, cx| this.serialize(cx));
|
||||
let conversation = this.read_with(&cx, |this, cx| this.serialize(cx))?;
|
||||
let path = if let Some(old_path) = old_path {
|
||||
old_path
|
||||
} else {
|
||||
@ -2026,7 +2001,7 @@ impl Conversation {
|
||||
fs.create_dir(CONVERSATIONS_DIR.as_ref()).await?;
|
||||
fs.atomic_write(path.clone(), serde_json::to_string(&conversation).unwrap())
|
||||
.await?;
|
||||
this.update(&mut cx, |this, _| this.path = Some(path));
|
||||
this.update(&mut cx, |this, _| this.path = Some(path))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -2069,7 +2044,7 @@ impl ConversationEditor {
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let conversation =
|
||||
cx.add_model(|cx| Conversation::new(language_registry, cx, completion_provider));
|
||||
cx.build_model(|cx| Conversation::new(language_registry, cx, completion_provider));
|
||||
Self::for_conversation(conversation, fs, workspace, cx)
|
||||
}
|
||||
|
||||
@ -2079,7 +2054,7 @@ impl ConversationEditor {
|
||||
workspace: WeakView<Workspace>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let editor = cx.add_view(|cx| {
|
||||
let editor = cx.build_view(|cx| {
|
||||
let mut editor = Editor::for_buffer(conversation.read(cx).buffer.clone(), None, cx);
|
||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
||||
editor.set_show_gutter(false, cx);
|
||||
@ -2093,7 +2068,7 @@ impl ConversationEditor {
|
||||
cx.observe(&conversation, |_, _, cx| cx.notify()),
|
||||
cx.subscribe(&conversation, Self::handle_conversation_event),
|
||||
cx.subscribe(&editor, Self::handle_editor_event),
|
||||
cx.on_focus(&focus_handle, |this, _, cx| cx.focus(&this.editor)),
|
||||
cx.on_focus(&focus_handle, |this, cx| cx.focus_view(&this.editor)),
|
||||
];
|
||||
|
||||
let mut this = Self {
|
||||
@ -2155,7 +2130,7 @@ impl ConversationEditor {
|
||||
.conversation
|
||||
.update(cx, |conversation, _| conversation.cancel_last_assist())
|
||||
{
|
||||
cx.propagate_action();
|
||||
cx.propagate();
|
||||
}
|
||||
}
|
||||
|
||||
@ -2247,8 +2222,8 @@ impl ConversationEditor {
|
||||
.anchor()
|
||||
.scroll_position(&snapshot.display_snapshot);
|
||||
|
||||
let scroll_bottom = scroll_position.y() + editor.visible_line_count().unwrap_or(0.);
|
||||
if (scroll_position.y()..scroll_bottom).contains(&cursor_row) {
|
||||
let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
|
||||
if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
|
||||
Some(ScrollPosition {
|
||||
cursor,
|
||||
offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
|
||||
@ -2286,7 +2261,7 @@ impl ConversationEditor {
|
||||
})
|
||||
.on_click({
|
||||
let conversation = conversation.clone();
|
||||
move |_, _, cx| {
|
||||
move |_, cx| {
|
||||
conversation.update(cx, |conversation, cx| {
|
||||
conversation.cycle_message_roles(
|
||||
HashSet::from_iter(Some(message_id)),
|
||||
@ -2302,18 +2277,16 @@ impl ConversationEditor {
|
||||
.border_color(gpui::red())
|
||||
.child(sender)
|
||||
.child(Label::new(message.sent_at.format("%I:%M%P").to_string()))
|
||||
.with_children(
|
||||
if let MessageStatus::Error(error) = &message.status {
|
||||
Some(
|
||||
div()
|
||||
.id("error")
|
||||
.tooltip(|cx| Tooltip::text(error, cx))
|
||||
.child(IconElement::new(Icon::XCircle)),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
.children(if let MessageStatus::Error(error) = &message.status {
|
||||
Some(
|
||||
div()
|
||||
.id("error")
|
||||
.tooltip(|cx| Tooltip::text(error, cx))
|
||||
.child(IconElement::new(Icon::XCircle)),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
}),
|
||||
@ -2342,36 +2315,35 @@ impl ConversationEditor {
|
||||
return;
|
||||
};
|
||||
|
||||
let text = editor.read_with(cx, |editor, cx| {
|
||||
let range = editor.selections.newest::<usize>(cx).range();
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let start_language = buffer.language_at(range.start);
|
||||
let end_language = buffer.language_at(range.end);
|
||||
let language_name = if start_language == end_language {
|
||||
start_language.map(|language| language.name())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let language_name = language_name.as_deref().unwrap_or("").to_lowercase();
|
||||
let editor = editor.read(cx);
|
||||
let range = editor.selections.newest::<usize>(cx).range();
|
||||
let buffer = editor.buffer().read(cx).snapshot(cx);
|
||||
let start_language = buffer.language_at(range.start);
|
||||
let end_language = buffer.language_at(range.end);
|
||||
let language_name = if start_language == end_language {
|
||||
start_language.map(|language| language.name())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let language_name = language_name.as_deref().unwrap_or("").to_lowercase();
|
||||
|
||||
let selected_text = buffer.text_for_range(range).collect::<String>();
|
||||
if selected_text.is_empty() {
|
||||
None
|
||||
let selected_text = buffer.text_for_range(range).collect::<String>();
|
||||
let text = if selected_text.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(if language_name == "markdown" {
|
||||
selected_text
|
||||
.lines()
|
||||
.map(|line| format!("> {}", line))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
} else {
|
||||
Some(if language_name == "markdown" {
|
||||
selected_text
|
||||
.lines()
|
||||
.map(|line| format!("> {}", line))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
} else {
|
||||
format!("```{language_name}\n{selected_text}\n```")
|
||||
})
|
||||
}
|
||||
});
|
||||
format!("```{language_name}\n{selected_text}\n```")
|
||||
})
|
||||
};
|
||||
|
||||
// Activate the panel
|
||||
if !panel.read(cx).has_focus(cx) {
|
||||
if !panel.focus_handle(cx).contains_focused(cx) {
|
||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||
}
|
||||
|
||||
@ -2415,13 +2387,12 @@ impl ConversationEditor {
|
||||
}
|
||||
|
||||
if spanned_messages > 1 {
|
||||
cx.platform()
|
||||
.write_to_clipboard(ClipboardItem::new(copied_text));
|
||||
cx.write_to_clipboard(ClipboardItem::new(copied_text));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cx.propagate_action();
|
||||
cx.propagate();
|
||||
}
|
||||
|
||||
fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
|
||||
@ -2492,15 +2463,30 @@ impl Render for ConversationEditor {
|
||||
type Element = Div;
|
||||
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
div().relative().child(self.editor.clone()).child(
|
||||
h_stack()
|
||||
.absolute()
|
||||
.gap_1()
|
||||
.top_3()
|
||||
.right_5()
|
||||
.child(self.render_current_model(cx))
|
||||
.children(self.render_remaining_tokens(cx)),
|
||||
)
|
||||
div()
|
||||
.relative()
|
||||
.capture_action(cx.listener(ConversationEditor::cancel_last_assist))
|
||||
.capture_action(cx.listener(ConversationEditor::save))
|
||||
.capture_action(cx.listener(ConversationEditor::copy))
|
||||
.capture_action(cx.listener(ConversationEditor::cycle_message_role))
|
||||
.on_action(cx.listener(ConversationEditor::assist))
|
||||
.on_action(cx.listener(ConversationEditor::split))
|
||||
.child(self.editor.clone())
|
||||
.child(
|
||||
h_stack()
|
||||
.absolute()
|
||||
.gap_1()
|
||||
.top_3()
|
||||
.right_5()
|
||||
.child(self.render_current_model(cx))
|
||||
.children(self.render_remaining_tokens(cx)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for ConversationEditor {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@ -2577,30 +2563,40 @@ impl Render for InlineAssistant {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
|
||||
let measurements = self.measurements.get();
|
||||
h_stack()
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::toggle_include_conversation))
|
||||
.on_action(cx.listener(Self::toggle_retrieve_context))
|
||||
.on_action(cx.listener(Self::move_up))
|
||||
.on_action(cx.listener(Self::move_down))
|
||||
.child(
|
||||
h_stack()
|
||||
.justify_center()
|
||||
.w(measurements.gutter_width)
|
||||
.child(
|
||||
IconButton::new("include_conversation", Icon::Ai)
|
||||
.action(ToggleIncludeConversation)
|
||||
.action(Box::new(ToggleIncludeConversation))
|
||||
.selected(self.include_conversation)
|
||||
.tooltip(Tooltip::for_action(
|
||||
"Include Conversation",
|
||||
&ToggleIncludeConversation,
|
||||
cx,
|
||||
)),
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action(
|
||||
"Include Conversation",
|
||||
&ToggleIncludeConversation,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.children(if SemanticIndex::enabled(cx) {
|
||||
Some(
|
||||
IconButton::new("retrieve_context", Icon::MagnifyingGlass)
|
||||
.action(ToggleRetrieveContext)
|
||||
.action(Box::new(ToggleRetrieveContext))
|
||||
.selected(self.retrieve_context)
|
||||
.tooltip(Tooltip::for_action(
|
||||
"Retrieve Context",
|
||||
&ToggleRetrieveContext,
|
||||
cx,
|
||||
)),
|
||||
.tooltip(|cx| {
|
||||
Tooltip::for_action(
|
||||
"Retrieve Context",
|
||||
&ToggleRetrieveContext,
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
@ -2629,6 +2625,12 @@ impl Render for InlineAssistant {
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for InlineAssistant {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl InlineAssistant {
|
||||
fn new(
|
||||
id: usize,
|
||||
@ -2656,10 +2658,7 @@ impl InlineAssistant {
|
||||
let mut subscriptions = vec![
|
||||
cx.observe(&codegen, Self::handle_codegen_changed),
|
||||
cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events),
|
||||
cx.on_focus(
|
||||
&focus_handle,
|
||||
cx.listener(|this, _, cx| cx.focus(&this.prompt_editor)),
|
||||
),
|
||||
cx.on_focus(&focus_handle, |this, cx| cx.focus_view(&this.prompt_editor)),
|
||||
];
|
||||
|
||||
if let Some(semantic_index) = semantic_index.clone() {
|
||||
@ -2939,42 +2938,17 @@ impl InlineAssistant {
|
||||
div()
|
||||
.id("update")
|
||||
.tooltip(|cx| Tooltip::text(status_text, cx))
|
||||
.child(IconElement::new(Icon::Update).color(color))
|
||||
.child(IconElement::new(Icon::Update).color(Color::Info))
|
||||
.into_any_element()
|
||||
Svg::new("icons/update.svg")
|
||||
.with_color(theme.assistant.inline.context_status.in_progress_icon.color)
|
||||
.constrained()
|
||||
.with_width(theme.assistant.inline.context_status.in_progress_icon.width)
|
||||
.contained()
|
||||
.with_style(theme.assistant.inline.context_status.in_progress_icon.container)
|
||||
.with_tooltip::<ContextStatusIcon>(
|
||||
self.id,
|
||||
status_text,
|
||||
None,
|
||||
theme.tooltip.clone(),
|
||||
cx,
|
||||
)
|
||||
.aligned()
|
||||
.into_any(),
|
||||
)
|
||||
}
|
||||
|
||||
SemanticIndexStatus::Indexed {} => Some(
|
||||
Svg::new("icons/check.svg")
|
||||
.with_color(theme.assistant.inline.context_status.complete_icon.color)
|
||||
.constrained()
|
||||
.with_width(theme.assistant.inline.context_status.complete_icon.width)
|
||||
.contained()
|
||||
.with_style(theme.assistant.inline.context_status.complete_icon.container)
|
||||
.with_tooltip::<ContextStatusIcon>(
|
||||
self.id,
|
||||
"Index up to date",
|
||||
None,
|
||||
theme.tooltip.clone(),
|
||||
cx,
|
||||
)
|
||||
.aligned()
|
||||
.into_any(),
|
||||
div()
|
||||
.id("check")
|
||||
.tooltip(|cx| Tooltip::text("Index up to date", cx))
|
||||
.child(IconElement::new(Icon::Check).color(Color::Success))
|
||||
.into_any_element()
|
||||
),
|
||||
}
|
||||
} else {
|
||||
@ -3083,7 +3057,8 @@ mod tests {
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
|
||||
let completion_provider = Arc::new(FakeCompletionProvider::new());
|
||||
let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider));
|
||||
let conversation =
|
||||
cx.build_model(|cx| Conversation::new(registry, cx, completion_provider));
|
||||
let buffer = conversation.read(cx).buffer.clone();
|
||||
|
||||
let message_1 = conversation.read(cx).message_anchors[0].clone();
|
||||
@ -3213,7 +3188,8 @@ mod tests {
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
let completion_provider = Arc::new(FakeCompletionProvider::new());
|
||||
|
||||
let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider));
|
||||
let conversation =
|
||||
cx.build_model(|cx| Conversation::new(registry, cx, completion_provider));
|
||||
let buffer = conversation.read(cx).buffer.clone();
|
||||
|
||||
let message_1 = conversation.read(cx).message_anchors[0].clone();
|
||||
@ -3310,7 +3286,8 @@ mod tests {
|
||||
init(cx);
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
let completion_provider = Arc::new(FakeCompletionProvider::new());
|
||||
let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider));
|
||||
let conversation =
|
||||
cx.build_model(|cx| Conversation::new(registry, cx, completion_provider));
|
||||
let buffer = conversation.read(cx).buffer.clone();
|
||||
|
||||
let message_1 = conversation.read(cx).message_anchors[0].clone();
|
||||
@ -3394,7 +3371,7 @@ mod tests {
|
||||
let registry = Arc::new(LanguageRegistry::test());
|
||||
let completion_provider = Arc::new(FakeCompletionProvider::new());
|
||||
let conversation =
|
||||
cx.add_model(|cx| Conversation::new(registry.clone(), cx, completion_provider));
|
||||
cx.build_model(|cx| Conversation::new(registry.clone(), cx, completion_provider));
|
||||
let buffer = conversation.read(cx).buffer.clone();
|
||||
let message_0 = conversation.read(cx).message_anchors[0].id;
|
||||
let message_1 = conversation.update(cx, |conversation, cx| {
|
||||
@ -3427,7 +3404,7 @@ mod tests {
|
||||
]
|
||||
);
|
||||
|
||||
let deserialized_conversation = cx.add_model(|cx| {
|
||||
let deserialized_conversation = cx.build_model(|cx| {
|
||||
Conversation::deserialize(
|
||||
conversation.read(cx).serialize(cx),
|
||||
Default::default(),
|
||||
|
@ -1961,14 +1961,14 @@ impl Editor {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
// pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
|
||||
// self.cursor_shape = cursor_shape;
|
||||
// cx.notify();
|
||||
// }
|
||||
pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
|
||||
self.cursor_shape = cursor_shape;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
// pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
|
||||
// self.collapse_matches = collapse_matches;
|
||||
// }
|
||||
pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
|
||||
self.collapse_matches = collapse_matches;
|
||||
}
|
||||
|
||||
pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
|
||||
if self.collapse_matches {
|
||||
@ -1977,56 +1977,47 @@ impl Editor {
|
||||
range.clone()
|
||||
}
|
||||
|
||||
// pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
|
||||
// if self.display_map.read(cx).clip_at_line_ends != clip {
|
||||
// self.display_map
|
||||
// .update(cx, |map, _| map.clip_at_line_ends = clip);
|
||||
// }
|
||||
// }
|
||||
pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
|
||||
if self.display_map.read(cx).clip_at_line_ends != clip {
|
||||
self.display_map
|
||||
.update(cx, |map, _| map.clip_at_line_ends = clip);
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn set_keymap_context_layer<Tag: 'static>(
|
||||
// &mut self,
|
||||
// context: KeymapContext,
|
||||
// cx: &mut ViewContext<Self>,
|
||||
// ) {
|
||||
// self.keymap_context_layers
|
||||
// .insert(TypeId::of::<Tag>(), context);
|
||||
// cx.notify();
|
||||
// }
|
||||
pub fn set_keymap_context_layer<Tag: 'static>(
|
||||
&mut self,
|
||||
context: KeyContext,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
self.keymap_context_layers
|
||||
.insert(TypeId::of::<Tag>(), context);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
// pub fn remove_keymap_context_layer<Tag: 'static>(&mut self, cx: &mut ViewContext<Self>) {
|
||||
// self.keymap_context_layers.remove(&TypeId::of::<Tag>());
|
||||
// cx.notify();
|
||||
// }
|
||||
pub fn remove_keymap_context_layer<Tag: 'static>(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.keymap_context_layers.remove(&TypeId::of::<Tag>());
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
// pub fn set_input_enabled(&mut self, input_enabled: bool) {
|
||||
// self.input_enabled = input_enabled;
|
||||
// }
|
||||
pub fn set_input_enabled(&mut self, input_enabled: bool) {
|
||||
self.input_enabled = input_enabled;
|
||||
}
|
||||
|
||||
// pub fn set_autoindent(&mut self, autoindent: bool) {
|
||||
// if autoindent {
|
||||
// self.autoindent_mode = Some(AutoindentMode::EachLine);
|
||||
// } else {
|
||||
// self.autoindent_mode = None;
|
||||
// }
|
||||
// }
|
||||
pub fn set_autoindent(&mut self, autoindent: bool) {
|
||||
if autoindent {
|
||||
self.autoindent_mode = Some(AutoindentMode::EachLine);
|
||||
} else {
|
||||
self.autoindent_mode = None;
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn read_only(&self) -> bool {
|
||||
// self.read_only
|
||||
// }
|
||||
pub fn read_only(&self) -> bool {
|
||||
self.read_only
|
||||
}
|
||||
|
||||
// pub fn set_read_only(&mut self, read_only: bool) {
|
||||
// self.read_only = read_only;
|
||||
// }
|
||||
|
||||
// pub fn set_field_editor_style(
|
||||
// &mut self,
|
||||
// style: Option<Arc<GetFieldEditorTheme>>,
|
||||
// cx: &mut ViewContext<Self>,
|
||||
// ) {
|
||||
// self.get_field_editor_theme = style;
|
||||
// cx.notify();
|
||||
// }
|
||||
pub fn set_read_only(&mut self, read_only: bool) {
|
||||
self.read_only = read_only;
|
||||
}
|
||||
|
||||
fn selections_did_change(
|
||||
&mut self,
|
||||
|
@ -2816,3 +2816,9 @@ impl From<(&'static str, EntityId)> for ElementId {
|
||||
ElementId::NamedInteger(name.into(), id.as_u64() as usize)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&'static str, usize)> for ElementId {
|
||||
fn from((name, id): (&'static str, usize)) -> Self {
|
||||
ElementId::NamedInteger(name.into(), id)
|
||||
}
|
||||
}
|
||||
|
@ -44,12 +44,18 @@ impl<'a, T: ?Sized> From<&'a T> for ArcCow<'a, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Arc<T>> for ArcCow<'_, T> {
|
||||
impl<T: ?Sized> From<Arc<T>> for ArcCow<'_, T> {
|
||||
fn from(s: Arc<T>) -> Self {
|
||||
Self::Owned(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> From<&'_ Arc<T>> for ArcCow<'_, T> {
|
||||
fn from(s: &'_ Arc<T>) -> Self {
|
||||
Self::Owned(s.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for ArcCow<'_, str> {
|
||||
fn from(value: String) -> Self {
|
||||
Self::Owned(value.into())
|
||||
|
Loading…
Reference in New Issue
Block a user