void UpdateLineLayout(bool invalidateAll)
        {
            if (this.canvas == null)
            {
                // Not yet... no template
                return;
            }

            // Determine number of visible lines
            this.VisibleLineCount = (int)Math.Ceiling(this.canvas.ActualHeight / this.lineHeight);
            this.VerticalScrollPageSize = this.VisibleLineCount - 1;
            this.VerticalScrollRange = this.Buffer.TextData.Lines.Count;

            var lineNumbers = this.displayedLines.Keys.ToArray();
            List<TextLineVisual> recycledLines = null;

            // Remove any line not in visible range
            if (invalidateAll)
            {
                // Put all currently displayed lines in the recycle bin
                recycledLines = this.displayedLines.Values.ToList();
                this.displayedLines.Clear();
            }
            else
            {
                foreach (var line in lineNumbers)
                {
                    if (line < this.TopVisibleLine || line >= this.TopVisibleLine + this.VisibleLineCount)
                    {
                        // Put the line in the recycle bin, leaving it parented by the canvas.  This
                        // greatly reduces visual tree noise.
                        if (recycledLines == null)
                        {
                            recycledLines = new List<TextLineVisual>();
                        }

                        recycledLines.Add(this.displayedLines[line]);
                        this.displayedLines.Remove(line);
                    }
                }
            }

            // Now update and position each line.
            for (int i = 0; i < this.VisibleLineCount && i + this.TopVisibleLine < this.Buffer.TextData.Lines.Count; i++)
            {
                int lineIndex = i + this.TopVisibleLine;
                TextLineVisual visual;

                if (!this.displayedLines.TryGetValue(lineIndex, out visual))
                {
                    if (recycledLines != null && recycledLines.Count > 0)
                    {
                        visual = recycledLines[recycledLines.Count - 1];
                        recycledLines.RemoveAt(recycledLines.Count - 1);
                        visual.SetLineIndex(lineIndex);
                    }
                    else
                    {
                        visual = new TextLineVisual(this, lineIndex);
                        visual.SetBinding(Canvas.LeftProperty, this.horizontalOffsetBinding);
                        this.canvas.Children.Add(visual);
                    }

                    this.displayedLines[lineIndex] = visual;
                }

                Canvas.SetTop(visual, i * this.lineHeight);
            }

            // Remove any remaining lines in the recycle bin
            if (recycledLines != null)
            {
                foreach (var visual in recycledLines)
                {
                    this.canvas.Children.Remove(visual);
                }
            }

            PlaceCaretAndSelection();
        }
        TextLineVisual GetLineVisual(int line)
        {
            TextLineVisual visual;

            if (!this.displayedLines.TryGetValue(line, out visual))
            {
                // Note: This will not be displayed and will just GC away when
                // the caller is done with it.  Still useful for coord/loc mapping.
                visual = new TextLineVisual(this, line);
            }

            return visual;
        }