Coverage for src/debputy/lsp/text_edit.py: 10%
66 statements
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-07 12:14 +0200
« prev ^ index » next coverage.py v7.2.7, created at 2024-04-07 12:14 +0200
1# Copied and adapted from on python-lsp-server
2#
3# Copyright 2017-2020 Palantir Technologies, Inc.
4# Copyright 2021- Python Language Server Contributors.
5# License: Expat (MIT/X11)
6#
7from typing import List
9from lsprotocol.types import Range, TextEdit, Position
12def get_well_formatted_range(lsp_range: Range) -> Range:
13 start = lsp_range.start
14 end = lsp_range.end
16 if start.line > end.line or (
17 start.line == end.line and start.character > end.character
18 ):
19 return Range(end, start)
21 return lsp_range
24def get_well_formatted_edit(text_edit: TextEdit) -> TextEdit:
25 lsp_range = get_well_formatted_range(text_edit.range)
26 if lsp_range != text_edit.range:
27 return TextEdit(new_text=text_edit.new_text, range=lsp_range)
29 return text_edit
32def compare_text_edits(a: TextEdit, b: TextEdit) -> int:
33 diff = a.range.start.line - b.range.start.line
34 if diff == 0:
35 return a.range.start.character - b.range.start.character
37 return diff
40def merge_sort_text_edits(text_edits: List[TextEdit]) -> List[TextEdit]:
41 if len(text_edits) <= 1:
42 return text_edits
44 p = len(text_edits) // 2
45 left = text_edits[:p]
46 right = text_edits[p:]
48 merge_sort_text_edits(left)
49 merge_sort_text_edits(right)
51 left_idx = 0
52 right_idx = 0
53 i = 0
54 while left_idx < len(left) and right_idx < len(right):
55 ret = compare_text_edits(left[left_idx], right[right_idx])
56 if ret <= 0:
57 # smaller_equal -> take left to preserve order
58 text_edits[i] = left[left_idx]
59 i += 1
60 left_idx += 1
61 else:
62 # greater -> take right
63 text_edits[i] = right[right_idx]
64 i += 1
65 right_idx += 1
66 while left_idx < len(left):
67 text_edits[i] = left[left_idx]
68 i += 1
69 left_idx += 1
70 while right_idx < len(right):
71 text_edits[i] = right[right_idx]
72 i += 1
73 right_idx += 1
74 return text_edits
77class OverLappingTextEditException(Exception):
78 """
79 Text edits are expected to be sorted
80 and compressed instead of overlapping.
81 This error is raised when two edits
82 are overlapping.
83 """
86def offset_at_position(lines: List[str], server_position: Position) -> int:
87 row, col = server_position.line, server_position.character
88 return col + sum(len(line) for line in lines[:row])
91def apply_text_edits(text: str, lines: List[str], text_edits: List[TextEdit]) -> str:
92 sorted_edits = merge_sort_text_edits(
93 [get_well_formatted_edit(e) for e in text_edits]
94 )
95 last_modified_offset = 0
96 spans = []
97 for e in sorted_edits:
98 start_offset = offset_at_position(lines, e.range.start)
99 if start_offset < last_modified_offset:
100 raise OverLappingTextEditException("overlapping edit")
102 if start_offset > last_modified_offset:
103 spans.append(text[last_modified_offset:start_offset])
105 if e.new_text != "":
106 spans.append(e.new_text)
107 last_modified_offset = offset_at_position(lines, e.range.end)
109 spans.append(text[last_modified_offset:])
110 return "".join(spans)