protected override void ApplyDocumentTextChanged(DocumentId document, SourceText newText) { if (_openDocumentId != document) { return; } ITextSnapshot appliedText; using (var edit = _openTextContainer.GetTextBuffer().CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null)) { var oldText = _openTextContainer.CurrentText; var changes = newText.GetTextChanges(oldText); foreach (var change in changes) { edit.Replace(change.Span.Start, change.Span.Length, change.NewText); } appliedText = edit.Apply(); } this.OnDocumentTextChanged(document, appliedText.AsText(), PreservationMode.PreserveIdentity); }
public void UpdateText(SourceText newText) { _updatding = true; _editor.Document.BeginUpdate(); var caret = _editor.CaretOffset; var offset = 0; try { var changes = newText.GetTextChanges(_currentText); foreach (var change in changes) { _editor.Document.Replace(change.Span.Start + offset, change.Span.Length, new StringTextSource(change.NewText)); offset += change.NewText.Length - change.Span.Length; } _currentText = newText; } finally { _updatding = false; var carretOffset = caret + offset; if (carretOffset < 0) { carretOffset = 0; } if (carretOffset > newText.Length) { carretOffset = newText.Length; } _editor.CaretOffset = carretOffset; _editor.Document.EndUpdate(); } }
private void PushTextChanges(Document oldDocument, Document newDocument) { // this pushes text changes to the remote side if it can. // this is purely perf optimization. whether this pushing text change // worked or not doesn't affect feature's functionality. // // this basically see whether it can cheaply find out text changes // between 2 snapshots, if it can, it will send out that text changes to // remote side. // // the remote side, once got the text change, will again see whether // it can use that text change information without any high cost and // create new snapshot from it. // // otherwise, it will do the normal behavior of getting full text from // VS side. this optimization saves times we need to do full text // synchronization for typing scenario. SourceText oldText = null; SourceText newText = null; if ((oldDocument.TryGetText(out oldText) == false) || (newDocument.TryGetText(out newText) == false)) { // we only support case where text already exist return; } // get text changes var textChanges = newText.GetTextChanges(oldText); if (textChanges.Count == 0) { // no changes return; } // whole document case if (textChanges.Count == 1 && textChanges[0].Span.Length == oldText.Length) { // no benefit here. pulling from remote host is more efficient return; } // only cancelled when remote host gets shutdown var token = Listener.BeginAsyncOperation(nameof(PushTextChanges)); _textChangeQueue.ScheduleTask(async() => { var client = await _service.Workspace.TryGetRemoteHostClientAsync(CancellationToken).ConfigureAwait(false); if (client == null) { return; } var state = await oldDocument.State.GetStateChecksumsAsync(CancellationToken).ConfigureAwait(false); await client.TryRunRemoteAsync( WellKnownRemoteHostServices.RemoteHostService, nameof(IRemoteHostService.SynchronizeTextAsync), new object[] { oldDocument.Id, state.Text, textChanges }, CancellationToken).ConfigureAwait(false); }, CancellationToken).CompletesAsyncOperation(token); }
protected override void ApplyDocumentTextChanged(DocumentId id, SourceText text) { var document = GetDocument(id); if (document == null) { return; } bool isOpen; var filePath = document.FilePath; Projection projection = null; foreach (var entry in ProjectionList) { var p = entry.Projections.FirstOrDefault(proj => FilePath.PathComparer.Equals(proj.Document.FileName, filePath)); if (p != null) { filePath = entry.File.FilePath; projection = p; break; } } var data = TextFileProvider.Instance.GetTextEditorData(filePath, out isOpen); var oldFile = isOpen ? document.GetTextAsync().Result : new MonoDevelopSourceText(data); var changes = text.GetTextChanges(oldFile).OrderByDescending(c => c.Span.Start).ToList(); int delta = 0; if (!isOpen) { delta = ApplyChanges(projection, data, changes); var formatter = CodeFormatterService.GetFormatter(data.MimeType); var mp = GetMonoProject(CurrentSolution.GetProject(id.ProjectId)); string currentText = data.Text; foreach (var change in changes) { delta -= change.Span.Length - change.NewText.Length; var startOffset = change.Span.Start - delta; if (projection != null) { int originalOffset; if (projection.TryConvertFromProjectionToOriginal(startOffset, out originalOffset)) { startOffset = originalOffset; } } string str; if (change.NewText.Length == 0) { str = formatter.FormatText(mp.Policies, currentText, TextSegment.FromBounds(Math.Max(0, startOffset - 1), Math.Min(data.Length, startOffset + 1))); } else { str = formatter.FormatText(mp.Policies, currentText, new TextSegment(startOffset, change.NewText.Length)); } data.ReplaceText(startOffset, change.NewText.Length, str); } data.Save(); OnDocumentTextChanged(id, new MonoDevelopSourceText(data), PreservationMode.PreserveValue); FileService.NotifyFileChanged(filePath); } else { var formatter = CodeFormatterService.GetFormatter(data.MimeType); var documentContext = IdeApp.Workbench.Documents.FirstOrDefault(d => FilePath.PathComparer.Compare(d.FileName, filePath) == 0); if (documentContext != null) { var editor = (TextEditor)data; using (var undo = editor.OpenUndoGroup()) { delta = ApplyChanges(projection, data, changes); foreach (var change in changes) { delta -= change.Span.Length - change.NewText.Length; var startOffset = change.Span.Start - delta; if (projection != null) { int originalOffset; if (projection.TryConvertFromProjectionToOriginal(startOffset, out originalOffset)) { startOffset = originalOffset; } } if (change.NewText.Length == 0) { formatter.OnTheFlyFormat(editor, documentContext, TextSegment.FromBounds(Math.Max(0, startOffset - 1), Math.Min(data.Length, startOffset + 1))); } else { formatter.OnTheFlyFormat(editor, documentContext, new TextSegment(startOffset, change.NewText.Length)); } } } } OnDocumentTextChanged(id, new MonoDevelopSourceText(data.CreateDocumentSnapshot()), PreservationMode.PreserveValue); Runtime.RunInMainThread(() => { if (IdeApp.Workbench != null) { foreach (var w in IdeApp.Workbench.Documents) { w.StartReparseThread(); } } }); } }
public override IReadOnlyList <TextChange> GetTextChanges(SourceText oldText) => _sourceText.GetTextChanges(oldText);
protected override void ApplyDocumentTextChanged(DocumentId id, SourceText text) { var document = GetDocument(id); if (document == null) { return; } bool isOpen; var filePath = document.FilePath; var data = TextFileProvider.Instance.GetTextEditorData(filePath, out isOpen); // Guard against already done changes in linked files. // This shouldn't happen but the roslyn merging seems not to be working correctly in all cases :/ if (document.GetLinkedDocumentIds().Length > 0 && isOpen && !(text.GetType().FullName == "Microsoft.CodeAnalysis.Text.ChangedText")) { return; } SourceText formerText; if (changedFiles.TryGetValue(filePath, out formerText)) { if (formerText.Length == text.Length && formerText.ToString() == text.ToString()) { return; } } changedFiles [filePath] = text; Projection projection = null; foreach (var entry in ProjectionList) { var p = entry.Projections.FirstOrDefault(proj => FilePath.PathComparer.Equals(proj.Document.FileName, filePath)); if (p != null) { filePath = entry.File.FilePath; projection = p; break; } } SourceText oldFile; if (!isOpen || !document.TryGetText(out oldFile)) { oldFile = new MonoDevelopSourceText(data); } var changes = text.GetTextChanges(oldFile).OrderByDescending(c => c.Span.Start).ToList(); int delta = 0; if (!isOpen) { delta = ApplyChanges(projection, data, changes); var formatter = CodeFormatterService.GetFormatter(data.MimeType); var mp = GetMonoProject(CurrentSolution.GetProject(id.ProjectId)); string currentText = data.Text; foreach (var change in changes) { delta -= change.Span.Length - change.NewText.Length; var startOffset = change.Span.Start - delta; if (projection != null) { int originalOffset; if (projection.TryConvertFromProjectionToOriginal(startOffset, out originalOffset)) { startOffset = originalOffset; } } string str; if (change.NewText.Length == 0) { str = formatter.FormatText(mp.Policies, currentText, TextSegment.FromBounds(Math.Max(0, startOffset - 1), Math.Min(data.Length, startOffset + 1))); } else { str = formatter.FormatText(mp.Policies, currentText, new TextSegment(startOffset, change.NewText.Length)); } data.ReplaceText(startOffset, change.NewText.Length, str); } data.Save(); OnDocumentTextChanged(id, new MonoDevelopSourceText(data), PreservationMode.PreserveValue); FileService.NotifyFileChanged(filePath); } else { var formatter = CodeFormatterService.GetFormatter(data.MimeType); var documentContext = IdeApp.Workbench.Documents.FirstOrDefault(d => FilePath.PathComparer.Compare(d.FileName, filePath) == 0); if (documentContext != null) { var editor = (TextEditor)data; using (var undo = editor.OpenUndoGroup()) { delta = ApplyChanges(projection, data, changes); foreach (var change in changes) { delta -= change.Span.Length - change.NewText.Length; var startOffset = change.Span.Start - delta; if (projection != null) { int originalOffset; if (projection.TryConvertFromProjectionToOriginal(startOffset, out originalOffset)) { startOffset = originalOffset; } } if (change.NewText.Length == 0) { formatter.OnTheFlyFormat(editor, documentContext, TextSegment.FromBounds(Math.Max(0, startOffset - 1), Math.Min(data.Length, startOffset + 1))); } else { formatter.OnTheFlyFormat(editor, documentContext, new TextSegment(startOffset, change.NewText.Length)); } } } } OnDocumentTextChanged(id, new MonoDevelopSourceText(data.CreateDocumentSnapshot()), PreservationMode.PreserveValue); Runtime.RunInMainThread(() => { if (IdeApp.Workbench != null) { foreach (var w in IdeApp.Workbench.Documents) { w.StartReparseThread(); } } }); } }
internal List <UnchangedSegment> GetReuseMapTo(SourceText newVersion) { SourceText oldVersion = this.Version; if (oldVersion == null || newVersion == null) { return(null); } // if (!oldVersion.BelongsToSameDocumentAs(newVersion)) // return null; List <UnchangedSegment> reuseMap = new List <UnchangedSegment>(); reuseMap.Add(new UnchangedSegment(0, 0, this.TextLength)); foreach (var change in oldVersion.GetTextChanges(newVersion)) { bool needsSegmentRemoval = false; var insertionLength = change.NewText != null ? change.NewText.Length : 0; for (int i = 0; i < reuseMap.Count; i++) { UnchangedSegment segment = reuseMap[i]; if (segment.NewOffset + segment.Length <= change.Span.Start) { // change is completely after this segment continue; } if (change.Span.End <= segment.NewOffset) { // change is completely before this segment segment.NewOffset += insertionLength - change.Span.Length; reuseMap[i] = segment; continue; } // Change is overlapping segment. // Split segment into two parts: the part before change, and the part after change. var segmentBefore = new UnchangedSegment(segment.OldOffset, segment.NewOffset, change.Span.Start - segment.NewOffset); Debug.Assert(segmentBefore.Length < segment.Length); int lengthAtEnd = segment.NewOffset + segment.Length - (change.Span.End); var segmentAfter = new UnchangedSegment( segment.OldOffset + segment.Length - lengthAtEnd, change.Span.Start + insertionLength, lengthAtEnd); Debug.Assert(segmentAfter.Length < segment.Length); Debug.Assert(segmentBefore.Length + segmentAfter.Length <= segment.Length); Debug.Assert(segmentBefore.NewOffset + segmentBefore.Length <= segmentAfter.NewOffset); Debug.Assert(segmentBefore.OldOffset + segmentBefore.Length <= segmentAfter.OldOffset); if (segmentBefore.Length > 0 && segmentAfter.Length > 0) { reuseMap[i] = segmentBefore; reuseMap.Insert(++i, segmentAfter); } else if (segmentBefore.Length > 0) { reuseMap[i] = segmentBefore; } else { reuseMap[i] = segmentAfter; if (segmentAfter.Length <= 0) { needsSegmentRemoval = true; } } } if (needsSegmentRemoval) { reuseMap.RemoveAll(s => s.Length <= 0); } } return(reuseMap); }