Skip to content
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
17 changes: 17 additions & 0 deletions crates/embers-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ pub enum Command {
#[arg(long)]
force: bool,
},
#[command(name = "rename-session")]
RenameSession {
#[arg(short = 't', long = "target")]
target: Option<String>,
name: String,
},
#[command(name = "list-buffers")]
ListBuffers {
#[arg(short = 't', long = "target")]
Expand Down Expand Up @@ -258,6 +264,17 @@ async fn execute(socket: &Path, command: Command) -> Result<String> {
.await?;
Ok(String::new())
}
Command::RenameSession { target, name } => {
let session = connection.resolve_session_record(target.as_deref()).await?;
connection
.request(ClientMessage::Session(SessionRequest::Rename {
request_id: new_request_id(),
session_id: session.id,
name,
}))
.await?;
Ok(String::new())
}
Command::ListBuffers {
target,
attached,
Expand Down
24 changes: 22 additions & 2 deletions crates/embers-cli/tests/sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,38 @@ async fn session_commands_round_trip_through_cli() {
assert_eq!(stdout(&listed).trim(), "1\talpha");

cli_command(&server)
.arg("has-session")
.arg("rename-session")
.arg("-t")
.arg("alpha")
.arg("ops")
.assert()
.success()
.stdout(predicate::str::is_empty());

let listed = run_cli(&server, ["list-sessions"]);
assert_eq!(stdout(&listed).trim(), "1\tops");

cli_command(&server)
.arg("kill-session")
.arg("has-session")
.arg("-t")
.arg("ops")
.assert()
.success()
.stdout(predicate::str::is_empty());
Comment thread
coderabbitai[bot] marked this conversation as resolved.

cli_command(&server)
.arg("has-session")
.arg("-t")
.arg("alpha")
.assert()
.failure()
.stderr(predicate::str::contains("session 'alpha' was not found"));

cli_command(&server)
.arg("kill-session")
.arg("-t")
.arg("ops")
.assert()
.success()
.stdout(predicate::str::is_empty());

Expand Down
5 changes: 5 additions & 0 deletions crates/embers-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ where
self.resync_detached_buffers().await
}
ServerEvent::SessionClosed(_) => self.resync_detached_buffers().await,
ServerEvent::SessionRenamed(event) => {
self.state
.apply_event(&ServerEvent::SessionRenamed(event.clone()));
self.resync_session(event.session_id).await
}
ServerEvent::BufferCreated(_)
| ServerEvent::BufferDetached(_)
| ServerEvent::FocusChanged(_)
Expand Down
9 changes: 9 additions & 0 deletions crates/embers-client/src/configured_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,7 @@ where
ServerEvent::RenderInvalidated(event) => self.session_id_for_buffer(event.buffer_id),
ServerEvent::SessionCreated(_)
| ServerEvent::SessionClosed(_)
| ServerEvent::SessionRenamed(_)
| ServerEvent::NodeChanged(_)
| ServerEvent::FloatingChanged(_)
| ServerEvent::FocusChanged(_) => None,
Expand Down Expand Up @@ -2279,6 +2280,7 @@ fn event_name(event: &ServerEvent) -> &'static str {
match event {
ServerEvent::SessionCreated(_) => "session_created",
ServerEvent::SessionClosed(_) => "session_closed",
ServerEvent::SessionRenamed(_) => "session_renamed",
ServerEvent::BufferCreated(_) => "buffer_created",
ServerEvent::BufferDetached(_) => "buffer_detached",
ServerEvent::NodeChanged(_) => "node_changed",
Expand All @@ -2304,6 +2306,13 @@ fn event_info(name: &str, event: &ServerEvent) -> EventInfo {
node_id: None,
floating_id: None,
},
ServerEvent::SessionRenamed(event) => EventInfo {
name: name.to_owned(),
session_id: Some(event.session_id),
buffer_id: None,
node_id: None,
floating_id: None,
},
ServerEvent::BufferCreated(event) => EventInfo {
name: name.to_owned(),
session_id: None,
Expand Down
7 changes: 7 additions & 0 deletions crates/embers-client/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,13 @@ impl ClientState {
self.dirty_sessions.insert(event.session.id);
}
ServerEvent::SessionClosed(event) => self.remove_session(event.session_id),
ServerEvent::SessionRenamed(event) => {
if let Some(session) = self.sessions.get_mut(&event.session_id) {
session.name = event.name.clone();
} else {
self.dirty_sessions.insert(event.session_id);
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
ServerEvent::BufferCreated(event) => {
self.buffers.insert(event.buffer.id, event.buffer.clone());
}
Expand Down
8 changes: 8 additions & 0 deletions crates/embers-protocol/schema/embers.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ enum MessageKind : ubyte {
FloatingChangedEvent = 45,
FocusChangedEvent = 46,
RenderInvalidatedEvent = 47,
SessionRenamedEvent = 48,
}

enum ErrorCodeWire : ubyte {
Expand All @@ -56,6 +57,7 @@ enum SessionOp : ubyte {
SelectRootTab = 5,
RenameRootTab = 6,
CloseRootTab = 7,
Rename = 8,
}

enum BufferOp : ubyte {
Expand Down Expand Up @@ -380,6 +382,11 @@ table SessionClosedEvent {
session_id:ulong;
}

table SessionRenamedEvent {
session_id:ulong;
name:string;
}

table BufferCreatedEvent {
buffer:BufferRecord;
}
Expand Down Expand Up @@ -441,6 +448,7 @@ table Envelope {
floating_changed_event:FloatingChangedEvent;
focus_changed_event:FocusChangedEvent;
render_invalidated_event:RenderInvalidatedEvent;
session_renamed_event:SessionRenamedEvent;
}

root_type Envelope;
Expand Down
153 changes: 153 additions & 0 deletions crates/embers-protocol/src/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,18 @@ fn encode_session_request<'a>(
*force,
0,
),
SessionRequest::Rename {
session_id, name, ..
} => (
fb::SessionOp::Rename,
(*session_id).into(),
0,
0,
Some(name.as_str()),
None,
false,
0,
),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
SessionRequest::AddRootTab {
session_id,
title,
Expand Down Expand Up @@ -1402,6 +1414,25 @@ fn encode_server_event<'a>(
},
)
}
ServerEvent::SessionRenamed(e) => {
let name = builder.create_string(&e.name);
let event = fb::SessionRenamedEvent::create(
builder,
&fb::SessionRenamedEventArgs {
session_id: e.session_id.into(),
name: Some(name),
},
);
fb::Envelope::create(
builder,
&fb::EnvelopeArgs {
request_id: 0,
kind: fb::MessageKind::SessionRenamedEvent,
session_renamed_event: Some(event),
..Default::default()
},
)
}
ServerEvent::BufferCreated(e) => {
let buffer = encode_buffer_record(builder, &e.buffer);
let event = fb::BufferCreatedEvent::create(
Expand Down Expand Up @@ -1764,6 +1795,11 @@ pub fn decode_client_message(bytes: &[u8]) -> Result<ClientMessage, ProtocolErro
session_id: SessionId(req.session_id()),
force: req.force(),
},
fb::SessionOp::Rename => SessionRequest::Rename {
request_id,
session_id: SessionId(req.session_id()),
name: required(req.name(), "session_request.name")?.to_owned(),
},
fb::SessionOp::AddRootTab => SessionRequest::AddRootTab {
request_id,
session_id: SessionId(req.session_id()),
Expand Down Expand Up @@ -2265,6 +2301,16 @@ pub fn decode_server_envelope(bytes: &[u8]) -> Result<ServerEnvelope, ProtocolEr
},
)))
}
fb::MessageKind::SessionRenamedEvent => {
let event = required(envelope.session_renamed_event(), "session_renamed_event")?;
let name = required(event.name(), "session_renamed_event.name")?;
Ok(ServerEnvelope::Event(ServerEvent::SessionRenamed(
SessionRenamedEvent {
session_id: SessionId(event.session_id()),
name: name.to_owned(),
},
)))
}
fb::MessageKind::BufferCreatedEvent => {
let event = required(envelope.buffer_created_event(), "buffer_created_event")?;
let buffer = required(event.buffer(), "buffer_created_event.buffer")?;
Expand Down Expand Up @@ -2663,4 +2709,111 @@ mod tests {
ProtocolError::InvalidMessage("node_record.tabs.tabs")
));
}

#[test]
fn encode_decode_session_renamed_event_roundtrip() {
let original = ServerEnvelope::Event(ServerEvent::SessionRenamed(SessionRenamedEvent {
session_id: SessionId(123),
name: "my-session".to_string(),
}));

let encoded = encode_server_envelope(&original).expect("encode should succeed");

let decoded = decode_server_envelope(&encoded).expect("decode should succeed");

let ServerEnvelope::Event(ServerEvent::SessionRenamed(decoded_event)) = decoded else {
panic!(
"expected ServerEnvelope::Event(ServerEvent::SessionRenamed), got {:?}",
decoded
);
};

assert_eq!(decoded_event.session_id, SessionId(123));
assert_eq!(decoded_event.name, "my-session");
}

#[test]
fn encode_decode_session_rename_roundtrip() {
let _builder = FlatBufferBuilder::new();

// Create a SessionRequest::Rename
let original = SessionRequest::Rename {
request_id: RequestId(42),
session_id: SessionId(123),
name: "my-session".to_string(),
};

// Encode it
let encoded = encode_client_message(&ClientMessage::Session(original.clone()))
.expect("encode should succeed");

// Decode it back
let decoded = decode_client_message(&encoded).expect("decode should succeed");

// Verify round-trip
let ClientMessage::Session(SessionRequest::Rename {
request_id: decoded_req_id,
session_id: decoded_session_id,
name: decoded_name,
}) = &decoded
else {
panic!("expected SessionRequest::Rename, got {:?}", decoded);
};

// Also extract fields from original for comparison
let SessionRequest::Rename {
request_id: orig_req_id,
session_id: orig_session_id,
name: orig_name,
..
} = &original
else {
panic!("expected SessionRequest::Rename, got {:?}", original);
};

assert_eq!(decoded_req_id, orig_req_id);
assert_eq!(decoded_session_id, orig_session_id);
assert_eq!(decoded_name, orig_name);
}

#[test]
fn decode_session_rename_rejects_missing_name() {
let mut builder = FlatBufferBuilder::new();

// Construct the wire Rename payload WITHOUT a name field
let session_req = fb::SessionRequest::create(
&mut builder,
&fb::SessionRequestArgs {
op: fb::SessionOp::Rename,
session_id: 123,
buffer_id: 0,
child_node_id: 0,
name: None, // Missing name!
title: None,
force: false,
index: 0,
},
);

let envelope = fb::Envelope::create(
&mut builder,
&fb::EnvelopeArgs {
request_id: 42,
kind: fb::MessageKind::SessionRequest,
session_request: Some(session_req),
..Default::default()
},
);

builder.finish(envelope, Some("EMBR"));

// Decode should fail with the expected error
let error = decode_client_message(builder.finished_data())
.expect_err("missing name should be rejected");

assert!(matches!(
error,
ProtocolError::InvalidMessage("session_request.name")
));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
6 changes: 3 additions & 3 deletions crates/embers-protocol/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub use types::{
FocusChangedEvent, InputRequest, NodeChangedEvent, NodeRecord, NodeRecordKind, NodeRequest,
OkResponse, PingRequest, PingResponse, RenderInvalidatedEvent, ScrollbackSliceResponse,
ServerEnvelope, ServerEvent, ServerResponse, SessionClosedEvent, SessionCreatedEvent,
SessionRecord, SessionRequest, SessionSnapshot, SessionSnapshotResponse, SessionsResponse,
SnapshotResponse, SplitRecord, SubscribeRequest, SubscriptionAckResponse, TabRecord,
TabsRecord, UnsubscribeRequest, VisibleSnapshotResponse,
SessionRecord, SessionRenamedEvent, SessionRequest, SessionSnapshot, SessionSnapshotResponse,
SessionsResponse, SnapshotResponse, SplitRecord, SubscribeRequest, SubscriptionAckResponse,
TabRecord, TabsRecord, UnsubscribeRequest, VisibleSnapshotResponse,
};
Loading
Loading