mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-18 18:08:07 +03:00
assistant: Add term
slash command (#13162)
This adds a `term` slash command to the assistant which allows to inject the latest terminal output into the context. Release Notes: - N/A
This commit is contained in:
parent
710c387395
commit
0150192e26
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -405,6 +405,7 @@ dependencies = [
|
|||||||
"strsim 0.11.1",
|
"strsim 0.11.1",
|
||||||
"strum",
|
"strum",
|
||||||
"telemetry_events",
|
"telemetry_events",
|
||||||
|
"terminal_view",
|
||||||
"theme",
|
"theme",
|
||||||
"tiktoken-rs",
|
"tiktoken-rs",
|
||||||
"toml 0.8.10",
|
"toml 0.8.10",
|
||||||
|
@ -55,6 +55,7 @@ smol.workspace = true
|
|||||||
strsim = "0.11"
|
strsim = "0.11"
|
||||||
strum.workspace = true
|
strum.workspace = true
|
||||||
telemetry_events.workspace = true
|
telemetry_events.workspace = true
|
||||||
|
terminal_view.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
tiktoken-rs.workspace = true
|
tiktoken-rs.workspace = true
|
||||||
toml.workspace = true
|
toml.workspace = true
|
||||||
|
@ -27,7 +27,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use settings::{Settings, SettingsStore};
|
use settings::{Settings, SettingsStore};
|
||||||
use slash_command::{
|
use slash_command::{
|
||||||
active_command, default_command, diagnostics_command, fetch_command, file_command, now_command,
|
active_command, default_command, diagnostics_command, fetch_command, file_command, now_command,
|
||||||
project_command, prompt_command, rustdoc_command, search_command, tabs_command,
|
project_command, prompt_command, rustdoc_command, search_command, tabs_command, term_command,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{self, Display},
|
fmt::{self, Display},
|
||||||
@ -314,6 +314,7 @@ fn register_slash_commands(cx: &mut AppContext) {
|
|||||||
slash_command_registry.register_command(search_command::SearchSlashCommand, true);
|
slash_command_registry.register_command(search_command::SearchSlashCommand, true);
|
||||||
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
|
slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
|
||||||
slash_command_registry.register_command(default_command::DefaultSlashCommand, true);
|
slash_command_registry.register_command(default_command::DefaultSlashCommand, true);
|
||||||
|
slash_command_registry.register_command(term_command::TermSlashCommand, true);
|
||||||
slash_command_registry.register_command(now_command::NowSlashCommand, true);
|
slash_command_registry.register_command(now_command::NowSlashCommand, true);
|
||||||
slash_command_registry.register_command(diagnostics_command::DiagnosticsCommand, true);
|
slash_command_registry.register_command(diagnostics_command::DiagnosticsCommand, true);
|
||||||
slash_command_registry.register_command(rustdoc_command::RustdocSlashCommand, false);
|
slash_command_registry.register_command(rustdoc_command::RustdocSlashCommand, false);
|
||||||
|
@ -28,6 +28,7 @@ pub mod prompt_command;
|
|||||||
pub mod rustdoc_command;
|
pub mod rustdoc_command;
|
||||||
pub mod search_command;
|
pub mod search_command;
|
||||||
pub mod tabs_command;
|
pub mod tabs_command;
|
||||||
|
pub mod term_command;
|
||||||
|
|
||||||
pub(crate) struct SlashCommandCompletionProvider {
|
pub(crate) struct SlashCommandCompletionProvider {
|
||||||
commands: Arc<SlashCommandRegistry>,
|
commands: Arc<SlashCommandRegistry>,
|
||||||
|
105
crates/assistant/src/slash_command/term_command.rs
Normal file
105
crates/assistant/src/slash_command/term_command.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
||||||
|
use gpui::{AppContext, Task, WeakView};
|
||||||
|
use language::{CodeLabel, LspAdapterDelegate};
|
||||||
|
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
|
||||||
|
use ui::prelude::*;
|
||||||
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
use super::create_label_for_command;
|
||||||
|
|
||||||
|
pub(crate) struct TermSlashCommand;
|
||||||
|
|
||||||
|
const LINE_COUNT_ARG: &str = "--line-count";
|
||||||
|
|
||||||
|
impl SlashCommand for TermSlashCommand {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"term".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label(&self, cx: &AppContext) -> CodeLabel {
|
||||||
|
create_label_for_command("term", &[LINE_COUNT_ARG], cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn description(&self) -> String {
|
||||||
|
"insert terminal output".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn menu_text(&self) -> String {
|
||||||
|
"Insert terminal output".into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn requires_argument(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_argument(
|
||||||
|
self: Arc<Self>,
|
||||||
|
_query: String,
|
||||||
|
_cancel: Arc<AtomicBool>,
|
||||||
|
_workspace: Option<WeakView<Workspace>>,
|
||||||
|
_cx: &mut AppContext,
|
||||||
|
) -> Task<Result<Vec<String>>> {
|
||||||
|
Task::ready(Ok(vec![LINE_COUNT_ARG.to_string()]))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
self: Arc<Self>,
|
||||||
|
argument: Option<&str>,
|
||||||
|
workspace: WeakView<Workspace>,
|
||||||
|
_delegate: Arc<dyn LspAdapterDelegate>,
|
||||||
|
cx: &mut WindowContext,
|
||||||
|
) -> Task<Result<SlashCommandOutput>> {
|
||||||
|
let Some(workspace) = workspace.upgrade() else {
|
||||||
|
return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
|
||||||
|
};
|
||||||
|
let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
|
||||||
|
return Task::ready(Err(anyhow::anyhow!("no terminal panel open")));
|
||||||
|
};
|
||||||
|
let Some(active_terminal) = terminal_panel
|
||||||
|
.read(cx)
|
||||||
|
.pane()
|
||||||
|
.read(cx)
|
||||||
|
.active_item()
|
||||||
|
.and_then(|t| t.downcast::<TerminalView>())
|
||||||
|
else {
|
||||||
|
return Task::ready(Err(anyhow::anyhow!("no active terminal")));
|
||||||
|
};
|
||||||
|
|
||||||
|
let line_count = argument.and_then(|a| parse_argument(a)).unwrap_or(20);
|
||||||
|
|
||||||
|
let lines = active_terminal
|
||||||
|
.read(cx)
|
||||||
|
.model()
|
||||||
|
.read(cx)
|
||||||
|
.last_n_non_empty_lines(line_count);
|
||||||
|
|
||||||
|
let mut text = String::new();
|
||||||
|
text.push_str("Terminal output:\n");
|
||||||
|
text.push_str(&lines.join("\n"));
|
||||||
|
let range = 0..text.len();
|
||||||
|
|
||||||
|
Task::ready(Ok(SlashCommandOutput {
|
||||||
|
text,
|
||||||
|
sections: vec![SlashCommandOutputSection {
|
||||||
|
range,
|
||||||
|
icon: IconName::Terminal,
|
||||||
|
label: "Terminal".into(),
|
||||||
|
}],
|
||||||
|
run_commands_in_text: false,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_argument(argument: &str) -> Option<usize> {
|
||||||
|
let mut args = argument.split(' ');
|
||||||
|
if args.next() == Some(LINE_COUNT_ARG) {
|
||||||
|
if let Some(line_count) = args.next().and_then(|s| s.parse::<usize>().ok()) {
|
||||||
|
return Some(line_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
@ -1083,6 +1083,31 @@ impl Terminal {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn last_n_non_empty_lines(&self, n: usize) -> Vec<String> {
|
||||||
|
let term = self.term.clone();
|
||||||
|
let terminal = term.lock_unfair();
|
||||||
|
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
let mut current_line = terminal.bottommost_line();
|
||||||
|
while lines.len() < n {
|
||||||
|
let mut line_buffer = String::new();
|
||||||
|
for cell in &terminal.grid()[current_line] {
|
||||||
|
line_buffer.push(cell.c);
|
||||||
|
}
|
||||||
|
let line = line_buffer.trim_end();
|
||||||
|
if !line.is_empty() {
|
||||||
|
lines.push(line.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if current_line == terminal.topmost_line() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current_line = Line(current_line.0 - 1);
|
||||||
|
}
|
||||||
|
lines.reverse();
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
|
||||||
pub fn focus_in(&self) {
|
pub fn focus_in(&self) {
|
||||||
if self.last_content.mode.contains(TermMode::FOCUS_IN_OUT) {
|
if self.last_content.mode.contains(TermMode::FOCUS_IN_OUT) {
|
||||||
self.write_to_pty("\x1b[I".to_string());
|
self.write_to_pty("\x1b[I".to_string());
|
||||||
|
Loading…
Reference in New Issue
Block a user