/// <summary> /// Add a new atomic edit to the array. The edits cannot intersect each other. /// The spans in each edit must be based on the current state of the buffer, /// and not based on post-edit spans. This EditArray will calculate the /// post edit spans for you. /// </summary> /// <param name="editSpan"></param> internal void Add(EditSpan editSpan) { if (editSpan == null) { throw new ArgumentNullException("editSpan"); } for (int i = this.editList.Count - 1; i >= 0; i--) { EditSpan e = (EditSpan)this.editList[i]; if (TextSpanHelper.Intersects(editSpan.Span, e.Span)) { string msg = SR.GetString(SR.EditIntersects, i); #if LANGTRACE Debug.Assert(false, msg); TraceEdits(); #endif throw new System.ArgumentException(msg); } if (TextSpanHelper.StartsAfterStartOf(editSpan.Span, e.Span)) { this.editList.Insert(i + 1, editSpan); return; } } this.editList.Insert(0, editSpan); }
/// <summary> /// Construct a new edit span object /// </summary> /// <param name="toReplace">The text span to remove from the buffer (can be empty)</param> /// <param name="insertText">The text to insert in it's place (can be null)</param> internal EditSpan(TextSpan toReplace, string insertText) { if (!TextSpanHelper.IsPositive(toReplace)) { TextSpanHelper.MakePositive(ref toReplace); } this.span = toReplace; this.text = insertText; this.lineCount = -1; }
// It is important that this function not throw an exception. protected override void OnNavigate(EventArgs e) { try { TextSpan span = this.span; if (textLineMarker != null) { TextSpan[] spanArray = new TextSpan[1]; if (NativeMethods.Failed(textLineMarker.GetCurrentSpan(spanArray))) { Debug.Assert(false, "Unexpected error getting current span in OnNavigate"); return; } span = spanArray[0]; } IVsUIHierarchy hierarchy; uint itemID; IVsWindowFrame docFrame; IVsTextView textView; try { VsShell.OpenDocument(this.site, this.fileName, NativeMethods.LOGVIEWID_Code, out hierarchy, out itemID, out docFrame, out textView); } catch (System.ArgumentException) { // No assert here because this can legitimately happen when quickly doing F8 during a refresh of language service errors (see 4846) return; } catch (System.IO.FileNotFoundException) { // No assert here because this can legitimately happen, e.g. with type provider errors (which are attributed to "FSC" file), or other cases return; } if (NativeMethods.Failed(docFrame.Show())) { // No assert here because this can legitimately happen when quickly doing F8 during a refresh of language service errors (see 4846) return; } if (textView != null) { // In the off-chance these methods fail, we 'recover' by continuing. It is more helpful to show the user the file if possible than not. textView.SetCaretPos(span.iStartLine, span.iStartIndex); TextSpanHelper.MakePositive(ref span); textView.SetSelection(span.iStartLine, span.iStartIndex, span.iEndLine, span.iEndIndex); textView.EnsureSpanVisible(span); } base.OnNavigate(e); } catch (Exception exn) { System.Diagnostics.Debug.Assert(false, "Unexpected exception thrown from DocumentTask.OnNavigate" + exn.ToString() + exn.StackTrace); } }
// This method simulates what VS does in debug mode so that we can catch the // errors in managed code before they go to the native debug assert. internal static bool ValidSpan(ISource src, TextSpan span) { if (!ValidCoord(src, span.iStartLine, span.iStartIndex)) { return(false); } if (!ValidCoord(src, span.iEndLine, span.iEndIndex)) { return(false); } // end must be >= start if (!TextSpanHelper.IsPositive(span)) { return(false); } return(true); }
internal static bool Intersects(TextSpan span1, TextSpan span2) { return(TextSpanHelper.StartsBeforeEndOf(span1, span2) && TextSpanHelper.EndsAfterStartOf(span1, span2)); }
//returns true is span1 is Embedded in span2 internal static bool IsEmbedded(TextSpan span1, TextSpan span2) { return(!TextSpanHelper.IsSameSpan(span1, span2) && TextSpanHelper.StartsAfterStartOf(span1, span2) && TextSpanHelper.EndsBeforeEndOf(span1, span2)); }
internal virtual bool HandlePreExec(ref Guid guidCmdGroup, uint nCmdId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { if (!this.expansionActive || this.expansionSession == null) { return(false); } this.completorActiveDuringPreExec = this.IsCompletorActive(this.view); if (guidCmdGroup == typeof(VsCommands2K).GUID) { VsCommands2K cmd = (VsCommands2K)nCmdId; #if TRACE_EXEC Trace.WriteLine(String.Format("ExecCommand: {0}", cmd.ToString())); #endif switch (cmd) { case VsCommands2K.CANCEL: if (this.completorActiveDuringPreExec) { return(false); } EndTemplateEditing(true); return(true); case VsCommands2K.RETURN: bool leaveCaret = false; int line = 0, col = 0; if (NativeMethods.Succeeded(this.view.GetCaretPos(out line, out col))) { TextSpan span = GetExpansionSpan(); if (!TextSpanHelper.ContainsExclusive(span, line, col)) { leaveCaret = true; } } if (this.completorActiveDuringPreExec) { return(false); } if (this.completorActiveDuringPreExec) { return(false); } EndTemplateEditing(leaveCaret); if (leaveCaret) { return(false); } return(true); case VsCommands2K.BACKTAB: if (this.completorActiveDuringPreExec) { return(false); } this.expansionSession.GoToPreviousExpansionField(); return(true); case VsCommands2K.TAB: if (this.completorActiveDuringPreExec) { return(false); } this.expansionSession.GoToNextExpansionField(0); // fCommitIfLast=false return(true); #if TRACE_EXEC case VsCommands2K.TYPECHAR: if (pvaIn != IntPtr.Zero) { Variant v = Variant.ToVariant(pvaIn); char ch = v.ToChar(); Trace.WriteLine(String.Format("TYPECHAR: {0}, '{1}', {2}", cmd.ToString(), ch.ToString(), (int)ch)); } return(true); #endif } } return(false); }
void UpdateSelection(ArrayList edits) { int lineDelta = 0; int indexDelta = 0; int currentLine = 0; bool updateStart = true; bool updateEnd = true; bool selectionIsEmpty = TextSpanHelper.IsEmpty(this.selection); foreach (EditSpan es in edits) { TextSpan span = es.Span; string text = es.Text; int lastLine = currentLine; int lastDelta = indexDelta; if (currentLine != span.iStartLine) { // We have moved to a new line, so the indexDelta is no longer relevant. currentLine = span.iStartLine; indexDelta = 0; } // Now adjust the span based on the current deltas. span.iStartIndex += indexDelta; if (currentLine == span.iEndLine) { span.iEndIndex += indexDelta; } span.iStartLine += lineDelta; span.iEndLine += lineDelta; if (updateStart) { TextSpan original = es.Span; if (TextSpanHelper.ContainsInclusive(original, this.selection.iStartLine, this.selection.iStartIndex)) { bool atEnd = (this.selection.iStartLine == original.iEndLine && this.selection.iStartIndex == original.iEndIndex); this.selection.iStartLine = span.iStartLine; this.selection.iStartIndex = span.iStartIndex; if (atEnd) { // Selection was positioned at the end of the span, so // skip past the inserted text to approximate that location. if (es.LineCount > 0) { this.selection.iStartLine += es.LineCount; this.selection.iStartIndex = es.LengthOfLastLine; } else { this.selection.iStartIndex += es.LengthOfLastLine; } } updateStart = false; // done } else if (TextSpanHelper.StartsAfterStartOf(original, this.selection)) { if (this.selection.iStartLine == lastLine) { this.selection.iStartIndex += lastDelta; } this.selection.iStartLine += lineDelta; updateStart = false; // done. } if (!updateStart && selectionIsEmpty) { this.selection.iEndLine = this.selection.iStartLine; this.selection.iEndIndex = this.selection.iStartIndex; updateEnd = false; // done } } if (updateEnd) { TextSpan original = es.Span; if (TextSpanHelper.StartsAfterEndOf(original, this.selection)) { if (this.selection.iEndLine == lastLine) { this.selection.iEndIndex += lastDelta; } this.selection.iEndLine += lineDelta; updateEnd = false; // done. } else if (TextSpanHelper.ContainsInclusive(original, this.selection.iEndLine, this.selection.iEndIndex)) { this.selection.iEndLine = span.iStartLine; this.selection.iEndIndex = span.iStartIndex; // Now include the text we are inserting in the selection if (es.LineCount > 0) { this.selection.iEndLine += es.LineCount; this.selection.iEndIndex = es.LengthOfLastLine; } else { this.selection.iEndIndex += es.LengthOfLastLine; } updateEnd = false; // done. } } // Now adjust the deltas based on whether we just deleted anything. if (span.iStartLine != span.iEndLine) { // We are deleting one or more lines. lineDelta += (span.iStartLine - span.iEndLine); indexDelta = -span.iEndIndex; currentLine = span.iStartLine; } else if (span.iStartIndex != span.iEndIndex) { indexDelta += (span.iStartIndex - span.iEndIndex); } // Now adjust the deltas based on what we just inserted if (!string.IsNullOrEmpty(text)) { lineDelta += es.LineCount; if (span.iStartLine != span.iEndLine) // we removed multiple lines { if (es.LineCount == 0) // but we are not inserting any new lines // Then we are appending to this line. { indexDelta = span.iStartIndex + es.LengthOfLastLine; } else { indexDelta = es.LengthOfLastLine; // otherwise we just started a new line. } } else if (es.LineCount != 0) // we inserted new lines // then calculate delta between new position versus position on original line. { indexDelta += es.LengthOfLastLine - span.iStartIndex; } else { indexDelta += es.LengthOfLastLine; // then delta is simply what we just inserted } } } if (updateStart) { // Then start of selection is off the end of the list of edits. if (this.selection.iStartLine == currentLine) { this.selection.iStartIndex += indexDelta; } this.selection.iStartLine += lineDelta; } if (updateEnd) { // Then end of selection is off the end of the list of edits. if (this.selection.iEndLine == currentLine) { this.selection.iEndIndex += indexDelta; } this.selection.iEndLine += lineDelta; } }
const int ChunkThreshold = 1000; // don't combine chunks separate by more than 1000 characters. ArrayList MergeEdits(ArrayList edits) { StringBuilder buffer = new StringBuilder(); EditSpan combined = null; ArrayList merged = new ArrayList(); ArrayList markers = GetTextMarkers(); int markerPos = 0; TextSpan marker = (markers.Count > 0) ? (TextSpan)markers[0] : new TextSpan(); foreach (EditSpan editSpan in edits) { TextSpan span = editSpan.Span; string text = editSpan.Text; if (markerPos < markers.Count && (TextSpanHelper.StartsAfterStartOf(span, marker) || TextSpanHelper.EndsAfterStartOf(span, marker))) { AddCombinedEdit(combined, buffer, merged); if (TextSpanHelper.Intersects(span, marker)) { combined = null; // Have to apply this as a distinct edit operation. merged.Add(editSpan); } else { combined = editSpan; buffer.Append(text); } while (++markerPos < markers.Count) { marker = (TextSpan)markers[markerPos]; if (!TextSpanHelper.StartsAfterStartOf(span, marker) && !TextSpanHelper.EndsAfterStartOf(span, marker)) { break; } } } else if (combined == null) { combined = editSpan; buffer.Append(text); } else { // A little sanity check here, if there are too many characters in between the two // edits, then keep them separate. int startOffset = source.GetPositionOfLineIndex(combined.Span.iEndLine, combined.Span.iEndIndex); int endOffset = source.GetPositionOfLineIndex(span.iStartLine, span.iStartIndex); if (endOffset - startOffset > ChunkThreshold) { AddCombinedEdit(combined, buffer, merged); combined = editSpan; buffer.Append(text); } else { // merge edit spans by adding the text in-between the current and previous spans. TextSpan s = combined.Span; string between = this.source.GetText(s.iEndLine, s.iEndIndex, span.iStartLine, span.iStartIndex); buffer.Append(between); buffer.Append(text); // and add the new text. s.iEndIndex = span.iEndIndex; s.iEndLine = span.iEndLine; combined.Span = s; } } } AddCombinedEdit(combined, buffer, merged); return(merged); }