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