internal HeightTreeNode(DocumentLine documentLine, double height) { this.documentLine = documentLine; this.totalCount = 1; this.lineNode = new HeightTreeLineNode(height); this.totalHeight = height; }
void ILineTracker.BeforeRemoveLine(DocumentLine line) { ILineTracker targetTracker = targetObject.Target as ILineTracker; if (targetTracker != null) targetTracker.BeforeRemoveLine(line); else Deregister(); }
void ILineTracker.LineInserted(DocumentLine insertionPos, DocumentLine newLine) { ILineTracker targetTracker = targetObject.Target as ILineTracker; if (targetTracker != null) targetTracker.LineInserted(insertionPos, newLine); else Deregister(); }
public VisualLine(TextLayer parent, DocumentLine documentLine) : base(parent) { DocumentLine = documentLine; elements = new List<VisualLineElement>(); Init(); documentLine.TextChanged += OnTextChanged; }
internal CollapsedLineSection(HeightTree heightTree, DocumentLine start, DocumentLine end) { this.heightTree = heightTree; this.start = start; this.end = end; #if DEBUG unchecked { this.ID = " #" + (nextId++); } #endif }
public double GetHeight(DocumentLine line) { return GetNode(line).lineNode.height; }
void CheckIsInSection(CollapsedLineSection cs, DocumentLine line) { HeightTreeNode node = GetNode(line); if (node.lineNode.collapsedSections != null && node.lineNode.collapsedSections.Contains(cs)) return; while (node != null) { if (node.collapsedSections != null && node.collapsedSections.Contains(cs)) return; node = node.parent; } throw new InvalidOperationException(cs + " not found for line " + line); }
void HighlightLineAndUpdateTreeList(DocumentLine line, int lineNumber) { //Debug.WriteLine("Highlight line " + lineNumber + (highlightedLine != null ? "" : " (span stack only)")); spanStack = storedSpanStacks[lineNumber - 1]; HighlightLineInternal(line); if (!EqualSpanStacks(spanStack, storedSpanStacks[lineNumber])) { isValid[lineNumber] = true; //Debug.WriteLine("Span stack in line " + lineNumber + " changed from " + storedSpanStacks[lineNumber] + " to " + spanStack); storedSpanStacks[lineNumber] = spanStack; if (lineNumber + 1 < isValid.Count) { isValid[lineNumber + 1] = false; firstInvalidLine = lineNumber + 1; } else { firstInvalidLine = int.MaxValue; } OnHighlightStateChanged(line, lineNumber); } else if (firstInvalidLine == lineNumber) { isValid[lineNumber] = true; firstInvalidLine = isValid.IndexOf(false); if (firstInvalidLine < 0) firstInvalidLine = int.MaxValue; } }
HeightTreeNode GetNode(DocumentLine ls) { return GetNodeByIndex(ls.LineNumber - 1); }
HeightTreeNode InsertAfter(HeightTreeNode node, DocumentLine newLine) { HeightTreeNode newNode = new HeightTreeNode(newLine, defaultLineHeight); if (node.right == null) { if (node.lineNode.collapsedSections != null) { // we are inserting directly after node - so copy all collapsedSections // that do not end at node. foreach (CollapsedLineSection cs in node.lineNode.collapsedSections) { if (cs.End != node.documentLine) newNode.AddDirectlyCollapsed(cs); } } InsertAsRight(node, newNode); } else { node = node.right.LeftMost; if (node.lineNode.collapsedSections != null) { // we are inserting directly before node - so copy all collapsedSections // that do not start at node. foreach (CollapsedLineSection cs in node.lineNode.collapsedSections) { if (cs.Start != node.documentLine) newNode.AddDirectlyCollapsed(cs); } } InsertAsLeft(node, newNode); } return newNode; }
public void LineInserted(DocumentLine insertionPos, DocumentLine newDocLine) { var prevLine = lines[insertionPos]; var line = new VisualLine(this, newDocLine); line.BringNextToControl(prevLine,true); lines.Add(newDocLine,line); }
void HighlightLineInternal(DocumentLine line) { lineStartOffset = line.Offset; lineText = document.GetText(line.Offset, line.Length); position = 0; ResetColorStack(); HighlightingRuleSet currentRuleSet = this.CurrentRuleSet; Stack<Match[]> storedMatchArrays = new Stack<Match[]>(); Match[] matches = AllocateMatchArray(currentRuleSet.Spans.Count); Match endSpanMatch = null; while (true) { for (int i = 0; i < matches.Length; i++) { if (matches[i] == null || (matches[i].Success && matches[i].Index < position)) matches[i] = currentRuleSet.Spans[i].StartExpression.Match(lineText, position); } if (endSpanMatch == null && !spanStack.IsEmpty) endSpanMatch = spanStack.Peek().EndExpression.Match(lineText, position); Match firstMatch = Minimum(matches, endSpanMatch); if (firstMatch == null) break; HighlightNonSpans(firstMatch.Index); Debug.Assert(position == firstMatch.Index); if (firstMatch == endSpanMatch) { HighlightingSpan poppedSpan = spanStack.Peek(); if (!poppedSpan.SpanColorIncludesEnd) PopColor(); // pop SpanColor PushColor(poppedSpan.EndColor); position = firstMatch.Index + firstMatch.Length; PopColor(); // pop EndColor if (poppedSpan.SpanColorIncludesEnd) PopColor(); // pop SpanColor spanStack = spanStack.Pop(); currentRuleSet = this.CurrentRuleSet; //FreeMatchArray(matches); if (storedMatchArrays.Count > 0) { matches = storedMatchArrays.Pop(); int index = currentRuleSet.Spans.IndexOf(poppedSpan); Debug.Assert(index >= 0 && index < matches.Length); if (matches[index].Index == position) { throw new InvalidOperationException( "A highlighting span matched 0 characters, which would cause an endless loop.\n" + "Change the highlighting definition so that either the start or the end regex matches at least one character.\n" + "Start regex: " + poppedSpan.StartExpression + "\n" + "End regex: " + poppedSpan.EndExpression); } } else { matches = AllocateMatchArray(currentRuleSet.Spans.Count); } } else { int index = Array.IndexOf(matches, firstMatch); Debug.Assert(index >= 0); HighlightingSpan newSpan = currentRuleSet.Spans[index]; spanStack = spanStack.Push(newSpan); currentRuleSet = this.CurrentRuleSet; storedMatchArrays.Push(matches); matches = AllocateMatchArray(currentRuleSet.Spans.Count); if (newSpan.SpanColorIncludesStart) PushColor(newSpan.SpanColor); PushColor(newSpan.StartColor); position = firstMatch.Index + firstMatch.Length; PopColor(); if (!newSpan.SpanColorIncludesStart) PushColor(newSpan.SpanColor); } endSpanMatch = null; } HighlightNonSpans(line.Length); PopAllColors(); }
public static ISegment GetTrailingWhitespace(TextDocument document, DocumentLine documentLine) { if (documentLine == null) throw new ArgumentNullException("documentLine"); ISegment segment = GetWhitespaceBefore(document, documentLine.EndOffset); // If the whole line consists of whitespace, we consider all of it as leading whitespace, // so return an empty segment as trailing whitespace. if (segment.Offset == documentLine.Offset) return new SimpleSegment(documentLine.EndOffset, 0); else return segment; }
protected override void OnHighlightStateChanged(DocumentLine line, int lineNumber) { base.OnHighlightStateChanged(line, lineNumber); if (colorizer.lineNumberBeingColorized != lineNumber) { // Ignore notifications for any line except the one we're interested in. // This improves the performance as Redraw() can take quite some time when called repeatedly // while scanning the document (above the visible area) for highlighting changes. return; } if (textLayer.Document != this.Document) { // May happen if document on text view was changed but some user code is still using the // existing IHighlighter instance. return; } // The user may have inserted "/*" into the current line, and so far only that line got redrawn. // So when the highlighting state is changed, we issue a redraw for the line immediately below. // If the highlighting state change applies to the lines below, too, the construction of each line // will invalidate the next line, and the construction pass will regenerate all lines. Debug.WriteLine("OnHighlightStateChanged forces redraw of line " + (lineNumber + 1)); // If the VisualLine construction is in progress, we have to avoid sending redraw commands for // anything above the line currently being constructed. // It takes some explanation to see why this cannot happen. // VisualLines always get constructed from top to bottom. // Each VisualLine construction calls into the highlighter and thus forces an update of the // highlighting state for all lines up to the one being constructed. // To guarantee that we don't redraw lines we just constructed, we need to show that when // a VisualLine is being reused, the highlighting state at that location is still up-to-date. // This isn't exactly trivial and the initial implementation was incorrect in the presence of external document changes // (e.g. split view). // For the first line in the view, the TextLayer.VisualLineConstructionStarting event is used to check that the // highlighting state is up-to-date. If it isn't, this method will be executed, and it'll mark the first line // in the view as requiring a redraw. This is safely possible because that event occurs before any lines are reused. // Once we take care of the first visual line, we won't get in trouble with other lines due to the top-to-bottom // construction process. // We'll prove that: if line N is being reused, then the highlighting state is up-to-date until (end of) line N-1. // Start of induction: the first line in view is reused only if the highlighting state was up-to-date // until line N-1 (no change detected in VisualLineConstructionStarting event). // Induction step: // If another line N+1 is being reused, then either // a) the previous line (the visual line containing document line N) was newly constructed // or b) the previous line was reused // In case a, the construction updated the highlighting state. This means the stack at end of line N is up-to-date. // In case b, the highlighting state at N-1 was up-to-date, and the text of line N was not changed. // (if the text was changed, the line could not have been reused). // From this follows that the highlighting state at N is still up-to-date. // The above proof holds even in the presence of folding: folding only ever hides text in the middle of a visual line. // Our Colorize-override ensures that the highlighting state is always updated for the LastDocumentLine, // so it will always invalidate the next visual line when a folded line is constructed // and the highlighting stack has changed. throw new NotImplementedException(); //textLayer.Redraw(line.NextLine, DispatcherPriority.Normal); /* * Meta-comment: "why does this have to be so complicated?" * * The problem is that I want to re-highlight only on-demand and incrementally; * and at the same time only repaint changed lines. * So the highlighter and the VisualLine construction both have to run in a single pass. * The highlighter must take care that it never touches already constructed visual lines; * if it detects that something must be redrawn because the highlighting state changed, * it must do so early enough in the construction process. * But doing it too early means it doesn't have the information necessary to re-highlight and redraw only the desired parts. */ }
void ILineTracker.SetLineLength(DocumentLine line, int newTotalLength) { ILineTracker targetTracker = targetObject.Target as ILineTracker; if (targetTracker != null) targetTracker.SetLineLength(line, newTotalLength); else Deregister(); }
/// <summary> /// Gets the location from an offset. /// </summary> /// <seealso cref="GetOffset(TextLocation)"/> public TextLocation GetLocation(int offset) { DocumentLine line = GetLineByOffset(offset); return(new TextLocation(line.LineNumber, offset - line.Offset + 1)); }
/// <summary> /// Collapses the specified text section. /// Runtime: O(log n) /// </summary> public CollapsedLineSection CollapseText(DocumentLine start, DocumentLine end) { if (!document.Lines.Contains(start)) throw new ArgumentException("Line is not part of this document", "start"); if (!document.Lines.Contains(end)) throw new ArgumentException("Line is not part of this document", "end"); int length = end.LineNumber - start.LineNumber + 1; if (length < 0) throw new ArgumentException("start must be a line before end"); CollapsedLineSection section = new CollapsedLineSection(this, start, end); AddCollapsedSection(section, length); #if DEBUG CheckProperties(); #endif return section; }
public void Rebuild() { // keep the first document line DocumentLine ls = documentLineTree.GetByNumber(1); SimpleSegment ds = NewLineFinder.NextNewLine(document, 0); List<DocumentLine> lines = new List<DocumentLine>(); int lastDelimiterEnd = 0; while (ds != SimpleSegment.Invalid) { ls.TotalLength = ds.Offset + ds.Length - lastDelimiterEnd; ls.DelimiterLength = ds.Length; lastDelimiterEnd = ds.Offset + ds.Length; lines.Add(ls); ls = new DocumentLine(document); ds = NewLineFinder.NextNewLine(document, lastDelimiterEnd); } ls.ResetLine(); ls.TotalLength = document.TextLength - lastDelimiterEnd; lines.Add(ls); documentLineTree.RebuildTree(lines); foreach (ILineTracker lineTracker in lineTrackers) lineTracker.RebuildDocument(); }
/// <summary> /// Sets the total line length and checks the delimiter. /// This method can cause line to be deleted when it contains a single '\n' character /// and the previous line ends with '\r'. /// </summary> /// <returns>Usually returns <paramref name="line"/>, but if line was deleted due to /// the "\r\n" merge, returns the previous line.</returns> DocumentLine SetLineLength(DocumentLine line, int newTotalLength) { // changedLines.Add(line); // deletedOrChangedLines.Add(line); int delta = newTotalLength - line.TotalLength; if (delta != 0) { foreach (ILineTracker lt in lineTrackers) lt.SetLineLength(line, newTotalLength); line.TotalLength = newTotalLength; DocumentLineTree.UpdateAfterChildrenChange(line); } // determine new DelimiterLength if (newTotalLength == 0) { line.DelimiterLength = 0; } else { int lineOffset = line.Offset; char lastChar = document.GetCharAt(lineOffset + newTotalLength - 1); if (lastChar == '\r') { line.DelimiterLength = 1; } else if (lastChar == '\n') { if (newTotalLength >= 2 && document.GetCharAt(lineOffset + newTotalLength - 2) == '\r') { line.DelimiterLength = 2; } else if (newTotalLength == 1 && lineOffset > 0 && document.GetCharAt(lineOffset - 1) == '\r') { // we need to join this line with the previous line DocumentLine previousLine = line.PreviousLine; RemoveLine(line); return SetLineLength(previousLine, previousLine.TotalLength + 1); } else { line.DelimiterLength = 1; } } else { line.DelimiterLength = 0; } } line.RaiseTextChanged(); return line; }
void RemoveLine(DocumentLine lineToRemove) { foreach (ILineTracker lt in lineTrackers) lt.BeforeRemoveLine(lineToRemove); documentLineTree.RemoveLine(lineToRemove); // foreach (ILineTracker lt in lineTracker) // lt.AfterRemoveLine(lineToRemove); // deletedLines.Add(lineToRemove); // deletedOrChangedLines.Add(lineToRemove); }
DocumentLine InsertLineAfter(DocumentLine line, int length) { DocumentLine newLine = documentLineTree.InsertLineAfter(line, length); foreach (ILineTracker lt in lineTrackers) lt.LineInserted(line, newLine); return newLine; }
/// <summary> /// Creates a new VisualLineConstructionStartEventArgs instance. /// </summary> public VisualLineConstructionStartEventArgs(DocumentLine firstLineInView) { if (firstLineInView == null) throw new ArgumentNullException("firstLineInView"); this.FirstLineInView = firstLineInView; }
public double GetVisualPosition(DocumentLine line) { return GetVisualPositionFromNode(GetNode(line)); }
public static ISegment GetLeadingWhitespace(TextDocument document, DocumentLine documentLine) { if (documentLine == null) throw new ArgumentNullException("documentLine"); return GetWhitespaceAfter(document, documentLine.Offset); }
void ILineTracker.BeforeRemoveLine(DocumentLine line) { HeightTreeNode node = GetNode(line); if (node.lineNode.collapsedSections != null) { foreach (CollapsedLineSection cs in node.lineNode.collapsedSections.ToArray()) { if (cs.Start == line && cs.End == line) { cs.Start = null; cs.End = null; } else if (cs.Start == line) { Uncollapse(cs); cs.Start = line.NextLine; AddCollapsedSection(cs, cs.End.LineNumber - cs.Start.LineNumber + 1); } else if (cs.End == line) { Uncollapse(cs); cs.End = line.PreviousLine; AddCollapsedSection(cs, cs.End.LineNumber - cs.Start.LineNumber + 1); } } } BeginRemoval(); RemoveNode(node); // clear collapsedSections from removed line: prevent damage if removed line is in "nodesToCheckForMerging" node.lineNode.collapsedSections = null; EndRemoval(); }
// optimization note: I tried packing color and isDeleted into a single byte field, but that // actually increased the memory requirements. The JIT packs two bools and a byte (delimiterSize) // into a single DWORD, but two bytes get each their own DWORD. Three bytes end up in the same DWORD, so // apparently the JIT only optimizes for memory when there are at least three small fields. // Currently, DocumentLine takes 36 bytes on x86 (8 byte object overhead, 3 pointers, 3 ints, and another DWORD // for the small fields). // TODO: a possible optimization would be to combine 'totalLength' and the small fields into a single uint. // delimiterSize takes only two bits, the two bools take another two bits; so there's still // 28 bits left for totalLength. 268435455 characters per line should be enough for everyone :) /// <summary> /// Resets the line to enable its reuse after a document rebuild. /// </summary> internal void ResetLine() { totalLength = delimiterLength = 0; isDeleted = color = false; left = right = parent = null; }
// void ILineTracker.AfterRemoveLine(DocumentLine line) // { // // } void ILineTracker.LineInserted(DocumentLine insertionPos, DocumentLine newLine) { InsertAfter(GetNode(insertionPos), newLine); #if DEBUG CheckProperties(); #endif }
public void BeforeRemoveLine(DocumentLine documentLine) { var line = lines[documentLine]; RemoveChild(line,true); lines.Remove(documentLine); }
void ILineTracker.SetLineLength(DocumentLine ls, int newTotalLength) { }
public void SetLineLength(DocumentLine line, int newTotalLength) { }
public void SetHeight(DocumentLine line, double val) { var node = GetNode(line); node.lineNode.height = val; UpdateAfterChildrenChange(node); }