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