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
23 changes: 23 additions & 0 deletions mux/bindings/conformance/fixtures.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,29 @@
}
]
},
{
"name": "server-client-control",
"steps": [
{
"type": "command",
"request": { "id": 1, "cmd": "ping" },
"expect": { "id": 1, "ok": true, "data": { "ok": true, "protocol": 6 } },
"match": "partial"
},
{
"type": "command",
"request": { "id": 2, "cmd": "set-window-title", "title": "cmux-conformance" },
"expect": { "id": 2, "ok": true, "data": {} },
"match": "exact"
},
{
"type": "command",
"request": { "id": 3, "cmd": "clear-window-title" },
"expect": { "id": 3, "ok": true, "data": {} },
"match": "exact"
}
]
},
{
"name": "workspace-tab-split-rename",
"steps": [
Expand Down
4 changes: 4 additions & 0 deletions mux/bindings/conformance/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,10 @@ def check_requires(fixture: Dict[str, Any], socket_path: str) -> None:
def dispatch(client: CmuxClient, cmd: str, params: Dict[str, Any]) -> Any:
mapping = {
"identify": client.identify,
"ping": client.ping,
"reload-config": client.reload_config,
"set-window-title": client.set_window_title,
"clear-window-title": lambda **kw: client.clear_window_title(),
Comment on lines +192 to +195

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Drop the unnecessary lambda for clear-window-title.

clear_window_title() takes no arguments, so the lambda wrapper is redundant — ping and reload-config map their no-arg methods directly. The lambda also triggers a Ruff ARG005 warning for the unused kw parameter.

♻️ Proposed fix
-        "clear-window-title": lambda **kw: client.clear_window_title(),
+        "clear-window-title": client.clear_window_title,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"ping": client.ping,
"reload-config": client.reload_config,
"set-window-title": client.set_window_title,
"clear-window-title": lambda **kw: client.clear_window_title(),
"ping": client.ping,
"reload-config": client.reload_config,
"set-window-title": client.set_window_title,
"clear-window-title": client.clear_window_title,
🧰 Tools
🪛 Ruff (0.15.20)

[warning] 195-195: Unused lambda argument: kw

(ARG005)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mux/bindings/conformance/runner.py` around lines 192 - 195, The
`clear-window-title` entry in `runner.py` uses a redundant lambda wrapper around
`client.clear_window_title`, which also introduces an unused `kw` parameter
warning. Update the mapping alongside `ping`, `reload-config`, and
`set-window-title` so `clear-window-title` points directly to
`client.clear_window_title` without any wrapper.

Source: Linters/SAST tools

"list-workspaces": client.list_workspaces,
"export-layout": client.export_layout,
"apply-layout": client.apply_layout,
Expand Down
40 changes: 40 additions & 0 deletions mux/bindings/python/cmux/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ class IdentifyResult:
pid: int


@dataclass(frozen=True)
class PingResult:
ok: bool
version: str
protocol: int


@dataclass(frozen=True)
class ReloadConfigResult:
reloaded: bool
path: Optional[str]


@dataclass(frozen=True)
class SurfaceResult:
surface: int
Expand Down Expand Up @@ -136,6 +149,8 @@ class Event:
rows: Optional[int] = None
data: Optional[str] = None
replay: Optional[str] = None
offset: Optional[int] = None
at_bottom: Optional[bool] = None

@property
def bytes_data(self) -> Optional[bytes]:
Expand Down Expand Up @@ -302,6 +317,21 @@ def identify(self) -> IdentifyResult:
self._protocol = result.protocol
return result

def ping(self) -> PingResult:
data = self._request("ping")
return PingResult(
ok=bool(data["ok"]),
version=str(data["version"]),
protocol=int(data["protocol"]),
)

def reload_config(self) -> ReloadConfigResult:
data = self._request("reload-config")
return ReloadConfigResult(
reloaded=bool(data.get("reloaded", False)),
path=data.get("path"),
)

def list_workspaces(self) -> Tree:
return _parse_tree(self._request("list-workspaces"))

Expand Down Expand Up @@ -410,6 +440,14 @@ def set_default_colors(self, fg: Optional[str] = None, bg: Optional[str] = None)
self._request("set-default-colors", fg=fg, bg=bg)
return EmptyResult()

def set_window_title(self, title: str) -> EmptyResult:
self._request("set-window-title", title=title)
return EmptyResult()

def clear_window_title(self) -> EmptyResult:
self._request("clear-window-title")
return EmptyResult()

def close_surface(self, surface: int) -> EmptyResult:
self._request("close-surface", surface=surface)
return EmptyResult()
Expand Down Expand Up @@ -570,4 +608,6 @@ def _parse_event(value: Dict[str, Any]) -> Event:
rows=value.get("rows"),
data=value.get("data"),
replay=value.get("replay"),
offset=value.get("offset"),
at_bottom=value.get("at_bottom"),
)
30 changes: 24 additions & 6 deletions mux/crates/mux-core/src/mux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ pub enum MuxEvent {
Bell(SurfaceId),
Notification(NotificationEvent),
Status(String),
/// A frontend should reload its local mux configuration and redraw.
ConfigReloadRequested,
/// A frontend should set its host terminal window title. Empty clears it.
WindowTitleRequested(String),
/// A PTY surface viewport moved within its scrollback.
ScrollChanged {
surface: SurfaceId,
offset: u64,
at_bottom: bool,
},
/// The workspace/screen/pane/tab tree changed (from any frontend or
/// the control socket).
TreeChanged,
Expand Down Expand Up @@ -192,7 +202,7 @@ pub struct Mux {
next_id: AtomicU64,
next_notification_id: AtomicU64,
next_active_at: AtomicU64,
surface_options: SurfaceOptions,
surface_options: Mutex<SurfaceOptions>,
browser_runtime: Mutex<Option<Arc<BrowserRuntime>>>,
cell_pixels: Mutex<(u16, u16)>,
default_colors: Mutex<DefaultColors>,
Expand Down Expand Up @@ -227,7 +237,7 @@ impl Mux {
next_id: AtomicU64::new(1),
next_notification_id: AtomicU64::new(1),
next_active_at: AtomicU64::new(1),
surface_options,
surface_options: Mutex::new(surface_options),
browser_runtime: Mutex::new(None),
cell_pixels: Mutex::new((8, 16)),
default_colors: Mutex::new(DefaultColors::default()),
Expand Down Expand Up @@ -286,7 +296,7 @@ impl Mux {
size: Option<(u16, u16)>,
) -> anyhow::Result<Arc<Surface>> {
let id = self.next_id();
let mut opts = self.surface_options.clone();
let mut opts = self.surface_options.lock().unwrap().clone();
if cwd.is_some() {
opts.cwd = cwd;
}
Expand Down Expand Up @@ -326,7 +336,7 @@ impl Mux {
size: Option<(u16, u16)>,
) -> Arc<Surface> {
let id = self.next_id();
let opts = self.surface_options.clone();
let opts = self.surface_options.lock().unwrap().clone();
let size = size.unwrap_or((opts.cols, opts.rows));
let cell_pixels = *self.cell_pixels.lock().unwrap();
let surface = browser::new_surface(id, url.clone(), size, cell_pixels, &opts);
Expand All @@ -340,7 +350,8 @@ impl Mux {
if let Some(existing) = runtime.as_ref().filter(|existing| !existing.is_closed()) {
return Ok(existing.clone());
}
let created = BrowserRuntime::connect(&self.surface_options)?;
let opts = self.surface_options.lock().unwrap().clone();
let created = BrowserRuntime::connect(&opts)?;
*runtime = Some(created.clone());
Ok(created)
}
Expand Down Expand Up @@ -509,6 +520,13 @@ impl Mux {
}
}

/// Update options used for future surface/browser launches.
pub fn update_surface_options(&self, update: impl FnOnce(&mut SurfaceOptions)) {
let mut options = self.surface_options.lock().unwrap();
update(&mut options);
options.browser_session_name = self.session.clone();
}

pub fn set_cell_pixel_size(&self, width_px: u16, height_px: u16) {
let next = (width_px.max(1), height_px.max(1));
{
Expand Down Expand Up @@ -890,7 +908,7 @@ impl Mux {
(pane_id, size)
};
let id = self.next_id();
let opts = self.surface_options.clone();
let opts = self.surface_options.lock().unwrap().clone();
let size = size.unwrap_or((opts.cols, opts.rows));
let cell_pixels = *self.cell_pixels.lock().unwrap();
let surface = browser::new_surface(id, url.clone(), size, cell_pixels, &opts);
Expand Down
Loading
Loading