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