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, View, 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 Task::ready(Ok(Vec::new()))
51 }
52
53 fn run(
54 self: Arc<Self>,
55 arguments: &[String],
56 workspace: WeakView<Workspace>,
57 _delegate: Option<Arc<dyn LspAdapterDelegate>>,
58 cx: &mut WindowContext,
59 ) -> Task<Result<SlashCommandOutput>> {
60 let Some(workspace) = workspace.upgrade() else {
61 return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
62 };
63
64 let Some(active_terminal) = resolve_active_terminal(&workspace, cx) else {
65 return Task::ready(Err(anyhow::anyhow!("no active terminal")));
66 };
67
68 let line_count = arguments
69 .get(0)
70 .and_then(|s| s.parse::<usize>().ok())
71 .unwrap_or(DEFAULT_CONTEXT_LINES);
72
73 let lines = active_terminal
74 .read(cx)
75 .model()
76 .read(cx)
77 .last_n_non_empty_lines(line_count);
78
79 let mut text = String::new();
80 text.push_str("Terminal output:\n");
81 text.push_str(&lines.join("\n"));
82 let range = 0..text.len();
83
84 Task::ready(Ok(SlashCommandOutput {
85 text,
86 sections: vec![SlashCommandOutputSection {
87 range,
88 icon: IconName::Terminal,
89 label: "Terminal".into(),
90 }],
91 run_commands_in_text: false,
92 }))
93 }
94}
95
96fn resolve_active_terminal(
97 workspace: &View<Workspace>,
98 cx: &WindowContext,
99) -> Option<View<TerminalView>> {
100 if let Some(terminal_view) = workspace
101 .read(cx)
102 .active_item(cx)
103 .and_then(|item| item.act_as::<TerminalView>(cx))
104 {
105 return Some(terminal_view);
106 }
107
108 let terminal_panel = workspace.read(cx).panel::<TerminalPanel>(cx)?;
109 terminal_panel.read(cx).pane().and_then(|pane| {
110 pane.read(cx)
111 .active_item()
112 .and_then(|t| t.downcast::<TerminalView>())
113 })
114}