Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ux): allow renaming sessions #2903

Merged
merged 3 commits into from
Nov 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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