# Copyright 2017-2020 Palantir Technologies, Inc. # Copyright 2021- Python Language Server Contributors. def get_well_formatted_range(lsp_range): start = lsp_range["start"] end = lsp_range["end"] if start["line"] > end["line"] or ( start["line"] == end["line"] and start["character"] > end["character"] ): return {"start": end, "end": start} return lsp_range def get_well_formatted_edit(text_edit): lsp_range = get_well_formatted_range(text_edit["range"]) if lsp_range != text_edit["range"]: return {"newText": text_edit["newText"], "range": lsp_range} return text_edit def compare_text_edits(a, b): diff = a["range"]["start"]["line"] - b["range"]["start"]["line"] if diff == 0: return a["range"]["start"]["character"] - b["range"]["start"]["character"] return diff def merge_sort_text_edits(text_edits): if len(text_edits) <= 1: return text_edits p = len(text_edits) // 2 left = text_edits[:p] right = text_edits[p:] merge_sort_text_edits(left) merge_sort_text_edits(right) left_idx = 0 right_idx = 0 i = 0 while left_idx < len(left) and right_idx < len(right): ret = compare_text_edits(left[left_idx], right[right_idx]) if ret <= 0: # smaller_equal -> take left to preserve order text_edits[i] = left[left_idx] i += 1 left_idx += 1 else: # greater -> take right text_edits[i] = right[right_idx] i += 1 right_idx += 1 while left_idx < len(left): text_edits[i] = left[left_idx] i += 1 left_idx += 1 while right_idx < len(right): text_edits[i] = right[right_idx] i += 1 right_idx += 1 return text_edits class OverLappingTextEditException(Exception): """ Text edits are expected to be sorted and compressed instead of overlapping. This error is raised when two edits are overlapping. """ def apply_text_edits(doc, text_edits): text = doc.source sorted_edits = merge_sort_text_edits(list(map(get_well_formatted_edit, text_edits))) last_modified_offset = 0 spans = [] for e in sorted_edits: start_offset = doc.offset_at_position(e["range"]["start"]) if start_offset < last_modified_offset: raise OverLappingTextEditException("overlapping edit") if start_offset > last_modified_offset: spans.append(text[last_modified_offset:start_offset]) if len(e["newText"]): spans.append(e["newText"]) last_modified_offset = doc.offset_at_position(e["range"]["end"]) spans.append(text[last_modified_offset:]) return "".join(spans)