feat(sessions): resurrect sessions through the session-manager (and plugin API) (#2902)
* working with table and scrolling * ui and functionality complete * fix formatting * refactor: background jobs * style(fmt): rustfmt
This commit is contained in:
parent
37bc6364fa
commit
4c6b03acc1
16 changed files with 737 additions and 97 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -3325,6 +3325,7 @@ dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
"chrono",
|
"chrono",
|
||||||
"fuzzy-matcher",
|
"fuzzy-matcher",
|
||||||
|
"humantime",
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"zellij-tile",
|
"zellij-tile",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,4 @@ zellij-tile = { path = "../../zellij-tile" }
|
||||||
chrono = "0.4.0"
|
chrono = "0.4.0"
|
||||||
fuzzy-matcher = "0.3.7"
|
fuzzy-matcher = "0.3.7"
|
||||||
unicode-width = "0.1.10"
|
unicode-width = "0.1.10"
|
||||||
|
humantime = "2.1.0"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod resurrectable_sessions;
|
||||||
mod session_list;
|
mod session_list;
|
||||||
mod ui;
|
mod ui;
|
||||||
use zellij_tile::prelude::*;
|
use zellij_tile::prelude::*;
|
||||||
|
|
@ -5,18 +6,24 @@ use zellij_tile::prelude::*;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use ui::{
|
use ui::{
|
||||||
components::{render_controls_line, render_new_session_line, render_prompt, Colors},
|
components::{
|
||||||
|
render_controls_line, render_new_session_line, render_prompt, render_resurrection_toggle,
|
||||||
|
Colors,
|
||||||
|
},
|
||||||
SessionUiInfo,
|
SessionUiInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use resurrectable_sessions::ResurrectableSessions;
|
||||||
use session_list::SessionList;
|
use session_list::SessionList;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct State {
|
struct State {
|
||||||
session_name: Option<String>,
|
session_name: Option<String>,
|
||||||
sessions: SessionList,
|
sessions: SessionList,
|
||||||
|
resurrectable_sessions: ResurrectableSessions,
|
||||||
search_term: String,
|
search_term: String,
|
||||||
new_session_name: Option<String>,
|
new_session_name: Option<String>,
|
||||||
|
browsing_resurrection_sessions: bool,
|
||||||
colors: Colors,
|
colors: Colors,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +52,9 @@ impl ZellijPlugin for State {
|
||||||
Event::PermissionRequestResult(_result) => {
|
Event::PermissionRequestResult(_result) => {
|
||||||
should_render = true;
|
should_render = true;
|
||||||
},
|
},
|
||||||
Event::SessionUpdate(session_infos) => {
|
Event::SessionUpdate(session_infos, resurrectable_session_list) => {
|
||||||
|
self.resurrectable_sessions
|
||||||
|
.update(resurrectable_session_list);
|
||||||
self.update_session_infos(session_infos);
|
self.update_session_infos(session_infos);
|
||||||
should_render = true;
|
should_render = true;
|
||||||
},
|
},
|
||||||
|
|
@ -55,6 +64,11 @@ impl ZellijPlugin for State {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render(&mut self, rows: usize, cols: usize) {
|
fn render(&mut self, rows: usize, cols: usize) {
|
||||||
|
if self.browsing_resurrection_sessions {
|
||||||
|
self.resurrectable_sessions.render(rows, cols);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
render_resurrection_toggle(cols, false);
|
||||||
render_prompt(
|
render_prompt(
|
||||||
self.new_session_name.is_some(),
|
self.new_session_name.is_some(),
|
||||||
&self.search_term,
|
&self.search_term,
|
||||||
|
|
@ -94,12 +108,16 @@ impl State {
|
||||||
}
|
}
|
||||||
should_render = true;
|
should_render = true;
|
||||||
} else if let Key::Down = key {
|
} else if let Key::Down = key {
|
||||||
if self.new_session_name.is_none() {
|
if self.browsing_resurrection_sessions {
|
||||||
|
self.resurrectable_sessions.move_selection_down();
|
||||||
|
} else if self.new_session_name.is_none() {
|
||||||
self.sessions.move_selection_down();
|
self.sessions.move_selection_down();
|
||||||
}
|
}
|
||||||
should_render = true;
|
should_render = true;
|
||||||
} else if let Key::Up = key {
|
} else if let Key::Up = key {
|
||||||
if self.new_session_name.is_none() {
|
if self.browsing_resurrection_sessions {
|
||||||
|
self.resurrectable_sessions.move_selection_up();
|
||||||
|
} else if self.new_session_name.is_none() {
|
||||||
self.sessions.move_selection_up();
|
self.sessions.move_selection_up();
|
||||||
}
|
}
|
||||||
should_render = true;
|
should_render = true;
|
||||||
|
|
@ -108,6 +126,8 @@ impl State {
|
||||||
self.handle_selection();
|
self.handle_selection();
|
||||||
} else if let Some(new_session_name) = self.new_session_name.as_mut() {
|
} else if let Some(new_session_name) = self.new_session_name.as_mut() {
|
||||||
new_session_name.push(character);
|
new_session_name.push(character);
|
||||||
|
} else if self.browsing_resurrection_sessions {
|
||||||
|
self.resurrectable_sessions.handle_character(character);
|
||||||
} else {
|
} else {
|
||||||
self.search_term.push(character);
|
self.search_term.push(character);
|
||||||
self.sessions
|
self.sessions
|
||||||
|
|
@ -121,6 +141,8 @@ impl State {
|
||||||
} else {
|
} else {
|
||||||
new_session_name.pop();
|
new_session_name.pop();
|
||||||
}
|
}
|
||||||
|
} else if self.browsing_resurrection_sessions {
|
||||||
|
self.resurrectable_sessions.handle_backspace();
|
||||||
} else {
|
} else {
|
||||||
self.search_term.pop();
|
self.search_term.pop();
|
||||||
self.sessions
|
self.sessions
|
||||||
|
|
@ -153,13 +175,33 @@ impl State {
|
||||||
hide_self();
|
hide_self();
|
||||||
}
|
}
|
||||||
should_render = true;
|
should_render = true;
|
||||||
|
} else if let Key::BackTab = key {
|
||||||
|
self.browsing_resurrection_sessions = !self.browsing_resurrection_sessions;
|
||||||
|
should_render = true;
|
||||||
|
} else if let Key::Delete = key {
|
||||||
|
if self.browsing_resurrection_sessions {
|
||||||
|
self.resurrectable_sessions.delete_selected_session();
|
||||||
|
should_render = true;
|
||||||
|
}
|
||||||
|
} else if let Key::Ctrl('d') = key {
|
||||||
|
if self.browsing_resurrection_sessions {
|
||||||
|
self.resurrectable_sessions
|
||||||
|
.show_delete_all_sessions_warning();
|
||||||
|
should_render = true;
|
||||||
|
}
|
||||||
} else if let Key::Esc = key {
|
} else if let Key::Esc = key {
|
||||||
hide_self();
|
hide_self();
|
||||||
}
|
}
|
||||||
should_render
|
should_render
|
||||||
}
|
}
|
||||||
fn handle_selection(&mut self) {
|
fn handle_selection(&mut self) {
|
||||||
if let Some(new_session_name) = &self.new_session_name {
|
if self.browsing_resurrection_sessions {
|
||||||
|
if let Some(session_name_to_resurrect) =
|
||||||
|
self.resurrectable_sessions.get_selected_session_name()
|
||||||
|
{
|
||||||
|
switch_session(Some(&session_name_to_resurrect));
|
||||||
|
}
|
||||||
|
} else if let Some(new_session_name) = &self.new_session_name {
|
||||||
if new_session_name.is_empty() {
|
if new_session_name.is_empty() {
|
||||||
switch_session(None);
|
switch_session(None);
|
||||||
} else if self.session_name.as_ref() == Some(new_session_name) {
|
} else if self.session_name.as_ref() == Some(new_session_name) {
|
||||||
|
|
|
||||||
338
default-plugins/session-manager/src/resurrectable_sessions.rs
Normal file
338
default-plugins/session-manager/src/resurrectable_sessions.rs
Normal file
|
|
@ -0,0 +1,338 @@
|
||||||
|
use fuzzy_matcher::skim::SkimMatcherV2;
|
||||||
|
use fuzzy_matcher::FuzzyMatcher;
|
||||||
|
use humantime::format_duration;
|
||||||
|
|
||||||
|
use crate::ui::components::render_resurrection_toggle;
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use zellij_tile::shim::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ResurrectableSessions {
|
||||||
|
pub all_resurrectable_sessions: Vec<(String, Duration)>,
|
||||||
|
pub selected_index: Option<usize>,
|
||||||
|
pub selected_search_index: Option<usize>,
|
||||||
|
pub search_results: Vec<SearchResult>,
|
||||||
|
pub is_searching: bool,
|
||||||
|
pub search_term: String,
|
||||||
|
pub delete_all_dead_sessions_warning: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResurrectableSessions {
|
||||||
|
pub fn update(&mut self, mut list: Vec<(String, Duration)>) {
|
||||||
|
list.sort_by(|a, b| a.1.cmp(&b.1));
|
||||||
|
self.all_resurrectable_sessions = list;
|
||||||
|
}
|
||||||
|
pub fn render(&self, rows: usize, columns: usize) {
|
||||||
|
if self.delete_all_dead_sessions_warning {
|
||||||
|
self.render_delete_all_sessions_warning(rows, columns);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
render_resurrection_toggle(columns, true);
|
||||||
|
let search_indication = Text::new(format!("> {}_", self.search_term)).color_range(1, ..);
|
||||||
|
let table_rows = rows.saturating_sub(3);
|
||||||
|
let table_columns = columns;
|
||||||
|
let table = if self.is_searching {
|
||||||
|
self.render_search_results(table_rows, columns)
|
||||||
|
} else {
|
||||||
|
self.render_all_entries(table_rows, columns)
|
||||||
|
};
|
||||||
|
print_text_with_coordinates(search_indication, 0, 0, None, None);
|
||||||
|
print_table_with_coordinates(table, 0, 1, Some(table_columns), Some(table_rows));
|
||||||
|
self.render_controls_line(rows);
|
||||||
|
}
|
||||||
|
fn render_search_results(&self, table_rows: usize, _table_columns: usize) -> Table {
|
||||||
|
let mut table = Table::new().add_row(vec![" ", " ", " "]); // skip the title row
|
||||||
|
let (first_row_index_to_render, last_row_index_to_render) = self.range_to_render(
|
||||||
|
table_rows,
|
||||||
|
self.search_results.len(),
|
||||||
|
self.selected_search_index,
|
||||||
|
);
|
||||||
|
for i in first_row_index_to_render..last_row_index_to_render {
|
||||||
|
if let Some(search_result) = self.search_results.get(i) {
|
||||||
|
let is_selected = Some(i) == self.selected_search_index;
|
||||||
|
let mut table_cells = vec![
|
||||||
|
self.render_session_name(
|
||||||
|
&search_result.session_name,
|
||||||
|
Some(search_result.indices.clone()),
|
||||||
|
),
|
||||||
|
self.render_ctime(&search_result.ctime),
|
||||||
|
self.render_more_indication_or_enter_as_needed(
|
||||||
|
i,
|
||||||
|
first_row_index_to_render,
|
||||||
|
last_row_index_to_render,
|
||||||
|
self.search_results.len(),
|
||||||
|
is_selected,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
if is_selected {
|
||||||
|
table_cells = table_cells.drain(..).map(|t| t.selected()).collect();
|
||||||
|
}
|
||||||
|
table = table.add_styled_row(table_cells);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table
|
||||||
|
}
|
||||||
|
fn render_all_entries(&self, table_rows: usize, _table_columns: usize) -> Table {
|
||||||
|
let mut table = Table::new().add_row(vec![" ", " ", " "]); // skip the title row
|
||||||
|
let (first_row_index_to_render, last_row_index_to_render) = self.range_to_render(
|
||||||
|
table_rows,
|
||||||
|
self.all_resurrectable_sessions.len(),
|
||||||
|
self.selected_index,
|
||||||
|
);
|
||||||
|
for i in first_row_index_to_render..last_row_index_to_render {
|
||||||
|
if let Some(session) = self.all_resurrectable_sessions.get(i) {
|
||||||
|
let is_selected = Some(i) == self.selected_index;
|
||||||
|
let mut table_cells = vec![
|
||||||
|
self.render_session_name(&session.0, None),
|
||||||
|
self.render_ctime(&session.1),
|
||||||
|
self.render_more_indication_or_enter_as_needed(
|
||||||
|
i,
|
||||||
|
first_row_index_to_render,
|
||||||
|
last_row_index_to_render,
|
||||||
|
self.all_resurrectable_sessions.len(),
|
||||||
|
is_selected,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
if is_selected {
|
||||||
|
table_cells = table_cells.drain(..).map(|t| t.selected()).collect();
|
||||||
|
}
|
||||||
|
table = table.add_styled_row(table_cells);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table
|
||||||
|
}
|
||||||
|
fn render_delete_all_sessions_warning(&self, rows: usize, columns: usize) {
|
||||||
|
if rows == 0 || columns == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let session_count = self.all_resurrectable_sessions.len();
|
||||||
|
let session_count_len = session_count.to_string().chars().count();
|
||||||
|
let warning_description_text =
|
||||||
|
format!("This will delete {} resurrectable sessions", session_count,);
|
||||||
|
let confirmation_text = "Are you sure? (y/n)";
|
||||||
|
let warning_y_location = (rows / 2).saturating_sub(1);
|
||||||
|
let confirmation_y_location = (rows / 2) + 1;
|
||||||
|
let warning_x_location =
|
||||||
|
columns.saturating_sub(warning_description_text.chars().count()) / 2;
|
||||||
|
let confirmation_x_location = columns.saturating_sub(confirmation_text.chars().count()) / 2;
|
||||||
|
print_text_with_coordinates(
|
||||||
|
Text::new(warning_description_text).color_range(0, 17..18 + session_count_len),
|
||||||
|
warning_x_location,
|
||||||
|
warning_y_location,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
print_text_with_coordinates(
|
||||||
|
Text::new(confirmation_text).color_indices(2, vec![15, 17]),
|
||||||
|
confirmation_x_location,
|
||||||
|
confirmation_y_location,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
fn range_to_render(
|
||||||
|
&self,
|
||||||
|
table_rows: usize,
|
||||||
|
results_len: usize,
|
||||||
|
selected_index: Option<usize>,
|
||||||
|
) -> (usize, usize) {
|
||||||
|
if table_rows <= results_len {
|
||||||
|
let row_count_to_render = table_rows.saturating_sub(1); // 1 for the title
|
||||||
|
let first_row_index_to_render = selected_index
|
||||||
|
.unwrap_or(0)
|
||||||
|
.saturating_sub(row_count_to_render / 2);
|
||||||
|
let last_row_index_to_render = first_row_index_to_render + row_count_to_render;
|
||||||
|
(first_row_index_to_render, last_row_index_to_render)
|
||||||
|
} else {
|
||||||
|
let first_row_index_to_render = 0;
|
||||||
|
let last_row_index_to_render = results_len;
|
||||||
|
(first_row_index_to_render, last_row_index_to_render)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render_session_name(&self, session_name: &str, indices: Option<Vec<usize>>) -> Text {
|
||||||
|
let text = Text::new(&session_name).color_range(0, ..);
|
||||||
|
match indices {
|
||||||
|
Some(indices) => text.color_indices(1, indices),
|
||||||
|
None => text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render_ctime(&self, ctime: &Duration) -> Text {
|
||||||
|
let duration = format_duration(ctime.clone()).to_string();
|
||||||
|
let duration_parts = duration.split_whitespace();
|
||||||
|
let mut formatted_duration = String::new();
|
||||||
|
for part in duration_parts {
|
||||||
|
if !part.ends_with('s') {
|
||||||
|
if !formatted_duration.is_empty() {
|
||||||
|
formatted_duration.push(' ');
|
||||||
|
}
|
||||||
|
formatted_duration.push_str(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if formatted_duration.is_empty() {
|
||||||
|
formatted_duration.push_str("<1m");
|
||||||
|
}
|
||||||
|
let duration_len = formatted_duration.chars().count();
|
||||||
|
Text::new(format!("Created {} ago", formatted_duration)).color_range(2, 8..9 + duration_len)
|
||||||
|
}
|
||||||
|
fn render_more_indication_or_enter_as_needed(
|
||||||
|
&self,
|
||||||
|
i: usize,
|
||||||
|
first_row_index_to_render: usize,
|
||||||
|
last_row_index_to_render: usize,
|
||||||
|
results_len: usize,
|
||||||
|
is_selected: bool,
|
||||||
|
) -> Text {
|
||||||
|
if is_selected {
|
||||||
|
Text::new(format!("<ENTER> - Resurrect Session")).color_range(3, 0..7)
|
||||||
|
} else if i == first_row_index_to_render && i > 0 {
|
||||||
|
Text::new(format!("+ {} more", first_row_index_to_render)).color_range(1, ..)
|
||||||
|
} else if i == last_row_index_to_render.saturating_sub(1)
|
||||||
|
&& last_row_index_to_render < results_len
|
||||||
|
{
|
||||||
|
Text::new(format!(
|
||||||
|
"+ {} more",
|
||||||
|
results_len.saturating_sub(last_row_index_to_render)
|
||||||
|
))
|
||||||
|
.color_range(1, ..)
|
||||||
|
} else {
|
||||||
|
Text::new(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn render_controls_line(&self, rows: usize) {
|
||||||
|
let controls_line = Text::new(format!(
|
||||||
|
"Help: <↓↑> - Navigate, <DEL> - Delete Session, <Ctrl d> - Delete all sessions"
|
||||||
|
))
|
||||||
|
.color_range(3, 6..10)
|
||||||
|
.color_range(3, 23..29)
|
||||||
|
.color_range(3, 47..56);
|
||||||
|
print_text_with_coordinates(controls_line, 0, rows.saturating_sub(1), None, None);
|
||||||
|
}
|
||||||
|
pub fn move_selection_down(&mut self) {
|
||||||
|
if self.is_searching {
|
||||||
|
if let Some(selected_index) = self.selected_search_index.as_mut() {
|
||||||
|
if *selected_index == self.search_results.len().saturating_sub(1) {
|
||||||
|
*selected_index = 0;
|
||||||
|
} else {
|
||||||
|
*selected_index = *selected_index + 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.selected_search_index = Some(0);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(selected_index) = self.selected_index.as_mut() {
|
||||||
|
if *selected_index == self.all_resurrectable_sessions.len().saturating_sub(1) {
|
||||||
|
*selected_index = 0;
|
||||||
|
} else {
|
||||||
|
*selected_index = *selected_index + 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.selected_index = Some(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn move_selection_up(&mut self) {
|
||||||
|
if self.is_searching {
|
||||||
|
if let Some(selected_index) = self.selected_search_index.as_mut() {
|
||||||
|
if *selected_index == 0 {
|
||||||
|
*selected_index = self.search_results.len().saturating_sub(1);
|
||||||
|
} else {
|
||||||
|
*selected_index = selected_index.saturating_sub(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.selected_search_index = Some(self.search_results.len().saturating_sub(1));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Some(selected_index) = self.selected_index.as_mut() {
|
||||||
|
if *selected_index == 0 {
|
||||||
|
*selected_index = self.all_resurrectable_sessions.len().saturating_sub(1);
|
||||||
|
} else {
|
||||||
|
*selected_index = selected_index.saturating_sub(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.selected_index = Some(self.all_resurrectable_sessions.len().saturating_sub(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_selected_session_name(&self) -> Option<String> {
|
||||||
|
if self.is_searching {
|
||||||
|
self.selected_search_index
|
||||||
|
.and_then(|i| self.search_results.get(i))
|
||||||
|
.map(|search_result| search_result.session_name.clone())
|
||||||
|
} else {
|
||||||
|
self.selected_index
|
||||||
|
.and_then(|i| self.all_resurrectable_sessions.get(i))
|
||||||
|
.map(|session_name_and_creation_time| session_name_and_creation_time.0.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn delete_selected_session(&mut self) {
|
||||||
|
self.selected_index
|
||||||
|
.and_then(|i| {
|
||||||
|
if self.all_resurrectable_sessions.len() > i {
|
||||||
|
// optimistic update
|
||||||
|
if i == 0 {
|
||||||
|
self.selected_index = None;
|
||||||
|
} else if i == self.all_resurrectable_sessions.len().saturating_sub(1) {
|
||||||
|
self.selected_index = Some(i.saturating_sub(1));
|
||||||
|
}
|
||||||
|
Some(self.all_resurrectable_sessions.remove(i))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|session_name_and_creation_time| {
|
||||||
|
delete_dead_session(&session_name_and_creation_time.0)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fn delete_all_sessions(&mut self) {
|
||||||
|
// optimistic update
|
||||||
|
self.all_resurrectable_sessions = vec![];
|
||||||
|
self.delete_all_dead_sessions_warning = false;
|
||||||
|
delete_all_dead_sessions();
|
||||||
|
}
|
||||||
|
pub fn show_delete_all_sessions_warning(&mut self) {
|
||||||
|
self.delete_all_dead_sessions_warning = true;
|
||||||
|
}
|
||||||
|
pub fn handle_character(&mut self, character: char) {
|
||||||
|
if self.delete_all_dead_sessions_warning && character == 'y' {
|
||||||
|
self.delete_all_sessions();
|
||||||
|
} else if self.delete_all_dead_sessions_warning && character == 'n' {
|
||||||
|
self.delete_all_dead_sessions_warning = false;
|
||||||
|
} else {
|
||||||
|
self.search_term.push(character);
|
||||||
|
self.update_search_term();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn handle_backspace(&mut self) {
|
||||||
|
self.search_term.pop();
|
||||||
|
self.update_search_term();
|
||||||
|
}
|
||||||
|
fn update_search_term(&mut self) {
|
||||||
|
let mut matches = vec![];
|
||||||
|
let matcher = SkimMatcherV2::default().use_cache(true);
|
||||||
|
for (session_name, ctime) in &self.all_resurrectable_sessions {
|
||||||
|
if let Some((score, indices)) = matcher.fuzzy_indices(&session_name, &self.search_term)
|
||||||
|
{
|
||||||
|
matches.push(SearchResult {
|
||||||
|
session_name: session_name.to_owned(),
|
||||||
|
ctime: ctime.clone(),
|
||||||
|
score,
|
||||||
|
indices,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matches.sort_by(|a, b| b.score.cmp(&a.score));
|
||||||
|
self.search_results = matches;
|
||||||
|
self.is_searching = !self.search_term.is_empty();
|
||||||
|
self.selected_search_index = Some(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SearchResult {
|
||||||
|
score: i64,
|
||||||
|
indices: Vec<usize>,
|
||||||
|
session_name: String,
|
||||||
|
ctime: Duration,
|
||||||
|
}
|
||||||
|
|
@ -478,12 +478,63 @@ pub fn minimize_lines(
|
||||||
pub fn render_prompt(typing_session_name: bool, search_term: &str, colors: Colors) {
|
pub fn render_prompt(typing_session_name: bool, search_term: &str, colors: Colors) {
|
||||||
if !typing_session_name {
|
if !typing_session_name {
|
||||||
let prompt = colors.bold(&format!("> {}_", search_term));
|
let prompt = colors.bold(&format!("> {}_", search_term));
|
||||||
println!("{}\n", prompt);
|
println!("\u{1b}[H{}\n", prompt);
|
||||||
} else {
|
} else {
|
||||||
println!("\n");
|
println!("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render_resurrection_toggle(cols: usize, resurrection_screen_is_active: bool) {
|
||||||
|
let key_indication_text = "<TAB>";
|
||||||
|
let running_sessions_text = "Running";
|
||||||
|
let exited_sessions_text = "Exited";
|
||||||
|
let key_indication_len = key_indication_text.chars().count() + 1;
|
||||||
|
let first_ribbon_length = running_sessions_text.chars().count() + 4;
|
||||||
|
let second_ribbon_length = exited_sessions_text.chars().count() + 4;
|
||||||
|
let key_indication_x =
|
||||||
|
cols.saturating_sub(key_indication_len + first_ribbon_length + second_ribbon_length);
|
||||||
|
let first_ribbon_x = key_indication_x + key_indication_len;
|
||||||
|
let second_ribbon_x = first_ribbon_x + first_ribbon_length;
|
||||||
|
print_text_with_coordinates(
|
||||||
|
Text::new(key_indication_text).color_range(3, ..),
|
||||||
|
key_indication_x,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
if resurrection_screen_is_active {
|
||||||
|
print_ribbon_with_coordinates(
|
||||||
|
Text::new(running_sessions_text),
|
||||||
|
first_ribbon_x,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
print_ribbon_with_coordinates(
|
||||||
|
Text::new(exited_sessions_text).selected(),
|
||||||
|
second_ribbon_x,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
print_ribbon_with_coordinates(
|
||||||
|
Text::new(running_sessions_text).selected(),
|
||||||
|
first_ribbon_x,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
print_ribbon_with_coordinates(
|
||||||
|
Text::new(exited_sessions_text),
|
||||||
|
second_ribbon_x,
|
||||||
|
0,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render_new_session_line(session_name: &Option<String>, is_searching: bool, colors: Colors) {
|
pub fn render_new_session_line(session_name: &Option<String>, is_searching: bool, colors: Colors) {
|
||||||
if is_searching {
|
if is_searching {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
use zellij_utils::async_std::task;
|
use zellij_utils::async_std::task;
|
||||||
use zellij_utils::consts::{
|
use zellij_utils::consts::{
|
||||||
session_info_cache_file_name, session_info_folder_for_session, session_layout_cache_file_name,
|
session_info_cache_file_name, session_info_folder_for_session, session_layout_cache_file_name,
|
||||||
ZELLIJ_SOCK_DIR,
|
ZELLIJ_SESSION_INFO_CACHE_DIR, ZELLIJ_SOCK_DIR,
|
||||||
};
|
};
|
||||||
use zellij_utils::data::{Event, HttpVerb, SessionInfo};
|
use zellij_utils::data::{Event, HttpVerb, SessionInfo};
|
||||||
use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType};
|
use zellij_utils::errors::{prelude::*, BackgroundJobContext, ContextType};
|
||||||
use zellij_utils::surf::{
|
use zellij_utils::surf::{
|
||||||
self,
|
|
||||||
http::{Method, Url},
|
http::{Method, Url},
|
||||||
RequestBuilder,
|
RequestBuilder,
|
||||||
};
|
};
|
||||||
|
|
@ -35,7 +34,7 @@ pub enum BackgroundJob {
|
||||||
StopPluginLoadingAnimation(u32), // u32 - plugin_id
|
StopPluginLoadingAnimation(u32), // u32 - plugin_id
|
||||||
ReadAllSessionInfosOnMachine, // u32 - plugin_id
|
ReadAllSessionInfosOnMachine, // u32 - plugin_id
|
||||||
ReportSessionInfo(String, SessionInfo), // String - session name
|
ReportSessionInfo(String, SessionInfo), // String - session name
|
||||||
ReportLayoutInfo((String, BTreeMap<String, String>)), // HashMap<file_name, pane_contents>
|
ReportLayoutInfo((String, BTreeMap<String, String>)), // BTreeMap<file_name, pane_contents>
|
||||||
RunCommand(
|
RunCommand(
|
||||||
PluginId,
|
PluginId,
|
||||||
ClientId,
|
ClientId,
|
||||||
|
|
@ -163,89 +162,23 @@ pub(crate) fn background_jobs_main(bus: Bus<BackgroundJob>) -> Result<()> {
|
||||||
let current_session_layout = current_session_layout.clone();
|
let current_session_layout = current_session_layout.clone();
|
||||||
async move {
|
async move {
|
||||||
loop {
|
loop {
|
||||||
// write state of current session
|
|
||||||
|
|
||||||
// write it to disk
|
|
||||||
let current_session_name =
|
let current_session_name =
|
||||||
current_session_name.lock().unwrap().to_string();
|
current_session_name.lock().unwrap().to_string();
|
||||||
let metadata_cache_file_name =
|
|
||||||
session_info_cache_file_name(¤t_session_name);
|
|
||||||
let current_session_info = current_session_info.lock().unwrap().clone();
|
let current_session_info = current_session_info.lock().unwrap().clone();
|
||||||
let (current_session_layout, layout_files_to_write) =
|
let current_session_layout =
|
||||||
current_session_layout.lock().unwrap().clone();
|
current_session_layout.lock().unwrap().clone();
|
||||||
let _wrote_metadata_file = std::fs::create_dir_all(
|
write_session_state_to_disk(
|
||||||
session_info_folder_for_session(¤t_session_name).as_path(),
|
current_session_name.clone(),
|
||||||
)
|
current_session_info,
|
||||||
.and_then(|_| std::fs::File::create(metadata_cache_file_name))
|
current_session_layout,
|
||||||
.and_then(|mut f| write!(f, "{}", current_session_info.to_string()));
|
|
||||||
|
|
||||||
if !current_session_layout.is_empty() {
|
|
||||||
let layout_cache_file_name =
|
|
||||||
session_layout_cache_file_name(¤t_session_name);
|
|
||||||
let _wrote_layout_file = std::fs::create_dir_all(
|
|
||||||
session_info_folder_for_session(¤t_session_name)
|
|
||||||
.as_path(),
|
|
||||||
)
|
|
||||||
.and_then(|_| std::fs::File::create(layout_cache_file_name))
|
|
||||||
.and_then(|mut f| write!(f, "{}", current_session_layout))
|
|
||||||
.and_then(|_| {
|
|
||||||
let session_info_folder =
|
|
||||||
session_info_folder_for_session(¤t_session_name);
|
|
||||||
for (external_file_name, external_file_contents) in
|
|
||||||
layout_files_to_write
|
|
||||||
{
|
|
||||||
std::fs::File::create(
|
|
||||||
session_info_folder.join(external_file_name),
|
|
||||||
)
|
|
||||||
.and_then(|mut f| write!(f, "{}", external_file_contents))
|
|
||||||
.unwrap_or_else(
|
|
||||||
|e| {
|
|
||||||
log::error!(
|
|
||||||
"Failed to write layout metadata file: {:?}",
|
|
||||||
e
|
|
||||||
);
|
);
|
||||||
},
|
let session_infos_on_machine =
|
||||||
);
|
read_other_live_session_states(¤t_session_name);
|
||||||
}
|
let resurrectable_sessions =
|
||||||
Ok(())
|
find_resurrectable_sessions(&session_infos_on_machine);
|
||||||
});
|
|
||||||
}
|
|
||||||
// start a background job (if not already running) that'll periodically read this and other
|
|
||||||
// sesion infos and report back
|
|
||||||
|
|
||||||
// read state of all sessions
|
|
||||||
let mut other_session_names = vec![];
|
|
||||||
let mut session_infos_on_machine = BTreeMap::new();
|
|
||||||
// we do this so that the session infos will be actual and we're
|
|
||||||
// reasonably sure their session is running
|
|
||||||
if let Ok(files) = fs::read_dir(&*ZELLIJ_SOCK_DIR) {
|
|
||||||
files.for_each(|file| {
|
|
||||||
if let Ok(file) = file {
|
|
||||||
if let Ok(file_name) = file.file_name().into_string() {
|
|
||||||
if file.file_type().unwrap().is_socket() {
|
|
||||||
other_session_names.push(file_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for session_name in other_session_names {
|
|
||||||
let session_cache_file_name =
|
|
||||||
session_info_cache_file_name(&session_name);
|
|
||||||
if let Ok(raw_session_info) =
|
|
||||||
fs::read_to_string(&session_cache_file_name)
|
|
||||||
{
|
|
||||||
if let Ok(session_info) = SessionInfo::from_string(
|
|
||||||
&raw_session_info,
|
|
||||||
¤t_session_name,
|
|
||||||
) {
|
|
||||||
session_infos_on_machine.insert(session_name, session_info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let _ = senders.send_to_screen(ScreenInstruction::UpdateSessionInfos(
|
let _ = senders.send_to_screen(ScreenInstruction::UpdateSessionInfos(
|
||||||
session_infos_on_machine,
|
session_infos_on_machine,
|
||||||
|
resurrectable_sessions,
|
||||||
));
|
));
|
||||||
let _ = senders.send_to_screen(ScreenInstruction::DumpLayoutToHd);
|
let _ = senders.send_to_screen(ScreenInstruction::DumpLayoutToHd);
|
||||||
task::sleep(std::time::Duration::from_millis(SESSION_READ_DURATION))
|
task::sleep(std::time::Duration::from_millis(SESSION_READ_DURATION))
|
||||||
|
|
@ -396,3 +329,110 @@ fn job_already_running(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_session_state_to_disk(
|
||||||
|
current_session_name: String,
|
||||||
|
current_session_info: SessionInfo,
|
||||||
|
current_session_layout: (String, BTreeMap<String, String>),
|
||||||
|
) {
|
||||||
|
let metadata_cache_file_name = session_info_cache_file_name(¤t_session_name);
|
||||||
|
let (current_session_layout, layout_files_to_write) = current_session_layout;
|
||||||
|
let _wrote_metadata_file =
|
||||||
|
std::fs::create_dir_all(session_info_folder_for_session(¤t_session_name).as_path())
|
||||||
|
.and_then(|_| std::fs::File::create(metadata_cache_file_name))
|
||||||
|
.and_then(|mut f| write!(f, "{}", current_session_info.to_string()));
|
||||||
|
|
||||||
|
if !current_session_layout.is_empty() {
|
||||||
|
let layout_cache_file_name = session_layout_cache_file_name(¤t_session_name);
|
||||||
|
let _wrote_layout_file = std::fs::create_dir_all(
|
||||||
|
session_info_folder_for_session(¤t_session_name).as_path(),
|
||||||
|
)
|
||||||
|
.and_then(|_| std::fs::File::create(layout_cache_file_name))
|
||||||
|
.and_then(|mut f| write!(f, "{}", current_session_layout))
|
||||||
|
.and_then(|_| {
|
||||||
|
let session_info_folder = session_info_folder_for_session(¤t_session_name);
|
||||||
|
for (external_file_name, external_file_contents) in layout_files_to_write {
|
||||||
|
std::fs::File::create(session_info_folder.join(external_file_name))
|
||||||
|
.and_then(|mut f| write!(f, "{}", external_file_contents))
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
log::error!("Failed to write layout metadata file: {:?}", e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_other_live_session_states(current_session_name: &str) -> BTreeMap<String, SessionInfo> {
|
||||||
|
let mut other_session_names = vec![];
|
||||||
|
let mut session_infos_on_machine = BTreeMap::new();
|
||||||
|
// we do this so that the session infos will be actual and we're
|
||||||
|
// reasonably sure their session is running
|
||||||
|
if let Ok(files) = fs::read_dir(&*ZELLIJ_SOCK_DIR) {
|
||||||
|
files.for_each(|file| {
|
||||||
|
if let Ok(file) = file {
|
||||||
|
if let Ok(file_name) = file.file_name().into_string() {
|
||||||
|
if file.file_type().unwrap().is_socket() {
|
||||||
|
other_session_names.push(file_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for session_name in other_session_names {
|
||||||
|
let session_cache_file_name = session_info_cache_file_name(&session_name);
|
||||||
|
if let Ok(raw_session_info) = fs::read_to_string(&session_cache_file_name) {
|
||||||
|
if let Ok(session_info) =
|
||||||
|
SessionInfo::from_string(&raw_session_info, ¤t_session_name)
|
||||||
|
{
|
||||||
|
session_infos_on_machine.insert(session_name, session_info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
session_infos_on_machine
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_resurrectable_sessions(
|
||||||
|
session_infos_on_machine: &BTreeMap<String, SessionInfo>,
|
||||||
|
) -> BTreeMap<String, Duration> {
|
||||||
|
match fs::read_dir(&*ZELLIJ_SESSION_INFO_CACHE_DIR) {
|
||||||
|
Ok(files_in_session_info_folder) => {
|
||||||
|
let files_that_are_folders = files_in_session_info_folder
|
||||||
|
.filter_map(|f| f.ok().map(|f| f.path()))
|
||||||
|
.filter(|f| f.is_dir());
|
||||||
|
files_that_are_folders
|
||||||
|
.filter_map(|folder_name| {
|
||||||
|
let session_name = folder_name.file_name()?.to_str()?.to_owned();
|
||||||
|
if session_infos_on_machine.contains_key(&session_name) {
|
||||||
|
// this is not a dead session...
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let layout_file_name = session_layout_cache_file_name(&session_name);
|
||||||
|
let ctime = match std::fs::metadata(&layout_file_name)
|
||||||
|
.and_then(|metadata| metadata.created())
|
||||||
|
{
|
||||||
|
Ok(created) => Some(created),
|
||||||
|
Err(e) => {
|
||||||
|
log::error!(
|
||||||
|
"Failed to read created stamp of resurrection file: {:?}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let elapsed_duration = ctime
|
||||||
|
.map(|ctime| {
|
||||||
|
Duration::from_secs(ctime.elapsed().ok().unwrap_or_default().as_secs())
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
Some((session_name, elapsed_duration))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to read session info cache dir: {:?}", e);
|
||||||
|
BTreeMap::new()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ use url::Url;
|
||||||
use crate::{panes::PaneId, screen::ScreenInstruction};
|
use crate::{panes::PaneId, screen::ScreenInstruction};
|
||||||
|
|
||||||
use zellij_utils::{
|
use zellij_utils::{
|
||||||
consts::VERSION,
|
consts::{VERSION, ZELLIJ_SESSION_INFO_CACHE_DIR, ZELLIJ_SOCK_DIR},
|
||||||
data::{
|
data::{
|
||||||
CommandToRun, Direction, Event, EventType, FileToOpen, InputMode, PluginCommand, PluginIds,
|
CommandToRun, Direction, Event, EventType, FileToOpen, InputMode, PluginCommand, PluginIds,
|
||||||
PluginMessage, Resize, ResizeStrategy,
|
PluginMessage, Resize, ResizeStrategy,
|
||||||
|
|
@ -224,6 +224,10 @@ fn host_run_plugin_command(env: FunctionEnvMut<ForeignFunctionEnv>) {
|
||||||
connect_to_session.tab_position,
|
connect_to_session.tab_position,
|
||||||
connect_to_session.pane_id,
|
connect_to_session.pane_id,
|
||||||
)?,
|
)?,
|
||||||
|
PluginCommand::DeleteDeadSession(session_name) => {
|
||||||
|
delete_dead_session(session_name)?
|
||||||
|
},
|
||||||
|
PluginCommand::DeleteAllDeadSessions => delete_all_dead_sessions()?,
|
||||||
PluginCommand::OpenFileInPlace(file_to_open) => {
|
PluginCommand::OpenFileInPlace(file_to_open) => {
|
||||||
open_file_in_place(env, file_to_open)
|
open_file_in_place(env, file_to_open)
|
||||||
},
|
},
|
||||||
|
|
@ -837,6 +841,52 @@ fn switch_session(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn delete_dead_session(session_name: String) -> Result<()> {
|
||||||
|
std::fs::remove_dir_all(&*ZELLIJ_SESSION_INFO_CACHE_DIR.join(&session_name))
|
||||||
|
.with_context(|| format!("Failed to delete dead session: {:?}", &session_name))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete_all_dead_sessions() -> Result<()> {
|
||||||
|
use std::os::unix::fs::FileTypeExt;
|
||||||
|
let mut live_sessions = vec![];
|
||||||
|
if let Ok(files) = std::fs::read_dir(&*ZELLIJ_SOCK_DIR) {
|
||||||
|
files.for_each(|file| {
|
||||||
|
if let Ok(file) = file {
|
||||||
|
if let Ok(file_name) = file.file_name().into_string() {
|
||||||
|
if file.file_type().unwrap().is_socket() {
|
||||||
|
live_sessions.push(file_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let dead_sessions: Vec<String> = match std::fs::read_dir(&*ZELLIJ_SESSION_INFO_CACHE_DIR) {
|
||||||
|
Ok(files_in_session_info_folder) => {
|
||||||
|
let files_that_are_folders = files_in_session_info_folder
|
||||||
|
.filter_map(|f| f.ok().map(|f| f.path()))
|
||||||
|
.filter(|f| f.is_dir());
|
||||||
|
files_that_are_folders
|
||||||
|
.filter_map(|folder_name| {
|
||||||
|
let session_name = folder_name.file_name()?.to_str()?.to_owned();
|
||||||
|
if live_sessions.contains(&session_name) {
|
||||||
|
// this is not a dead session...
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(session_name)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Failed to read session info cache dir: {:?}", e);
|
||||||
|
vec![]
|
||||||
|
},
|
||||||
|
};
|
||||||
|
for session in dead_sessions {
|
||||||
|
delete_dead_session(session)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn edit_scrollback(env: &ForeignFunctionEnv) {
|
fn edit_scrollback(env: &ForeignFunctionEnv) {
|
||||||
let action = Action::EditScrollback;
|
let action = Action::EditScrollback;
|
||||||
let error_msg = || format!("Failed to edit scrollback");
|
let error_msg = || format!("Failed to edit scrollback");
|
||||||
|
|
@ -1263,6 +1313,8 @@ fn check_command_permission(
|
||||||
| PluginCommand::RenameTerminalPane(..)
|
| PluginCommand::RenameTerminalPane(..)
|
||||||
| PluginCommand::RenamePluginPane(..)
|
| PluginCommand::RenamePluginPane(..)
|
||||||
| PluginCommand::SwitchSession(..)
|
| PluginCommand::SwitchSession(..)
|
||||||
|
| PluginCommand::DeleteDeadSession(..)
|
||||||
|
| PluginCommand::DeleteAllDeadSessions
|
||||||
| PluginCommand::RenameTab(..) => PermissionType::ChangeApplicationState,
|
| PluginCommand::RenameTab(..) => PermissionType::ChangeApplicationState,
|
||||||
_ => return (PermissionStatus::Granted, None),
|
_ => return (PermissionStatus::Granted, None),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use zellij_utils::data::{
|
use zellij_utils::data::{
|
||||||
Direction, PaneManifest, PluginPermission, Resize, ResizeStrategy, SessionInfo,
|
Direction, PaneManifest, PluginPermission, Resize, ResizeStrategy, SessionInfo,
|
||||||
|
|
@ -301,7 +302,10 @@ pub enum ScreenInstruction {
|
||||||
BreakPane(Box<Layout>, Option<TerminalAction>, ClientId),
|
BreakPane(Box<Layout>, Option<TerminalAction>, ClientId),
|
||||||
BreakPaneRight(ClientId),
|
BreakPaneRight(ClientId),
|
||||||
BreakPaneLeft(ClientId),
|
BreakPaneLeft(ClientId),
|
||||||
UpdateSessionInfos(BTreeMap<String, SessionInfo>), // String is the session name
|
UpdateSessionInfos(
|
||||||
|
BTreeMap<String, SessionInfo>, // String is the session name
|
||||||
|
BTreeMap<String, Duration>, // resurrectable sessions - <name, created>
|
||||||
|
),
|
||||||
ReplacePane(
|
ReplacePane(
|
||||||
PaneId,
|
PaneId,
|
||||||
HoldForCommand,
|
HoldForCommand,
|
||||||
|
|
@ -558,6 +562,8 @@ pub(crate) struct Screen {
|
||||||
session_name: String,
|
session_name: String,
|
||||||
session_infos_on_machine: BTreeMap<String, SessionInfo>, // String is the session name, can
|
session_infos_on_machine: BTreeMap<String, SessionInfo>, // String is the session name, can
|
||||||
// also be this session
|
// also be this session
|
||||||
|
resurrectable_sessions: BTreeMap<String, Duration>, // String is the session name, duration is
|
||||||
|
// its creation time
|
||||||
default_layout: Box<Layout>,
|
default_layout: Box<Layout>,
|
||||||
default_shell: Option<PathBuf>,
|
default_shell: Option<PathBuf>,
|
||||||
arrow_fonts: bool,
|
arrow_fonts: bool,
|
||||||
|
|
@ -585,6 +591,7 @@ impl Screen {
|
||||||
let session_name = mode_info.session_name.clone().unwrap_or_default();
|
let session_name = mode_info.session_name.clone().unwrap_or_default();
|
||||||
let session_info = SessionInfo::new(session_name.clone());
|
let session_info = SessionInfo::new(session_name.clone());
|
||||||
let mut session_infos_on_machine = BTreeMap::new();
|
let mut session_infos_on_machine = BTreeMap::new();
|
||||||
|
let resurrectable_sessions = BTreeMap::new();
|
||||||
session_infos_on_machine.insert(session_name.clone(), session_info);
|
session_infos_on_machine.insert(session_name.clone(), session_info);
|
||||||
Screen {
|
Screen {
|
||||||
bus,
|
bus,
|
||||||
|
|
@ -616,6 +623,7 @@ impl Screen {
|
||||||
serialize_pane_viewport,
|
serialize_pane_viewport,
|
||||||
scrollback_lines_to_serialize,
|
scrollback_lines_to_serialize,
|
||||||
arrow_fonts,
|
arrow_fonts,
|
||||||
|
resurrectable_sessions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1417,14 +1425,22 @@ impl Screen {
|
||||||
pub fn update_session_infos(
|
pub fn update_session_infos(
|
||||||
&mut self,
|
&mut self,
|
||||||
new_session_infos: BTreeMap<String, SessionInfo>,
|
new_session_infos: BTreeMap<String, SessionInfo>,
|
||||||
|
resurrectable_sessions: BTreeMap<String, Duration>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.session_infos_on_machine = new_session_infos;
|
self.session_infos_on_machine = new_session_infos;
|
||||||
|
self.resurrectable_sessions = resurrectable_sessions;
|
||||||
self.bus
|
self.bus
|
||||||
.senders
|
.senders
|
||||||
.send_to_plugin(PluginInstruction::Update(vec![(
|
.send_to_plugin(PluginInstruction::Update(vec![(
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
Event::SessionUpdate(self.session_infos_on_machine.values().cloned().collect()),
|
Event::SessionUpdate(
|
||||||
|
self.session_infos_on_machine.values().cloned().collect(),
|
||||||
|
self.resurrectable_sessions
|
||||||
|
.iter()
|
||||||
|
.map(|(n, c)| (n.clone(), c.clone()))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
)]))
|
)]))
|
||||||
.context("failed to update session info")?;
|
.context("failed to update session info")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -3421,8 +3437,8 @@ pub(crate) fn screen_thread_main(
|
||||||
ScreenInstruction::BreakPaneLeft(client_id) => {
|
ScreenInstruction::BreakPaneLeft(client_id) => {
|
||||||
screen.break_pane_to_new_tab(Direction::Left, client_id)?;
|
screen.break_pane_to_new_tab(Direction::Left, client_id)?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::UpdateSessionInfos(new_session_infos) => {
|
ScreenInstruction::UpdateSessionInfos(new_session_infos, resurrectable_sessions) => {
|
||||||
screen.update_session_infos(new_session_infos)?;
|
screen.update_session_infos(new_session_infos, resurrectable_sessions)?;
|
||||||
},
|
},
|
||||||
ScreenInstruction::ReplacePane(
|
ScreenInstruction::ReplacePane(
|
||||||
new_pane_id,
|
new_pane_id,
|
||||||
|
|
|
||||||
|
|
@ -666,6 +666,22 @@ pub fn switch_session_with_focus(
|
||||||
unsafe { host_run_plugin_command() };
|
unsafe { host_run_plugin_command() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Permanently delete a resurrectable session with the given name
|
||||||
|
pub fn delete_dead_session(name: &str) {
|
||||||
|
let plugin_command = PluginCommand::DeleteDeadSession(name.to_owned());
|
||||||
|
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||||
|
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||||
|
unsafe { host_run_plugin_command() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Permanently delete aall resurrectable sessions on this machine
|
||||||
|
pub fn delete_all_dead_sessions() {
|
||||||
|
let plugin_command = PluginCommand::DeleteAllDeadSessions;
|
||||||
|
let protobuf_plugin_command: ProtobufPluginCommand = plugin_command.try_into().unwrap();
|
||||||
|
object_to_stdout(&protobuf_plugin_command.encode_to_vec());
|
||||||
|
unsafe { host_run_plugin_command() };
|
||||||
|
}
|
||||||
|
|
||||||
// Utility Functions
|
// Utility Functions
|
||||||
|
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,8 @@ pub mod event {
|
||||||
pub struct SessionUpdatePayload {
|
pub struct SessionUpdatePayload {
|
||||||
#[prost(message, repeated, tag = "1")]
|
#[prost(message, repeated, tag = "1")]
|
||||||
pub session_manifests: ::prost::alloc::vec::Vec<SessionManifest>,
|
pub session_manifests: ::prost::alloc::vec::Vec<SessionManifest>,
|
||||||
|
#[prost(message, repeated, tag = "2")]
|
||||||
|
pub resurrectable_sessions: ::prost::alloc::vec::Vec<ResurrectableSession>,
|
||||||
}
|
}
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
|
@ -173,6 +175,14 @@ pub struct SessionManifest {
|
||||||
}
|
}
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
|
pub struct ResurrectableSession {
|
||||||
|
#[prost(string, tag = "1")]
|
||||||
|
pub name: ::prost::alloc::string::String,
|
||||||
|
#[prost(uint64, tag = "2")]
|
||||||
|
pub creation_time: u64,
|
||||||
|
}
|
||||||
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
pub struct PaneInfo {
|
pub struct PaneInfo {
|
||||||
#[prost(uint32, tag = "1")]
|
#[prost(uint32, tag = "1")]
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ pub struct PluginCommand {
|
||||||
pub name: i32,
|
pub name: i32,
|
||||||
#[prost(
|
#[prost(
|
||||||
oneof = "plugin_command::Payload",
|
oneof = "plugin_command::Payload",
|
||||||
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44"
|
tags = "2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45"
|
||||||
)]
|
)]
|
||||||
pub payload: ::core::option::Option<plugin_command::Payload>,
|
pub payload: ::core::option::Option<plugin_command::Payload>,
|
||||||
}
|
}
|
||||||
|
|
@ -100,6 +100,8 @@ pub mod plugin_command {
|
||||||
RunCommandPayload(super::RunCommandPayload),
|
RunCommandPayload(super::RunCommandPayload),
|
||||||
#[prost(message, tag = "44")]
|
#[prost(message, tag = "44")]
|
||||||
WebRequestPayload(super::WebRequestPayload),
|
WebRequestPayload(super::WebRequestPayload),
|
||||||
|
#[prost(string, tag = "45")]
|
||||||
|
DeleteDeadSessionPayload(::prost::alloc::string::String),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||||
|
|
@ -311,6 +313,8 @@ pub enum CommandName {
|
||||||
OpenFileInPlace = 70,
|
OpenFileInPlace = 70,
|
||||||
RunCommand = 71,
|
RunCommand = 71,
|
||||||
WebRequest = 72,
|
WebRequest = 72,
|
||||||
|
DeleteDeadSession = 73,
|
||||||
|
DeleteAllDeadSessions = 74,
|
||||||
}
|
}
|
||||||
impl CommandName {
|
impl CommandName {
|
||||||
/// String value of the enum field names used in the ProtoBuf definition.
|
/// String value of the enum field names used in the ProtoBuf definition.
|
||||||
|
|
@ -392,6 +396,8 @@ impl CommandName {
|
||||||
CommandName::OpenFileInPlace => "OpenFileInPlace",
|
CommandName::OpenFileInPlace => "OpenFileInPlace",
|
||||||
CommandName::RunCommand => "RunCommand",
|
CommandName::RunCommand => "RunCommand",
|
||||||
CommandName::WebRequest => "WebRequest",
|
CommandName::WebRequest => "WebRequest",
|
||||||
|
CommandName::DeleteDeadSession => "DeleteDeadSession",
|
||||||
|
CommandName::DeleteAllDeadSessions => "DeleteAllDeadSessions",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Creates an enum from field names used in the ProtoBuf definition.
|
/// Creates an enum from field names used in the ProtoBuf definition.
|
||||||
|
|
@ -470,6 +476,8 @@ impl CommandName {
|
||||||
"OpenFileInPlace" => Some(Self::OpenFileInPlace),
|
"OpenFileInPlace" => Some(Self::OpenFileInPlace),
|
||||||
"RunCommand" => Some(Self::RunCommand),
|
"RunCommand" => Some(Self::RunCommand),
|
||||||
"WebRequest" => Some(Self::WebRequest),
|
"WebRequest" => Some(Self::WebRequest),
|
||||||
|
"DeleteDeadSession" => Some(Self::DeleteDeadSession),
|
||||||
|
"DeleteAllDeadSessions" => Some(Self::DeleteAllDeadSessions),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use std::time::Duration;
|
||||||
use strum_macros::{Display, EnumDiscriminants, EnumIter, EnumString, ToString};
|
use strum_macros::{Display, EnumDiscriminants, EnumIter, EnumString, ToString};
|
||||||
|
|
||||||
pub type ClientId = u16; // TODO: merge with crate type?
|
pub type ClientId = u16; // TODO: merge with crate type?
|
||||||
|
|
@ -495,7 +496,10 @@ pub enum Event {
|
||||||
FileSystemDelete(Vec<PathBuf>),
|
FileSystemDelete(Vec<PathBuf>),
|
||||||
/// A Result of plugin permission request
|
/// A Result of plugin permission request
|
||||||
PermissionRequestResult(PermissionStatus),
|
PermissionRequestResult(PermissionStatus),
|
||||||
SessionUpdate(Vec<SessionInfo>),
|
SessionUpdate(
|
||||||
|
Vec<SessionInfo>,
|
||||||
|
Vec<(String, Duration)>, // resurrectable sessions
|
||||||
|
),
|
||||||
RunCommandResult(Option<i32>, Vec<u8>, Vec<u8>, BTreeMap<String, String>), // exit_code, STDOUT, STDERR,
|
RunCommandResult(Option<i32>, Vec<u8>, Vec<u8>, BTreeMap<String, String>), // exit_code, STDOUT, STDERR,
|
||||||
// context
|
// context
|
||||||
WebRequestResult(
|
WebRequestResult(
|
||||||
|
|
@ -1082,6 +1086,8 @@ pub enum PluginCommand {
|
||||||
ReportPanic(String), // stringified panic
|
ReportPanic(String), // stringified panic
|
||||||
RequestPluginPermissions(Vec<PermissionType>),
|
RequestPluginPermissions(Vec<PermissionType>),
|
||||||
SwitchSession(ConnectToSession),
|
SwitchSession(ConnectToSession),
|
||||||
|
DeleteDeadSession(String), // String -> session name
|
||||||
|
DeleteAllDeadSessions, // String -> session name
|
||||||
OpenTerminalInPlace(FileToOpen), // only used for the path as cwd
|
OpenTerminalInPlace(FileToOpen), // only used for the path as cwd
|
||||||
OpenFileInPlace(FileToOpen),
|
OpenFileInPlace(FileToOpen),
|
||||||
OpenCommandPaneInPlace(CommandToRun),
|
OpenCommandPaneInPlace(CommandToRun),
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ message Event {
|
||||||
|
|
||||||
message SessionUpdatePayload {
|
message SessionUpdatePayload {
|
||||||
repeated SessionManifest session_manifests = 1;
|
repeated SessionManifest session_manifests = 1;
|
||||||
|
repeated ResurrectableSession resurrectable_sessions = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RunCommandResultPayload {
|
message RunCommandResultPayload {
|
||||||
|
|
@ -153,6 +154,11 @@ message SessionManifest {
|
||||||
bool is_current_session = 5;
|
bool is_current_session = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ResurrectableSession {
|
||||||
|
string name = 1;
|
||||||
|
uint64 creation_time = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message PaneInfo {
|
message PaneInfo {
|
||||||
uint32 id = 1;
|
uint32 id = 1;
|
||||||
bool is_plugin = 2;
|
bool is_plugin = 2;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ pub use super::generated_api::api::{
|
||||||
EventType as ProtobufEventType, InputModeKeybinds as ProtobufInputModeKeybinds,
|
EventType as ProtobufEventType, InputModeKeybinds as ProtobufInputModeKeybinds,
|
||||||
KeyBind as ProtobufKeyBind, ModeUpdatePayload as ProtobufModeUpdatePayload,
|
KeyBind as ProtobufKeyBind, ModeUpdatePayload as ProtobufModeUpdatePayload,
|
||||||
PaneInfo as ProtobufPaneInfo, PaneManifest as ProtobufPaneManifest,
|
PaneInfo as ProtobufPaneInfo, PaneManifest as ProtobufPaneManifest,
|
||||||
|
ResurrectableSession as ProtobufResurrectableSession,
|
||||||
SessionManifest as ProtobufSessionManifest, TabInfo as ProtobufTabInfo, *,
|
SessionManifest as ProtobufSessionManifest, TabInfo as ProtobufTabInfo, *,
|
||||||
},
|
},
|
||||||
input_mode::InputMode as ProtobufInputMode,
|
input_mode::InputMode as ProtobufInputMode,
|
||||||
|
|
@ -23,6 +24,7 @@ use crate::input::actions::Action;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
impl TryFrom<ProtobufEvent> for Event {
|
impl TryFrom<ProtobufEvent> for Event {
|
||||||
type Error = &'static str;
|
type Error = &'static str;
|
||||||
|
|
@ -176,10 +178,19 @@ impl TryFrom<ProtobufEvent> for Event {
|
||||||
protobuf_session_update_payload,
|
protobuf_session_update_payload,
|
||||||
)) => {
|
)) => {
|
||||||
let mut session_infos: Vec<SessionInfo> = vec![];
|
let mut session_infos: Vec<SessionInfo> = vec![];
|
||||||
|
let mut resurrectable_sessions: Vec<(String, Duration)> = vec![];
|
||||||
for protobuf_session_info in protobuf_session_update_payload.session_manifests {
|
for protobuf_session_info in protobuf_session_update_payload.session_manifests {
|
||||||
session_infos.push(SessionInfo::try_from(protobuf_session_info)?);
|
session_infos.push(SessionInfo::try_from(protobuf_session_info)?);
|
||||||
}
|
}
|
||||||
Ok(Event::SessionUpdate(session_infos))
|
for protobuf_resurrectable_session in
|
||||||
|
protobuf_session_update_payload.resurrectable_sessions
|
||||||
|
{
|
||||||
|
resurrectable_sessions.push(protobuf_resurrectable_session.into());
|
||||||
|
}
|
||||||
|
Ok(Event::SessionUpdate(
|
||||||
|
session_infos,
|
||||||
|
resurrectable_sessions.into(),
|
||||||
|
))
|
||||||
},
|
},
|
||||||
_ => Err("Malformed payload for the SessionUpdate Event"),
|
_ => Err("Malformed payload for the SessionUpdate Event"),
|
||||||
},
|
},
|
||||||
|
|
@ -359,13 +370,18 @@ impl TryFrom<Event> for ProtobufEvent {
|
||||||
)),
|
)),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
Event::SessionUpdate(session_infos) => {
|
Event::SessionUpdate(session_infos, resurrectable_sessions) => {
|
||||||
let mut protobuf_session_manifests = vec![];
|
let mut protobuf_session_manifests = vec![];
|
||||||
for session_info in session_infos {
|
for session_info in session_infos {
|
||||||
protobuf_session_manifests.push(session_info.try_into()?);
|
protobuf_session_manifests.push(session_info.try_into()?);
|
||||||
}
|
}
|
||||||
|
let mut protobuf_resurrectable_sessions = vec![];
|
||||||
|
for resurrectable_session in resurrectable_sessions {
|
||||||
|
protobuf_resurrectable_sessions.push(resurrectable_session.into());
|
||||||
|
}
|
||||||
let session_update_payload = SessionUpdatePayload {
|
let session_update_payload = SessionUpdatePayload {
|
||||||
session_manifests: protobuf_session_manifests,
|
session_manifests: protobuf_session_manifests,
|
||||||
|
resurrectable_sessions: protobuf_resurrectable_sessions,
|
||||||
};
|
};
|
||||||
Ok(ProtobufEvent {
|
Ok(ProtobufEvent {
|
||||||
name: ProtobufEventType::SessionUpdate as i32,
|
name: ProtobufEventType::SessionUpdate as i32,
|
||||||
|
|
@ -887,6 +903,24 @@ impl TryFrom<EventType> for ProtobufEventType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ProtobufResurrectableSession> for (String, Duration) {
|
||||||
|
fn from(protobuf_resurrectable_session: ProtobufResurrectableSession) -> (String, Duration) {
|
||||||
|
(
|
||||||
|
protobuf_resurrectable_session.name,
|
||||||
|
Duration::from_secs(protobuf_resurrectable_session.creation_time),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<(String, Duration)> for ProtobufResurrectableSession {
|
||||||
|
fn from(session_name_and_creation_time: (String, Duration)) -> ProtobufResurrectableSession {
|
||||||
|
ProtobufResurrectableSession {
|
||||||
|
name: session_name_and_creation_time.0,
|
||||||
|
creation_time: session_name_and_creation_time.1.as_secs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serialize_mode_update_event() {
|
fn serialize_mode_update_event() {
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
|
|
@ -1249,7 +1283,7 @@ fn serialize_file_system_delete_event() {
|
||||||
#[test]
|
#[test]
|
||||||
fn serialize_session_update_event() {
|
fn serialize_session_update_event() {
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
let session_update_event = Event::SessionUpdate(Default::default());
|
let session_update_event = Event::SessionUpdate(Default::default(), Default::default());
|
||||||
let protobuf_event: ProtobufEvent = session_update_event.clone().try_into().unwrap();
|
let protobuf_event: ProtobufEvent = session_update_event.clone().try_into().unwrap();
|
||||||
let serialized_protobuf_event = protobuf_event.encode_to_vec();
|
let serialized_protobuf_event = protobuf_event.encode_to_vec();
|
||||||
let deserialized_protobuf_event: ProtobufEvent =
|
let deserialized_protobuf_event: ProtobufEvent =
|
||||||
|
|
@ -1360,8 +1394,9 @@ fn serialize_session_update_event_with_non_default_values() {
|
||||||
is_current_session: false,
|
is_current_session: false,
|
||||||
};
|
};
|
||||||
let session_infos = vec![session_info_1, session_info_2];
|
let session_infos = vec![session_info_1, session_info_2];
|
||||||
|
let resurrectable_sessions = vec![];
|
||||||
|
|
||||||
let session_update_event = Event::SessionUpdate(session_infos);
|
let session_update_event = Event::SessionUpdate(session_infos, resurrectable_sessions);
|
||||||
let protobuf_event: ProtobufEvent = session_update_event.clone().try_into().unwrap();
|
let protobuf_event: ProtobufEvent = session_update_event.clone().try_into().unwrap();
|
||||||
let serialized_protobuf_event = protobuf_event.encode_to_vec();
|
let serialized_protobuf_event = protobuf_event.encode_to_vec();
|
||||||
let deserialized_protobuf_event: ProtobufEvent =
|
let deserialized_protobuf_event: ProtobufEvent =
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,8 @@ enum CommandName {
|
||||||
OpenFileInPlace = 70;
|
OpenFileInPlace = 70;
|
||||||
RunCommand = 71;
|
RunCommand = 71;
|
||||||
WebRequest = 72;
|
WebRequest = 72;
|
||||||
|
DeleteDeadSession = 73;
|
||||||
|
DeleteAllDeadSessions = 74;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PluginCommand {
|
message PluginCommand {
|
||||||
|
|
@ -132,6 +134,7 @@ message PluginCommand {
|
||||||
OpenCommandPanePayload open_command_pane_in_place_payload = 42;
|
OpenCommandPanePayload open_command_pane_in_place_payload = 42;
|
||||||
RunCommandPayload run_command_payload = 43;
|
RunCommandPayload run_command_payload = 43;
|
||||||
WebRequestPayload web_request_payload = 44;
|
WebRequestPayload web_request_payload = 44;
|
||||||
|
string delete_dead_session_payload = 45;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -628,6 +628,13 @@ impl TryFrom<ProtobufPluginCommand> for PluginCommand {
|
||||||
},
|
},
|
||||||
_ => Err("Mismatched payload for WebRequest"),
|
_ => Err("Mismatched payload for WebRequest"),
|
||||||
},
|
},
|
||||||
|
Some(CommandName::DeleteDeadSession) => match protobuf_plugin_command.payload {
|
||||||
|
Some(Payload::DeleteDeadSessionPayload(dead_session_name)) => {
|
||||||
|
Ok(PluginCommand::DeleteDeadSession(dead_session_name))
|
||||||
|
},
|
||||||
|
_ => Err("Mismatched payload for DeleteDeadSession"),
|
||||||
|
},
|
||||||
|
Some(CommandName::DeleteAllDeadSessions) => Ok(PluginCommand::DeleteAllDeadSessions),
|
||||||
None => Err("Unrecognized plugin command"),
|
None => Err("Unrecognized plugin command"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1044,6 +1051,14 @@ impl TryFrom<PluginCommand> for ProtobufPluginCommand {
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
PluginCommand::DeleteDeadSession(dead_session_name) => Ok(ProtobufPluginCommand {
|
||||||
|
name: CommandName::DeleteDeadSession as i32,
|
||||||
|
payload: Some(Payload::DeleteDeadSessionPayload(dead_session_name)),
|
||||||
|
}),
|
||||||
|
PluginCommand::DeleteAllDeadSessions => Ok(ProtobufPluginCommand {
|
||||||
|
name: CommandName::DeleteAllDeadSessions as i32,
|
||||||
|
payload: None,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue