debugger: Allow users to shutdown debug sessions while they're booting (#34362)
This solves problems where users couldn't shut down sessions while locators or build tasks are running. I renamed `debugger::Session::Mode` enum to `SessionState` to be more clear when it's referenced in other crates. I also embedded the boot task that is created in `SessionState::Building` variant. This allows sessions to shut down all created threads in their boot process in a clean and idiomatic way. Finally, I added a method on terminal that allows killing the active task. Release Notes: - Debugger: Allow shutting down debug sessions while they're booting up
This commit is contained in:
parent
970a1066f5
commit
8f6b9f0d65
5 changed files with 156 additions and 79 deletions
|
@ -27,7 +27,7 @@ use text::ToPoint as _;
|
||||||
|
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use project::debugger::session::{Session, SessionQuirks, SessionStateEvent};
|
use project::debugger::session::{Session, SessionQuirks, SessionState, SessionStateEvent};
|
||||||
use project::{DebugScenarioContext, Fs, ProjectPath, TaskSourceKind, WorktreeId};
|
use project::{DebugScenarioContext, Fs, ProjectPath, TaskSourceKind, WorktreeId};
|
||||||
use project::{Project, debugger::session::ThreadStatus};
|
use project::{Project, debugger::session::ThreadStatus};
|
||||||
use rpc::proto::{self};
|
use rpc::proto::{self};
|
||||||
|
@ -36,7 +36,7 @@ use std::sync::{Arc, LazyLock};
|
||||||
use task::{DebugScenario, TaskContext};
|
use task::{DebugScenario, TaskContext};
|
||||||
use tree_sitter::{Query, StreamingIterator as _};
|
use tree_sitter::{Query, StreamingIterator as _};
|
||||||
use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};
|
use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};
|
||||||
use util::{ResultExt, maybe};
|
use util::{ResultExt, debug_panic, maybe};
|
||||||
use workspace::SplitDirection;
|
use workspace::SplitDirection;
|
||||||
use workspace::item::SaveOptions;
|
use workspace::item::SaveOptions;
|
||||||
use workspace::{
|
use workspace::{
|
||||||
|
@ -278,22 +278,34 @@ impl DebugPanel {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.spawn(async move |_, cx| {
|
let boot_task = cx.spawn({
|
||||||
if let Err(error) = task.await {
|
let session = session.clone();
|
||||||
log::error!("{error}");
|
|
||||||
session
|
async move |_, cx| {
|
||||||
.update(cx, |session, cx| {
|
if let Err(error) = task.await {
|
||||||
session
|
log::error!("{error}");
|
||||||
.console_output(cx)
|
session
|
||||||
.unbounded_send(format!("error: {}", error))
|
.update(cx, |session, cx| {
|
||||||
.ok();
|
session
|
||||||
session.shutdown(cx)
|
.console_output(cx)
|
||||||
})?
|
.unbounded_send(format!("error: {}", error))
|
||||||
.await;
|
.ok();
|
||||||
|
session.shutdown(cx)
|
||||||
|
})?
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
anyhow::Ok(())
|
});
|
||||||
})
|
|
||||||
.detach_and_log_err(cx);
|
session.update(cx, |session, _| match &mut session.mode {
|
||||||
|
SessionState::Building(state_task) => {
|
||||||
|
*state_task = Some(boot_task);
|
||||||
|
}
|
||||||
|
SessionState::Running(_) => {
|
||||||
|
debug_panic!("Session state should be in building because we are just starting it");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn rerun_last_session(
|
pub(crate) fn rerun_last_session(
|
||||||
|
@ -826,13 +838,24 @@ impl DebugPanel {
|
||||||
.on_click(window.listener_for(
|
.on_click(window.listener_for(
|
||||||
&running_state,
|
&running_state,
|
||||||
|this, _, _window, cx| {
|
|this, _, _window, cx| {
|
||||||
this.stop_thread(cx);
|
if this.session().read(cx).is_building() {
|
||||||
|
this.session().update(cx, |session, cx| {
|
||||||
|
session.shutdown(cx).detach()
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.stop_thread(cx);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.disabled(active_session.as_ref().is_none_or(
|
||||||
|
|session| {
|
||||||
|
session
|
||||||
|
.read(cx)
|
||||||
|
.session(cx)
|
||||||
|
.read(cx)
|
||||||
|
.is_terminated()
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
.disabled(
|
|
||||||
thread_status != ThreadStatus::Stopped
|
|
||||||
&& thread_status != ThreadStatus::Running,
|
|
||||||
)
|
|
||||||
.tooltip({
|
.tooltip({
|
||||||
let focus_handle = focus_handle.clone();
|
let focus_handle = focus_handle.clone();
|
||||||
let label = if capabilities
|
let label = if capabilities
|
||||||
|
|
|
@ -34,7 +34,7 @@ use loaded_source_list::LoadedSourceList;
|
||||||
use module_list::ModuleList;
|
use module_list::ModuleList;
|
||||||
use project::{
|
use project::{
|
||||||
DebugScenarioContext, Project, WorktreeId,
|
DebugScenarioContext, Project, WorktreeId,
|
||||||
debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus},
|
debugger::session::{self, Session, SessionEvent, SessionStateEvent, ThreadId, ThreadStatus},
|
||||||
terminals::TerminalKind,
|
terminals::TerminalKind,
|
||||||
};
|
};
|
||||||
use rpc::proto::ViewId;
|
use rpc::proto::ViewId;
|
||||||
|
@ -770,6 +770,15 @@ impl RunningState {
|
||||||
cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
|
cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
|
||||||
this.serialize_layout(window, cx);
|
this.serialize_layout(window, cx);
|
||||||
}),
|
}),
|
||||||
|
cx.subscribe(
|
||||||
|
&session,
|
||||||
|
|this, session, event: &SessionStateEvent, cx| match event {
|
||||||
|
SessionStateEvent::Shutdown if session.read(cx).is_building() => {
|
||||||
|
this.shutdown(cx);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut pane_close_subscriptions = HashMap::default();
|
let mut pane_close_subscriptions = HashMap::default();
|
||||||
|
@ -884,6 +893,7 @@ impl RunningState {
|
||||||
let weak_project = project.downgrade();
|
let weak_project = project.downgrade();
|
||||||
let weak_workspace = workspace.downgrade();
|
let weak_workspace = workspace.downgrade();
|
||||||
let is_local = project.read(cx).is_local();
|
let is_local = project.read(cx).is_local();
|
||||||
|
|
||||||
cx.spawn_in(window, async move |this, cx| {
|
cx.spawn_in(window, async move |this, cx| {
|
||||||
let DebugScenario {
|
let DebugScenario {
|
||||||
adapter,
|
adapter,
|
||||||
|
@ -1599,9 +1609,21 @@ impl RunningState {
|
||||||
})
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
|
|
||||||
self.session.update(cx, |session, cx| {
|
let is_building = self.session.update(cx, |session, cx| {
|
||||||
session.shutdown(cx).detach();
|
session.shutdown(cx).detach();
|
||||||
})
|
matches!(session.mode, session::SessionState::Building(_))
|
||||||
|
});
|
||||||
|
|
||||||
|
if is_building {
|
||||||
|
self.debug_terminal.update(cx, |terminal, cx| {
|
||||||
|
if let Some(view) = terminal.terminal.as_ref() {
|
||||||
|
view.update(cx, |view, cx| {
|
||||||
|
view.terminal()
|
||||||
|
.update(cx, |terminal, _| terminal.kill_active_task())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop_thread(&self, cx: &mut Context<Self>) {
|
pub fn stop_thread(&self, cx: &mut Context<Self>) {
|
||||||
|
|
|
@ -134,8 +134,8 @@ pub struct Watcher {
|
||||||
pub presentation_hint: Option<VariablePresentationHint>,
|
pub presentation_hint: Option<VariablePresentationHint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Mode {
|
pub enum SessionState {
|
||||||
Building,
|
Building(Option<Task<Result<()>>>),
|
||||||
Running(RunningMode),
|
Running(RunningMode),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -560,15 +560,15 @@ impl RunningMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mode {
|
impl SessionState {
|
||||||
pub(super) fn request_dap<R: LocalDapCommand>(&self, request: R) -> Task<Result<R::Response>>
|
pub(super) fn request_dap<R: LocalDapCommand>(&self, request: R) -> Task<Result<R::Response>>
|
||||||
where
|
where
|
||||||
<R::DapRequest as dap::requests::Request>::Response: 'static,
|
<R::DapRequest as dap::requests::Request>::Response: 'static,
|
||||||
<R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,
|
<R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Mode::Running(debug_adapter_client) => debug_adapter_client.request(request),
|
SessionState::Running(debug_adapter_client) => debug_adapter_client.request(request),
|
||||||
Mode::Building => Task::ready(Err(anyhow!(
|
SessionState::Building(_) => Task::ready(Err(anyhow!(
|
||||||
"no adapter running to send request: {request:?}"
|
"no adapter running to send request: {request:?}"
|
||||||
))),
|
))),
|
||||||
}
|
}
|
||||||
|
@ -577,13 +577,13 @@ impl Mode {
|
||||||
/// Did this debug session stop at least once?
|
/// Did this debug session stop at least once?
|
||||||
pub(crate) fn has_ever_stopped(&self) -> bool {
|
pub(crate) fn has_ever_stopped(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Mode::Building => false,
|
SessionState::Building(_) => false,
|
||||||
Mode::Running(running_mode) => running_mode.has_ever_stopped,
|
SessionState::Running(running_mode) => running_mode.has_ever_stopped,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stopped(&mut self) {
|
fn stopped(&mut self) {
|
||||||
if let Mode::Running(running) = self {
|
if let SessionState::Running(running) = self {
|
||||||
running.has_ever_stopped = true;
|
running.has_ever_stopped = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -660,7 +660,7 @@ type IsEnabled = bool;
|
||||||
pub struct OutputToken(pub usize);
|
pub struct OutputToken(pub usize);
|
||||||
/// Represents a current state of a single debug adapter and provides ways to mutate it.
|
/// Represents a current state of a single debug adapter and provides ways to mutate it.
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
pub mode: Mode,
|
pub mode: SessionState,
|
||||||
id: SessionId,
|
id: SessionId,
|
||||||
label: Option<SharedString>,
|
label: Option<SharedString>,
|
||||||
adapter: DebugAdapterName,
|
adapter: DebugAdapterName,
|
||||||
|
@ -828,10 +828,9 @@ impl Session {
|
||||||
BreakpointStoreEvent::SetDebugLine | BreakpointStoreEvent::ClearDebugLines => {}
|
BreakpointStoreEvent::SetDebugLine | BreakpointStoreEvent::ClearDebugLines => {}
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
// cx.on_app_quit(Self::on_app_quit).detach();
|
|
||||||
|
|
||||||
let this = Self {
|
let this = Self {
|
||||||
mode: Mode::Building,
|
mode: SessionState::Building(None),
|
||||||
id: session_id,
|
id: session_id,
|
||||||
child_session_ids: HashSet::default(),
|
child_session_ids: HashSet::default(),
|
||||||
parent_session,
|
parent_session,
|
||||||
|
@ -869,8 +868,8 @@ impl Session {
|
||||||
|
|
||||||
pub fn worktree(&self) -> Option<Entity<Worktree>> {
|
pub fn worktree(&self) -> Option<Entity<Worktree>> {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::Building => None,
|
SessionState::Building(_) => None,
|
||||||
Mode::Running(local_mode) => local_mode.worktree.upgrade(),
|
SessionState::Running(local_mode) => local_mode.worktree.upgrade(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -929,7 +928,18 @@ impl Session {
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
this.mode = Mode::Running(mode);
|
match &mut this.mode {
|
||||||
|
SessionState::Building(task) if task.is_some() => {
|
||||||
|
task.take().unwrap().detach_and_log_err(cx);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug_assert!(
|
||||||
|
this.parent_session.is_some(),
|
||||||
|
"Booting a root debug session without a boot task"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.mode = SessionState::Running(mode);
|
||||||
cx.emit(SessionStateEvent::Running);
|
cx.emit(SessionStateEvent::Running);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
@ -1022,8 +1032,8 @@ impl Session {
|
||||||
|
|
||||||
pub fn binary(&self) -> Option<&DebugAdapterBinary> {
|
pub fn binary(&self) -> Option<&DebugAdapterBinary> {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::Building => None,
|
SessionState::Building(_) => None,
|
||||||
Mode::Running(running_mode) => Some(&running_mode.binary),
|
SessionState::Running(running_mode) => Some(&running_mode.binary),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1068,26 +1078,26 @@ impl Session {
|
||||||
|
|
||||||
pub fn is_started(&self) -> bool {
|
pub fn is_started(&self) -> bool {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::Building => false,
|
SessionState::Building(_) => false,
|
||||||
Mode::Running(running) => running.is_started,
|
SessionState::Running(running) => running.is_started,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_building(&self) -> bool {
|
pub fn is_building(&self) -> bool {
|
||||||
matches!(self.mode, Mode::Building)
|
matches!(self.mode, SessionState::Building(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_running_mut(&mut self) -> Option<&mut RunningMode> {
|
pub fn as_running_mut(&mut self) -> Option<&mut RunningMode> {
|
||||||
match &mut self.mode {
|
match &mut self.mode {
|
||||||
Mode::Running(local_mode) => Some(local_mode),
|
SessionState::Running(local_mode) => Some(local_mode),
|
||||||
Mode::Building => None,
|
SessionState::Building(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_running(&self) -> Option<&RunningMode> {
|
pub fn as_running(&self) -> Option<&RunningMode> {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::Running(local_mode) => Some(local_mode),
|
SessionState::Running(local_mode) => Some(local_mode),
|
||||||
Mode::Building => None,
|
SessionState::Building(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1229,7 +1239,7 @@ impl Session {
|
||||||
let adapter_id = self.adapter().to_string();
|
let adapter_id = self.adapter().to_string();
|
||||||
let request = Initialize { adapter_id };
|
let request = Initialize { adapter_id };
|
||||||
|
|
||||||
let Mode::Running(running) = &self.mode else {
|
let SessionState::Running(running) = &self.mode else {
|
||||||
return Task::ready(Err(anyhow!(
|
return Task::ready(Err(anyhow!(
|
||||||
"Cannot send initialize request, task still building"
|
"Cannot send initialize request, task still building"
|
||||||
)));
|
)));
|
||||||
|
@ -1278,10 +1288,12 @@ impl Session {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Task<Result<()>> {
|
) -> Task<Result<()>> {
|
||||||
match &self.mode {
|
match &self.mode {
|
||||||
Mode::Running(local_mode) => {
|
SessionState::Running(local_mode) => {
|
||||||
local_mode.initialize_sequence(&self.capabilities, initialize_rx, dap_store, cx)
|
local_mode.initialize_sequence(&self.capabilities, initialize_rx, dap_store, cx)
|
||||||
}
|
}
|
||||||
Mode::Building => Task::ready(Err(anyhow!("cannot initialize, still building"))),
|
SessionState::Building(_) => {
|
||||||
|
Task::ready(Err(anyhow!("cannot initialize, still building")))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1292,7 +1304,7 @@ impl Session {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
match &mut self.mode {
|
match &mut self.mode {
|
||||||
Mode::Running(local_mode) => {
|
SessionState::Running(local_mode) => {
|
||||||
if !matches!(
|
if !matches!(
|
||||||
self.thread_states.thread_state(active_thread_id),
|
self.thread_states.thread_state(active_thread_id),
|
||||||
Some(ThreadStatus::Stopped)
|
Some(ThreadStatus::Stopped)
|
||||||
|
@ -1316,7 +1328,7 @@ impl Session {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
Mode::Building => {}
|
SessionState::Building(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1596,7 +1608,7 @@ impl Session {
|
||||||
|
|
||||||
fn request_inner<T: LocalDapCommand + PartialEq + Eq + Hash>(
|
fn request_inner<T: LocalDapCommand + PartialEq + Eq + Hash>(
|
||||||
capabilities: &Capabilities,
|
capabilities: &Capabilities,
|
||||||
mode: &Mode,
|
mode: &SessionState,
|
||||||
request: T,
|
request: T,
|
||||||
process_result: impl FnOnce(
|
process_result: impl FnOnce(
|
||||||
&mut Self,
|
&mut Self,
|
||||||
|
@ -1916,28 +1928,36 @@ impl Session {
|
||||||
self.thread_states.exit_all_threads();
|
self.thread_states.exit_all_threads();
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|
||||||
let task = if self
|
let task = match &mut self.mode {
|
||||||
.capabilities
|
SessionState::Running(_) => {
|
||||||
.supports_terminate_request
|
if self
|
||||||
.unwrap_or_default()
|
.capabilities
|
||||||
{
|
.supports_terminate_request
|
||||||
self.request(
|
.unwrap_or_default()
|
||||||
TerminateCommand {
|
{
|
||||||
restart: Some(false),
|
self.request(
|
||||||
},
|
TerminateCommand {
|
||||||
Self::clear_active_debug_line_response,
|
restart: Some(false),
|
||||||
cx,
|
},
|
||||||
)
|
Self::clear_active_debug_line_response,
|
||||||
} else {
|
cx,
|
||||||
self.request(
|
)
|
||||||
DisconnectCommand {
|
} else {
|
||||||
restart: Some(false),
|
self.request(
|
||||||
terminate_debuggee: Some(true),
|
DisconnectCommand {
|
||||||
suspend_debuggee: Some(false),
|
restart: Some(false),
|
||||||
},
|
terminate_debuggee: Some(true),
|
||||||
Self::clear_active_debug_line_response,
|
suspend_debuggee: Some(false),
|
||||||
cx,
|
},
|
||||||
)
|
Self::clear_active_debug_line_response,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SessionState::Building(build_task) => {
|
||||||
|
build_task.take();
|
||||||
|
Task::ready(Some(()))
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
cx.emit(SessionStateEvent::Shutdown);
|
cx.emit(SessionStateEvent::Shutdown);
|
||||||
|
@ -1987,8 +2007,8 @@ impl Session {
|
||||||
|
|
||||||
pub fn adapter_client(&self) -> Option<Arc<DebugAdapterClient>> {
|
pub fn adapter_client(&self) -> Option<Arc<DebugAdapterClient>> {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::Running(ref local) => Some(local.client.clone()),
|
SessionState::Running(ref local) => Some(local.client.clone()),
|
||||||
Mode::Building => None,
|
SessionState::Building(_) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2452,7 +2472,7 @@ impl Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_attached(&self) -> bool {
|
pub fn is_attached(&self) -> bool {
|
||||||
let Mode::Running(local_mode) = &self.mode else {
|
let SessionState::Running(local_mode) = &self.mode else {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
local_mode.binary.request_args.request == StartDebuggingRequestArgumentsRequest::Attach
|
local_mode.binary.request_args.request == StartDebuggingRequestArgumentsRequest::Attach
|
||||||
|
|
|
@ -121,6 +121,10 @@ impl PtyProcessInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn kill_current_process(&mut self) -> bool {
|
||||||
|
self.refresh().map_or(false, |process| process.kill())
|
||||||
|
}
|
||||||
|
|
||||||
fn load(&mut self) -> Option<ProcessInfo> {
|
fn load(&mut self) -> Option<ProcessInfo> {
|
||||||
let process = self.refresh()?;
|
let process = self.refresh()?;
|
||||||
let cwd = process.cwd().map_or(PathBuf::new(), |p| p.to_owned());
|
let cwd = process.cwd().map_or(PathBuf::new(), |p| p.to_owned());
|
||||||
|
|
|
@ -1824,6 +1824,14 @@ impl Terminal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn kill_active_task(&mut self) {
|
||||||
|
if let Some(task) = self.task() {
|
||||||
|
if task.status == TaskStatus::Running {
|
||||||
|
self.pty_info.kill_current_process();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn task(&self) -> Option<&TaskState> {
|
pub fn task(&self) -> Option<&TaskState> {
|
||||||
self.task.as_ref()
|
self.task.as_ref()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue