zed-industries/zed · error
row grapheme cursor
Error message
row grapheme cursor: {error:?} What it means
While resolving a row's grapheme cursor in the editor's columnar-selection code, the code iterates grapheme resolutions against the underlying rope. `GraphemeIncomplete::NextChunk`/`PrevChunk` are expected and handled by breaking out of the loop, but any other `GraphemeIncomplete` variant means the unicode-segmentation context protocol was violated (e.g. the context text supplied didn't match what the cursor expected). The code declares such states unreachable and panics with the error payload.
Solutions
- Refresh/recompute the grapheme cursor after any buffer edit instead of reusing cached cursors across revisions.
- Ensure `provide_context` is called with the exact text and start offset the cursor requested before expecting resolution to succeed.
- Handle more `GraphemeIncomplete` variants explicitly (like Chunk/Context) before the match instead of relying on unreachable.
- Add a debug assertion/test reproducing the selection shape that triggers the error and guard the resolution path.
Example fix
// before
Err(error) => unreachable!("row grapheme cursor: {error:?}"),
// after
Err(GraphemeIncomplete::Context(_)) => {
cursor.graphemes.provide_context(context_text, context.start);
continue;
}
Err(error) => return Err(anyhow!("row grapheme cursor: {error:?}")), Defensive patterns
Strategy: try-catch
Validate before calling
// ensure the cursor is fresh for the current buffer revision before resolving debug_assert_eq!(cursor.buffer_revision, buffer.revision());
Type guard
// only treat chunk errors as expected
fn is_expected_incomplete(e: &GraphemeIncomplete) -> bool {
matches!(e, GraphemeIncomplete::NextChunk | GraphemeIncomplete::PrevChunk)
} Try / catch
// Rust panics are not catchable normally; guard the code path instead
let resolved = std::panic::catch_unwind(|| resolve(cursor, row));
match resolved {
Ok(v) => v,
Err(_) => fallback_to_unresolved(cursor),
} Prevention
- Invalidate grapheme cursors on every buffer edit.
- Always call provide_context with the exact requested text and offset before resolving.
- Cover columnar selections over line boundaries in tests.
- Handle all GraphemeIncomplete variants explicitly rather than with unreachable!.
When it happens
Trigger: Resolving graphemes for a row whose surrounding text context is inconsistent — e.g. the `context_text`/`context.start` provided to `provide_context` doesn't cover the requested chunk, or a cursor is reused after the underlying buffer text changed (after an edit) so chunk offsets no longer line up.
Common situations: Editing text in one editor buffer while a columnar selection resolution runs against stale rows, or columnar selections spanning lines where line-start/end context assumptions (PrevChunk/NextChunk) don't hold and a different incomplete variant surfaces.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- internal error: entered unreachable code
- invalid display row
- Only tool result should be extracted
- row out of range
- Action name is required
AI-assisted analysis of zed-industries/zed@916fc2b8cb (2026-09-19).
Data as JSON: /api/errors/a16f0aac1025844c.
Report an issue: GitHub.
Appendix: source
Thrown at crates/editor/src/columnar_selection.rs:588
.is_boundary(text, start)
.map(|boundary| boundary.then_some(cursor.graphemes.cur_cursor())),
};
match result {
Ok(boundary) => return boundary.map(|byte| byte as u32),
Err(GraphemeIncomplete::PreContext(end)) => {
let index = self.locate(end - 1);
let context = self.chunk(index);
let context_text = &context.text[..end - context.start];
#[cfg(test)]
{
self.context_bytes += context_text.len();
}
cursor
.graphemes
.provide_context(context_text, context.start);
}
Err(GraphemeIncomplete::NextChunk | GraphemeIncomplete::PrevChunk) => break,
Err(error) => unreachable!("row grapheme cursor: {error:?}"),
}
}
}
}
}
impl BoundaryCursor {
fn byte(&self) -> u32 {
self.graphemes.cur_cursor() as u32
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
AddSelectionAbove, AddSelectionBelow, SelectionEffects,
display_map::{View on GitHub (pinned to 916fc2b8cb)