term_command.rs

  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 super::create_label_for_command;
 15
 16pub(crate) struct TermSlashCommand;
 17
 18const LINE_COUNT_ARG: &str = "--line-count";
 19
 20impl SlashCommand for TermSlashCommand {
 21    fn name(&self) -> String {
 22        "term".into()
 23    }
 24
 25    fn label(&self, cx: &AppContext) -> CodeLabel {
 26        create_label_for_command("term", &[LINE_COUNT_ARG], cx)
 27    }
 28
 29    fn description(&self) -> String {
 30        "insert terminal output".into()
 31    }
 32
 33    fn menu_text(&self) -> String {
 34        "Insert Terminal Output".into()
 35    }
 36
 37    fn requires_argument(&self) -> bool {
 38        false
 39    }
 40
 41    fn complete_argument(
 42        self: Arc<Self>,
 43        _query: String,
 44        _cancel: Arc<AtomicBool>,
 45        _workspace: Option<WeakView<Workspace>>,
 46        _cx: &mut AppContext,
 47    ) -> Task<Result<Vec<ArgumentCompletion>>> {
 48        Task::ready(Ok(vec![ArgumentCompletion {
 49            label: LINE_COUNT_ARG.to_string(),
 50            new_text: LINE_COUNT_ARG.to_string(),
 51            run_command: true,
 52        }]))
 53    }
 54
 55    fn run(
 56        self: Arc<Self>,
 57        argument: Option<&str>,
 58        workspace: WeakView<Workspace>,
 59        _delegate: Arc<dyn LspAdapterDelegate>,
 60        cx: &mut WindowContext,
 61    ) -> Task<Result<SlashCommandOutput>> {
 62        let Some(workspace) = workspace.upgrade() else {
 63            return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
 64        };
 65        let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
 66            return Task::ready(Err(anyhow::anyhow!("no terminal panel open")));
 67        };
 68        let Some(active_terminal) = terminal_panel.read(cx).pane().and_then(|pane| {
 69            pane.read(cx)
 70                .active_item()
 71                .and_then(|t| t.downcast::<TerminalView>())
 72        }) else {
 73            return Task::ready(Err(anyhow::anyhow!("no active terminal")));
 74        };
 75
 76        let line_count = argument.and_then(|a| parse_argument(a)).unwrap_or(20);
 77
 78        let lines = active_terminal
 79            .read(cx)
 80            .model()
 81            .read(cx)
 82            .last_n_non_empty_lines(line_count);
 83
 84        let mut text = String::new();
 85        text.push_str("Terminal output:\n");
 86        text.push_str(&lines.join("\n"));
 87        let range = 0..text.len();
 88
 89        Task::ready(Ok(SlashCommandOutput {
 90            text,
 91            sections: vec![SlashCommandOutputSection {
 92                range,
 93                icon: IconName::Terminal,
 94                label: "Terminal".into(),
 95            }],
 96            run_commands_in_text: false,
 97        }))
 98    }
 99}
100
101fn parse_argument(argument: &str) -> Option<usize> {
102    let mut args = argument.split(' ');
103    if args.next() == Some(LINE_COUNT_ARG) {
104        if let Some(line_count) = args.next().and_then(|s| s.parse::<usize>().ok()) {
105            return Some(line_count);
106        }
107    }
108    None
109}