Update placeholder text with key bindings to focus context panel and navigate history (#19447)

Nathan Sobo and Richard Feldman created

Hopefully, this will help people understand how easy it is to add
context to an inline transformation.

![CleanShot 2024-10-18 at 22 41
00@2x](https://github.com/user-attachments/assets/c09c1d89-3df2-4079-9849-9de7ac63c003)

@as-cii @maxdeviant @rtfeldman could somebody update this to display the
actual correct key bindings and ship it. I have them hard coded for now.

Release Notes:

- Updated placeholder text with key bindings to focus context panel and
navigate history.

---------

Co-authored-by: Richard Feldman <oss@rtfeldman.com>

Change summary

crates/assistant/src/inline_assistant.rs | 23 ++++++++++++++++++++---
crates/assistant/src/prompts.rs          |  2 +-
crates/ui/src/components/keybinding.rs   |  2 +-
3 files changed, 22 insertions(+), 5 deletions(-)

Detailed changes

crates/assistant/src/inline_assistant.rs 🔗

@@ -54,7 +54,7 @@ use telemetry_events::{AssistantEvent, AssistantKind, AssistantPhase};
 use terminal_view::terminal_panel::TerminalPanel;
 use text::{OffsetRangeExt, ToPoint as _};
 use theme::ThemeSettings;
-use ui::{prelude::*, CheckboxWithLabel, IconButtonShape, Popover, Tooltip};
+use ui::{prelude::*, text_for_action, CheckboxWithLabel, IconButtonShape, Popover, Tooltip};
 use util::{RangeExt, ResultExt};
 use workspace::{notifications::NotificationId, ItemHandle, Toast, Workspace};
 
@@ -1596,7 +1596,7 @@ impl PromptEditor {
             // always show the cursor (even when it isn't focused) because
             // typing in one will make what you typed appear in all of them.
             editor.set_show_cursor_when_unfocused(true, cx);
-            editor.set_placeholder_text("Add a prompt…", cx);
+            editor.set_placeholder_text(Self::placeholder_text(codegen.read(cx), cx), cx);
             editor
         });
 
@@ -1653,6 +1653,7 @@ impl PromptEditor {
         self.editor = cx.new_view(|cx| {
             let mut editor = Editor::auto_height(Self::MAX_LINES as usize, cx);
             editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
+            editor.set_placeholder_text(Self::placeholder_text(self.codegen.read(cx), cx), cx);
             editor.set_placeholder_text("Add a prompt…", cx);
             editor.set_text(prompt, cx);
             if focus {
@@ -1663,6 +1664,20 @@ impl PromptEditor {
         self.subscribe_to_editor(cx);
     }
 
+    fn placeholder_text(codegen: &Codegen, cx: &WindowContext) -> String {
+        let context_keybinding = text_for_action(&crate::ToggleFocus, cx)
+            .map(|keybinding| format!(" • {keybinding} for context"))
+            .unwrap_or_default();
+
+        let action = if codegen.is_insertion {
+            "Generate"
+        } else {
+            "Transform"
+        };
+
+        format!("{action}…{context_keybinding} • ↓↑ for history")
+    }
+
     fn prompt(&self, cx: &AppContext) -> String {
         self.editor.read(cx).text(cx)
     }
@@ -2260,6 +2275,7 @@ pub struct Codegen {
     initial_transaction_id: Option<TransactionId>,
     telemetry: Option<Arc<Telemetry>>,
     builder: Arc<PromptBuilder>,
+    is_insertion: bool,
 }
 
 impl Codegen {
@@ -2282,6 +2298,7 @@ impl Codegen {
             )
         });
         let mut this = Self {
+            is_insertion: range.to_offset(&buffer.read(cx).snapshot(cx)).is_empty(),
             alternatives: vec![codegen],
             active_alternative: 0,
             seen_alternatives: HashSet::default(),
@@ -2683,7 +2700,7 @@ impl CodegenAlternative {
 
         let prompt = self
             .builder
-            .generate_content_prompt(user_prompt, language_name, buffer, range)
+            .generate_inline_transformation_prompt(user_prompt, language_name, buffer, range)
             .map_err(|e| anyhow::anyhow!("Failed to generate content prompt: {}", e))?;
 
         let mut messages = Vec::new();

crates/assistant/src/prompts.rs 🔗

@@ -204,7 +204,7 @@ impl PromptBuilder {
         Ok(())
     }
 
-    pub fn generate_content_prompt(
+    pub fn generate_inline_transformation_prompt(
         &self,
         user_prompt: String,
         language_name: Option<&LanguageName>,

crates/ui/src/components/keybinding.rs 🔗

@@ -196,7 +196,7 @@ impl KeyIcon {
 }
 
 /// Returns a textual representation of the key binding for the given [`Action`].
-pub fn text_for_action(action: &dyn Action, cx: &mut WindowContext) -> Option<String> {
+pub fn text_for_action(action: &dyn Action, cx: &WindowContext) -> Option<String> {
     let key_binding = cx.bindings_for_action(action).last().cloned()?;
     Some(text_for_key_binding(key_binding, PlatformStyle::platform()))
 }