Compare commits
2 Commits
v0.211.4-p
...
display-on
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7692bf47a | ||
|
|
31fa0b4c30 |
@@ -3,6 +3,8 @@ mod diff;
|
||||
mod mention;
|
||||
mod terminal;
|
||||
|
||||
use ::terminal::TerminalBuilder;
|
||||
use ::terminal::terminal_settings::{AlternateScroll, CursorShape};
|
||||
use agent_settings::AgentSettings;
|
||||
use collections::HashSet;
|
||||
pub use connection::*;
|
||||
@@ -1144,8 +1146,32 @@ impl AcpThread {
|
||||
|
||||
match update {
|
||||
ToolCallUpdate::UpdateFields(update) => {
|
||||
// Check if there's terminal output in the meta field
|
||||
let terminal_output_result = update
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|meta| meta.get("terminal_output"))
|
||||
.and_then(|terminal_output| {
|
||||
match (
|
||||
terminal_output.get("terminal_id").and_then(|v| v.as_str()),
|
||||
terminal_output.get("data").and_then(|v| v.as_str()),
|
||||
) {
|
||||
(Some(terminal_id_str), Some(data_str)) => {
|
||||
let data = data_str.as_bytes().to_vec();
|
||||
let terminal_id = acp::TerminalId(terminal_id_str.into());
|
||||
Some((terminal_id, data))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
|
||||
let location_updated = update.fields.locations.is_some();
|
||||
call.update_fields(update.fields, languages, &self.terminals, cx)?;
|
||||
|
||||
if let Some((terminal_id, data)) = terminal_output_result {
|
||||
// Silently ignore errors - terminal output streaming is best-effort
|
||||
let _ = self.write_terminal_output(terminal_id, &data, cx);
|
||||
}
|
||||
if location_updated {
|
||||
self.resolve_locations(update.id, cx);
|
||||
}
|
||||
@@ -1949,6 +1975,7 @@ impl AcpThread {
|
||||
extra_env: Vec<acp::EnvVariable>,
|
||||
cwd: Option<PathBuf>,
|
||||
output_byte_limit: Option<u64>,
|
||||
is_display_only: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Terminal>>> {
|
||||
let env = match &cwd {
|
||||
@@ -1989,32 +2016,59 @@ impl AcpThread {
|
||||
)
|
||||
.redirect_stdin_to_dev_null()
|
||||
.build(Some(command), &args);
|
||||
let terminal = project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_terminal_task(
|
||||
task::SpawnInTerminal {
|
||||
command: Some(command.clone()),
|
||||
args: args.clone(),
|
||||
cwd: cwd.clone(),
|
||||
env,
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
let terminal = if is_display_only {
|
||||
cx.update(|cx| {
|
||||
TerminalBuilder::new_display_only(
|
||||
Some(format!("Display: {}", command).into()),
|
||||
CursorShape::Block,
|
||||
AlternateScroll::On,
|
||||
Some(10_000),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
})??
|
||||
} else {
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.create_terminal_task(
|
||||
task::SpawnInTerminal {
|
||||
command: Some(command.clone()),
|
||||
args: args.clone(),
|
||||
cwd: cwd.clone(),
|
||||
env,
|
||||
..Default::default()
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?
|
||||
};
|
||||
|
||||
cx.new(|cx| {
|
||||
Terminal::new(
|
||||
terminal_id,
|
||||
&format!("{} {}", command, args.join(" ")),
|
||||
cwd,
|
||||
output_byte_limit.map(|l| l as usize),
|
||||
terminal,
|
||||
language_registry,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
if is_display_only {
|
||||
// For display-only terminals, we need special handling
|
||||
cx.new(|cx| {
|
||||
Terminal::new_display_only(
|
||||
terminal_id,
|
||||
&format!("{} {}", command, args.join(" ")),
|
||||
cwd,
|
||||
output_byte_limit.map(|l| l as usize),
|
||||
terminal,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
cx.new(|cx| {
|
||||
Terminal::new(
|
||||
terminal_id,
|
||||
&format!("{} {}", command, args.join(" ")),
|
||||
cwd,
|
||||
output_byte_limit.map(|l| l as usize),
|
||||
terminal,
|
||||
language_registry,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2060,8 +2114,26 @@ impl AcpThread {
|
||||
pub fn terminal(&self, terminal_id: acp::TerminalId) -> Result<Entity<Terminal>> {
|
||||
self.terminals
|
||||
.get(&terminal_id)
|
||||
.context("Terminal not found")
|
||||
.cloned()
|
||||
.context("Terminal not found")
|
||||
}
|
||||
|
||||
pub fn write_terminal_output(
|
||||
&mut self,
|
||||
terminal_id: acp::TerminalId,
|
||||
output: &[u8],
|
||||
cx: &mut Context<Self>,
|
||||
) -> Result<()> {
|
||||
let terminal = self
|
||||
.terminals
|
||||
.get(&terminal_id)
|
||||
.context("Terminal not found")?;
|
||||
|
||||
terminal.update(cx, |terminal, cx| {
|
||||
terminal.write_output(output, cx);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn to_markdown(&self, cx: &App) -> String {
|
||||
|
||||
@@ -82,6 +82,70 @@ impl Terminal {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_display_only(
|
||||
id: acp::TerminalId,
|
||||
command_label: &str,
|
||||
working_dir: Option<PathBuf>,
|
||||
output_byte_limit: Option<usize>,
|
||||
terminal: Entity<terminal::Terminal>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
// Display-only terminals don't have a real process, so there's no exit status
|
||||
let command_task = Task::ready(None);
|
||||
|
||||
Self {
|
||||
id,
|
||||
command: cx.new(|_cx| {
|
||||
// For display-only terminals, we don't need the markdown wrapper
|
||||
// The terminal itself will handle the display
|
||||
Markdown::new(
|
||||
format!("```\n{}\n```", command_label).into(),
|
||||
None,
|
||||
None,
|
||||
_cx,
|
||||
)
|
||||
}),
|
||||
working_dir,
|
||||
terminal,
|
||||
started_at: Instant::now(),
|
||||
output: None,
|
||||
output_byte_limit,
|
||||
_output_task: cx
|
||||
.spawn(async move |this, cx| {
|
||||
// Display-only terminals don't really exit, but we need to handle this
|
||||
let exit_status = command_task.await;
|
||||
|
||||
this.update(cx, |this, cx| {
|
||||
let (content, original_content_len) = this.truncated_output(cx);
|
||||
let content_line_count = this.terminal.read(cx).total_lines();
|
||||
|
||||
this.output = Some(TerminalOutput {
|
||||
ended_at: Instant::now(),
|
||||
exit_status,
|
||||
content,
|
||||
original_content_len,
|
||||
content_line_count,
|
||||
});
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
|
||||
acp::TerminalExitStatus {
|
||||
exit_code: None,
|
||||
signal: None,
|
||||
meta: None,
|
||||
}
|
||||
})
|
||||
.shared(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_output(&mut self, data: &[u8], cx: &mut Context<Self>) {
|
||||
self.terminal.update(cx, |terminal, cx| {
|
||||
terminal.write_output(data, cx);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &acp::TerminalId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
@@ -1144,7 +1144,7 @@ impl ThreadEnvironment for AcpThreadEnvironment {
|
||||
cx: &mut AsyncApp,
|
||||
) -> Task<Result<Rc<dyn TerminalHandle>>> {
|
||||
let task = self.acp_thread.update(cx, |thread, cx| {
|
||||
thread.create_terminal(command, vec![], vec![], cwd, output_byte_limit, cx)
|
||||
thread.create_terminal(command, vec![], vec![], cwd, output_byte_limit, false, cx)
|
||||
});
|
||||
|
||||
let acp_thread = self.acp_thread.clone();
|
||||
|
||||
@@ -606,6 +606,14 @@ impl acp::Client for ClientDelegate {
|
||||
&self,
|
||||
args: acp::CreateTerminalRequest,
|
||||
) -> Result<acp::CreateTerminalResponse, acp::Error> {
|
||||
// Check if this is a display-only terminal from the metadata
|
||||
let is_display_only = args
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|meta| meta.get("display_only"))
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
|
||||
let terminal = self
|
||||
.session_thread(&args.session_id)?
|
||||
.update(&mut self.cx.clone(), |thread, cx| {
|
||||
@@ -615,6 +623,7 @@ impl acp::Client for ClientDelegate {
|
||||
args.env,
|
||||
args.cwd,
|
||||
args.output_byte_limit,
|
||||
is_display_only,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
|
||||
@@ -1228,9 +1228,8 @@ impl RunningState {
|
||||
|
||||
terminal.read_with(cx, |terminal, _| {
|
||||
terminal
|
||||
.pty_info
|
||||
.pid()
|
||||
.map(|pid| pid.as_u32())
|
||||
.pty_info()
|
||||
.and_then(|info| info.pid().map(|pid| pid.as_u32()))
|
||||
.context("Terminal was spawned but PID was not available")
|
||||
})?
|
||||
});
|
||||
|
||||
@@ -64,8 +64,8 @@ use std::{
|
||||
use thiserror::Error;
|
||||
|
||||
use gpui::{
|
||||
App, AppContext as _, Bounds, ClipboardItem, Context, EventEmitter, Hsla, Keystroke, Modifiers,
|
||||
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Rgba,
|
||||
App, AppContext as _, Bounds, ClipboardItem, Context, Entity, EventEmitter, Hsla, Keystroke,
|
||||
Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Rgba,
|
||||
ScrollWheelEvent, SharedString, Size, Task, TouchPhase, Window, actions, black, px,
|
||||
};
|
||||
|
||||
@@ -338,6 +338,73 @@ pub struct TerminalBuilder {
|
||||
}
|
||||
|
||||
impl TerminalBuilder {
|
||||
/// Creates a display-only terminal without a real PTY process
|
||||
pub fn new_display_only(
|
||||
title: Option<SharedString>,
|
||||
cursor_shape: CursorShape,
|
||||
alternate_scroll: AlternateScroll,
|
||||
max_scroll_history_lines: Option<usize>,
|
||||
cx: &mut App,
|
||||
) -> Result<Entity<Terminal>> {
|
||||
let (events_tx, events_rx) = unbounded();
|
||||
|
||||
let max_scroll_history_lines =
|
||||
max_scroll_history_lines.unwrap_or(DEFAULT_SCROLL_HISTORY_LINES);
|
||||
let config = Config {
|
||||
scrolling_history: max_scroll_history_lines,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let listener = ZedListener(events_tx.clone());
|
||||
let term = Arc::new(FairMutex::new(Term::new(
|
||||
config.clone(),
|
||||
&TerminalBounds::default(),
|
||||
listener.clone(),
|
||||
)));
|
||||
|
||||
let terminal = Terminal {
|
||||
mode: TerminalMode::DisplayOnly,
|
||||
task: None,
|
||||
completion_tx: None,
|
||||
term,
|
||||
term_config: config,
|
||||
title_override: title.clone(),
|
||||
events: VecDeque::with_capacity(10),
|
||||
last_content: Default::default(),
|
||||
last_mouse: None,
|
||||
matches: Vec::new(),
|
||||
selection_head: None,
|
||||
breadcrumb_text: title.map(|t| t.to_string()).unwrap_or_default(),
|
||||
scroll_px: px(0.),
|
||||
next_link_id: 0,
|
||||
selection_phase: SelectionPhase::Ended,
|
||||
hyperlink_regex_searches: RegexSearches::new(),
|
||||
vi_mode_enabled: false,
|
||||
is_ssh_terminal: false,
|
||||
last_mouse_move_time: Instant::now(),
|
||||
last_hyperlink_search_position: None,
|
||||
#[cfg(windows)]
|
||||
shell_program: None,
|
||||
activation_script: Vec::new(),
|
||||
template: CopyTemplate {
|
||||
shell: Shell::System,
|
||||
env: HashMap::default(),
|
||||
cursor_shape,
|
||||
alternate_scroll,
|
||||
max_scroll_history_lines: Some(max_scroll_history_lines),
|
||||
window_id: 0, // Not used for display-only terminals
|
||||
},
|
||||
child_exited: None,
|
||||
};
|
||||
|
||||
let builder = TerminalBuilder {
|
||||
terminal,
|
||||
events_rx,
|
||||
};
|
||||
|
||||
Ok(cx.new(|cx| builder.subscribe(cx)))
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
working_directory: Option<PathBuf>,
|
||||
task: Option<TaskState>,
|
||||
@@ -497,8 +564,11 @@ impl TerminalBuilder {
|
||||
let no_task = task.is_none();
|
||||
|
||||
let mut terminal = Terminal {
|
||||
mode: TerminalMode::Shell {
|
||||
pty_tx: Notifier(pty_tx),
|
||||
pty_info,
|
||||
},
|
||||
task,
|
||||
pty_tx: Notifier(pty_tx),
|
||||
completion_tx,
|
||||
term,
|
||||
term_config: config,
|
||||
@@ -508,7 +578,6 @@ impl TerminalBuilder {
|
||||
last_mouse: None,
|
||||
matches: Vec::new(),
|
||||
selection_head: None,
|
||||
pty_info,
|
||||
breadcrumb_text: String::new(),
|
||||
scroll_px: px(0.),
|
||||
next_link_id: 0,
|
||||
@@ -698,8 +767,16 @@ pub enum SelectionPhase {
|
||||
Ended,
|
||||
}
|
||||
|
||||
pub enum TerminalMode {
|
||||
Shell {
|
||||
pty_tx: Notifier,
|
||||
pty_info: PtyProcessInfo,
|
||||
},
|
||||
DisplayOnly,
|
||||
}
|
||||
|
||||
pub struct Terminal {
|
||||
pty_tx: Notifier,
|
||||
mode: TerminalMode,
|
||||
completion_tx: Option<Sender<Option<ExitStatus>>>,
|
||||
term: Arc<FairMutex<Term<ZedListener>>>,
|
||||
term_config: Config,
|
||||
@@ -710,7 +787,6 @@ pub struct Terminal {
|
||||
pub last_content: TerminalContent,
|
||||
pub selection_head: Option<AlacPoint>,
|
||||
pub breadcrumb_text: String,
|
||||
pub pty_info: PtyProcessInfo,
|
||||
title_override: Option<SharedString>,
|
||||
scroll_px: Pixels,
|
||||
next_link_id: usize,
|
||||
@@ -833,8 +909,10 @@ impl Terminal {
|
||||
AlacTermEvent::Wakeup => {
|
||||
cx.emit(Event::Wakeup);
|
||||
|
||||
if self.pty_info.has_changed() {
|
||||
cx.emit(Event::TitleChanged);
|
||||
if let TerminalMode::Shell { pty_info, .. } = &mut self.mode {
|
||||
if pty_info.has_changed() {
|
||||
cx.emit(Event::TitleChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
AlacTermEvent::ColorRequest(index, format) => {
|
||||
@@ -875,7 +953,9 @@ impl Terminal {
|
||||
|
||||
self.last_content.terminal_bounds = new_bounds;
|
||||
|
||||
self.pty_tx.0.send(Msg::Resize(new_bounds.into())).ok();
|
||||
if let TerminalMode::Shell { pty_tx, .. } = &self.mode {
|
||||
pty_tx.0.send(Msg::Resize(new_bounds.into())).ok();
|
||||
}
|
||||
|
||||
term.resize(new_bounds);
|
||||
}
|
||||
@@ -1237,7 +1317,35 @@ impl Terminal {
|
||||
|
||||
///Write the Input payload to the tty.
|
||||
fn write_to_pty(&self, input: impl Into<Cow<'static, [u8]>>) {
|
||||
self.pty_tx.notify(input.into());
|
||||
if let TerminalMode::Shell { pty_tx, .. } = &self.mode {
|
||||
pty_tx.notify(input.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// Write output directly to a display-only terminal
|
||||
pub fn write_output(&mut self, data: &[u8], cx: &mut Context<Self>) {
|
||||
if !matches!(self.mode, TerminalMode::DisplayOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
let mut term = self.term.lock();
|
||||
let text = String::from_utf8_lossy(data);
|
||||
for ch in text.chars() {
|
||||
term.input(ch);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify that content has changed
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn is_display_only(&self) -> bool {
|
||||
matches!(self.mode, TerminalMode::DisplayOnly)
|
||||
}
|
||||
|
||||
pub fn mode(&self) -> &TerminalMode {
|
||||
&self.mode
|
||||
}
|
||||
|
||||
pub fn input(&mut self, input: impl Into<Cow<'static, [u8]>>) {
|
||||
@@ -1541,7 +1649,9 @@ impl Terminal {
|
||||
&& let Some(bytes) =
|
||||
mouse_moved_report(point, e.pressed_button, e.modifiers, self.last_content.mode)
|
||||
{
|
||||
self.pty_tx.notify(bytes);
|
||||
if let TerminalMode::Shell { pty_tx, .. } = &self.mode {
|
||||
pty_tx.notify(bytes);
|
||||
}
|
||||
}
|
||||
} else if e.modifiers.secondary() {
|
||||
self.word_from_position(e.position);
|
||||
@@ -1648,7 +1758,9 @@ impl Terminal {
|
||||
if let Some(bytes) =
|
||||
mouse_button_report(point, e.button, e.modifiers, true, self.last_content.mode)
|
||||
{
|
||||
self.pty_tx.notify(bytes);
|
||||
if let TerminalMode::Shell { pty_tx, .. } = &self.mode {
|
||||
pty_tx.notify(bytes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match e.button {
|
||||
@@ -1707,7 +1819,9 @@ impl Terminal {
|
||||
if let Some(bytes) =
|
||||
mouse_button_report(point, e.button, e.modifiers, false, self.last_content.mode)
|
||||
{
|
||||
self.pty_tx.notify(bytes);
|
||||
if let TerminalMode::Shell { pty_tx, .. } = &self.mode {
|
||||
pty_tx.notify(bytes);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if e.button == MouseButton::Left && setting.copy_on_select {
|
||||
@@ -1745,8 +1859,10 @@ impl Terminal {
|
||||
|
||||
if let Some(scrolls) = scroll_report(point, scroll_lines, e, self.last_content.mode)
|
||||
{
|
||||
for scroll in scrolls {
|
||||
self.pty_tx.notify(scroll);
|
||||
if let TerminalMode::Shell { pty_tx, .. } = &self.mode {
|
||||
for scroll in scrolls {
|
||||
pty_tx.notify(scroll);
|
||||
}
|
||||
}
|
||||
};
|
||||
} else if self
|
||||
@@ -1755,7 +1871,9 @@ impl Terminal {
|
||||
.contains(TermMode::ALT_SCREEN | TermMode::ALTERNATE_SCROLL)
|
||||
&& !e.shift
|
||||
{
|
||||
self.pty_tx.notify(alt_scroll(scroll_lines))
|
||||
if let TerminalMode::Shell { pty_tx, .. } = &self.mode {
|
||||
pty_tx.notify(alt_scroll(scroll_lines))
|
||||
}
|
||||
} else if scroll_lines != 0 {
|
||||
let scroll = AlacScroll::Delta(scroll_lines);
|
||||
|
||||
@@ -1826,10 +1944,12 @@ impl Terminal {
|
||||
/// This does *not* return the working directory of the shell that runs on the
|
||||
/// remote host, in case Zed is connected to a remote host.
|
||||
fn client_side_working_directory(&self) -> Option<PathBuf> {
|
||||
self.pty_info
|
||||
.current
|
||||
.as_ref()
|
||||
.map(|process| process.cwd.clone())
|
||||
match &self.mode {
|
||||
TerminalMode::Shell { pty_info, .. } => {
|
||||
pty_info.current.as_ref().map(|info| info.cwd.clone())
|
||||
}
|
||||
TerminalMode::DisplayOnly => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn title(&self, truncate: bool) -> String {
|
||||
@@ -1847,46 +1967,56 @@ impl Terminal {
|
||||
.as_ref()
|
||||
.map(|title_override| title_override.to_string())
|
||||
.unwrap_or_else(|| {
|
||||
self.pty_info
|
||||
.current
|
||||
.as_ref()
|
||||
.map(|fpi| {
|
||||
let process_file = fpi
|
||||
.cwd
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
match &self.mode {
|
||||
TerminalMode::Shell { pty_info, .. } => pty_info.current.as_ref(),
|
||||
TerminalMode::DisplayOnly => None,
|
||||
}
|
||||
.map(|fpi| {
|
||||
let process_file = fpi
|
||||
.cwd
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
|
||||
let argv = fpi.argv.as_slice();
|
||||
let process_name = format!(
|
||||
"{}{}",
|
||||
fpi.name,
|
||||
if !argv.is_empty() {
|
||||
format!(" {}", (argv[1..]).join(" "))
|
||||
} else {
|
||||
"".to_string()
|
||||
}
|
||||
);
|
||||
let (process_file, process_name) = if truncate {
|
||||
(
|
||||
truncate_and_trailoff(&process_file, MAX_CHARS),
|
||||
truncate_and_trailoff(&process_name, MAX_CHARS),
|
||||
)
|
||||
let argv = fpi.argv.as_slice();
|
||||
let process_name = format!(
|
||||
"{}{}",
|
||||
fpi.name,
|
||||
if !argv.is_empty() {
|
||||
format!(" {}", (argv[1..]).join(" "))
|
||||
} else {
|
||||
(process_file, process_name)
|
||||
};
|
||||
format!("{process_file} — {process_name}")
|
||||
})
|
||||
.unwrap_or_else(|| "Terminal".to_string())
|
||||
"".to_string()
|
||||
}
|
||||
);
|
||||
let (process_file, process_name) = if truncate {
|
||||
(
|
||||
truncate_and_trailoff(&process_file, MAX_CHARS),
|
||||
truncate_and_trailoff(&process_name, MAX_CHARS),
|
||||
)
|
||||
} else {
|
||||
(process_file, process_name)
|
||||
};
|
||||
format!("{process_file} — {process_name}")
|
||||
})
|
||||
.unwrap_or_else(|| "Terminal".to_string())
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pty_info(&self) -> Option<&PtyProcessInfo> {
|
||||
match &self.mode {
|
||||
TerminalMode::Shell { pty_info, .. } => Some(pty_info),
|
||||
TerminalMode::DisplayOnly => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kill_active_task(&mut self) {
|
||||
if let Some(task) = self.task()
|
||||
&& task.status == TaskStatus::Running
|
||||
{
|
||||
self.pty_info.kill_current_process();
|
||||
if let TerminalMode::Shell { pty_info, .. } = &mut self.mode {
|
||||
pty_info.kill_current_process();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1896,6 +2026,11 @@ impl Terminal {
|
||||
|
||||
pub fn wait_for_completed_task(&self, cx: &App) -> Task<Option<ExitStatus>> {
|
||||
if let Some(task) = self.task() {
|
||||
if matches!(self.mode, TerminalMode::DisplayOnly) {
|
||||
// Display-only terminals don't have a real process, so there's no exit status
|
||||
return Task::ready(None);
|
||||
}
|
||||
|
||||
if task.status == TaskStatus::Running {
|
||||
let completion_receiver = task.completion_rx.clone();
|
||||
return cx.spawn(async move |_| completion_receiver.recv().await.ok().flatten());
|
||||
@@ -2073,7 +2208,9 @@ unsafe fn append_text_to_term(term: &mut Term<ZedListener>, text_lines: &[&str])
|
||||
|
||||
impl Drop for Terminal {
|
||||
fn drop(&mut self) {
|
||||
self.pty_tx.0.send(Msg::Shutdown).ok();
|
||||
if let TerminalMode::Shell { pty_tx, .. } = &self.mode {
|
||||
pty_tx.0.send(Msg::Shutdown).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ use ui::{Divider, prelude::*, tooltip_container};
|
||||
|
||||
pub struct TerminalTooltip {
|
||||
title: SharedString,
|
||||
pid: u32,
|
||||
pid: Option<u32>,
|
||||
}
|
||||
|
||||
impl TerminalTooltip {
|
||||
pub fn new(title: impl Into<SharedString>, pid: u32) -> Self {
|
||||
pub fn new(title: impl Into<SharedString>, pid: Option<u32>) -> Self {
|
||||
Self {
|
||||
title: title.into(),
|
||||
pid,
|
||||
@@ -25,11 +25,13 @@ impl Render for TerminalTooltip {
|
||||
.gap_1()
|
||||
.child(Label::new(self.title.clone()))
|
||||
.child(Divider::horizontal())
|
||||
.child(
|
||||
Label::new(format!("Process ID (PID): {}", self.pid))
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small),
|
||||
),
|
||||
.when_some(self.pid, |this, pid| {
|
||||
this.child(
|
||||
Label::new(format!("Process ID (PID): {}", pid))
|
||||
.color(Color::Muted)
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1142,7 +1142,9 @@ impl Item for TerminalView {
|
||||
fn tab_tooltip_content(&self, cx: &App) -> Option<TabTooltipContent> {
|
||||
let terminal = self.terminal().read(cx);
|
||||
let title = terminal.title(false);
|
||||
let pid = terminal.pty_info.pid_getter().fallback_pid();
|
||||
let pid = terminal
|
||||
.pty_info()
|
||||
.map(|info| info.pid_getter().fallback_pid());
|
||||
|
||||
Some(TabTooltipContent::Custom(Box::new(move |_window, cx| {
|
||||
cx.new(|_| TerminalTooltip::new(title.clone(), pid)).into()
|
||||
|
||||
Reference in New Issue
Block a user