{"record":{"id":"17f14f2754e233d1","repo":"charmbracelet/crush","slug":"overlapping-edits-detected-between-edit-d-and-d","errorCode":null,"errorMessage":"overlapping edits detected between edit %d and %d","messagePattern":"overlapping edits detected between edit (.+?) and (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/lsp/util/edit.go","lineNumber":44,"sourceCode":"\t// Detect line ending style\n\tvar lineEnding string\n\tif bytes.Contains(content, []byte(\"\\r\\n\")) {\n\t\tlineEnding = \"\\r\\n\"\n\t} else {\n\t\tlineEnding = \"\\n\"\n\t}\n\n\t// Track if file ends with a newline\n\tendsWithNewline := len(content) > 0 && bytes.HasSuffix(content, []byte(lineEnding))\n\n\t// Split into lines without the endings\n\tlines := strings.Split(string(content), lineEnding)\n\n\t// Check for overlapping edits\n\tfor i, edit1 := range edits {\n\t\tfor j := i + 1; j < len(edits); j++ {\n\t\t\tif rangesOverlap(edit1.Range, edits[j].Range) {\n\t\t\t\treturn fmt.Errorf(\"overlapping edits detected between edit %d and %d\", i, j)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Sort edits in reverse order\n\tsortedEdits := make([]protocol.TextEdit, len(edits))\n\tcopy(sortedEdits, edits)\n\tsort.Slice(sortedEdits, func(i, j int) bool {\n\t\tif sortedEdits[i].Range.Start.Line != sortedEdits[j].Range.Start.Line {\n\t\t\treturn sortedEdits[i].Range.Start.Line > sortedEdits[j].Range.Start.Line\n\t\t}\n\t\treturn sortedEdits[i].Range.Start.Character > sortedEdits[j].Range.Start.Character\n\t})\n\n\t// Apply each edit\n\tfor _, edit := range sortedEdits {\n\t\tnewLines, err := applyTextEdit(lines, edit, encoding)\n\t\tif err != nil {","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/lsp/util/edit.go#L26-L62","documentation":"Before applying edits, applyTextEdits performs an O(n^2) pairwise check for overlapping protocol.TextEdit ranges. If two edits touch overlapping regions of the document, applying both would produce undefined/corrupted results, so the operation is rejected with the indices of the conflicting edits.","triggerScenarios":"ApplyWorkspaceEdit receiving an array of TextEdit where edit[i].Range and edit[j].Range intersect (e.g. two edits covering the same lines, or an insertion inside another edit's span).","commonSituations":"Multiple LSP code actions or refactoring results merged into one edit list without deduplication; a quickfix and a rename both targeting the same symbol; clients batching edits from several diagnostics.","solutions":["Merge overlapping edits into a single edit covering the union range.","Filter out redundant edits before calling the API, keeping the highest-priority one.","Split the request into multiple sequential ApplyWorkspaceEdit calls with non-overlapping ranges.","Normalize/sort edit ranges and deduplicate identical ranges beforehand."],"exampleFix":"// before\nedits := append(quickFixEdits, renameEdits...)\nutil.ApplyWorkspaceEdit(ctx, edit)\n// after\nedits := dedupeNonOverlapping(quickFixEdits, renameEdits)\nutil.ApplyWorkspaceEdit(ctx, edit)","handlingStrategy":"validation","validationCode":"func hasOverlap(a, b protocol.Range) bool {\n    return a.Start.Line <= b.End.Line && b.Start.Line <= a.End.Line\n}\nfor i := 0; i < len(edits); i++ {\n    for j := i + 1; j < len(edits); j++ {\n        if hasOverlap(edits[i].Range, edits[j].Range) {\n            return fmt.Errorf(\"edits %d and %d overlap\", i, j)\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":"if strings.Contains(err.Error(), \"overlapping edits\") {\n    edits := mergeOverlapping(edits) // then retry once\n}","preventionTips":["Deduplicate edits when merging results from multiple LSP features.","Keep one edit per source diagnostic/symbol.","Normalize and sort edit ranges before batching."],"tags":["lsp","validation","workspace-edit"],"backgroundTag":"overlapping-edits","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}