{"record":{"id":"a16f0aac1025844c","repo":"zed-industries/zed","slug":"row-grapheme-cursor-error","errorCode":null,"errorMessage":"row grapheme cursor: {error:?}","messagePattern":"row grapheme cursor: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/editor/src/columnar_selection.rs","lineNumber":588,"sourceCode":"                        .is_boundary(text, start)\n                        .map(|boundary| boundary.then_some(cursor.graphemes.cur_cursor())),\n                };\n                match result {\n                    Ok(boundary) => return boundary.map(|byte| byte as u32),\n                    Err(GraphemeIncomplete::PreContext(end)) => {\n                        let index = self.locate(end - 1);\n                        let context = self.chunk(index);\n                        let context_text = &context.text[..end - context.start];\n                        #[cfg(test)]\n                        {\n                            self.context_bytes += context_text.len();\n                        }\n                        cursor\n                            .graphemes\n                            .provide_context(context_text, context.start);\n                    }\n                    Err(GraphemeIncomplete::NextChunk | GraphemeIncomplete::PrevChunk) => break,\n                    Err(error) => unreachable!(\"row grapheme cursor: {error:?}\"),\n                }\n            }\n        }\n    }\n}\n\nimpl BoundaryCursor {\n    fn byte(&self) -> u32 {\n        self.graphemes.cur_cursor() as u32\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use crate::{\n        AddSelectionAbove, AddSelectionBelow, SelectionEffects,\n        display_map::{","sourceCodeStart":570,"sourceCodeEnd":606,"githubUrl":"https://github.com/zed-industries/zed/blob/916fc2b8cb3a815cbef4a3b40e13081be72036b6/crates/editor/src/columnar_selection.rs#L570-L606","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nErr(error) => unreachable!(\"row grapheme cursor: {error:?}\"),\n// after\nErr(GraphemeIncomplete::Context(_)) => {\n    cursor.graphemes.provide_context(context_text, context.start);\n    continue;\n}\nErr(error) => return Err(anyhow!(\"row grapheme cursor: {error:?}\")),","handlingStrategy":"try-catch","validationCode":"// ensure the cursor is fresh for the current buffer revision before resolving\ndebug_assert_eq!(cursor.buffer_revision, buffer.revision());","typeGuard":"// only treat chunk errors as expected\nfn is_expected_incomplete(e: &GraphemeIncomplete) -> bool {\n    matches!(e, GraphemeIncomplete::NextChunk | GraphemeIncomplete::PrevChunk)\n}","tryCatchPattern":"// Rust panics are not catchable normally; guard the code path instead\nlet resolved = std::panic::catch_unwind(|| resolve(cursor, row));\nmatch resolved {\n    Ok(v) => v,\n    Err(_) => fallback_to_unresolved(cursor),\n}","preventionTips":["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!."],"tags":["editor","unicode","grapheme","unreachable","panic"],"backgroundTag":"internal-invariant-violation","analyzedSha":"916fc2b8cb3a815cbef4a3b40e13081be72036b6","analyzedAt":"2026-09-19T19:09:50.599Z","contentChangedAt":"2026-09-19T19:09:50.599Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}