1use std::sync::atomic::AtomicBool;
2use std::sync::Arc;
3
4use anyhow::Result;
5use assistant_slash_command::{
6 ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
7};
8use gpui::{AppContext, Task, WeakView};
9use language::{CodeLabel, LspAdapterDelegate};
10use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
11use ui::prelude::*;
12use workspace::{dock::Panel, Workspace};
13
14use crate::DEFAULT_CONTEXT_LINES;
15
16use super::create_label_for_command;
17
18pub(crate) struct TerminalSlashCommand;
19
20const LINE_COUNT_ARG: &str = "--line-count";
21
22impl SlashCommand for TerminalSlashCommand {
23 fn name(&self) -> String {
24 "terminal".into()
25 }
26
27 fn label(&self, cx: &AppContext) -> CodeLabel {
28 create_label_for_command("terminal", &[LINE_COUNT_ARG], cx)
29 }
30
31 fn description(&self) -> String {
32 "insert terminal output".into()
33 }
34
35 fn menu_text(&self) -> String {
36 "Insert Terminal Output".into()
37 }
38
39 fn requires_argument(&self) -> bool {
40 false
41 }
42
43 fn complete_argument(
44 self: Arc<Self>,
45 arguments: &[String],
46 _cancel: Arc<AtomicBool>,
47 _workspace: Option<WeakView<Workspace>>,
48 _cx: &mut WindowContext,
49 ) -> Task<Result<Vec<ArgumentCompletion>>> {
50 let completions = if arguments.iter().any(|arg| arg == LINE_COUNT_ARG) {
51 Vec::new()
52 } else {
53 vec![ArgumentCompletion {
54 label: LINE_COUNT_ARG.into(),
55 new_text: LINE_COUNT_ARG.to_string(),
56 run_command: false,
57 }]
58 };
59 Task::ready(Ok(completions))
60 }
61
62 fn run(
63 self: Arc<Self>,
64 arguments: &[String],
65 workspace: WeakView<Workspace>,
66 _delegate: Option<Arc<dyn LspAdapterDelegate>>,
67 cx: &mut WindowContext,
68 ) -> Task<Result<SlashCommandOutput>> {
69 let Some(workspace) = workspace.upgrade() else {
70 return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
71 };
72 let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
73 return Task::ready(Err(anyhow::anyhow!("no terminal panel open")));
74 };
75 let Some(active_terminal) = terminal_panel.read(cx).pane().and_then(|pane| {
76 pane.read(cx)
77 .active_item()
78 .and_then(|t| t.downcast::<TerminalView>())
79 }) else {
80 return Task::ready(Err(anyhow::anyhow!("no active terminal")));
81 };
82
83 let mut line_count = DEFAULT_CONTEXT_LINES;
84 if arguments.get(0).map(|s| s.as_str()) == Some(LINE_COUNT_ARG) {
85 if let Some(parsed_line_count) = arguments.get(1).and_then(|s| s.parse::<usize>().ok())
86 {
87 line_count = parsed_line_count;
88 }
89 }
90
91 let lines = active_terminal
92 .read(cx)
93 .model()
94 .read(cx)
95 .last_n_non_empty_lines(line_count);
96
97 let mut text = String::new();
98 text.push_str("Terminal output:\n");
99 text.push_str(&lines.join("\n"));
100 let range = 0..text.len();
101
102 Task::ready(Ok(SlashCommandOutput {
103 text,
104 sections: vec![SlashCommandOutputSection {
105 range,
106 icon: IconName::Terminal,
107 label: "Terminal".into(),
108 }],
109 run_commands_in_text: false,
110 }))
111 }
112}