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