Skip to content

Commit 5936879

Browse files
committed
Merge auto-render ops with the op just before it.
This ensures that auto-render is considered as part of a batch edit, thus preventing situations where ctrl-z appears to have no effect. Fixes: #852
1 parent 00966be commit 5936879

9 files changed

Lines changed: 886 additions & 231 deletions

File tree

crates/koharu-app/bin/pipeline.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ async fn run() -> Result<()> {
229229
text_node_ids: None,
230230
reading_order: None,
231231
region: None,
232+
merge_with_previous_op: false,
232233
},
233234
};
234235

crates/koharu-app/src/history.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,25 @@ impl History {
8686
Ok(self.epoch)
8787
}
8888

89+
/// Does the same as apply, but merges the op with the previous undo entry so they undo together.
90+
pub fn apply_merge_up(&mut self, scene: &mut Scene, mut op: Op) -> Result<u64> {
91+
op.apply(scene).context("apply op to scene")?;
92+
self.epoch += 1;
93+
self.write_frame(&op)?;
94+
95+
if let Some(back) = self.undo_stack.pop_back() {
96+
self.push_undo(Op::Batch {
97+
ops: vec![back, op],
98+
label: "Merged undo entry".into(),
99+
});
100+
} else {
101+
self.push_undo(op);
102+
}
103+
104+
self.redo_stack.clear();
105+
Ok(self.epoch)
106+
}
107+
89108
/// Undo the most recent op. Applies its inverse, records the inverse in
90109
/// the log, and moves the original onto the redo stack. Returns the new
91110
/// epoch + the inverse op that was just applied (so the RPC layer can
@@ -94,6 +113,7 @@ impl History {
94113
let Some(original) = self.undo_stack.pop_back() else {
95114
return Ok(None);
96115
};
116+
97117
let mut inverse = original.inverse();
98118
inverse.apply(scene).context("apply inverse op")?;
99119
self.epoch += 1;

crates/koharu-app/src/pipeline/engine.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ pub struct PipelineRunOptions {
6363
/// and process just that one block. Other engines ignore it.
6464
pub region: Option<Region>,
6565
pub reading_order: Option<ReadingOrder>,
66+
/// If enabled, merge any ops generated by this with the entry above it, ensuring that they undo as a batch.
67+
/// Used for implementing auto-render so that the render doesn't get a separate undo entry.
68+
pub merge_with_previous_op: bool,
6669
}
6770

6871
// ---------------------------------------------------------------------------

crates/koharu-app/src/pipeline/mod.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,14 @@ pub async fn run(
242242
ops,
243243
label: format!("{}: page {}", info.id, page_id),
244244
};
245-
if let Err(err) = session.apply(batch) {
245+
246+
let apply_res = if spec.options.merge_with_previous_op {
247+
session.apply_merge_up(batch)
248+
} else {
249+
session.apply(batch)
250+
};
251+
252+
if let Err(err) = apply_res {
246253
report_step_failure(
247254
info.id,
248255
page_id,

crates/koharu-app/src/session.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,13 @@ impl ProjectSession {
130130
history.apply(&mut scene, op)
131131
}
132132

133+
/// Apply an op, merges with the op immediately before if possible.
134+
pub fn apply_merge_up(&self, op: Op) -> Result<u64> {
135+
let mut history = self.history.lock();
136+
let mut scene = self.scene.write();
137+
history.apply_merge_up(&mut scene, op)
138+
}
139+
133140
pub fn undo(&self) -> Result<Option<(u64, Op)>> {
134141
let mut history = self.history.lock();
135142
let mut scene = self.scene.write();

crates/koharu-rpc/src/mcp/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ pub struct StartPipelineInput {
9898
pub system_prompt: Option<String>,
9999
pub default_font: Option<String>,
100100
pub reading_order: Option<ReadingOrder>,
101+
pub merge_with_previous_op: bool,
101102
}
102103

103104
#[derive(Debug, Clone, Serialize, schemars::JsonSchema)]
@@ -192,6 +193,7 @@ impl KoharuServer {
192193
text_node_ids: input.text_node_ids,
193194
reading_order: input.reading_order,
194195
region: None,
196+
merge_with_previous_op: input.merge_with_previous_op,
195197
},
196198
};
197199
let job_id = Uuid::new_v4().to_string();

crates/koharu-rpc/src/routes/pipelines.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ pub struct StartPipelineRequest {
5050
pub default_font: Option<String>,
5151
#[serde(default)]
5252
pub reading_order: Option<ReadingOrder>,
53+
/// If enabled, merge any ops generated by this with the entry above it, ensuring that they undo as a batch.
54+
/// Used for implementing auto-render so that the render doesn't get a separate undo entry.
55+
#[serde(default)]
56+
pub merge_with_previous_op: bool,
5357
}
5458

5559
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
@@ -88,6 +92,7 @@ async fn start_pipeline(
8892
text_node_ids: req.text_node_ids,
8993
region: req.region,
9094
reading_order: req.reading_order,
95+
merge_with_previous_op: req.merge_with_previous_op,
9196
},
9297
};
9398

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,36 @@
11
/**
2-
* Generated by orval v8.8.1 🍺
2+
* Generated by orval v8.19.0 🍺
33
* Do not edit manually.
4-
* OpenAPI spec version: 0.0.1
54
*/
6-
import type { NodeId } from './nodeId'
7-
import type { PageId } from './pageId'
8-
import type { ReadingOrder } from './readingOrder'
9-
import type { Region } from './region'
5+
import type { NodeId } from './nodeId';
6+
import type { PageId } from './pageId';
7+
import type { ReadingOrder } from './readingOrder';
8+
import type { Region } from './region';
109

1110
export interface StartPipelineRequest {
1211
/** @nullable */
13-
defaultFont?: string | null
12+
defaultFont?: string | null;
1413
/**
15-
* `None` → whole project, `Some(pages)` → just those pages.
16-
* @nullable
17-
*/
18-
pages?: PageId[] | null
19-
readingOrder?: null | ReadingOrder
20-
region?: null | Region
14+
* If enabled, merge any ops generated by this with the entry above it, ensuring that they undo as a batch.
15+
* Used for implementing auto-render so that the render doesn't get a separate undo entry.
16+
*/
17+
mergeWithPreviousOp?: boolean;
18+
/**
19+
* `None` → whole project, `Some(pages)` → just those pages.
20+
* @nullable
21+
*/
22+
pages?: PageId[] | null;
23+
readingOrder?: null | ReadingOrder;
24+
region?: null | Region;
2125
/** Engine ids (`inventory::submit!` ids) to run in order. */
22-
steps: string[]
26+
steps: string[];
2327
/** @nullable */
24-
systemPrompt?: string | null
28+
systemPrompt?: string | null;
2529
/** @nullable */
26-
targetLanguage?: string | null
30+
targetLanguage?: string | null;
2731
/**
28-
* Optional text-node ids for engines that can operate on individual blocks.
29-
* @nullable
30-
*/
31-
textNodeIds?: NodeId[] | null
32+
* Optional text-node ids for engines that can operate on individual blocks.
33+
* @nullable
34+
*/
35+
textNodeIds?: NodeId[] | null;
3236
}

0 commit comments

Comments
 (0)