diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/NewLineElementGenerator.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/NewLineElementGenerator.cs index 9fac3f4fed..dea23c35d7 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/NewLineElementGenerator.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/NewLineElementGenerator.cs @@ -77,6 +77,11 @@ namespace ICSharpCode.AvalonEdit.Rendering } } + public override bool IsWhitespace(int visualColumn) + { + return true; + } + public override bool HandlesLineBorders { get { return true; } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/SingleCharacterElementGenerator.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/SingleCharacterElementGenerator.cs index 90f616571f..da1aacbc45 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/SingleCharacterElementGenerator.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/SingleCharacterElementGenerator.cs @@ -118,6 +118,11 @@ namespace ICSharpCode.AvalonEdit.Rendering else return -1; } + + public override bool IsWhitespace(int visualColumn) + { + return true; + } } sealed class TabTextElement : VisualLineElement @@ -148,6 +153,11 @@ namespace ICSharpCode.AvalonEdit.Rendering else return -1; } + + public override bool IsWhitespace(int visualColumn) + { + return true; + } } sealed class TabGlyphRun : TextEmbeddedObject diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs index ca0cd38af8..faf2ca022a 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/TextView.cs @@ -544,7 +544,7 @@ namespace ICSharpCode.AvalonEdit.Rendering VisualLine l = GetVisualLine(documentLine.LineNumber); if (l == null) { TextRunProperties globalTextRunProperties = CreateGlobalTextRunProperties(); - TextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties); + VisualLineTextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties); while (heightTree.GetIsCollapsed(documentLine)) { documentLine = documentLine.PreviousLine; @@ -710,7 +710,7 @@ namespace ICSharpCode.AvalonEdit.Rendering double CreateAndMeasureVisualLines(Size availableSize) { TextRunProperties globalTextRunProperties = CreateGlobalTextRunProperties(); - TextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties); + VisualLineTextParagraphProperties paragraphProperties = CreateParagraphProperties(globalTextRunProperties); Debug.WriteLine("Measure availableSize=" + availableSize + ", scrollOffset=" + scrollOffset); var firstLineInView = heightTree.GetLineByVisualPosition(scrollOffset.Y); @@ -786,7 +786,7 @@ namespace ICSharpCode.AvalonEdit.Rendering }; } - TextParagraphProperties CreateParagraphProperties(TextRunProperties defaultTextRunProperties) + VisualLineTextParagraphProperties CreateParagraphProperties(TextRunProperties defaultTextRunProperties) { return new VisualLineTextParagraphProperties { defaultTextRunProperties = defaultTextRunProperties, @@ -797,7 +797,7 @@ namespace ICSharpCode.AvalonEdit.Rendering VisualLine BuildVisualLine(DocumentLine documentLine, TextRunProperties globalTextRunProperties, - TextParagraphProperties paragraphProperties, + VisualLineTextParagraphProperties paragraphProperties, VisualLineElementGenerator[] elementGeneratorsArray, IVisualLineTransformer[] lineTransformersArray, Size availableSize) @@ -829,6 +829,8 @@ namespace ICSharpCode.AvalonEdit.Rendering int textOffset = 0; TextLineBreak lastLineBreak = null; var textLines = new List(); + paragraphProperties.indent = 0; + paragraphProperties.firstLineInParagraph = true; while (textOffset <= visualLine.VisualLength) { TextLine textLine = formatter.FormatLine( textSource, @@ -840,12 +842,52 @@ namespace ICSharpCode.AvalonEdit.Rendering textLines.Add(textLine); textOffset += textLine.Length; + // exit loop so that we don't do the indentation calculation if there's only a single line + if (textOffset >= visualLine.VisualLength) + break; + + if (paragraphProperties.firstLineInParagraph) { + paragraphProperties.firstLineInParagraph = false; + + TextEditorOptions options = this.Options; + double indentation = 0; + if (options.InheritWordWrapIndentation) { + // determine indentation for next line: + int indentVisualColumn = GetIndentationVisualColumn(visualLine); + if (indentVisualColumn > 0 && indentVisualColumn < textOffset) { + indentation = textLine.GetDistanceFromCharacterHit(new CharacterHit(indentVisualColumn, 0)); + } + } + indentation += options.WordWrapIndentation; + // apply the calculated indentation unless it's more than half of the text editor size: + if (indentation > 0 && indentation * 2 < availableSize.Width) + paragraphProperties.indent = indentation; + } lastLineBreak = textLine.GetTextLineBreak(); } visualLine.SetTextLines(textLines); heightTree.SetHeight(visualLine.FirstDocumentLine, visualLine.Height); return visualLine; } + + static int GetIndentationVisualColumn(VisualLine visualLine) + { + if (visualLine.Elements.Count == 0) + return 0; + int column = 0; + int elementIndex = 0; + VisualLineElement element = visualLine.Elements[elementIndex]; + while (element.IsWhitespace(column)) { + column++; + if (column == element.VisualColumn + element.VisualLength) { + elementIndex++; + if (elementIndex == visualLine.Elements.Count) + break; + element = visualLine.Elements[elementIndex]; + } + } + return column; + } #endregion #region Arrange diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineElement.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineElement.cs index d7ec8f2e83..026e9c0f99 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineElement.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineElement.cs @@ -200,6 +200,14 @@ namespace ICSharpCode.AvalonEdit.Rendering return -1; } + /// + /// Gets whether the specified offset in this element is considered whitespace. + /// + public virtual bool IsWhitespace(int visualColumn) + { + return false; + } + /// /// Gets whether the implementation handles line borders. /// If this property returns false, the caller of GetNextCaretPosition should handle the line diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineText.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineText.cs index 7b6f1c28fe..3cefe1e264 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineText.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineText.cs @@ -57,6 +57,13 @@ namespace ICSharpCode.AvalonEdit.Rendering return new TextCharacters(text, 0, text.Length, this.TextRunProperties); } + /// + public override bool IsWhitespace(int visualColumn) + { + int offset = visualColumn - this.VisualColumn + parentVisualLine.FirstDocumentLine.Offset + this.RelativeTextOffset; + return char.IsWhiteSpace(parentVisualLine.Document.GetCharAt(offset)); + } + /// public override TextSpan GetPrecedingText(int visualColumnLimit, ITextRunConstructionContext context) { diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineTextParagraphProperties.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineTextParagraphProperties.cs index 5dc067a0f1..c97cd6b6c0 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineTextParagraphProperties.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLineTextParagraphProperties.cs @@ -7,11 +7,13 @@ using System.Windows.Media.TextFormatting; namespace ICSharpCode.AvalonEdit.Rendering { - class VisualLineTextParagraphProperties : TextParagraphProperties + sealed class VisualLineTextParagraphProperties : TextParagraphProperties { internal TextRunProperties defaultTextRunProperties; internal TextWrapping textWrapping; internal double tabSize; + internal double indent; + internal bool firstLineInParagraph; public override double DefaultIncrementalTab { get { return tabSize; } @@ -20,10 +22,10 @@ namespace ICSharpCode.AvalonEdit.Rendering public override FlowDirection FlowDirection { get { return FlowDirection.LeftToRight; } } public override TextAlignment TextAlignment { get { return TextAlignment.Left; } } public override double LineHeight { get { return double.NaN; } } - public override bool FirstLineInParagraph { get { return false; } } + public override bool FirstLineInParagraph { get { return firstLineInParagraph; } } public override TextRunProperties DefaultTextRunProperties { get { return defaultTextRunProperties; } } public override TextWrapping TextWrapping { get { return textWrapping; } } public override TextMarkerProperties TextMarkerProperties { get { return null; } } - public override double Indent { get { return 0; } } + public override double Indent { get { return indent; } } } } diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorOptions.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorOptions.cs index e382171ded..8d4b93f126 100644 --- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorOptions.cs +++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/TextEditorOptions.cs @@ -267,5 +267,42 @@ namespace ICSharpCode.AvalonEdit } } } + + double wordWrapIndentation = 100; + + /// + /// Gets/Sets the indentation used for all lines except the first when word-wrapping. + /// The default value is 0. + /// + [DefaultValue(100)] + public virtual double WordWrapIndentation { + get { return wordWrapIndentation; } + set { + if (double.IsNaN(value) || double.IsInfinity(value)) + throw new ArgumentOutOfRangeException("value", value, "value must not be NaN/infinity"); + if (value != wordWrapIndentation) { + wordWrapIndentation = value; + OnPropertyChanged("WordWrapIndentation"); + } + } + } + + bool inheritWordWrapIndentation = true; + + /// + /// Gets/Sets whether the indentation is inherited from the first line when word-wrapping. + /// The default value is true. + /// + /// When combined with , the inherited indentation is added to the word wrap indentation. + [DefaultValue(true)] + public virtual bool InheritWordWrapIndentation { + get { return inheritWordWrapIndentation; } + set { + if (value != inheritWordWrapIndentation) { + inheritWordWrapIndentation = value; + OnPropertyChanged("InheritWordWrapIndentation"); + } + } + } } }