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

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 

8 

9from lsprotocol.types import Range, TextEdit, Position 

10 

11 

12def get_well_formatted_range(lsp_range: Range) -> Range: 

13 start = lsp_range.start 

14 end = lsp_range.end 

15 

16 if start.line > end.line or ( 

17 start.line == end.line and start.character > end.character 

18 ): 

19 return Range(end, start) 

20 

21 return lsp_range 

22 

23 

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) 

28 

29 return text_edit 

30 

31 

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 

36 

37 return diff 

38 

39 

40def merge_sort_text_edits(text_edits: List[TextEdit]) -> List[TextEdit]: 

41 if len(text_edits) <= 1: 

42 return text_edits 

43 

44 p = len(text_edits) // 2 

45 left = text_edits[:p] 

46 right = text_edits[p:] 

47 

48 merge_sort_text_edits(left) 

49 merge_sort_text_edits(right) 

50 

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 

75 

76 

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 """ 

84 

85 

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]) 

89 

90 

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") 

101 

102 if start_offset > last_modified_offset: 

103 spans.append(text[last_modified_offset:start_offset]) 

104 

105 if e.new_text != "": 

106 spans.append(e.new_text) 

107 last_modified_offset = offset_at_position(lines, e.range.end) 

108 

109 spans.append(text[last_modified_offset:]) 

110 return "".join(spans)