Skip to content

Commit

Permalink
feat(ux): allow renaming sessions (#2903)
Browse files Browse the repository at this point in the history
* change session name through the cli

* change session name from the session-manager

* style(fmt): rustfmt
  • Loading branch information
imsnif authored Nov 5, 2023
1 parent 9eb9734 commit 28a165a
Show file tree
Hide file tree
Showing 23 changed files with 315 additions and 16 deletions.
91 changes: 84 additions & 7 deletions default-plugins/session-manager/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use std::collections::BTreeMap;

use ui::{
components::{
render_controls_line, render_new_session_line, render_prompt, render_resurrection_toggle,
Colors,
render_controls_line, render_error, render_new_session_line, render_prompt,
render_renaming_session_screen, render_resurrection_toggle, Colors,
},
SessionUiInfo,
};
Expand All @@ -23,6 +23,8 @@ struct State {
resurrectable_sessions: ResurrectableSessions,
search_term: String,
new_session_name: Option<String>,
renaming_session_name: Option<String>,
error: Option<String>,
browsing_resurrection_sessions: bool,
colors: Colors,
}
Expand Down Expand Up @@ -67,6 +69,9 @@ impl ZellijPlugin for State {
if self.browsing_resurrection_sessions {
self.resurrectable_sessions.render(rows, cols);
return;
} else if let Some(new_session_name) = self.renaming_session_name.as_ref() {
render_renaming_session_screen(&new_session_name, rows, cols);
return;
}
render_resurrection_toggle(cols, false);
render_prompt(
Expand All @@ -87,7 +92,11 @@ impl ZellijPlugin for State {
self.sessions.is_searching,
self.colors,
);
render_controls_line(self.sessions.is_searching, rows, cols, self.colors);
if let Some(error) = self.error.as_ref() {
render_error(&error, rows, cols);
} else {
render_controls_line(self.sessions.is_searching, rows, cols, self.colors);
}
}
}

Expand All @@ -96,6 +105,10 @@ impl State {
self.sessions.reset_selected_index();
}
fn handle_key(&mut self, key: Key) -> bool {
if self.error.is_some() {
self.error = None;
return true;
}
let mut should_render = false;
if let Key::Right = key {
if self.new_session_name.is_none() {
Expand All @@ -110,14 +123,14 @@ impl State {
} else if let Key::Down = key {
if self.browsing_resurrection_sessions {
self.resurrectable_sessions.move_selection_down();
} else if self.new_session_name.is_none() {
} else if self.new_session_name.is_none() && self.renaming_session_name.is_none() {
self.sessions.move_selection_down();
}
should_render = true;
} else if let Key::Up = key {
if self.browsing_resurrection_sessions {
self.resurrectable_sessions.move_selection_up();
} else if self.new_session_name.is_none() {
} else if self.new_session_name.is_none() && self.renaming_session_name.is_none() {
self.sessions.move_selection_up();
}
should_render = true;
Expand All @@ -126,6 +139,8 @@ impl State {
self.handle_selection();
} else if let Some(new_session_name) = self.new_session_name.as_mut() {
new_session_name.push(character);
} else if let Some(renaming_session_name) = self.renaming_session_name.as_mut() {
renaming_session_name.push(character);
} else if self.browsing_resurrection_sessions {
self.resurrectable_sessions.handle_character(character);
} else {
Expand All @@ -141,6 +156,12 @@ impl State {
} else {
new_session_name.pop();
}
} else if let Some(renaming_session_name) = self.renaming_session_name.as_mut() {
if renaming_session_name.is_empty() {
self.renaming_session_name = None;
} else {
renaming_session_name.pop();
}
} else if self.browsing_resurrection_sessions {
self.resurrectable_sessions.handle_backspace();
} else {
Expand All @@ -150,21 +171,36 @@ impl State {
}
should_render = true;
} else if let Key::Ctrl('w') = key {
if self.sessions.is_searching {
if self.sessions.is_searching || self.browsing_resurrection_sessions {
// no-op
} else if self.new_session_name.is_some() {
self.new_session_name = None;
} else {
self.new_session_name = Some(String::new());
}
should_render = true;
} else if let Key::Ctrl('r') = key {
if self.sessions.is_searching || self.browsing_resurrection_sessions {
// no-op
} else if self.renaming_session_name.is_some() {
self.renaming_session_name = None;
} else {
self.renaming_session_name = Some(String::new());
}
should_render = true;
} else if let Key::Ctrl('c') = key {
if let Some(new_session_name) = self.new_session_name.as_mut() {
if new_session_name.is_empty() {
self.new_session_name = None;
} else {
new_session_name.clear()
}
} else if let Some(renaming_session_name) = self.renaming_session_name.as_mut() {
if renaming_session_name.is_empty() {
self.renaming_session_name = None;
} else {
renaming_session_name.clear()
}
} else if !self.search_term.is_empty() {
self.search_term.clear();
self.sessions
Expand All @@ -190,7 +226,15 @@ impl State {
should_render = true;
}
} else if let Key::Esc = key {
hide_self();
if self.renaming_session_name.is_some() {
self.renaming_session_name = None;
should_render = true;
} else if self.new_session_name.is_some() {
self.new_session_name = None;
should_render = true;
} else {
hide_self();
}
}
should_render
}
Expand All @@ -210,6 +254,29 @@ impl State {
} else {
switch_session(Some(new_session_name));
}
} else if let Some(renaming_session_name) = &self.renaming_session_name.take() {
if renaming_session_name.is_empty() {
// TODO: implement these, then implement the error UI, then implement the renaming
// session screen, then test it
self.show_error("New name must not be empty.");
return; // s that we don't hide self
} else if self.session_name.as_ref() == Some(renaming_session_name) {
// noop - we're already called that!
return; // s that we don't hide self
} else if self.sessions.has_session(&renaming_session_name) {
self.show_error("A session by this name already exists.");
return; // s that we don't hide self
} else if self
.resurrectable_sessions
.has_session(&renaming_session_name)
{
self.show_error("A resurrectable session by this name already exists.");
return; // s that we don't hide self
} else {
self.update_current_session_name_in_ui(&renaming_session_name);
rename_session(&renaming_session_name);
return; // s that we don't hide self
}
} else if let Some(selected_session_name) = self.sessions.get_selected_session_name() {
let selected_tab = self.sessions.get_selected_tab_position();
let selected_pane = self.sessions.get_selected_pane_id();
Expand All @@ -235,6 +302,16 @@ impl State {
.update_search_term(&self.search_term, &self.colors);
hide_self();
}
fn show_error(&mut self, error_text: &str) {
self.error = Some(error_text.to_owned());
}
fn update_current_session_name_in_ui(&mut self, new_name: &str) {
if let Some(old_session_name) = self.session_name.as_ref() {
self.sessions
.update_session_name(&old_session_name, new_name);
}
self.session_name = Some(new_name.to_owned());
}
fn update_session_infos(&mut self, session_infos: Vec<SessionInfo>) {
let session_infos: Vec<SessionUiInfo> = session_infos
.iter()
Expand Down
5 changes: 5 additions & 0 deletions default-plugins/session-manager/src/resurrectable_sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,11 @@ impl ResurrectableSessions {
self.search_term.pop();
self.update_search_term();
}
pub fn has_session(&self, session_name: &str) -> bool {
self.all_resurrectable_sessions
.iter()
.any(|s| s.0 == session_name)
}
fn update_search_term(&mut self) {
let mut matches = vec![];
let matcher = SkimMatcherV2::default().use_cache(true);
Expand Down
9 changes: 9 additions & 0 deletions default-plugins/session-manager/src/session_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,15 @@ impl SessionList {
pub fn reset_selected_index(&mut self) {
self.selected_index.reset();
}
pub fn has_session(&self, session_name: &str) -> bool {
self.session_ui_infos.iter().any(|s| s.name == session_name)
}
pub fn update_session_name(&mut self, old_name: &str, new_name: &str) {
self.session_ui_infos
.iter_mut()
.find(|s| s.name == old_name)
.map(|s| s.name = new_name.to_owned());
}
}

#[derive(Debug, Clone, Default)]
Expand Down
51 changes: 45 additions & 6 deletions default-plugins/session-manager/src/ui/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,29 +558,68 @@ pub fn render_new_session_line(session_name: &Option<String>, is_searching: bool
}
}

pub fn render_error(error_text: &str, rows: usize, columns: usize) {
print_text_with_coordinates(
Text::new(format!("Error: {}", error_text)).color_range(3, ..),
0,
rows,
Some(columns),
None,
);
}

pub fn render_renaming_session_screen(new_session_name: &str, rows: usize, columns: usize) {
if rows == 0 || columns == 0 {
return;
}
let prompt_text = "NEW NAME FOR CURRENT SESSION";
let new_session_name = format!("{}_", new_session_name);
let prompt_y_location = (rows / 2).saturating_sub(1);
let session_name_y_location = (rows / 2) + 1;
let prompt_x_location = columns.saturating_sub(prompt_text.chars().count()) / 2;
let session_name_x_location = columns.saturating_sub(new_session_name.chars().count()) / 2;
print_text_with_coordinates(
Text::new(prompt_text).color_range(0, ..),
prompt_x_location,
prompt_y_location,
None,
None,
);
print_text_with_coordinates(
Text::new(new_session_name).color_range(3, ..),
session_name_x_location,
session_name_y_location,
None,
None,
);
}

pub fn render_controls_line(is_searching: bool, row: usize, max_cols: usize, colors: Colors) {
let (arrows, navigate) = if is_searching {
(colors.magenta("<↓↑>"), colors.bold("Navigate"))
} else {
(colors.magenta("<←↓↑→>"), colors.bold("Navigate and Expand"))
};
let rename = colors.magenta("<Ctrl r>");
let rename_text = colors.bold("Rename session");
let enter = colors.magenta("<ENTER>");
let select = colors.bold("Switch to selected");
let esc = colors.magenta("<ESC>");
let to_hide = colors.bold("Hide");

if max_cols >= 80 {
if max_cols >= 104 {
print!(
"\u{1b}[m\u{1b}[{row}HHelp: {arrows} - {navigate}, {enter} - {select}, {esc} - {to_hide}"
"\u{1b}[m\u{1b}[{row}HHelp: {arrows} - {navigate}, {enter} - {select}, {rename} - {rename_text}, {esc} - {to_hide}"
);
} else if max_cols >= 57 {
} else if max_cols >= 73 {
let navigate = colors.bold("Navigate");
let select = colors.bold("Switch");
let rename_text = colors.bold("Rename");
print!(
"\u{1b}[m\u{1b}[{row}HHelp: {arrows} - {navigate}, {enter} - {select}, {esc} - {to_hide}"
"\u{1b}[m\u{1b}[{row}HHelp: {arrows} - {navigate}, {enter} - {select}, {rename} - {rename_text}, {esc} - {to_hide}"
);
} else if max_cols >= 20 {
print!("\u{1b}[m\u{1b}[{row}H{arrows}/{enter}/{esc}");
} else if max_cols >= 28 {
print!("\u{1b}[m\u{1b}[{row}H{arrows}/{enter}/{rename}/{esc}");
}
}

Expand Down
4 changes: 4 additions & 0 deletions zellij-client/src/cli_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ pub fn start_cli_client(os_input: Box<dyn ClientOsApi>, session_name: &str, acti
log_lines.iter().for_each(|line| println!("{line}"));
process::exit(0);
},
Some((ServerToClientMsg::LogError(log_lines), _)) => {
log_lines.iter().for_each(|line| eprintln!("{line}"));
process::exit(2);
},
_ => {},
}
}
Expand Down
8 changes: 8 additions & 0 deletions zellij-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub(crate) enum ClientInstruction {
StartedParsingStdinQuery,
DoneParsingStdinQuery,
Log(Vec<String>),
LogError(Vec<String>),
SwitchSession(ConnectToSession),
SetSynchronizedOutput(Option<SyncOutput>),
}
Expand All @@ -62,6 +63,7 @@ impl From<ServerToClientMsg> for ClientInstruction {
ServerToClientMsg::Connected => ClientInstruction::Connected,
ServerToClientMsg::ActiveClients(clients) => ClientInstruction::ActiveClients(clients),
ServerToClientMsg::Log(log_lines) => ClientInstruction::Log(log_lines),
ServerToClientMsg::LogError(log_lines) => ClientInstruction::LogError(log_lines),
ServerToClientMsg::SwitchSession(connect_to_session) => {
ClientInstruction::SwitchSession(connect_to_session)
},
Expand All @@ -80,6 +82,7 @@ impl From<&ClientInstruction> for ClientContext {
ClientInstruction::Connected => ClientContext::Connected,
ClientInstruction::ActiveClients(_) => ClientContext::ActiveClients,
ClientInstruction::Log(_) => ClientContext::Log,
ClientInstruction::LogError(_) => ClientContext::LogError,
ClientInstruction::StartedParsingStdinQuery => ClientContext::StartedParsingStdinQuery,
ClientInstruction::DoneParsingStdinQuery => ClientContext::DoneParsingStdinQuery,
ClientInstruction::SwitchSession(..) => ClientContext::SwitchSession,
Expand Down Expand Up @@ -473,6 +476,11 @@ pub fn start_client(
log::info!("{line}");
}
},
ClientInstruction::LogError(lines_to_log) => {
for line in lines_to_log {
log::error!("{line}");
}
},
ClientInstruction::SwitchSession(connect_to_session) => {
reconnect_to_session = Some(connect_to_session);
os_input.send_to_server(ClientToServerMsg::ClientExited);
Expand Down
15 changes: 15 additions & 0 deletions zellij-server/src/plugins/zellij_exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ fn host_run_plugin_command(env: FunctionEnvMut<ForeignFunctionEnv>) {
PluginCommand::OpenCommandPaneInPlace(command_to_run) => {
open_command_pane_in_place(env, command_to_run)
},
PluginCommand::RenameSession(new_session_name) => {
rename_session(env, new_session_name)
},
},
(PermissionStatus::Denied, permission) => {
log::error!(
Expand Down Expand Up @@ -1188,6 +1191,17 @@ fn rename_tab(env: &ForeignFunctionEnv, tab_index: u32, new_name: &str) {
apply_action!(rename_tab_action, error_msg, env);
}

fn rename_session(env: &ForeignFunctionEnv, new_session_name: String) {
let error_msg = || {
format!(
"failed to rename session in plugin {}",
env.plugin_env.name()
)
};
let action = Action::RenameSession(new_session_name);
apply_action!(action, error_msg, env);
}

// Custom panic handler for plugins.
//
// This is called when a panic occurs in a plugin. Since most panics will likely originate in the
Expand Down Expand Up @@ -1315,6 +1329,7 @@ fn check_command_permission(
| PluginCommand::SwitchSession(..)
| PluginCommand::DeleteDeadSession(..)
| PluginCommand::DeleteAllDeadSessions
| PluginCommand::RenameSession(..)
| PluginCommand::RenameTab(..) => PermissionType::ChangeApplicationState,
_ => return (PermissionStatus::Granted, None),
};
Expand Down
5 changes: 5 additions & 0 deletions zellij-server/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,11 @@ pub(crate) fn route_action(
.send_to_screen(ScreenInstruction::BreakPaneLeft(client_id))
.with_context(err_context)?;
},
Action::RenameSession(name) => {
senders
.send_to_screen(ScreenInstruction::RenameSession(name, client_id))
.with_context(err_context)?;
},
}
Ok(should_break)
}
Expand Down
Loading

0 comments on commit 28a165a

Please sign in to comment.