feat: store large paste inputs as artifacts

This commit is contained in:
2026-09-01 17:26:58 +09:00
parent 5cc78d63c6
commit 04e296a4ef
21 changed files with 1164 additions and 23 deletions
+52 -2
View File
@@ -61,6 +61,7 @@ impl FlowRefAtom {
pub enum Atom {
Char(char),
Paste(PasteRef),
PasteArtifact(protocol::PasteArtifactRef),
FileRef(FileRefAtom),
FlowRef(FlowRefAtom),
}
@@ -72,6 +73,13 @@ impl Atom {
match self {
Atom::Char(_) => None,
Atom::Paste(p) => Some((Style::default().fg(Color::Magenta), p.label())),
Atom::PasteArtifact(artifact) => Some((
Style::default().fg(Color::Magenta),
format!(
"[Paste artifact {} | {} chars, {} lines]",
artifact.artifact_id, artifact.char_count, artifact.line_count
),
)),
Atom::FileRef(r) => Some((Style::default().fg(Color::Cyan), r.label())),
Atom::FlowRef(r) => Some((Style::default().fg(Color::Yellow), r.label())),
}
@@ -102,7 +110,9 @@ enum WordKind {
fn atom_class(atom: &Atom) -> AtomClass {
match atom {
Atom::Char(c) => char_class(*c),
Atom::Paste(_) | Atom::FileRef(_) | Atom::FlowRef(_) => AtomClass::Chip,
Atom::Paste(_) | Atom::PasteArtifact(_) | Atom::FileRef(_) | Atom::FlowRef(_) => {
AtomClass::Chip
}
}
}
@@ -190,6 +200,9 @@ impl InputBuffer {
content: content.clone(),
}));
}
protocol::Segment::PasteArtifact { artifact } => {
self.atoms.push(Atom::PasteArtifact(artifact.clone()));
}
protocol::Segment::FileRef { path } => {
self.atoms
.push(Atom::FileRef(FileRefAtom { path: path.clone() }));
@@ -225,6 +238,13 @@ impl InputBuffer {
match atom {
Atom::Char(c) => text.push(*c),
Atom::Paste(paste) => text.push_str(&paste.content),
Atom::PasteArtifact(artifact) => {
text.push_str(&protocol::Segment::flatten_to_text(&[
protocol::Segment::PasteArtifact {
artifact: artifact.clone(),
},
]))
}
Atom::FileRef(file) => text.push_str(&file.path),
Atom::FlowRef(flow) => text.push_str(&flow.selector),
}
@@ -497,6 +517,12 @@ impl InputBuffer {
content: p.content.clone(),
});
}
Atom::PasteArtifact(artifact) => {
flush_text(&mut buf, &mut out);
out.push(protocol::Segment::PasteArtifact {
artifact: artifact.clone(),
});
}
Atom::FileRef(r) => {
flush_text(&mut buf, &mut out);
out.push(protocol::Segment::FileRef {
@@ -902,6 +928,28 @@ mod submit_segments_tests {
}
}
#[test]
fn restored_paste_artifact_remains_a_typed_segment() {
let artifact = protocol::PasteArtifactRef {
artifact_id: "019ca7c8-57b6-7f05-8edf-524147aba7b2".to_string(),
byte_len: 65_536,
char_count: 65_530,
line_count: 200,
sha256: "a".repeat(64),
source_entry_id: "entry-1".to_string(),
};
let original = Segment::PasteArtifact {
artifact: artifact.clone(),
};
let mut buf = InputBuffer::new();
buf.replace_with_segments(std::slice::from_ref(&original));
assert_eq!(
buf.submit_segments(),
vec![Segment::PasteArtifact { artifact }]
);
}
#[test]
fn empty_buffer_yields_empty_segments() {
let buf = InputBuffer::new();
@@ -1219,7 +1267,9 @@ mod word_motion_tests {
for a in &buf.atoms {
match a {
Atom::Char(c) => out.push(*c),
Atom::Paste(_) | Atom::FileRef(_) | Atom::FlowRef(_) => out.push_str("<P>"),
Atom::Paste(_) | Atom::PasteArtifact(_) | Atom::FileRef(_) | Atom::FlowRef(_) => {
out.push_str("<P>")
}
}
}
out
+11
View File
@@ -1296,6 +1296,13 @@ fn chip_span_for(seg: &Segment, fallback: Style) -> (Style, String) {
Style::default().fg(Color::Magenta),
format!("[Clipboard #{id} | {chars} chars, {line_count} lines]"),
),
Segment::PasteArtifact { artifact } => (
Style::default().fg(Color::Magenta),
format!(
"[Paste artifact {} | {} chars, {} lines]",
artifact.artifact_id, artifact.char_count, artifact.line_count
),
),
Segment::FileRef { path } => (Style::default().fg(Color::Cyan), format!("@{path}")),
Segment::Flow { selector } => (
Style::default().fg(Color::Yellow),
@@ -1314,6 +1321,10 @@ fn segment_display_text(seg: &Segment) -> String {
Segment::Paste {
id, chars, lines, ..
} => format!("[Clipboard #{id} | {chars} chars, {lines} lines]"),
Segment::PasteArtifact { artifact } => format!(
"[Paste artifact {} | {} chars, {} lines]",
artifact.artifact_id, artifact.char_count, artifact.line_count
),
Segment::FileRef { path } => format!("@{path}"),
Segment::Flow { selector } => format!("[Flow: {selector}]"),
Segment::Unknown => "[unknown segment]".to_owned(),