feat(vterm): bind exact frontend terminal contexts
Resolve terminal commands and buffer switches against the authenticated invoking frontend, publish copied selections to that frontend, and wire daemon rendering, resize, focus, paste, bell, and detach lifecycle through exact view identities. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
7c3953563c
commit
0a846d91ce
|
|
@ -1069,10 +1069,20 @@ fn dispatcher_loop(
|
||||||
// the sweep below); other-frontend snapshots lag by
|
// the sweep below); other-frontend snapshots lag by
|
||||||
// at most one tick. Imperceptible at frame cadence.
|
// at most one tick. Imperceptible at frame cadence.
|
||||||
let other_presences = session_registry.other_presences_for(*fid);
|
let other_presences = session_registry.other_presences_for(*fid);
|
||||||
|
let render_size = render_states
|
||||||
|
.get(fid)
|
||||||
|
.expect("render_state present for attached grid fid")
|
||||||
|
.size();
|
||||||
|
let terminal_snapshots = editor.prepare_terminal_views(*fid, render_size);
|
||||||
let render_state = render_states
|
let render_state = render_states
|
||||||
.get_mut(fid)
|
.get_mut(fid)
|
||||||
.expect("render_state present for attached grid fid");
|
.expect("render_state present for attached grid fid");
|
||||||
render_state.render_frame(editor, &other_presences)
|
render_state.render_frame(
|
||||||
|
editor,
|
||||||
|
*fid,
|
||||||
|
&terminal_snapshots,
|
||||||
|
&other_presences,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
// T M10.6 per-frontend presence sweep. The snapshot is
|
// T M10.6 per-frontend presence sweep. The snapshot is
|
||||||
|
|
@ -1103,7 +1113,7 @@ fn dispatcher_loop(
|
||||||
s.negotiated_capabilities.crdt_replica && s.negotiated_protocol_version >= 4
|
s.negotiated_capabilities.crdt_replica && s.negotiated_protocol_version >= 4
|
||||||
}) && let Some(stream) = streams.get_mut(fid)
|
}) && let Some(stream) = streams.get_mut(fid)
|
||||||
{
|
{
|
||||||
let idle_now = editor.dispatch_idle();
|
let idle_now = editor.dispatch_idle_for(*fid);
|
||||||
if last_dispatch_idle_sent.get(fid) != Some(&idle_now) {
|
if last_dispatch_idle_sent.get(fid) != Some(&idle_now) {
|
||||||
if let Err(e) =
|
if let Err(e) =
|
||||||
write_message(stream, &InstanceMessage::DispatchIdle { idle: idle_now })
|
write_message(stream, &InstanceMessage::DispatchIdle { idle: idle_now })
|
||||||
|
|
@ -1298,6 +1308,7 @@ fn dispatcher_loop(
|
||||||
last_dispatch_idle_sent.remove(fid);
|
last_dispatch_idle_sent.remove(fid);
|
||||||
last_active_buffer_sent.remove(fid);
|
last_active_buffer_sent.remove(fid);
|
||||||
terminal_bell_baselines.remove(fid);
|
terminal_bell_baselines.remove(fid);
|
||||||
|
editor.detach_frontend_input(*fid);
|
||||||
editor.terminal_manager.borrow_mut().detach_frontend(*fid);
|
editor.terminal_manager.borrow_mut().detach_frontend(*fid);
|
||||||
session_registry.unregister_session(*fid);
|
session_registry.unregister_session(*fid);
|
||||||
editor
|
editor
|
||||||
|
|
@ -1370,6 +1381,15 @@ fn dispatcher_loop(
|
||||||
Err(mpsc::RecvTimeoutError::Disconnected) => break,
|
Err(mpsc::RecvTimeoutError::Disconnected) => break,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Accepted terminal context controls PTY size. Apply any focus,
|
||||||
|
// window, or resize changes before consuming another child-output
|
||||||
|
// batch so screen reflow and subsequent bytes share one geometry.
|
||||||
|
for frontend_id in &attached_fids {
|
||||||
|
if let Some(size) = term_sizes.get(frontend_id).copied() {
|
||||||
|
editor.sync_terminal_layout(*frontend_id, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// `tick_async` last: the M4.5 async bridge settles awaiters
|
// `tick_async` last: the M4.5 async bridge settles awaiters
|
||||||
// inside `tick_lsp` (via the message bus); draining + resuming
|
// inside `tick_lsp` (via the message bus); draining + resuming
|
||||||
// in the same frame keeps LSP `:await()` latency at one frame
|
// in the same frame keeps LSP `:await()` latency at one frame
|
||||||
|
|
@ -1658,6 +1678,12 @@ fn handle_dispatcher_event(
|
||||||
editor.dispatch_menu_pointer(source, index, invoke);
|
editor.dispatch_menu_pointer(source, index, invoke);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
FrontendEvent::FocusGained(_) => {
|
||||||
|
editor.dispatch_focus(source, true);
|
||||||
|
}
|
||||||
|
FrontendEvent::FocusLost(_) => {
|
||||||
|
editor.dispatch_focus(source, false);
|
||||||
|
}
|
||||||
FrontendEvent::Paste {
|
FrontendEvent::Paste {
|
||||||
frontend_id: claimed_fid,
|
frontend_id: claimed_fid,
|
||||||
data,
|
data,
|
||||||
|
|
@ -1680,7 +1706,9 @@ fn handle_dispatcher_event(
|
||||||
// source's command chain (Q#KR2), and it fires
|
// source's command chain (Q#KR2), and it fires
|
||||||
// `buffer.after-edit` like any other edit (Q#KR10b)
|
// `buffer.after-edit` like any other edit (Q#KR10b)
|
||||||
// — previously it never did, so LSP missed pastes.
|
// — previously it never did, so LSP missed pastes.
|
||||||
handle_inbound_paste(editor, source, claimed_fid, &data);
|
if !editor.dispatch_paste(source, &data) {
|
||||||
|
handle_inbound_paste(editor, source, claimed_fid, &data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let term_size = *term_sizes
|
let term_size = *term_sizes
|
||||||
|
|
@ -1721,6 +1749,7 @@ fn handle_dispatcher_event(
|
||||||
last_dispatch_idle_sent.remove(&frontend_id);
|
last_dispatch_idle_sent.remove(&frontend_id);
|
||||||
last_active_buffer_sent.remove(&frontend_id);
|
last_active_buffer_sent.remove(&frontend_id);
|
||||||
terminal_bell_baselines.remove(&frontend_id);
|
terminal_bell_baselines.remove(&frontend_id);
|
||||||
|
editor.detach_frontend_input(frontend_id);
|
||||||
editor
|
editor
|
||||||
.terminal_manager
|
.terminal_manager
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
|
|
|
||||||
|
|
@ -2484,8 +2484,13 @@ impl EditorCore {
|
||||||
/// read (`C-k`'s killed line, an appended chain), so the Lua ring
|
/// read (`C-k`'s killed line, an appended chain), so the Lua ring
|
||||||
/// pushes the exact bytes here.
|
/// pushes the exact bytes here.
|
||||||
pub fn clipboard_set(&mut self, bytes: Vec<u8>) {
|
pub fn clipboard_set(&mut self, bytes: Vec<u8>) {
|
||||||
|
self.clipboard_set_for(self.active_frontend, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set and publish clipboard bytes to one authenticated frontend.
|
||||||
|
pub fn clipboard_set_for(&mut self, frontend_id: FrontendId, bytes: Vec<u8>) {
|
||||||
self.clipboard_slot.clone_from(&bytes);
|
self.clipboard_slot.clone_from(&bytes);
|
||||||
self.pending_clipboard = Some((self.active_frontend, bytes));
|
self.pending_clipboard = Some((frontend_id, bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The clipboard slot's current bytes, or `None` when empty (kill
|
/// The clipboard slot's current bytes, or `None` when empty (kill
|
||||||
|
|
@ -2895,15 +2900,21 @@ impl EditorCore {
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Switch the active window to a different buffer, allocating a
|
/// Switch one frontend's active window to a different buffer, allocating
|
||||||
/// fresh [`TextView`] for it.
|
/// a fresh [`TextView`] for it without changing global active state.
|
||||||
pub fn switch_active_buffer(&mut self, buffer_id: BufferId) -> Result<(), String> {
|
pub fn switch_active_buffer_for(
|
||||||
|
&mut self,
|
||||||
|
frontend_id: FrontendId,
|
||||||
|
buffer_id: BufferId,
|
||||||
|
) -> Result<(), String> {
|
||||||
let text_view = {
|
let text_view = {
|
||||||
let reg = self.registry.borrow();
|
let reg = self.registry.borrow();
|
||||||
let buf = reg.get(buffer_id).map_err(|e| e.to_string())?;
|
let buf = reg.get(buffer_id).map_err(|e| e.to_string())?;
|
||||||
TextView::new(buf)
|
TextView::new(buf)
|
||||||
};
|
};
|
||||||
let aw = self.active_window_mut();
|
let aw = self
|
||||||
|
.active_window_mut_for(frontend_id)
|
||||||
|
.ok_or_else(|| format!("frontend {frontend_id:?} has no active window"))?;
|
||||||
aw.buffer_id = buffer_id;
|
aw.buffer_id = buffer_id;
|
||||||
aw.text_view = text_view;
|
aw.text_view = text_view;
|
||||||
// Overlays were keyed to the previous buffer's coordinates;
|
// Overlays were keyed to the previous buffer's coordinates;
|
||||||
|
|
@ -2917,6 +2928,11 @@ impl EditorCore {
|
||||||
aw.goal_col = None;
|
aw.goal_col = None;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Switch the globally active frontend's active window.
|
||||||
|
pub fn switch_active_buffer(&mut self, buffer_id: BufferId) -> Result<(), String> {
|
||||||
|
self.switch_active_buffer_for(self.active_frontend, buffer_id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ use crate::buffer_registry::BufferRegistry;
|
||||||
use crate::cell::{Color, Style, UnderlineStyle};
|
use crate::cell::{Color, Style, UnderlineStyle};
|
||||||
use crate::command::{Command, CommandError, CommandRegistry, SourceLocation};
|
use crate::command::{Command, CommandError, CommandRegistry, SourceLocation};
|
||||||
use crate::editor_core::EditorCore;
|
use crate::editor_core::EditorCore;
|
||||||
|
use crate::editor::InteractiveCommandOrigin;
|
||||||
use crate::highlight::SyntaxHighlightView;
|
use crate::highlight::SyntaxHighlightView;
|
||||||
use crate::hook::{Hook, HookRegistry};
|
use crate::hook::{Hook, HookRegistry};
|
||||||
use crate::key::{display_sequence, parse_sequence};
|
use crate::key::{display_sequence, parse_sequence};
|
||||||
|
|
@ -8134,6 +8135,111 @@ pub fn make_terminal_manager(
|
||||||
Ok(manager)
|
Ok(manager)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn terminal_shared_core(lua: &Lua, operation: &str) -> mlua::Result<SharedCore> {
|
||||||
|
lua.app_data_ref::<SharedCore>()
|
||||||
|
.map(|core| core.clone())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
mlua::Error::external(format!(
|
||||||
|
"pmacs.terminal.{operation}: editor core unavailable"
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn terminal_command_frontend(lua: &Lua, core: &SharedCore) -> crate::protocol::FrontendId {
|
||||||
|
lua.app_data_ref::<InteractiveCommandOrigin>()
|
||||||
|
.and_then(|origin| origin.current())
|
||||||
|
.unwrap_or_else(|| core.borrow().active_frontend)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn active_terminal_view_key(
|
||||||
|
lua: &Lua,
|
||||||
|
core: &SharedCore,
|
||||||
|
operation: &str,
|
||||||
|
) -> mlua::Result<crate::terminal::TerminalViewKey> {
|
||||||
|
let frontend_id = lua
|
||||||
|
.app_data_ref::<InteractiveCommandOrigin>()
|
||||||
|
.and_then(|origin| origin.current())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
mlua::Error::external(format!(
|
||||||
|
"pmacs.terminal.{operation}: requires an interactive frontend context"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
let core = core.borrow();
|
||||||
|
let window = core.active_window_for(frontend_id).ok_or_else(|| {
|
||||||
|
mlua::Error::external(format!(
|
||||||
|
"pmacs.terminal.{operation}: invoking frontend has no active window"
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
Ok(crate::terminal::TerminalViewKey::new(
|
||||||
|
frontend_id,
|
||||||
|
core.views
|
||||||
|
.get(&frontend_id)
|
||||||
|
.expect("active window implies registered frontend view")
|
||||||
|
.active,
|
||||||
|
window.buffer_id,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn terminal_view_key_from_context(
|
||||||
|
core: &SharedCore,
|
||||||
|
context: &Table,
|
||||||
|
) -> mlua::Result<Option<crate::terminal::TerminalViewKey>> {
|
||||||
|
const FIELDS: &[&str] = &["frontend", "window", "buffer", "active"];
|
||||||
|
let mut unknown = None;
|
||||||
|
context.clone().for_each(|key: Value, _: Value| {
|
||||||
|
if unknown.is_none() {
|
||||||
|
match key {
|
||||||
|
Value::String(value) => {
|
||||||
|
let value = value.to_str()?;
|
||||||
|
if !FIELDS.contains(&value.as_ref()) {
|
||||||
|
unknown = Some(value.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => unknown = Some(format!("{other:?}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
if let Some(field) = unknown {
|
||||||
|
return Err(mlua::Error::external(format!(
|
||||||
|
"pmacs.terminal.view_state: unknown field `{field}`"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let frontend_raw = context.get::<i64>("frontend")?;
|
||||||
|
let frontend_id = crate::protocol::FrontendId(u64::try_from(frontend_raw).map_err(|_| {
|
||||||
|
mlua::Error::external("pmacs.terminal.view_state: `frontend` must be nonnegative")
|
||||||
|
})?);
|
||||||
|
let window_raw = u64::try_from(context.get::<i64>("window")?).map_err(|_| {
|
||||||
|
mlua::Error::external("pmacs.terminal.view_state: `window` must be nonnegative")
|
||||||
|
})?;
|
||||||
|
let buffer_id = context.get::<BufferIdLua>("buffer")?.0;
|
||||||
|
|
||||||
|
let core = core.borrow();
|
||||||
|
let Some(view) = core.views.get(&frontend_id) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let Some(window_id) = view
|
||||||
|
.layout
|
||||||
|
.iter_ids()
|
||||||
|
.into_iter()
|
||||||
|
.find(|window_id| window_id.raw() == window_raw)
|
||||||
|
else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
if core
|
||||||
|
.windows
|
||||||
|
.get(&window_id)
|
||||||
|
.is_none_or(|window| window.buffer_id != buffer_id)
|
||||||
|
{
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
Ok(Some(crate::terminal::TerminalViewKey::new(
|
||||||
|
frontend_id,
|
||||||
|
window_id,
|
||||||
|
buffer_id,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
fn install_terminal(
|
fn install_terminal(
|
||||||
lua: &Lua,
|
lua: &Lua,
|
||||||
manager: &crate::terminal::SharedTerminalManager,
|
manager: &crate::terminal::SharedTerminalManager,
|
||||||
|
|
@ -8155,6 +8261,12 @@ fn install_terminal(
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
mlua::Error::external("pmacs.terminal.open: editor core unavailable")
|
mlua::Error::external("pmacs.terminal.open: editor core unavailable")
|
||||||
})?;
|
})?;
|
||||||
|
let frontend_id = terminal_command_frontend(lua, &core);
|
||||||
|
if core.borrow().active_window_for(frontend_id).is_none() {
|
||||||
|
return Err(mlua::Error::external(
|
||||||
|
"pmacs.terminal.open: target frontend has no active window",
|
||||||
|
));
|
||||||
|
}
|
||||||
let buffer_id = {
|
let buffer_id = {
|
||||||
let mut manager = manager.borrow_mut();
|
let mut manager = manager.borrow_mut();
|
||||||
manager
|
manager
|
||||||
|
|
@ -8167,7 +8279,7 @@ fn install_terminal(
|
||||||
};
|
};
|
||||||
let key = {
|
let key = {
|
||||||
let mut core = core.borrow_mut();
|
let mut core = core.borrow_mut();
|
||||||
if let Err(error) = core.switch_active_buffer(buffer_id) {
|
if let Err(error) = core.switch_active_buffer_for(frontend_id, buffer_id) {
|
||||||
let _ = core.registry.borrow_mut().remove(buffer_id);
|
let _ = core.registry.borrow_mut().remove(buffer_id);
|
||||||
manager
|
manager
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
|
|
@ -8177,18 +8289,27 @@ fn install_terminal(
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
crate::terminal::TerminalViewKey::new(
|
crate::terminal::TerminalViewKey::new(
|
||||||
core.active_frontend,
|
frontend_id,
|
||||||
core.active_window_id(),
|
core.views
|
||||||
|
.get(&frontend_id)
|
||||||
|
.expect("checked frontend has active view")
|
||||||
|
.active,
|
||||||
buffer_id,
|
buffer_id,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
{
|
let claimed = {
|
||||||
let mut manager = manager.borrow_mut();
|
let mut manager = manager.borrow_mut();
|
||||||
if !manager.register_view(key) || !manager.claim_controller(key) {
|
manager.register_view(key) && manager.claim_controller(key)
|
||||||
return Err(mlua::Error::external(
|
};
|
||||||
"pmacs.terminal.open: failed to claim the new terminal view",
|
if !claimed {
|
||||||
));
|
let mut core = core.borrow_mut();
|
||||||
}
|
let _ = core.registry.borrow_mut().remove(buffer_id);
|
||||||
|
manager
|
||||||
|
.borrow_mut()
|
||||||
|
.prune(&mut core, &mut supervisor.borrow_mut());
|
||||||
|
return Err(mlua::Error::external(
|
||||||
|
"pmacs.terminal.open: failed to claim the new terminal view",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
run_hook_if_defined(lua, "buffer.after-switch", mlua::MultiValue::new());
|
run_hook_if_defined(lua, "buffer.after-switch", mlua::MultiValue::new());
|
||||||
Ok(BufferIdLua(buffer_id))
|
Ok(BufferIdLua(buffer_id))
|
||||||
|
|
@ -8250,6 +8371,88 @@ fn install_terminal(
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let manager = manager.clone();
|
||||||
|
terminal.set(
|
||||||
|
"view_state",
|
||||||
|
lua.create_function(move |lua, context: Table| -> mlua::Result<Option<Table>> {
|
||||||
|
let core = terminal_shared_core(lua, "view_state")?;
|
||||||
|
let Some(key) = terminal_view_key_from_context(&core, &context)? else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let Some(status) = manager.borrow_mut().view_status(key) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let table = lua.create_table()?;
|
||||||
|
table.set("at_bottom", status.at_bottom)?;
|
||||||
|
table.set("scroll_offset", status.scroll_offset)?;
|
||||||
|
table.set("selection", status.selection)?;
|
||||||
|
Ok(Some(table))
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let manager = manager.clone();
|
||||||
|
terminal.set(
|
||||||
|
"scroll",
|
||||||
|
lua.create_function(move |lua, lines: i64| {
|
||||||
|
let lines = i32::try_from(lines).map_err(|_| {
|
||||||
|
mlua::Error::external("pmacs.terminal.scroll: `lines` exceeds i32 range")
|
||||||
|
})?;
|
||||||
|
let core = terminal_shared_core(lua, "scroll")?;
|
||||||
|
let key = active_terminal_view_key(lua, &core, "scroll")?;
|
||||||
|
Ok(manager.borrow_mut().scroll_lines(key, lines))
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let manager = manager.clone();
|
||||||
|
terminal.set(
|
||||||
|
"_scroll_page",
|
||||||
|
lua.create_function(move |lua, direction: i64| {
|
||||||
|
let direction = i32::try_from(direction).map_err(|_| {
|
||||||
|
mlua::Error::external(
|
||||||
|
"pmacs.terminal._scroll_page: `direction` exceeds i32 range",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let core = terminal_shared_core(lua, "_scroll_page")?;
|
||||||
|
let key = active_terminal_view_key(lua, &core, "_scroll_page")?;
|
||||||
|
Ok(manager.borrow_mut().scroll_page(key, direction))
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let manager = manager.clone();
|
||||||
|
terminal.set(
|
||||||
|
"scroll_to_bottom",
|
||||||
|
lua.create_function(move |lua, ()| {
|
||||||
|
let core = terminal_shared_core(lua, "scroll_to_bottom")?;
|
||||||
|
let key = active_terminal_view_key(lua, &core, "scroll_to_bottom")?;
|
||||||
|
Ok(manager.borrow_mut().scroll_to_bottom(key))
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let manager = manager.clone();
|
||||||
|
terminal.set(
|
||||||
|
"copy_selection",
|
||||||
|
lua.create_function(move |lua, ()| {
|
||||||
|
let core = terminal_shared_core(lua, "copy_selection")?;
|
||||||
|
let key = active_terminal_view_key(lua, &core, "copy_selection")?;
|
||||||
|
let Some(bytes) = manager.borrow_mut().copy_selection(key) else {
|
||||||
|
return Ok(false);
|
||||||
|
};
|
||||||
|
core.borrow_mut()
|
||||||
|
.clipboard_set_for(key.frontend_id, bytes);
|
||||||
|
Ok(true)
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
pmacs.set("terminal", terminal)
|
pmacs.set("terminal", terminal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3824,7 +3824,12 @@ mod tests {
|
||||||
let buffer_id = active_buffer(&state);
|
let buffer_id = active_buffer(&state);
|
||||||
sem.set_viewport(buffer_id, ByteRange { start: 0, end: 80 }, 0);
|
sem.set_viewport(buffer_id, ByteRange { start: 0, end: 80 }, 0);
|
||||||
|
|
||||||
let grid_msgs = grid.render_frame(&state, &[]);
|
let grid_msgs = grid.render_frame(
|
||||||
|
&state,
|
||||||
|
FrontendId::LOCAL,
|
||||||
|
&HashMap::new(),
|
||||||
|
&[],
|
||||||
|
);
|
||||||
let sem_msgs = sem.render_frame(&state);
|
let sem_msgs = sem.render_frame(&state);
|
||||||
assert!(
|
assert!(
|
||||||
matches!(grid_msgs[0], InstanceMessage::CellDelta { .. }),
|
matches!(grid_msgs[0], InstanceMessage::CellDelta { .. }),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue