/// <summary> /// Combines one text change with another /// </summary> public void Combine(TreeTextChange other) { TextChangeType = MathExtensions.Max(TextChangeType, other.TextChangeType); FullParseRequired |= other.FullParseRequired || TextChangeType == TextChangeType.Structure; // Combine two sequential changes into one. Note that damaged regions // typically don't shrink. There are some exceptions such as when // text was added and then deleted making the change effectively a no-op, // but this is not a typical case. For simplicity and fidelity // we'd rather parse more than less. if (FullParseRequired) { Start = 0; OldLength = OldTextProvider?.Length ?? 0; NewLength = NewTextProvider?.Length ?? 0; return; } var oldEnd = Math.Max(OldEnd, other.OldEnd); var newEnd = Math.Max(NewEnd, other.NewEnd); Start = OldTextProvider != null?Math.Min(this.Start, other.Start) : other.Start; NewLength = Math.Max(newEnd, other.NewEnd) - Start; OldLength = Math.Max(oldEnd, other.OldEnd) - Start; Debug.Assert(OldLength >= 0); Debug.Assert(NewLength >= 0); Debug.Assert(Start <= OldEnd); Debug.Assert(Start <= NewEnd); if (OldTextProvider == null) { OldTextProvider = other.OldTextProvider; } else { OldTextProvider = other.OldTextProvider.Version < OldTextProvider.Version ? other.OldTextProvider : OldTextProvider; } if (NewTextProvider == null) { NewTextProvider = other.NewTextProvider; } else { NewTextProvider = other.NewTextProvider.Version > NewTextProvider.Version ? other.NewTextProvider : NewTextProvider; } NewLength = Math.Min(NewLength, NewTextProvider.Length); OldLength = Math.Min(OldLength, OldTextProvider.Length); Version = NewTextProvider.Version; }
/// <summary> /// Handles non-trivial changes like changes that delete elements, /// change identifier names, introducing new braces: changes /// that cannot be handled without background parse. /// </summary> private void ProcessComplexChange(TextChangeContext context) { // Cancel background parse if it is running Cancel(); var c = context.PendingChanges; try { // Get write lock since there may be concurrent readers // of the tree. Note that there are no concurrent writers // since changes can only come from a background parser // and are always applied from the main thread. _editorTree.AcquireWriteLock(); int start, oldLength, newLength; if (Changes.FullParseRequired) { // When full parse is required, change is like replace the entire file start = 0; oldLength = c.OldTextProvider.Length; newLength = c.NewTextProvider.Length; // Remove damaged elements if any and reflect text change. // the tree remains usable outside of the damaged scope. _editorTree.InvalidateInRange(c.OldRange); _editorTree.NotifyTextChange(c.Start, c.OldLength, c.NewLength); } else { start = c.Start; oldLength = c.OldLength; newLength = c.NewLength; DeleteAndShiftElements(context); Debug.Assert(_editorTree.AstRoot.Children.Count > 0); } var ttc = new TreeTextChange(start, oldLength, newLength, _editorTree.BufferSnapshot, EditorBuffer.CurrentSnapshot); Changes.Combine(ttc); Changes.Version = EditorBuffer?.CurrentSnapshot?.Version ?? 1; _editorTree.BufferSnapshot = EditorBuffer.CurrentSnapshot; } finally { // Lock must be released before firing events otherwise we may hang _editorTree.ReleaseWriteLock(); } _editorTree.FireOnUpdateCompleted(TreeUpdateType.NodesRemoved); }
public TextChangeContext(IREditorTree editorTree, TreeTextChange change, TreeTextChange pendingChanges) { EditorTree = editorTree; TreeTextChange ttc; if (change.OldTextProvider == null || change.NewTextProvider == null) { var oldTextProvider = change.OldTextProvider ?? editorTree.AstRoot.TextProvider; var newTextProvider = change.NewTextProvider ?? editorTree.AstRoot.TextProvider; ttc = new TreeTextChange(change.Start, change.OldLength, change.NewLength, oldTextProvider, newTextProvider); } else { ttc = change; } PendingChanges = pendingChanges; PendingChanges.Combine(ttc); }