@@ -129,6 +129,65 @@ def _create_document(self, doc_uri, source=None, version=None):
129129 )
130130
131131
132+ def get_well_formatted_range (range ):
133+ start = range ['start' ]
134+ end = range ['end' ]
135+
136+ if start ['line' ] > end ['line' ] or (start ['line' ] == end ['line' ] and start ['character' ] > end ['character' ]):
137+ return { 'start' : end , 'end' : start }
138+
139+ return range
140+
141+ def get_well_formatted_edit (text_edit ):
142+ range = get_well_formatted_range (text_edit ['range' ])
143+ if range != text_edit ['range' ]:
144+ return { 'newText' : text_edit ['newText' ], 'range' : range }
145+
146+ return text_edit
147+
148+ def compare_text_edits (a , b ):
149+ diff = a ['range' ]['start' ]['line' ] - b ['range' ]['start' ]['line' ]
150+ if diff == 0 :
151+ return a ['range' ]['start' ]['character' ] - b ['range' ]['start' ]['character' ]
152+
153+ return diff
154+
155+ def merge_sort_text_edits (text_edits ):
156+ if len (text_edits ) <= 1 :
157+ return text_edits
158+
159+ p = len (text_edits ) // 2
160+ left = text_edits [:p ]
161+ right = text_edits [p :]
162+
163+ merge_sort_text_edits (left )
164+ merge_sort_text_edits (right )
165+
166+ left_idx = 0
167+ right_idx = 0
168+ i = 0
169+ while left_idx < len (left ) and right_idx < len (right ):
170+ ret = compare_text_edits (left [left_idx ], right [right_idx ])
171+ if ret <= 0 :
172+ # smaller_equal -> take left to preserve order
173+ text_edits [i ] = left [left_idx ]
174+ i += 1
175+ left_idx += 1
176+ else :
177+ # greater -> take right
178+ text_edits [i ] = right [right_idx ]
179+ i += 1
180+ right_idx += 1
181+ while left_idx < len (left ):
182+ text_edits [i ] = left [left_idx ]
183+ i += 1
184+ left_idx += 1
185+ while right_idx < len (right ):
186+ text_edits [i ] = right [right_idx ]
187+ i += 1
188+ right_idx += 1
189+ return text_edits
190+
132191class Document :
133192
134193 def __init__ (self , uri , workspace , source = None , version = None , local = True , extra_sys_path = None ,
@@ -216,6 +275,26 @@ def apply_change(self, change):
216275
217276 self ._source = new .getvalue ()
218277
278+ @lock
279+ def apply_text_edits (self , text_edits ):
280+ text = self ._source
281+ sorted_edits = merge_sort_text_edits (list (map (get_well_formatted_edit ,text_edits )))
282+ last_modified_offset = 0
283+ spans = []
284+ for e in sorted_edits :
285+ start_offset = self .offset_at_position (e ['range' ]['start' ])
286+ if start_offset < last_modified_offset :
287+ raise Exception ('overlapping edit' )
288+ elif start_offset > last_modified_offset :
289+ spans .append (text [last_modified_offset :start_offset ])
290+
291+ if len (e ['newText' ]):
292+ spans .append (e ['newText' ])
293+ last_modified_offset = self .offset_at_position (e ['range' ]['end' ])
294+
295+ spans .append (text [last_modified_offset :])
296+ return '' .join (spans )
297+
219298 def offset_at_position (self , position ):
220299 """Return the byte-offset pointed at by the given position."""
221300 return position ['character' ] + len ('' .join (self .lines [:position ['line' ]]))
0 commit comments