mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-19 18:41:56 +03:00
Introduce a new /tabs
command (#12382)
This inserts the content of the open tabs sorted by recency. Release Notes: - N/A --------- Co-authored-by: Nathan <nathan@zed.dev>
This commit is contained in:
parent
b466a8b828
commit
371abd37f6
@ -1,5 +1,5 @@
|
|||||||
use crate::prompts::{generate_content_prompt, PromptLibrary, PromptManager};
|
use crate::prompts::{generate_content_prompt, PromptLibrary, PromptManager};
|
||||||
use crate::slash_command::search_command;
|
use crate::slash_command::{search_command, tabs_command};
|
||||||
use crate::{
|
use crate::{
|
||||||
assistant_settings::{AssistantDockPosition, AssistantSettings, ZedDotDevModel},
|
assistant_settings::{AssistantDockPosition, AssistantSettings, ZedDotDevModel},
|
||||||
codegen::{self, Codegen, CodegenKind},
|
codegen::{self, Codegen, CodegenKind},
|
||||||
@ -210,6 +210,7 @@ impl AssistantPanel {
|
|||||||
prompt_command::PromptSlashCommand::new(prompt_library.clone()),
|
prompt_command::PromptSlashCommand::new(prompt_library.clone()),
|
||||||
);
|
);
|
||||||
slash_command_registry.register_command(active_command::ActiveSlashCommand);
|
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(project_command::ProjectSlashCommand);
|
||||||
slash_command_registry.register_command(search_command::SearchSlashCommand);
|
slash_command_registry.register_command(search_command::SearchSlashCommand);
|
||||||
|
|
||||||
@ -1883,15 +1884,15 @@ impl Conversation {
|
|||||||
async move {
|
async move {
|
||||||
let output = output.await;
|
let output = output.await;
|
||||||
this.update(&mut cx, |this, cx| match output {
|
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 sections = this.buffer.update(cx, |buffer, cx| {
|
||||||
let start = command_range.start.to_offset(buffer);
|
let start = command_range.start.to_offset(buffer);
|
||||||
let old_end = command_range.end.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);
|
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
|
let mut sections = output
|
||||||
.sections
|
.sections
|
||||||
|
@ -21,6 +21,7 @@ pub mod file_command;
|
|||||||
pub mod project_command;
|
pub mod project_command;
|
||||||
pub mod prompt_command;
|
pub mod prompt_command;
|
||||||
pub mod search_command;
|
pub mod search_command;
|
||||||
|
pub mod tabs_command;
|
||||||
|
|
||||||
pub(crate) struct SlashCommandCompletionProvider {
|
pub(crate) struct SlashCommandCompletionProvider {
|
||||||
editor: WeakView<ConversationEditor>,
|
editor: WeakView<ConversationEditor>,
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
|
use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
use collections::HashMap;
|
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{AppContext, Entity, Task, WeakView};
|
use gpui::{AppContext, Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{borrow::Cow, sync::Arc};
|
||||||
use ui::{IntoElement, WindowContext};
|
use ui::{IntoElement, WindowContext};
|
||||||
@ -45,33 +44,16 @@ impl SlashCommand for ActiveSlashCommand {
|
|||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> Task<Result<SlashCommandOutput>> {
|
) -> Task<Result<SlashCommandOutput>> {
|
||||||
let output = workspace.update(cx, |workspace, cx| {
|
let output = workspace.update(cx, |workspace, cx| {
|
||||||
let mut timestamps_by_entity_id = HashMap::default();
|
let Some(active_item) = workspace.active_item(cx) else {
|
||||||
for pane in workspace.panes() {
|
return Task::ready(Err(anyhow!("no active tab")));
|
||||||
let pane = pane.read(cx);
|
};
|
||||||
for entry in pane.activation_history() {
|
let Some(buffer) = active_item
|
||||||
timestamps_by_entity_id.insert(entry.entity_id, entry.timestamp);
|
.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")));
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((buffer, _)) = most_recent_buffer {
|
|
||||||
let snapshot = buffer.read(cx).snapshot();
|
let snapshot = buffer.read(cx).snapshot();
|
||||||
let path = snapshot.resolve_file_path(cx, true);
|
let path = snapshot.resolve_file_path(cx, true);
|
||||||
let text = cx.background_executor().spawn({
|
let text = cx.background_executor().spawn({
|
||||||
@ -115,9 +97,6 @@ impl SlashCommand for ActiveSlashCommand {
|
|||||||
}],
|
}],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
Task::ready(Err(anyhow!("no recent buffer found")))
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
output.unwrap_or_else(|error| Task::ready(Err(error)))
|
output.unwrap_or_else(|error| Task::ready(Err(error)))
|
||||||
}
|
}
|
||||||
|
116
crates/assistant/src/slash_command/tabs_command.rs
Normal file
116
crates/assistant/src/slash_command/tabs_command.rs
Normal file
@ -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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user