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::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
 69            .read(cx)
 70            .pane()
 71            .read(cx)
 72            .active_item()
 73            .and_then(|t| t.downcast::<TerminalView>())
 74        else {
 75            return Task::ready(Err(anyhow::anyhow!("no active terminal")));
 76        };
 77
 78        let line_count = argument.and_then(|a| parse_argument(a)).unwrap_or(20);
 79
 80        let lines = active_terminal
 81            .read(cx)
 82            .model()
 83            .read(cx)
 84            .last_n_non_empty_lines(line_count);
 85
 86        let mut text = String::new();
 87        text.push_str("Terminal output:\n");
 88        text.push_str(&lines.join("\n"));
 89        let range = 0..text.len();
 90
 91        Task::ready(Ok(SlashCommandOutput {
 92            text,
 93            sections: vec![SlashCommandOutputSection {
 94                range,
 95                icon: IconName::Terminal,
 96                label: "Terminal".into(),
 97            }],
 98            run_commands_in_text: false,
 99        }))
100    }
101}
102
103fn parse_argument(argument: &str) -> Option<usize> {
104    let mut args = argument.split(' ');
105    if args.next() == Some(LINE_COUNT_ARG) {
106        if let Some(line_count) = args.next().and_then(|s| s.parse::<usize>().ok()) {
107            return Some(line_count);
108        }
109    }
110    None
111}