terminal_slash_command.rs

  1use std::sync::Arc;
  2use std::sync::atomic::AtomicBool;
  3
  4use crate::{TerminalView, terminal_panel::TerminalPanel};
  5use anyhow::Result;
  6use assistant_slash_command::{
  7    ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
  8    SlashCommandResult,
  9};
 10use gpui::{App, Entity, Task, WeakEntity};
 11use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
 12use ui::prelude::*;
 13use workspace::{Workspace, dock::Panel};
 14
 15use assistant_slash_command::create_label_for_command;
 16
 17pub struct TerminalSlashCommand;
 18
 19const LINE_COUNT_ARG: &str = "--line-count";
 20
 21const DEFAULT_CONTEXT_LINES: usize = 50;
 22
 23impl SlashCommand for TerminalSlashCommand {
 24    fn name(&self) -> String {
 25        "terminal".into()
 26    }
 27
 28    fn label(&self, cx: &App) -> CodeLabel {
 29        create_label_for_command("terminal", &[LINE_COUNT_ARG], cx)
 30    }
 31
 32    fn description(&self) -> String {
 33        "Insert terminal output".into()
 34    }
 35
 36    fn icon(&self) -> IconName {
 37        IconName::Terminal
 38    }
 39
 40    fn menu_text(&self) -> String {
 41        self.description()
 42    }
 43
 44    fn requires_argument(&self) -> bool {
 45        false
 46    }
 47
 48    fn accepts_arguments(&self) -> bool {
 49        true
 50    }
 51
 52    fn complete_argument(
 53        self: Arc<Self>,
 54        _arguments: &[String],
 55        _cancel: Arc<AtomicBool>,
 56        _workspace: Option<WeakEntity<Workspace>>,
 57        _window: &mut Window,
 58        _cx: &mut App,
 59    ) -> Task<Result<Vec<ArgumentCompletion>>> {
 60        Task::ready(Ok(Vec::new()))
 61    }
 62
 63    fn run(
 64        self: Arc<Self>,
 65        arguments: &[String],
 66        _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
 67        _context_buffer: BufferSnapshot,
 68        workspace: WeakEntity<Workspace>,
 69        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
 70        _: &mut Window,
 71        cx: &mut App,
 72    ) -> Task<SlashCommandResult> {
 73        let Some(workspace) = workspace.upgrade() else {
 74            return Task::ready(Err(anyhow::anyhow!("workspace was dropped")));
 75        };
 76
 77        let Some(active_terminal) = resolve_active_terminal(&workspace, cx) else {
 78            return Task::ready(Err(anyhow::anyhow!("no active terminal")));
 79        };
 80
 81        let line_count = arguments
 82            .get(0)
 83            .and_then(|s| s.parse::<usize>().ok())
 84            .unwrap_or(DEFAULT_CONTEXT_LINES);
 85
 86        let lines = active_terminal
 87            .read(cx)
 88            .entity()
 89            .read(cx)
 90            .last_n_non_empty_lines(line_count);
 91
 92        let mut text = String::new();
 93        text.push_str("Terminal output:\n");
 94        text.push_str(&lines.join("\n"));
 95        let range = 0..text.len();
 96
 97        Task::ready(Ok(SlashCommandOutput {
 98            text,
 99            sections: vec![SlashCommandOutputSection {
100                range,
101                icon: IconName::Terminal,
102                label: "Terminal".into(),
103                metadata: None,
104            }],
105            run_commands_in_text: false,
106        }
107        .into_event_stream()))
108    }
109}
110
111fn resolve_active_terminal(
112    workspace: &Entity<Workspace>,
113    cx: &mut App,
114) -> Option<Entity<TerminalView>> {
115    if let Some(terminal_view) = workspace
116        .read(cx)
117        .active_item(cx)
118        .and_then(|item| item.act_as::<TerminalView>(cx))
119    {
120        return Some(terminal_view);
121    }
122
123    let terminal_panel = workspace.read(cx).panel::<TerminalPanel>(cx)?;
124    terminal_panel.read(cx).pane().and_then(|pane| {
125        pane.read(cx)
126            .active_item()
127            .and_then(|t| t.downcast::<TerminalView>())
128    })
129}