File: text_edit.py

package info (click to toggle)
python-lsp-server 1.12.0-3
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 796 kB
  • sloc: python: 7,791; sh: 12; makefile: 4
file content (97 lines) | stat: -rw-r--r-- 2,768 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# 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)