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

  1. Refresh/recompute the grapheme cursor after any buffer edit instead of reusing cached cursors across revisions.
  2. Ensure `provide_context` is called with the exact text and start offset the cursor requested before expecting resolution to succeed.
  3. Handle more `GraphemeIncomplete` variants explicitly (like Chunk/Context) before the match instead of relying on unreachable.
  4. 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

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


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)