{"record":{"id":"11bb58a1c6f4cabc","repo":"sinelaw/fresh","slug":"invalid-range-start-offset-end-offset","errorCode":null,"errorMessage":"Invalid range: start offset {} > end offset {}","messagePattern":"Invalid range: start offset (.+?) > end offset (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/fresh-editor/src/state.rs","lineNumber":2075,"sourceCode":"                DocumentPosition::LineColumn {\n                    line: pos.line,\n                    column: pos.column,\n                }\n            } else {\n                // Line index exists but metadata unavailable - fall back to byte offset\n                DocumentPosition::ByteOffset(offset)\n            }\n        } else {\n            DocumentPosition::ByteOffset(offset)\n        }\n    }\n\n    fn get_range(&mut self, start: DocumentPosition, end: DocumentPosition) -> Result<String> {\n        let start_offset = self.position_to_offset(start)?;\n        let end_offset = self.position_to_offset(end)?;\n\n        if start_offset > end_offset {\n            anyhow::bail!(\n                \"Invalid range: start offset {} > end offset {}\",\n                start_offset,\n                end_offset\n            );\n        }\n\n        let bytes = self\n            .buffer\n            .get_text_range_mut(start_offset, end_offset - start_offset)?;\n\n        Ok(String::from_utf8_lossy(&bytes).into_owned())\n    }\n\n    fn get_line_content(&mut self, line_number: usize) -> Option<String> {\n        if !self.has_line_index() {\n            return None;\n        }\n","sourceCodeStart":2057,"sourceCodeEnd":2093,"githubUrl":"https://github.com/sinelaw/fresh/blob/67894ca5463dbd7a89bb31add4627c27d6b79d83/crates/fresh-editor/src/state.rs#L2057-L2093","documentation":"get_range resolved both DocumentPosition endpoints to byte offsets and found start_offset > end_offset, so the requested range is inverted. state.rs:2075 bails because slicing a buffer backwards is meaningless. The caller must order the positions correctly.","triggerScenarios":"Calling get_range with a start position that comes after the end position in the document — e.g. swapping selection anchors, or passing cursor/anchor in the wrong order.","commonSituations":"Selection code where the user dragged backwards (anchor after cursor); programmatic edits passing unordered offsets; LSP range conversion done in the wrong direction.","solutions":["Order the two positions before the call (min/max on offsets)","If using a selection, sort anchor and cursor so start <= end","Verify the DocumentPosition values were not accidentally swapped at the call site"],"exampleFix":"// before\nlet text = doc.get_range(sel.cursor, sel.anchor)?;\n// after\nlet (s, e) = if sel.cursor <= sel.anchor { (sel.cursor, sel.anchor) } else { (sel.anchor, sel.cursor) };\nlet text = doc.get_range(s, e)?;","handlingStrategy":"validation","validationCode":"let (s, e) = (doc.position_to_offset(&start)?, doc.position_to_offset(&end)?);\nassert!(s <= e, \"range start {} after end {}\", s, e);","typeGuard":null,"tryCatchPattern":"match doc.get_range(start, end) {\n    Err(e) if e.to_string().contains(\"Invalid range: start offset\") => normalize_and_retry(start, end),\n    other => other,\n}","preventionTips":["Always normalize selection anchor/cursor to (min, max) before range reads","Add an assertion for start <= end at range-producing call sites","Centralize range construction in one helper that orders offsets"],"tags":["editor","document-model","range-validation","rust"],"backgroundTag":"invalid-argument-value","analyzedSha":"67894ca5463dbd7a89bb31add4627c27d6b79d83","analyzedAt":"2026-09-13T15:04:03.701Z","contentChangedAt":"2026-09-13T15:04:03.701Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}