terminal_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 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}