T63: a double press on a session's row renames it, which a user asked for after 0.1.6: Ely's ListItem::editor, added for this, draws a field in the title's place; Enter keeps the name, Escape drops it, a blur keeps it; the name stands over the program's title in the row, the header and the window's title until renamed again, and an empty one gives the title back; Ely pinned to 7f7a715
This commit is contained in:
9 files changed
+136
-17
No files matched your search
+44
-7
@@ -5,6 +5,7 @@ use std::{
|
||||
|
||||
use ely_gpui_component::{
|
||||
buttons::{ButtonVariant, IconButton},
|
||||
forms::{Input, TextInput},
|
||||
lists::ListItem,
|
||||
primitives::{Icon, IconName, Tooltip},
|
||||
theme::{ActiveTheme, ControlSize, IconSize},
|
||||
@@ -25,6 +26,10 @@ pub struct Session {
|
||||
pub terminal: Entity<TerminalView>,
|
||||
/// The program's title; empty while it names none.
|
||||
pub title: String,
|
||||
/// The name the user gave it; empty leaves the title to its program.
|
||||
pub name: String,
|
||||
/// The field its row shows while the user renames it.
|
||||
pub editing: Option<(Entity<TextInput>, Subscription)>,
|
||||
/// The folder it reports, else the one it started in.
|
||||
pub folder: PathBuf,
|
||||
/// Its shell has integration, or it reported a folder anyway.
|
||||
@@ -39,6 +44,14 @@ pub struct Session {
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// What it is called: the user's name, else its program's title, else the app's name.
|
||||
pub fn named(&self) -> &str {
|
||||
[self.name.as_str(), self.title.as_str()]
|
||||
.into_iter()
|
||||
.find(|title| !title.is_empty())
|
||||
.unwrap_or(TITLE)
|
||||
}
|
||||
|
||||
/// The look its mark takes from its terminal now.
|
||||
pub fn looks(&self, cx: &App) -> Option<Look> {
|
||||
let view = self.terminal.read(cx);
|
||||
@@ -103,17 +116,20 @@ pub fn row(
|
||||
) -> ListItem {
|
||||
let terminal = session.terminal.read(cx);
|
||||
let pid = terminal.pid() as usize;
|
||||
let title = if session.title.is_empty() {
|
||||
TITLE
|
||||
} else {
|
||||
&session.title
|
||||
};
|
||||
let (select, close) = (workspace.clone(), workspace.clone());
|
||||
let item = ListItem::new(("session", pid), title.to_string())
|
||||
let editing = session.editing.is_some();
|
||||
let item = ListItem::new(("session", pid), session.named().to_string())
|
||||
.description(shown(&session.folder, home))
|
||||
.selected(active)
|
||||
.strong(session.bell || session.waiting)
|
||||
.on_click(move |_, window, cx| select.update(cx, |this, cx| this.select(index, window, cx)))
|
||||
// A press in the field it renames with stays the field's.
|
||||
.on_click(
|
||||
move |event, window, cx| match (editing, event.click_count()) {
|
||||
(true, _) => {}
|
||||
(false, 2..) => select.update(cx, |this, cx| this.rename(index, window, cx)),
|
||||
(false, _) => select.update(cx, |this, cx| this.select(index, window, cx)),
|
||||
},
|
||||
)
|
||||
.trailing(
|
||||
IconButton::new(("close-session", pid), IconName::X)
|
||||
.variant(ButtonVariant::Ghost)
|
||||
@@ -124,6 +140,10 @@ pub fn row(
|
||||
close.update(cx, |this, cx| this.close(index, window, cx))
|
||||
}),
|
||||
);
|
||||
let item = match &session.editing {
|
||||
Some((input, _)) => item.editor(editor(workspace, session, input)),
|
||||
None => item,
|
||||
};
|
||||
let mark = mark(session, now, cx);
|
||||
let bell = session
|
||||
.bell
|
||||
@@ -141,6 +161,23 @@ pub fn row(
|
||||
)
|
||||
}
|
||||
|
||||
/// The field a session's row renames it with; Escape drops what it holds.
|
||||
fn editor(
|
||||
workspace: &Entity<Workspace>,
|
||||
session: &Session,
|
||||
input: &Entity<TextInput>,
|
||||
) -> impl IntoElement {
|
||||
let (workspace, id) = (workspace.clone(), session.terminal.entity_id());
|
||||
div()
|
||||
.on_key_down(move |event, window, cx| {
|
||||
if event.keystroke.key == "escape" {
|
||||
cx.stop_propagation();
|
||||
workspace.update(cx, |this, cx| this.end_rename(id, false, true, window, cx));
|
||||
}
|
||||
})
|
||||
.child(Input::new(input).size(ControlSize::Sm))
|
||||
}
|
||||
|
||||
/// What a mark shows: the most pressing record's state, else a failed last command.
|
||||
pub fn look(record: Option<&Record>, failed: bool) -> Option<Look> {
|
||||
Some(
|
||||
|
||||
+5
-6
@@ -29,6 +29,7 @@ use crate::{
|
||||
};
|
||||
|
||||
mod events;
|
||||
mod rename;
|
||||
mod widths;
|
||||
|
||||
use widths::{Dragged, Side};
|
||||
@@ -156,6 +157,8 @@ impl Workspace {
|
||||
self.sessions.push(Session {
|
||||
terminal,
|
||||
title: String::new(),
|
||||
name: String::new(),
|
||||
editing: None,
|
||||
folder: launch.cwd,
|
||||
reports: launch.reports,
|
||||
bell: false,
|
||||
@@ -268,13 +271,9 @@ impl Workspace {
|
||||
&self.sessions
|
||||
}
|
||||
|
||||
/// The shown session's title, or the app's name while its program names none.
|
||||
/// What the shown session is called.
|
||||
fn title(&self) -> &str {
|
||||
self.sessions
|
||||
.get(self.active)
|
||||
.map(|session| session.title.as_str())
|
||||
.filter(|title| !title.is_empty())
|
||||
.unwrap_or(TITLE)
|
||||
self.sessions.get(self.active).map_or(TITLE, Session::named)
|
||||
}
|
||||
|
||||
fn retitle(&self, window: &mut Window) {
|
||||
|
||||
@@ -21,7 +21,10 @@ impl Workspace {
|
||||
};
|
||||
let session = &mut self.sessions[index];
|
||||
match event {
|
||||
TerminalEvent::Title(title) => session.title.clone_from(title),
|
||||
TerminalEvent::Title(title) => {
|
||||
log::debug!("app: session {}'s program names it {title:?}", index + 1);
|
||||
session.title.clone_from(title);
|
||||
}
|
||||
TerminalEvent::Folder(folder) => {
|
||||
session.folder.clone_from(folder);
|
||||
session.reports = true;
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
//! A session renamed in place, as a double-click on its row asks.
|
||||
|
||||
use ely_gpui_component::forms::{InputEvent, TextInput};
|
||||
use gpui::{AppContext, Context, EntityId, Focusable, Window};
|
||||
|
||||
use super::Workspace;
|
||||
|
||||
impl Workspace {
|
||||
/// Opens a field over session `index`'s title, what it is called selected.
|
||||
pub fn rename(&mut self, index: usize, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(session) = self.sessions.get(index) else {
|
||||
return;
|
||||
};
|
||||
if session.editing.is_some() {
|
||||
return;
|
||||
}
|
||||
let named = session.named().to_string();
|
||||
let id = session.terminal.entity_id();
|
||||
let input = cx.new(|cx| {
|
||||
let mut input = TextInput::new(window, cx);
|
||||
input.set_text(named.clone(), cx);
|
||||
input.select(0..named.len(), cx);
|
||||
input
|
||||
});
|
||||
let events = cx.subscribe_in(
|
||||
&input,
|
||||
window,
|
||||
move |this, _, event, window, cx| match event {
|
||||
InputEvent::Submit => this.end_rename(id, true, true, window, cx),
|
||||
InputEvent::Blur => this.end_rename(id, true, false, window, cx),
|
||||
InputEvent::Changed | InputEvent::Focus => {}
|
||||
},
|
||||
);
|
||||
window.focus(&input.focus_handle(cx), cx);
|
||||
log::debug!("app: session {} is renamed in place", index + 1);
|
||||
self.sessions[index].editing = Some((input, events));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Closes the field renaming the session `id` names, `keep`ing what it holds or not, the shown terminal `focused` again or not.
|
||||
pub fn end_rename(
|
||||
&mut self,
|
||||
id: EntityId,
|
||||
keep: bool,
|
||||
focused: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let Some(session) = self
|
||||
.sessions
|
||||
.iter_mut()
|
||||
.find(|session| session.terminal.entity_id() == id)
|
||||
else {
|
||||
log::debug!("app: the session renamed is gone");
|
||||
return;
|
||||
};
|
||||
let Some((input, _)) = session.editing.take() else {
|
||||
return;
|
||||
};
|
||||
let pid = session.terminal.read(cx).pid();
|
||||
if !keep {
|
||||
log::debug!("app: session {pid} keeps its name");
|
||||
} else {
|
||||
session.name = input.read(cx).text().trim().to_string();
|
||||
if session.name.is_empty() {
|
||||
log::info!("app: session {pid} takes its program's title again");
|
||||
} else {
|
||||
log::info!("app: session {pid} is named {:?}", session.name);
|
||||
}
|
||||
}
|
||||
if focused && let Some(shown) = self.sessions.get(self.active) {
|
||||
window.focus(&shown.terminal.focus_handle(cx), cx);
|
||||
}
|
||||
self.retitle(window);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user