Detailed changes
@@ -1,5 +1,5 @@
use crate::prompts::{generate_content_prompt, PromptLibrary, PromptManager};
-use crate::slash_command::search_command;
+use crate::slash_command::{search_command, tabs_command};
use crate::{
assistant_settings::{AssistantDockPosition, AssistantSettings, ZedDotDevModel},
codegen::{self, Codegen, CodegenKind},
@@ -210,6 +210,7 @@ impl AssistantPanel {
prompt_command::PromptSlashCommand::new(prompt_library.clone()),
);
slash_command_registry.register_command(active_command::ActiveSlashCommand);
+ slash_command_registry.register_command(tabs_command::TabsSlashCommand);
slash_command_registry.register_command(project_command::ProjectSlashCommand);
slash_command_registry.register_command(search_command::SearchSlashCommand);
@@ -1883,15 +1884,15 @@ impl Conversation {
async move {
let output = output.await;
this.update(&mut cx, |this, cx| match output {
- Ok(output) => {
+ Ok(mut output) => {
+ if !output.text.ends_with('\n') {
+ output.text.push('\n');
+ }
+
let sections = this.buffer.update(cx, |buffer, cx| {
let start = command_range.start.to_offset(buffer);
let old_end = command_range.end.to_offset(buffer);
- let new_end = start + output.text.len();
buffer.edit([(start..old_end, output.text)], None, cx);
- if buffer.chars_at(new_end).next() != Some('\n') {
- buffer.edit([(new_end..new_end, "\n")], None, cx);
- }
let mut sections = output
.sections
@@ -21,6 +21,7 @@ pub mod file_command;
pub mod project_command;
pub mod prompt_command;
pub mod search_command;
+pub mod tabs_command;
pub(crate) struct SlashCommandCompletionProvider {
editor: WeakView<ConversationEditor>,
@@ -1,9 +1,8 @@
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
use assistant_slash_command::SlashCommandOutputSection;
-use collections::HashMap;
use editor::Editor;
-use gpui::{AppContext, Entity, Task, WeakView};
+use gpui::{AppContext, Task, WeakView};
use language::LspAdapterDelegate;
use std::{borrow::Cow, sync::Arc};
use ui::{IntoElement, WindowContext};
@@ -45,79 +44,59 @@ impl SlashCommand for ActiveSlashCommand {
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
let output = workspace.update(cx, |workspace, cx| {
- let mut timestamps_by_entity_id = HashMap::default();
- for pane in workspace.panes() {
- let pane = pane.read(cx);
- for entry in pane.activation_history() {
- timestamps_by_entity_id.insert(entry.entity_id, entry.timestamp);
- }
- }
-
- let mut most_recent_buffer = None;
- for editor in workspace.items_of_type::<Editor>(cx) {
- let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() else {
- continue;
- };
-
- let timestamp = timestamps_by_entity_id
- .get(&editor.entity_id())
- .copied()
- .unwrap_or_default();
- if most_recent_buffer
- .as_ref()
- .map_or(true, |(_, prev_timestamp)| timestamp > *prev_timestamp)
- {
- most_recent_buffer = Some((buffer, timestamp));
- }
- }
+ let Some(active_item) = workspace.active_item(cx) else {
+ return Task::ready(Err(anyhow!("no active tab")));
+ };
+ let Some(buffer) = active_item
+ .downcast::<Editor>()
+ .and_then(|editor| editor.read(cx).buffer().read(cx).as_singleton())
+ else {
+ return Task::ready(Err(anyhow!("active tab is not an editor")));
+ };
- if let Some((buffer, _)) = most_recent_buffer {
- let snapshot = buffer.read(cx).snapshot();
- let path = snapshot.resolve_file_path(cx, true);
- let text = cx.background_executor().spawn({
- let path = path.clone();
- async move {
- let path = path
- .as_ref()
- .map(|path| path.to_string_lossy())
- .unwrap_or_else(|| Cow::Borrowed("untitled"));
+ let snapshot = buffer.read(cx).snapshot();
+ let path = snapshot.resolve_file_path(cx, true);
+ let text = cx.background_executor().spawn({
+ let path = path.clone();
+ async move {
+ let path = path
+ .as_ref()
+ .map(|path| path.to_string_lossy())
+ .unwrap_or_else(|| Cow::Borrowed("untitled"));
- let mut output = String::with_capacity(path.len() + snapshot.len() + 9);
- output.push_str("```");
- output.push_str(&path);
+ let mut output = String::with_capacity(path.len() + snapshot.len() + 9);
+ output.push_str("```");
+ output.push_str(&path);
+ output.push('\n');
+ for chunk in snapshot.as_rope().chunks() {
+ output.push_str(chunk);
+ }
+ if !output.ends_with('\n') {
output.push('\n');
- for chunk in snapshot.as_rope().chunks() {
- output.push_str(chunk);
- }
- if !output.ends_with('\n') {
- output.push('\n');
- }
- output.push_str("```");
- output
}
- });
- cx.foreground_executor().spawn(async move {
- let text = text.await;
- let range = 0..text.len();
- Ok(SlashCommandOutput {
- text,
- sections: vec![SlashCommandOutputSection {
- range,
- render_placeholder: Arc::new(move |id, unfold, _| {
- FilePlaceholder {
- id,
- path: path.clone(),
- line_range: None,
- unfold,
- }
- .into_any_element()
- }),
- }],
- })
+ output.push_str("```");
+ output
+ }
+ });
+ cx.foreground_executor().spawn(async move {
+ let text = text.await;
+ let range = 0..text.len();
+ Ok(SlashCommandOutput {
+ text,
+ sections: vec![SlashCommandOutputSection {
+ range,
+ render_placeholder: Arc::new(move |id, unfold, _| {
+ FilePlaceholder {
+ id,
+ path: path.clone(),
+ line_range: None,
+ unfold,
+ }
+ .into_any_element()
+ }),
+ }],
})
- } else {
- Task::ready(Err(anyhow!("no recent buffer found")))
- }
+ })
});
output.unwrap_or_else(|error| Task::ready(Err(error)))
}
@@ -0,0 +1,116 @@
+use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
+use anyhow::{anyhow, Result};
+use assistant_slash_command::SlashCommandOutputSection;
+use collections::HashMap;
+use editor::Editor;
+use gpui::{AppContext, Entity, Task, WeakView};
+use language::LspAdapterDelegate;
+use std::{fmt::Write, path::Path, sync::Arc};
+use ui::{IntoElement, WindowContext};
+use workspace::Workspace;
+
+pub(crate) struct TabsSlashCommand;
+
+impl SlashCommand for TabsSlashCommand {
+ fn name(&self) -> String {
+ "tabs".into()
+ }
+
+ fn description(&self) -> String {
+ "insert content from open tabs".into()
+ }
+
+ fn tooltip_text(&self) -> String {
+ "insert open tabs".into()
+ }
+
+ fn requires_argument(&self) -> bool {
+ false
+ }
+
+ fn complete_argument(
+ &self,
+ _query: String,
+ _cancel: Arc<std::sync::atomic::AtomicBool>,
+ _cx: &mut AppContext,
+ ) -> Task<Result<Vec<String>>> {
+ Task::ready(Err(anyhow!("this command does not require argument")))
+ }
+
+ fn run(
+ self: Arc<Self>,
+ _argument: Option<&str>,
+ workspace: WeakView<Workspace>,
+ _delegate: Arc<dyn LspAdapterDelegate>,
+ cx: &mut WindowContext,
+ ) -> Task<Result<SlashCommandOutput>> {
+ let open_buffers = workspace.update(cx, |workspace, cx| {
+ let mut timestamps_by_entity_id = HashMap::default();
+ let mut open_buffers = Vec::new();
+
+ for pane in workspace.panes() {
+ let pane = pane.read(cx);
+ for entry in pane.activation_history() {
+ timestamps_by_entity_id.insert(entry.entity_id, entry.timestamp);
+ }
+ }
+
+ for editor in workspace.items_of_type::<Editor>(cx) {
+ if let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() {
+ if let Some(timestamp) = timestamps_by_entity_id.get(&editor.entity_id()) {
+ let snapshot = buffer.read(cx).snapshot();
+ let full_path = snapshot.resolve_file_path(cx, true);
+ open_buffers.push((full_path, snapshot, *timestamp));
+ }
+ }
+ }
+
+ open_buffers
+ });
+
+ match open_buffers {
+ Ok(mut open_buffers) => cx.background_executor().spawn(async move {
+ open_buffers.sort_by_key(|(_, _, timestamp)| *timestamp);
+
+ let mut sections = Vec::new();
+ let mut text = String::new();
+ for (full_path, buffer, _) in open_buffers {
+ let section_start_ix = text.len();
+ writeln!(
+ text,
+ "```{}\n",
+ full_path
+ .as_deref()
+ .unwrap_or(Path::new("untitled"))
+ .display()
+ )
+ .unwrap();
+ for chunk in buffer.as_rope().chunks() {
+ text.push_str(chunk);
+ }
+ if !text.ends_with('\n') {
+ text.push('\n');
+ }
+ writeln!(text, "```\n").unwrap();
+ let section_end_ix = text.len() - 1;
+
+ sections.push(SlashCommandOutputSection {
+ range: section_start_ix..section_end_ix,
+ render_placeholder: Arc::new(move |id, unfold, _| {
+ FilePlaceholder {
+ id,
+ path: full_path.clone(),
+ line_range: None,
+ unfold,
+ }
+ .into_any_element()
+ }),
+ });
+ }
+
+ Ok(SlashCommandOutput { text, sections })
+ }),
+ Err(error) => Task::ready(Err(error)),
+ }
+ }
+}