1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
//! Qem is a cross-platform text engine for Rust applications that need fast
//! file-backed reads, incremental line indexing, and responsive editing for
//! very large documents.
//!
//! At its core, Qem combines mmap-backed access, sparse on-disk line indexes,
//! and mutable rope or piece-table edit buffers so large-file workflows remain
//! responsive without requiring full materialization up front.
//!
//! `Qem` is the project name, not an expanded acronym.
//!
//! # Picking the Right Layer
//!
//! - Use [`Document`] when your application already owns tab state, session
//! state, and background-job orchestration.
//! - Use [`DocumentSession`] when you want a backend-first session wrapper with
//! generation tracking, async open/save helpers, forwarded viewport/edit
//! helpers, status snapshots, and progress polling while still owning cursor
//! and GUI behavior in your app.
//! - Use [`EditorTab`] when you additionally want convenience cursor state on
//! top of the same session machinery.
//! - Most GUI frontends render visible rows through [`Document::read_viewport`]
//! or [`DocumentSession::read_viewport`].
//! - Legacy compatibility wrappers that silently swallow edit errors or return
//! raw progress tuples remain available for migration only, but they are
//! deprecated and hidden from the main rustdoc surface in favor of the
//! typed/session-first APIs.
//!
//! # Recommended Entry Path
//!
//! For most frontend integrations, start with [`DocumentSession`].
//!
//! - Use [`ViewportRequest`], [`TextSelection`], [`TextRange`], and
//! [`SearchMatch`] as the main typed values passed between your app state and
//! Qem.
//! - Prefer bounded reads such as [`Document::read_viewport`],
//! [`Document::read_text`], and [`Document::read_selection`] over
//! full-document materialization through [`Document::text_lossy`],
//! [`DocumentSession::text`], or [`EditorTab::text`] in normal UI loops.
//! - Prefer the typed session-facing surface:
//! [`DocumentSession::loading_state`], [`DocumentSession::loading_phase`],
//! [`DocumentSession::save_state`], [`DocumentSession::background_issue`],
//! [`DocumentSession::take_background_issue`], [`DocumentSession::close_pending`],
//! and the typed `try_*` edit helpers.
//! - Treat [`DocumentSession::document_mut`], [`DocumentSession::set_path`],
//! unconditional [`Document::compact_piece_table`], and the full-text helpers
//! as advanced escape hatches for callers that intentionally manage those
//! trade-offs themselves.
//! - Reach for raw [`Document`] when your application deliberately owns tab
//! state, background-job orchestration, and save lifecycle itself.
//!
//! # Frontend Integration Recipe
//!
//! A typical GUI or TUI loop looks like this:
//!
//! 1. Open a file with [`Document::open`] or [`DocumentSession::open_file_async`].
//! 2. Poll [`DocumentSession::poll_background_job`] and cache
//! [`DocumentSession::status`] or the more focused
//! [`DocumentSession::loading_state`], [`DocumentSession::loading_phase`],
//! [`DocumentSession::save_state`], [`DocumentSession::background_issue`],
//! [`DocumentSession::take_background_issue`], [`DocumentSession::close_pending`], and
//! [`Document::indexing_state`] values from the app loop. Load progress
//! covers the asynchronous open path itself; once the document is ready,
//! continued line indexing is reported separately through
//! [`Document::indexing_state`]. If a background job fails or is
//! intentionally discarded as stale, [`DocumentSession::background_issue`]
//! keeps the last typed problem available even after the current
//! [`BackgroundActivity`] returns to idle. If [`DocumentSession::close_file`]
//! was requested while the session was busy, [`DocumentSession::close_pending`]
//! exposes that deferred-close state until the active worker finishes.
//! Call [`DocumentSession::take_background_issue`] after surfacing that
//! problem to clear the retained issue explicitly.
//! 3. Size scrollbars with [`Document::display_line_count`] while indexing is
//! still in progress.
//! 4. Render only the visible rows with [`Document::read_viewport`].
//! 5. Query [`Document::edit_capability_at`] when you want to disable editing
//! for positions that would exceed huge-file safety limits.
//! Avoid full-text materialization in hot paths: [`Document::text_lossy`],
//! [`DocumentSession::text`], and [`EditorTab::text`] build a fresh
//! `String` for the entire current document.
//! 6. Wait for [`DocumentSession::poll_background_job`] to finish before
//! applying session/tab edit helpers. While a background open/save is
//! active, those helpers return [`DocumentError::EditUnsupported`];
//! [`DocumentSession::document_mut`] is an escape hatch for callers that
//! coordinate that synchronization themselves. If it is used while busy,
//! the in-flight worker result is discarded on the next poll instead of
//! being applied over newer raw document changes. The same stale-result
//! rule applies to [`DocumentSession::set_path`] while busy. If a deferred
//! close was pending at the time, that new session state change also
//! cancels the deferred close.
//! 7. If the user closes a session/tab while it is still busy, keep polling:
//! [`DocumentSession::close_file`] defers the actual close until the active
//! background open/save completes instead of silently dropping that result.
//! Failed background saves cancel that deferred close so the dirty document
//! stays available for retry or explicit discard.
//! 8. Treat the active [`DocumentSession::loading_state`] or
//! [`DocumentSession::save_state`] path as authoritative while busy. Later
//! async open/save requests are rejected until that first worker result is
//! polled and applied. The actual file write runs in the background, but
//! `save_async` still snapshots the current document before the worker
//! starts, so very large edited buffers may make the call itself noticeable.
//! 9. Keep GUI selections as [`TextSelection`] values, read them through
//! [`Document::read_selection`], convert them through
//! [`Document::text_range_for_selection`], or edit them directly with
//! [`Document::try_replace_selection`], [`Document::try_delete_selection`],
//! [`Document::try_cut_selection`], [`Document::try_backspace_selection`],
//! or [`Document::try_delete_forward_selection`]. Literal search is exposed
//! through [`Document::find_next`], [`Document::find_prev`],
//! [`Document::find_all`], the compiled-query variants such as
//! [`Document::find_all_query`], the bounded range/position helpers, and
//! the session/tab wrappers as typed [`SearchMatch`] values.
//! 10. For long-lived edited piece-table documents, prefer
//! [`Document::maintenance_status`] or
//! [`Document::maintenance_status_with_policy`] (or the session/tab
//! wrappers) when the caller wants one explicit maintenance snapshot.
//! [`Document::maintenance_action`] and
//! [`DocumentMaintenanceStatus::recommended_action`] provide a lighter
//! high-level decision when the frontend only needs to know whether to do
//! idle maintenance now or wait for an explicit boundary.
//! Run [`Document::run_idle_compaction`] or
//! [`Document::run_idle_compaction_with_policy`] during idle time for
//! deferred maintenance. Keep
//! [`Document::compact_piece_table`] for explicit maintenance actions.
//! 11. Then save through [`Document::save_to`],
//! [`DocumentSession::save_async`], or [`DocumentSession::save_as_async`].
//!
//! ```no_run
//! # #[cfg(not(feature = "editor"))]
//! # fn main() {}
//! # #[cfg(feature = "editor")]
//! use qem::{DocumentSession, ViewportRequest};
//! use std::path::PathBuf;
//!
//! # #[cfg(feature = "editor")]
//! fn pump_frame(session: &mut DocumentSession, path: PathBuf) -> Result<(), Box<dyn std::error::Error>> {
//! if session.current_path().is_none() && !session.is_busy() {
//! session.open_file_async(path)?;
//! }
//!
//! if let Some(result) = session.poll_background_job() {
//! result?;
//! }
//!
//! let status = session.status();
//!
//! if let Some(progress) = status.indexing_state() {
//! println!(
//! "indexing: {}/{} bytes",
//! progress.completed_bytes(),
//! progress.total_bytes()
//! );
//! }
//!
//! let viewport = session.read_viewport(ViewportRequest::new(0, 40).with_columns(0, 160));
//! println!("scroll rows: {}", status.display_line_count());
//! println!("visible rows this frame: {}", viewport.len());
//!
//! Ok(())
//! }
//! #
//! # #[cfg(feature = "editor")]
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # let mut session = DocumentSession::new();
//! # let path = PathBuf::from("huge.log");
//! # pump_frame(&mut session, path)?;
//! # Ok(())
//! # }
//! ```
//!
//! # Cargo Features
//!
//! - `editor` (default): enables the backend-first session wrapper
//! [`DocumentSession`], the convenience cursor wrapper [`EditorTab`], and
//! the related progress/save helper types.
//!
//! # Official Integration Demos
//!
//! The repository workspace contains two `egui` demos that exercise the
//! frontend-facing path without pulling GUI dependencies into the library
//! crate itself:
//!
//! - `qem-egui-demo`: minimal viewer/editor integration.
//! - `qem-egui-demo --bin large_file`: large-file viewport integration with
//! gutter, explicit viewport window, caret, open/save, and visible
//! load/save/index status.
//!
//! # Current Support Contract
//!
//! ## UTF-8 and ASCII
//!
//! - UTF-8 / ASCII text is the primary stable fast path. Open, viewport reads,
//! edits, undo/redo, and save are supported without transcoding.
//! - Huge-file reads use the mmap-oriented path when possible. Frontends
//! should treat this as the main scalable contract for text viewing.
//! - Typed positions, ranges, selections, and viewport columns use document
//! text units. For UTF-8 text, line-local columns count Unicode scalar
//! values, not grapheme clusters and not display cells.
//! - Stored CRLF still counts as one text unit between lines for typed
//! range/edit/navigation semantics.
//!
//! ## Invalid UTF-8 and Other Encodings
//!
//! - Explicit legacy-encoding open/save is supported through
//! [`Document::open_with_encoding`], [`Document::save_to_with_encoding`],
//! and the matching session/tab wrappers.
//! - Auto-detect open currently recognizes BOM-backed UTF-16 files.
//! Otherwise Qem stays on the normal UTF-8 / ASCII path unless the caller
//! provides an explicit fallback through [`DocumentOpenOptions`].
//! - Non-UTF8 opens currently materialize into a rope-backed document instead
//! of using the mmap fast path. Very large legacy-encoded files may still be
//! rejected until the wider encoding contract expands.
//! - [`Document::decoding_had_errors`] means the source required lossy decode
//! replacement at open time. That does not automatically mean preserve-save
//! is forbidden.
//! - Preserve-save is rejected only when the write would materialize
//! lossy-decoded text. Callers can preflight this through
//! [`Document::preserve_save_error`] / [`Document::save_error_for_options`]
//! and explicitly convert through [`DocumentSaveOptions`] or
//! [`Document::save_to_with_encoding`].
//!
//! ## Large Files and Edit Limits
//!
//! - Large files are supported for mmap-backed reads, viewport rendering,
//! line-count estimation, and background indexing without full
//! materialization.
//! - Editing is allowed only when Qem can do it without violating built-in
//! safety limits. If an edit would require an unsafe promotion or full
//! materialization, Qem returns [`DocumentError::EditUnsupported`].
//! - Frontends should use [`Document::edit_capability_at`],
//! [`Document::edit_capability_for_range`], or
//! [`Document::edit_capability_for_selection`] when they need to surface
//! that boundary before the user commits the action.
//! - [`Document::display_line_count`] is the supported scroll-sizing value
//! while indexing is still in progress. Exact total line count may arrive
//! later through [`Document::indexing_state`] and the line-count status
//! helpers.
//!
//! ## Session and Background Job Guarantees
//!
//! - Typed session/status APIs such as [`DocumentSession::loading_state`],
//! [`DocumentSession::loading_phase`], [`DocumentSession::save_state`],
//! [`DocumentSession::background_issue`],
//! [`DocumentSession::take_background_issue`], and
//! [`DocumentSession::close_pending`] are the supported frontend-facing
//! async surface.
//! - [`DocumentSession`] and [`EditorTab`] typed edit helpers are idle-only.
//! While a background open/save is active they return
//! [`DocumentError::EditUnsupported`] instead of mutating state under an
//! in-flight worker result.
//! - [`DocumentSession::close_file`] is truthful. If a background open/save is
//! still running, close is deferred until that job completes instead of
//! silently dropping the worker result.
//! - Repeated async open/save requests use first-job-wins semantics. While a
//! load/save is active, later requests are rejected until
//! [`DocumentSession::poll_background_job`] consumes the active result.
//! - Raw [`DocumentSession::document_mut`] and [`DocumentSession::set_path`]
//! are escape hatches. Using them while busy invalidates the in-flight
//! worker result and turns the next poll into a discard/error path instead
//! of applying stale state.
//!
//! ## Search and Typed Reads
//!
//! - Literal search is part of the current public contract through
//! [`Document::find_next`], [`Document::find_prev`], [`Document::find_all`],
//! [`LiteralSearchQuery`], and the bounded query/range helpers.
//! - This is a typed, case-sensitive literal search surface. It is not a
//! regex subsystem.
//! - Bounded search returns only matches fully contained within the requested
//! typed range or boundary positions.
//!
//! ## Sidecars and Recovery
//!
//! - `.qem.lineidx` and `.qem.editlog` are internal sidecars used for
//! cache/recovery behavior.
//! - Qem validates them against file length, modification time, and sampled
//! content fingerprint. When they do not match, Qem may rebuild them,
//! discard them, or reopen cleanly instead of trusting stale state.
//! - Sidecar recovery behavior is public. Sidecar on-disk format is not.
//!
//! ## Public Behavior vs Internal Format
//!
//! - Stable public behavior in this release line includes the typed API
//! surface, open/save lifecycle, async progress semantics, huge-file read
//! contract, edit rejection semantics, and typed line/column rules.
//! - Internal implementation details include sidecar binary layout, cache
//! structure, exact storage layout, and backing/layout decisions that are
//! not explicitly promised by the typed API.
pub
pub
pub use ;
pub use ;
pub use ;