session 1 commit 1/4: workspace + identity types moved to pmacs-protocol
Workspace skeleton: root Cargo.toml becomes a workspace with members [".", "pmacs-protocol"]; [workspace.dependencies] pins serde, postcard, thiserror so both crates use byte-identical versions (the wire format depends on it). pmacs main package keeps its existing shape (no file moves); it just gains pmacs-protocol as a path dependency. Identity types moved: BufferId (from buffer.rs), FrontendId + ByteRange (from protocol.rs), Position type alias (from rope.rs). All four are self-contained — no custom-type dependencies — so the first stage of the move can land atomically without dragging cell/message types along. src/buffer.rs / src/protocol.rs / src/rope.rs each gain a 'pub use pmacs_protocol::...' re-export for the moved names, so existing internal imports (crate::buffer::BufferId, crate::rope::Position, etc.) continue to resolve unchanged. New consumers (pmacs-gpu, debug tools) will depend on pmacs-protocol directly. One visibility change: BufferId::from_raw was pub(crate); promoted to pub with a doc note that it's not stable API for external consumers. The (crate) restriction was advisory only — external deserialization already worked via the derived Deserialize, so making it pub doesn't widen the actual surface, just makes it honest. Lib gate: 1314 passed, no regressions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f7cea2ec3a
commit
14341d958e
|
|
@ -1249,6 +1249,7 @@ dependencies = [
|
|||
"loro",
|
||||
"mlua",
|
||||
"nix 0.29.0",
|
||||
"pmacs-protocol",
|
||||
"portable-pty",
|
||||
"postcard",
|
||||
"proptest",
|
||||
|
|
@ -1272,6 +1273,15 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pmacs-protocol"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"postcard",
|
||||
"serde",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-pty"
|
||||
version = "0.9.0"
|
||||
|
|
|
|||
29
Cargo.toml
29
Cargo.toml
|
|
@ -1,3 +1,19 @@
|
|||
[workspace]
|
||||
# Session 1 of the pmacs-gpu arc: `pmacs-protocol` is the wire-types
|
||||
# crate the post-v1.0 frontend (`pmacs-gpu`) will consume directly. The
|
||||
# root `pmacs` package stays a workspace member (no file moves); the
|
||||
# new crate lives under `pmacs-protocol/`. See
|
||||
# `docs/pmacs-gpu-design.md`.
|
||||
members = [".", "pmacs-protocol"]
|
||||
|
||||
[workspace.dependencies]
|
||||
# Shared between `pmacs` and `pmacs-protocol`. Pinned here so both
|
||||
# crates use byte-identical postcard / serde versions — the wire
|
||||
# format depends on it.
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
postcard = { version = "1", features = ["use-std"] }
|
||||
thiserror = "2"
|
||||
|
||||
[package]
|
||||
name = "pmacs"
|
||||
version = "1.0.0"
|
||||
|
|
@ -60,7 +76,7 @@ crdt = ["dep:loro"]
|
|||
|
||||
[dependencies]
|
||||
crossterm = "0.28"
|
||||
thiserror = "2"
|
||||
thiserror = { workspace = true }
|
||||
unicode-width = "0.2"
|
||||
# Work-stealing deque, MPMC channels, and parking primitives for the
|
||||
# M3 worker pool (spec §6.3). The umbrella crate re-exports
|
||||
|
|
@ -70,8 +86,15 @@ crossbeam = "0.8"
|
|||
# trait, `rmp-serde` is the MessagePack codec the spec calls out by
|
||||
# name; in-process and out-of-process workers must look identical to
|
||||
# Lua, which means even the in-process bus encodes through MessagePack.
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde = { workspace = true }
|
||||
rmp-serde = "1"
|
||||
# Wire-types crate (session 1 of pmacs-gpu arc). Owns the
|
||||
# `InstanceMessage` / `FrontendEvent` / capability / `SemanticFrame`
|
||||
# family, plus the cell/buffer/rope wire types they reference. The
|
||||
# `pmacs` crate re-exports through `crate::protocol`, `crate::cell`,
|
||||
# `crate::buffer`, and `crate::rope` so existing internal imports keep
|
||||
# working unchanged.
|
||||
pmacs-protocol = { version = "1.0.0", path = "pmacs-protocol" }
|
||||
# Wire format for the M5 frontend ↔ instance protocol (T M5.5b).
|
||||
# Length-prefix framing wraps postcard-encoded payloads. Chosen for
|
||||
# compactness on the cell-stream traffic (60 Hz cell deltas dominate
|
||||
|
|
@ -81,7 +104,7 @@ rmp-serde = "1"
|
|||
# `src/protocol.rs::Hello`. The worker-protocol encoding (§5.5 spec)
|
||||
# remains MessagePack via `rmp-serde`; different subsystems with
|
||||
# different requirements (compactness vs schema evolution).
|
||||
postcard = { version = "1", features = ["use-std"] }
|
||||
postcard = { workspace = true }
|
||||
# Signal handling for the M5.5 daemon (T M5.5e). Provides a safe
|
||||
# wrapper for installing handlers that set an AtomicBool flag, which
|
||||
# our accept loop polls between iterations. Used for SIGTERM/SIGINT
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
[package]
|
||||
name = "pmacs-protocol"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.95"
|
||||
description = "Wire types for the pmacs daemon ↔ frontend protocol (the SemanticFrame family, capabilities, attach handshake)"
|
||||
license = "MIT OR Apache-2.0"
|
||||
authors = ["Pmacs contributors"]
|
||||
readme = "../README.md"
|
||||
repository = "https://git.levineuwirth.org/neuwirth/pmacs"
|
||||
homepage = "https://levineuwirth.org/essays/pmacs"
|
||||
keywords = ["editor", "emacs", "protocol", "ipc"]
|
||||
categories = ["text-editors", "data-structures"]
|
||||
|
||||
[lints.rust]
|
||||
unsafe_code = "forbid"
|
||||
missing_docs = "warn"
|
||||
|
||||
[lints.clippy]
|
||||
all = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
cargo = { level = "warn", priority = -1 }
|
||||
# Allows: same set as the root `pmacs` crate, mirrored so wire types
|
||||
# moved here don't trip lints the originals didn't.
|
||||
module_name_repetitions = "allow"
|
||||
must_use_candidate = "allow"
|
||||
missing_errors_doc = "allow"
|
||||
missing_panics_doc = "allow"
|
||||
cast_possible_truncation = "allow"
|
||||
cast_sign_loss = "allow"
|
||||
cast_precision_loss = "allow"
|
||||
similar_names = "allow"
|
||||
multiple_crate_versions = "allow"
|
||||
|
||||
[features]
|
||||
# Mirrors the `crdt` feature on the parent `pmacs` crate. When enabled,
|
||||
# the `InstanceMessage::CrdtOp` / `FrontendEvent::CrdtOp` variants exist
|
||||
# and the wire-level `CrdtOp` struct is in scope. When disabled, those
|
||||
# variants are compiled out so v0.1-era binaries built without `crdt`
|
||||
# match the pre-v1.0 wire surface byte-for-byte.
|
||||
crdt = []
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true }
|
||||
postcard = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
//! Identity and range types — moved from `pmacs::buffer`,
|
||||
//! `pmacs::protocol`, and `pmacs::rope` in session 1 of the
|
||||
//! `pmacs-gpu` arc. The originals re-export these names so internal
|
||||
//! `pmacs` imports (`crate::buffer::BufferId`, `crate::rope::Position`,
|
||||
//! etc.) keep working unchanged.
|
||||
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
/// Opaque, per-process identifier for a buffer.
|
||||
///
|
||||
/// The internal representation is private (R22): callers cannot reach
|
||||
/// for `.0`; construction goes through [`BufferId::next`].
|
||||
///
|
||||
/// T M10.5: `Serialize` / `Deserialize` derived so `BufferId` can be
|
||||
/// the routing key on `InstanceMessage::CrdtOp` / `FrontendEvent::CrdtOp`.
|
||||
/// The serialized form is the bare `u64` (transparent newtype).
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct BufferId(u64);
|
||||
|
||||
impl BufferId {
|
||||
/// Allocate a fresh [`BufferId`] from the process-wide counter.
|
||||
///
|
||||
/// Threading: any thread.
|
||||
#[must_use]
|
||||
pub fn next() -> Self {
|
||||
static COUNTER: AtomicU64 = AtomicU64::new(1);
|
||||
Self(COUNTER.fetch_add(1, Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// Inspect the raw value. Useful for logging and FFI.
|
||||
#[must_use]
|
||||
pub const fn raw(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Rebuild an ID from a raw value for crate-internal references that
|
||||
/// persist an already-issued buffer identity in generated text.
|
||||
///
|
||||
/// Was `pub(crate)` before the session-1 crate split; promoted to
|
||||
/// `pub` to remain reachable from `pmacs` after the move. Not
|
||||
/// stable API for external consumers — external callers should
|
||||
/// either round-trip via `serde` or accept that the constructor
|
||||
/// may change.
|
||||
#[must_use]
|
||||
pub const fn from_raw(raw: u64) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
}
|
||||
|
||||
/// Opaque identifier for a frontend attached to an instance.
|
||||
///
|
||||
/// Every input event carries a `FrontendId`. v0.1 uses one ID per
|
||||
/// instance ([`FrontendId::LOCAL`]); v0.3 generalizes to multi-frontend
|
||||
/// (multi-window, multi-user) without a protocol break.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FrontendId(pub u64);
|
||||
|
||||
impl FrontendId {
|
||||
/// The single frontend used in v0.1's local-attach mode.
|
||||
///
|
||||
/// Future multi-frontend deployments allocate IDs from a counter
|
||||
/// starting after this value; the constant is reserved.
|
||||
pub const LOCAL: FrontendId = FrontendId(1);
|
||||
}
|
||||
|
||||
/// Byte offset into a rope. Buffer-wide; cursor / selection / span
|
||||
/// anchors all use this type. Type alias rather than newtype so
|
||||
/// arithmetic on offsets (slice ranges, byte deltas) doesn't need
|
||||
/// conversions.
|
||||
pub type Position = u64;
|
||||
|
||||
/// Half-open byte range `[start, end)` into a buffer's rope, matching
|
||||
/// the rope's own range convention.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ByteRange {
|
||||
/// Inclusive start byte offset.
|
||||
pub start: u64,
|
||||
/// Exclusive end byte offset.
|
||||
pub end: u64,
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
//! Wire types for the pmacs daemon ↔ frontend protocol.
|
||||
//!
|
||||
//! Session 1 of the pmacs-gpu arc — see `docs/pmacs-gpu-design.md` in
|
||||
//! the workspace root. This crate owns every type that appears on the
|
||||
//! `InstanceMessage` / `FrontendEvent` wire so a future `pmacs-gpu`
|
||||
//! frontend can depend on it directly without pulling in the `pmacs`
|
||||
//! main crate (and its Lua / tree-sitter / process-supervisor surface).
|
||||
//!
|
||||
//! What lives here:
|
||||
//! - The `SemanticFrame` family: `StyleSpans`, `Decorations`,
|
||||
//! `InlineAdornments`, `BlockAdornments`, `FoldState`,
|
||||
//! `ResourceOffer`, `FileStyleSummary`.
|
||||
//! - The grid-rendering family: `CellDelta`, plus `Cell`, `Glyph`,
|
||||
//! `Style`, `Color`, `UnderlineStyle`, `CellCoord`, `CellSize`,
|
||||
//! `DiffSpan`, `Attachment`.
|
||||
//! - Identity types: `BufferId`, `FrontendId`, `Position`, `ByteRange`.
|
||||
//! - The full message envelopes: `InstanceMessage`, `FrontendEvent`,
|
||||
//! `GoodbyeReason`, capability structs, `PresenceUpdate`, etc.
|
||||
//! - The optional `CrdtOp` wire variant (feature-gated on `crdt`).
|
||||
//!
|
||||
//! What does NOT live here:
|
||||
//! - `crate::cell::CellGrid` and `crate::cell::diff()` (rendering
|
||||
//! helpers, not wire types — stay in the `pmacs` crate).
|
||||
//! - `Buffer` / `BufferRegistry` / `Rope` / `Edit` / `Range`
|
||||
//! (instance-side editor machinery).
|
||||
//! - `AttachTarget` and the attach-CLI binding error types
|
||||
//! (`pmacs`-binary-only logic; `pmacs-gpu` builds its own attach
|
||||
//! client).
|
||||
//! - Lua / tree-sitter / process-supervisor everything.
|
||||
//!
|
||||
//! The `pmacs` crate re-exports back through its existing module paths
|
||||
//! (`crate::cell::Style`, `crate::buffer::BufferId`, etc.) so internal
|
||||
//! pmacs code doesn't churn its imports. New consumers
|
||||
//! (`pmacs-gpu`, debug tools, future ports) depend on this crate
|
||||
//! directly.
|
||||
|
||||
pub mod ids;
|
||||
|
||||
pub use ids::{BufferId, ByteRange, FrontendId, Position};
|
||||
|
||||
// Cell wire types, the SemanticFrame family, top-level message
|
||||
// envelopes, and the feature-gated `CrdtOp` follow in subsequent
|
||||
// commits within this PR.
|
||||
|
|
@ -27,7 +27,6 @@
|
|||
//! observe `&Buffer` while the buffer's own `&mut self` is held.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use crate::file_io::FileMeta;
|
||||
use crate::rope::{Edit, Position, Range, Rope, RopeError};
|
||||
|
|
@ -37,40 +36,12 @@ use crate::view::{InterceptContext, View};
|
|||
// Identifiers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Opaque, per-process identifier for a buffer.
|
||||
///
|
||||
/// The internal representation is private (R22): callers cannot reach for
|
||||
/// `.0`; construction goes through [`BufferId::next`].
|
||||
///
|
||||
/// T M10.5: `Serialize` / `Deserialize` derived so `BufferId` can be the
|
||||
/// routing key on `InstanceMessage::CrdtOp` / `FrontendEvent::CrdtOp`.
|
||||
/// The serialized form is the bare `u64` (transparent newtype).
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct BufferId(u64);
|
||||
|
||||
impl BufferId {
|
||||
/// Allocate a fresh [`BufferId`] from the process-wide counter.
|
||||
///
|
||||
/// Threading: any thread.
|
||||
#[must_use]
|
||||
pub fn next() -> Self {
|
||||
static COUNTER: AtomicU64 = AtomicU64::new(1);
|
||||
Self(COUNTER.fetch_add(1, Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// Inspect the raw value. Useful for logging and FFI.
|
||||
#[must_use]
|
||||
pub const fn raw(self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Rebuild an ID from a raw value for crate-internal references that
|
||||
/// persist an already-issued buffer identity in generated text.
|
||||
#[must_use]
|
||||
pub(crate) const fn from_raw(raw: u64) -> Self {
|
||||
Self(raw)
|
||||
}
|
||||
}
|
||||
/// Re-export of `pmacs_protocol::BufferId` (moved there in session 1
|
||||
/// of the `pmacs-gpu` arc — see `docs/pmacs-gpu-design.md`). Existing
|
||||
/// `crate::buffer::BufferId` import paths continue to resolve through
|
||||
/// this re-export; new consumers (`pmacs-gpu`, debug tools) should
|
||||
/// depend on `pmacs-protocol` directly.
|
||||
pub use pmacs_protocol::BufferId;
|
||||
|
||||
/// Opaque, per-buffer identifier for an attached view.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -44,24 +44,16 @@ use crate::cell::{Cell, CellCoord, CellSize, DiffSpan};
|
|||
use std::path::PathBuf;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Frontend identity
|
||||
// Frontend identity, byte ranges — re-exports from `pmacs-protocol`
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Opaque identifier for a frontend attached to an instance.
|
||||
///
|
||||
/// Every input event carries a `FrontendId`. v0.1 uses one ID per
|
||||
/// instance ([`FrontendId::LOCAL`]); v0.3 generalizes to multi-frontend
|
||||
/// (multi-window, multi-user) without a protocol break.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct FrontendId(pub u64);
|
||||
|
||||
impl FrontendId {
|
||||
/// The single frontend used in v0.1's local-attach mode.
|
||||
///
|
||||
/// Future multi-frontend deployments allocate IDs from a counter
|
||||
/// starting after this value; the constant is reserved.
|
||||
pub const LOCAL: FrontendId = FrontendId(1);
|
||||
}
|
||||
// Session 1 of the `pmacs-gpu` arc moved the wire-types subset of
|
||||
// this module into the `pmacs-protocol` crate. See
|
||||
// `docs/pmacs-gpu-design.md`. Existing `crate::protocol::FrontendId`
|
||||
// / `crate::protocol::ByteRange` imports resolve through these
|
||||
// re-exports; new consumers (`pmacs-gpu`, debug tools) should depend
|
||||
// on `pmacs-protocol` directly.
|
||||
pub use pmacs_protocol::{ByteRange, FrontendId};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Key encoding
|
||||
|
|
@ -793,15 +785,8 @@ pub struct SelectionSnapshot {
|
|||
// variants is mooted exactly as it is for `CursorByte`.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Half-open byte range `[start, end)` into a buffer's rope, matching
|
||||
/// the rope's own range convention.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ByteRange {
|
||||
/// Inclusive start byte offset.
|
||||
pub start: u64,
|
||||
/// Exclusive end byte offset.
|
||||
pub end: u64,
|
||||
}
|
||||
// `ByteRange` moved to `pmacs-protocol` (see the re-export near the
|
||||
// top of this file).
|
||||
|
||||
/// One run of buffer bytes carrying a resolved visual style. The
|
||||
/// instance is the single syntax/face authority; the frontend lays
|
||||
|
|
|
|||
10
src/rope.rs
10
src/rope.rs
|
|
@ -43,11 +43,11 @@ const MAX_CHILDREN: usize = 8;
|
|||
// Public types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Byte offset into a [`Rope`].
|
||||
///
|
||||
/// Not a codepoint index, not a grapheme index. Grapheme awareness is a
|
||||
/// view-layer concern.
|
||||
pub type Position = u64;
|
||||
// `Position` is re-exported from `pmacs-protocol` (session 1 of the
|
||||
// `pmacs-gpu` arc — see `docs/pmacs-gpu-design.md`). The type alias
|
||||
// is a `u64` byte offset into a [`Rope`]: not a codepoint index, not
|
||||
// a grapheme index. Grapheme awareness is a view-layer concern.
|
||||
pub use pmacs_protocol::Position;
|
||||
|
||||
/// A persistent rope of bytes.
|
||||
///
|
||||
|
|
|
|||
Loading…
Reference in New Issue