public IDictionary<TextPosition, TextPosition> CreateFolds(string text, TextPosition position, IDictionary<TextPosition, TextPosition> foldingPositions) {
            if (text == GetOpeningTag()) {
                return CreateEmptyFold(position, foldingPositions);
            }

            return text == GetClosingTag() ? RebuildFolds(position, foldingPositions) : null;
        }
        public void Deselect() {
            lastSelectionStart = null;
            lastSelectionEnd = null;
            isSelecting = false;

            visuals.Clear();
        }
        public TextPosition GetSelectionPosition(KeyEventArgs keyboardEvent) {
            TextPosition endingPosition = null;

            switch (keyboardEvent.Key) {
                case Key.Left:
                    endingPosition = new TextPosition(column: caretViewReader.CaretPosition.Column - 1, line: caretViewReader.CaretPosition.Line);
                    break;
                case Key.Home:
                    endingPosition = new TextPosition(column: 0, line: caretViewReader.CaretPosition.Line);
                    break;
                case Key.Right:
                    endingPosition = new TextPosition(column: caretViewReader.CaretPosition.Column + 1, line: caretViewReader.CaretPosition.Line);
                    break;
                case Key.End:
                    endingPosition = new TextPosition(column: textViewReader.GetLineLength(caretViewReader.CaretPosition.Line), line: caretViewReader.CaretPosition.Line);
                    break;
                case Key.Up:
                    endingPosition = new TextPosition(column: caretViewReader.CaretPosition.Column, line: caretViewReader.CaretPosition.Line - 1);
                    break;
                case Key.PageUp:
                    endingPosition = new TextPosition(column: caretViewReader.CaretPosition.Column, line: caretViewReader.CaretPosition.Line - GlobalConstants.PageSize);
                    break;
                case Key.Down:
                    endingPosition = new TextPosition(column: caretViewReader.CaretPosition.Column, line: caretViewReader.CaretPosition.Line + 1);
                    break;
                case Key.PageDown:
                    endingPosition = new TextPosition(column: caretViewReader.CaretPosition.Column, line: caretViewReader.CaretPosition.Line + GlobalConstants.PageSize);
                    break;
            }

            return endingPosition;
        }
        private IEnumerable<PointsPair> GetSelectionPointsForward(TextPosition start, TextPosition end) {
            var pairs = new List<PointsPair>();

            for (var i = start.Line; i <= end.Line; i++) {
                var tmpStartColumn = 0;
                var tmpStartLine = i;
                var tmpEndColumn = 0;
                var tmpEndLine = i;

                if (i == start.Line) {
                    tmpStartColumn = start.Column;
                }

                var lineLen = textViewReader.GetLineLength(i);

                if (i == end.Line) {
                    tmpEndColumn = end.Column > lineLen ? lineLen : end.Column;
                } else {
                    tmpEndColumn = lineLen;
                }

                pairs.Add(new PointsPair {
                    StartingPoint = (new TextPosition(column: tmpStartColumn, line: tmpStartLine)).GetPositionRelativeToParent(TextConfiguration.GetCharSize())
                                                                                                  .AlignToVisualLineTop(TextConfiguration.GetCharSize()),
                    EndingPoint = (new TextPosition(column: tmpEndColumn, line: tmpEndLine)).GetPositionRelativeToParent(TextConfiguration.GetCharSize())
                                                                                            .AlignToVisualLineBottom(TextConfiguration.GetCharSize())
                });
            }

            return pairs;
        }
        public void Execute(object parameter) {
            var keyboardEvent = parameter as KeyEventArgs;
            var mouseEvent = parameter as MouseButtonEventArgs;
            TextPosition newPos = null;

            if (keyboardEvent != null) {
                newPos = caretView.GetNextPosition(keyboardEvent.Key);
                keyboardEvent.Handled = true;
            } else if (mouseEvent != null) {
                newPos = mouseEvent.GetPosition(caretView).GetDocumentPosition(TextConfiguration.GetCharSize());
            }
            if (newPos == null) {
                return;
            }

            int column = -1, line = -1;

            if (newPos.Column > textViewReader.GetLineLength(newPos.Line)) {
                column = textViewReader.GetLineLength(newPos.Line);
            }
            if (newPos.Line >= textViewReader.LinesCount) {
                line = textViewReader.LinesCount - 1;
            }

            var moveDir = GetMoveDirection(newPos, caretView.CaretPosition);
            newPos = new TextPosition(column > -1 ? column : newPos.Column, line > -1 ? line : newPos.Line);
            newPos = textViewReader.AdjustStep(newPos, moveDir);

            caretView.MoveCursor(newPos);
        }
        public ChangeInLinesInfo GetChangeInLines(IReadOnlyList<VisualTextLine> lines, TextPosition startingTextPosition, Key key) {
            if (key == Key.Delete) {
                var isStartEqToTextLen = startingTextPosition.Column == lines[startingTextPosition.Line].Length;

                if (isStartEqToTextLen && startingTextPosition.Line == lines.Count) {
                    return new ChangeInLinesInfo { LinesToChange = new Dictionary<TextPosition, VisualTextLine>(), LinesToRemove = new int[0] };
                }
                if (isStartEqToTextLen) {
                    return DeleteNextLine(lines, startingTextPosition);
                } 

                return DeleteFromActiveLine(lines, startingTextPosition);
            } 

            var isStartEqZero = startingTextPosition.Column == 0;

            if (isStartEqZero && startingTextPosition.Line == 0) {
                return new ChangeInLinesInfo { LinesToChange = new Dictionary<TextPosition, VisualTextLine>(), LinesToRemove = new[] { startingTextPosition.Line } };
            }
            if (isStartEqZero) {
                return RemoveThisLine(lines, startingTextPosition);
            }

            return RemoveFromActiveLine(lines, startingTextPosition);
        }
        public TextPosition DeleteFolds(string text, TextPosition position, IDictionary<TextPosition, TextPosition> foldingPositions) {
            if (text == GetOpeningTag() || text == GetCollapsibleRepresentation()) {
                return position;
            }

            return text == GetClosingTag() ? DeleteFoldForClosePosition(position, foldingPositions) : null;
        }
        private IReadOnlyDictionary<int, string> TabPressed(IReadOnlyList<string> lines, string text, TextPosition startingTextPosition) {
            var textBeforeCursorPosition = string.Concat(lines[startingTextPosition.Line].Take(startingTextPosition.Column));
            var textAfterCursorPosition = string.Concat(lines[startingTextPosition.Line].Skip(startingTextPosition.Column));

            return new Dictionary<int, string> {
                [startingTextPosition.Line] = textBeforeCursorPosition + new string(' ', TextProperties.Properties.TabSize) + textAfterCursorPosition
            };
        }
        public void Draw(TextPosition position) {
            var formattedText = GetFormattedText(Symbol, runProperties);
            var textLocation = new Point(position.Column * charSize.Width - charSize.Width / 2, position.Line * charSize.Height);

            using (var drawingContext = RenderOpen()) {
                drawingContext.DrawText(formattedText, textLocation);
            }
        }
        public void Select(TextPosition position) {
            var selection = new VisualElement();

            visuals.Clear();
            SetSelectionState(position);
            selection.Draw(GetSelectionPoints(lastSelectionStart, lastSelectionEnd));
            visuals.Add(selection);
        }
        private IEnumerable<PointsPair> GetSelectionPoints(TextPosition start, TextPosition end) {
            visuals.Clear();

            if (start.Line <= end.Line) {
                return GetSelectionPointsForward(start, end);
            }

            return GetSelectionPointsInverted(start, end);
        }
        public TextPosition AdjustStep(TextPosition newPosition, CaretMoveDirection moveDirection) {
            var line = ((VisualTextLine)visuals[newPosition.Line]);
            var charInfo = line.RenderedText != string.Empty && newPosition.Column < GetLineLength(newPosition.Line) ? line.GetCharInfoAt(newPosition.Column) : null;

            if (charInfo != null) {
                return charInfo.IsCharacter ? newPosition : GetAdjustedPosition(charInfo, moveDirection);
            }

            return newPosition;
        }
        public bool IsInTextRange(TextPosition position) {
            if (position.Column < 0 || position.Line < 0) {
                return false;
            }
            if (position.Line >= LinesCount) {
                return false;
            }

            return position.Column <= GetLineLength(position.Line);
        }
        private void SetSelectionState(TextPosition position) {
            if (!isSelecting) {
                isSelecting = true;

                lastSelectionStart = position;
                lastSelectionEnd = lastSelectionStart;
            } else {
                lastSelectionEnd = position;
            }
        }
        private IDictionary<TextPosition, TextPosition> CreateEmptyFold(TextPosition position, IDictionary<TextPosition, TextPosition> foldingPositions) {
            TextPosition outVal;

            foldingPositions.TryGetValue(position, out outVal);

            var tmpDict = new Dictionary<TextPosition, TextPosition>(foldingPositions);

            tmpDict[position] = outVal;

            return tmpDict;
        }
        private ChangeInLinesInfo DeleteFromActiveLine(IReadOnlyList<VisualTextLine> lines, TextPosition startingTextPosition) {
            var currentLine = lines[startingTextPosition.Line];
            var firstPart = Cut(currentLine, 0, startingTextPosition.Column);
            var secondPart = Cut(currentLine, startingTextPosition.Column + 1);
            var lineAfterRemove = VisualTextLine.MergeLines(new[] { firstPart, secondPart }, currentLine.Index);

            return new ChangeInLinesInfo {
                LinesToChange = new Dictionary<TextPosition, VisualTextLine> {
                    [new TextPosition(startingTextPosition.Column, startingTextPosition.Line)] = lineAfterRemove
                },
                LinesToRemove = new int[0]
            };
        }
        public SelectionInfo LineSelection(MouseButtonEventArgs mouseEvent) {
            var clickPosition = mouseEvent.GetPosition(parent).GetDocumentPosition(TextConfiguration.GetCharSize());
            var startPosition = new TextPosition(column: 0, line: clickPosition.Line);
            var endPosition = new TextPosition(column: textViewReader.GetLineLength(clickPosition.Line), line: clickPosition.Line);
            var cursorColumn = clickPosition.Line + 1 < textViewReader.LinesCount ? 0 : textViewReader.GetLineLength(clickPosition.Line);
            var cursorLine = clickPosition.Line + 1 < textViewReader.LinesCount ? clickPosition.Line + 1 : clickPosition.Line;

            return new SelectionInfo {
                StartPosition = startPosition,
                EndPosition = endPosition,
                CursorPosition = new TextPosition(column: cursorColumn, line: cursorLine)
            };
        }
        public IReadOnlyDictionary<int, string> GetChangeInLines(IReadOnlyList<string> lines, TextPosition startingTextPosition, string text) {
            var replacedText = SpecialCharsRegex.Replace(text, string.Empty);

            if (text == TextProperties.Properties.NEWLINE) {
                return LineAdded(lines, text, startingTextPosition);
            } else if (text == TextProperties.Properties.TAB) {
                return TabPressed(lines, text, startingTextPosition);
            } else if (replacedText.Length == 1) {
                return CharacterEntered(lines, replacedText, startingTextPosition);
            } else {
                return TextPasted(lines, text, startingTextPosition);
            }
        }
        private ChangeInLinesInfo RemoveFromActiveLine(IReadOnlyList<VisualTextLine> lines, TextPosition startingTextPosition) {
            var currentLine = lines[startingTextPosition.Line];
            var attachRest = startingTextPosition.Column < lines[startingTextPosition.Line].Length;
            var firstPart = Cut(currentLine, 0, startingTextPosition.Column - 1);
            var secondPart = attachRest ? Cut(currentLine, startingTextPosition.Column) : null;
            var lineAfterRemove = secondPart != null ? VisualTextLine.MergeLines(new[] { firstPart, secondPart }, currentLine.Index) : firstPart;

            return new ChangeInLinesInfo {
                LinesToChange = new Dictionary<TextPosition, VisualTextLine> {
                    [new TextPosition(startingTextPosition.Column - 1, startingTextPosition.Line)] = lineAfterRemove
                },
                LinesToRemove = new int[0]
            };
        }
        private IReadOnlyDictionary<int, string> LineAdded(IReadOnlyList<string> lines, string text, TextPosition startingTextPosition) {
            var textBeforeCursorPosition = string.Concat(lines[startingTextPosition.Line].Take(startingTextPosition.Column));
            var textAfterCursorPosition = string.Concat(lines[startingTextPosition.Line].Skip(startingTextPosition.Column));
            var transformations = new Dictionary<int, string> {
                [startingTextPosition.Line] = textBeforeCursorPosition,
                [startingTextPosition.Line + 1] = textAfterCursorPosition
            };

            for (var i = startingTextPosition.Line + 1; i < lines.Count; i++) {
                transformations[i + 1] = lines[i];
            }

            return transformations;
        }
        private Dictionary<TextPosition, TextPosition> UpdateFolds(Dictionary<TextPosition, TextPosition> newFolds, TextPosition keyToUpdate, TextPosition foldValue) {
            var nextPositions = newFolds.Where(kvp => kvp.Key > keyToUpdate).OrderBy(kvp => kvp.Key);
            var oldPosition = newFolds[keyToUpdate];

            newFolds[keyToUpdate] = foldValue;

            foreach (var kvp in nextPositions) {
                var tmpPosition = kvp.Value;

                newFolds[kvp.Key] = oldPosition;
                oldPosition = tmpPosition;
            }

            return newFolds;
        }
        private IDictionary<TextPosition, TextPosition> RebuildFolds(TextPosition position, IDictionary<TextPosition, TextPosition> foldingPositions) {
            var newFolds = new Dictionary<TextPosition, TextPosition>(foldingPositions);
            var candidates = foldingPositions.Where(kvp => position > kvp.Key && (kvp.Value == null || position < kvp.Value));
            var closestCandidate = candidates.Max(kvp => kvp.Key);

            if (closestCandidate != null) {
                if (newFolds[closestCandidate] == null) {
                    newFolds[closestCandidate] = position;
                } else {
                    newFolds = UpdateFolds(newFolds, closestCandidate, position);
                }
            }

            return newFolds;
        }
        private IReadOnlyDictionary<int, string> CharacterEntered(IReadOnlyList<string> lines, string text, TextPosition startingTextPosition) {
            var currentTextLine = new StringBuilder();

            if (lines.Any()) {
                if (startingTextPosition.Column >= lines[startingTextPosition.Line].Length) {
                    currentTextLine.Append(lines[startingTextPosition.Line]).Append(text);
                } else {
                    currentTextLine.Append(lines[startingTextPosition.Line]).Insert(startingTextPosition.Column, text);
                }
            } else {
                currentTextLine.Append(text);
            }

            return new Dictionary<int, string> {
                [startingTextPosition.Line] = currentTextLine.ToString()
            };
        }
        public IEnumerable<string> GetTextPartsBetweenPositions(TextPosition startPosition, TextPosition endPosition) {
            var parts = visuals.ToEnumerableOf<VisualTextLine>().SelectMany(line => line.GetStringContents()).ToList();
                
            if (parts.Count == 0) {
                return parts;
            }
            if (startPosition.Line == endPosition.Line) {
                parts[0] = string.Join("", parts[0].Skip(startPosition.Column).Take(endPosition.Column - startPosition.Column));
            } else {
                parts[0] = parts[0].Substring(startPosition.Column);
            }

            var lastIndex = parts.Count - 1;
            var substringTo = endPosition.Column;

            if (startPosition.Line == endPosition.Line) {
                substringTo = endPosition.Column - startPosition.Column;
            }

            parts[lastIndex] = string.Join("", parts[lastIndex].Take(substringTo));

            return parts;
        }
        private ChangeInLinesInfo RemoveThisLine(IReadOnlyList<VisualTextLine> lines, TextPosition startingTextPosition) {
            var firstLine = lines[startingTextPosition.Line - 1];
            var linesAffected = new List<KeyValuePair<TextPosition, VisualTextLine>> {
                new KeyValuePair<TextPosition, VisualTextLine>(
                    new TextPosition(lines[startingTextPosition.Line - 1].Length, startingTextPosition.Line - 1),
                    VisualTextLine.MergeLines(new[] { firstLine, lines[startingTextPosition.Line] }, firstLine.Index))
            };

            for (var i = startingTextPosition.Line + 1; i < lines.Count; i++) {
                linesAffected.Add(new KeyValuePair<TextPosition, VisualTextLine>(new TextPosition(0, i - 1), lines[i].CloneWithIndexChange(i - 1)));
            }

            return new ChangeInLinesInfo {
                LinesToChange = linesAffected,
                LinesToRemove = new[] { lines.Count - 1 }
            };
        }
 public void HandleTextRemove(TextPosition newPosition) => MoveCaret(newPosition);
        public char GetCharAt(TextPosition position) {
            var info = ((VisualTextLine)visuals[position.Line]).GetCharInfoAt(position.Column);

            return info.IsCharacter ? info.Text[0] : default(char);
        }
 public void MoveCursor(TextPosition newPos) => MoveCaret(newPos);
        private TextPosition DeleteFoldForClosePosition(TextPosition position, IDictionary<TextPosition, TextPosition> foldingPositions) {
            var pair = foldingPositions.Where(kvp => kvp.Value == position).FirstOrDefault();

            return pair.Key;
        }
        private ChangeInLinesInfo DeleteNextLine(IReadOnlyList<VisualTextLine> lines, TextPosition startingTextPosition) {
            var firstLine = lines[startingTextPosition.Line];
            var secondLine = startingTextPosition.Line + 1 < lines.Count ? lines[startingTextPosition.Line + 1] : null;
            var linesToMerge = secondLine == null ? new[] { firstLine } : new[] { firstLine, secondLine };
            var linesAffected = new List<KeyValuePair<TextPosition, VisualTextLine>> {
                new KeyValuePair<TextPosition, VisualTextLine>(
                    new TextPosition(startingTextPosition.Column, startingTextPosition.Line),
                    VisualTextLine.MergeLines(linesToMerge, firstLine.Index))
            };

            for (var i = startingTextPosition.Line + 2; i < lines.Count; i++) {
                linesAffected.Add(new KeyValuePair<TextPosition, VisualTextLine>(new TextPosition(0, i - 1), lines[i].CloneWithIndexChange(i - 1)));
            }

            return new ChangeInLinesInfo {
                LinesToChange = linesAffected,
                LinesToRemove = new[] { lines.Count - 1 }
            };
        }