1178 строки
35 KiB
C#
1178 строки
35 KiB
C#
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
|
// software and associated documentation files (the "Software"), to deal in the Software
|
|
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
|
// to whom the Software is furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in all copies or
|
|
// substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Controls.Primitives;
|
|
using System.Windows.Data;
|
|
using System.Windows.Input;
|
|
using System.Windows.Markup;
|
|
using System.Windows.Media;
|
|
using System.Windows.Shapes;
|
|
|
|
using ICSharpCode.AvalonEdit.Document;
|
|
using ICSharpCode.AvalonEdit.Editing;
|
|
using ICSharpCode.AvalonEdit.Highlighting;
|
|
using ICSharpCode.AvalonEdit.Rendering;
|
|
using ICSharpCode.AvalonEdit.Utils;
|
|
|
|
namespace ICSharpCode.AvalonEdit
|
|
{
|
|
/// <summary>
|
|
/// The text editor control.
|
|
/// Contains a scrollable TextArea.
|
|
/// </summary>
|
|
[Localizability(LocalizationCategory.Text), ContentProperty("Text")]
|
|
public class TextEditor : Control, ITextEditorComponent, IServiceProvider, IWeakEventListener
|
|
{
|
|
#region Constructors
|
|
static TextEditor()
|
|
{
|
|
DefaultStyleKeyProperty.OverrideMetadata(typeof(TextEditor),
|
|
new FrameworkPropertyMetadata(typeof(TextEditor)));
|
|
FocusableProperty.OverrideMetadata(typeof(TextEditor),
|
|
new FrameworkPropertyMetadata(Boxes.True));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new TextEditor instance.
|
|
/// </summary>
|
|
public TextEditor() : this(new TextArea())
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new TextEditor instance.
|
|
/// </summary>
|
|
protected TextEditor(TextArea textArea)
|
|
{
|
|
if (textArea == null)
|
|
throw new ArgumentNullException("textArea");
|
|
this.textArea = textArea;
|
|
|
|
textArea.TextView.Services.AddService(typeof(TextEditor), this);
|
|
|
|
SetCurrentValue(OptionsProperty, textArea.Options);
|
|
SetCurrentValue(DocumentProperty, new TextDocument());
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <inheritdoc/>
|
|
protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer()
|
|
{
|
|
return new TextEditorAutomationPeer(this);
|
|
}
|
|
|
|
/// Forward focus to TextArea.
|
|
/// <inheritdoc/>
|
|
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
|
|
{
|
|
base.OnGotKeyboardFocus(e);
|
|
if (e.NewFocus == this) {
|
|
Keyboard.Focus(textArea);
|
|
e.Handled = true;
|
|
}
|
|
}
|
|
|
|
#region Document property
|
|
/// <summary>
|
|
/// Document property.
|
|
/// </summary>
|
|
public static readonly DependencyProperty DocumentProperty
|
|
= TextView.DocumentProperty.AddOwner(
|
|
typeof(TextEditor), new FrameworkPropertyMetadata(OnDocumentChanged));
|
|
|
|
/// <summary>
|
|
/// Gets/Sets the document displayed by the text editor.
|
|
/// This is a dependency property.
|
|
/// </summary>
|
|
public TextDocument Document {
|
|
get { return (TextDocument)GetValue(DocumentProperty); }
|
|
set { SetValue(DocumentProperty, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when the document property has changed.
|
|
/// </summary>
|
|
public event EventHandler DocumentChanged;
|
|
|
|
/// <summary>
|
|
/// Raises the <see cref="DocumentChanged"/> event.
|
|
/// </summary>
|
|
protected virtual void OnDocumentChanged(EventArgs e)
|
|
{
|
|
if (DocumentChanged != null) {
|
|
DocumentChanged(this, e);
|
|
}
|
|
}
|
|
|
|
static void OnDocumentChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
((TextEditor)dp).OnDocumentChanged((TextDocument)e.OldValue, (TextDocument)e.NewValue);
|
|
}
|
|
|
|
void OnDocumentChanged(TextDocument oldValue, TextDocument newValue)
|
|
{
|
|
if (oldValue != null) {
|
|
TextDocumentWeakEventManager.TextChanged.RemoveListener(oldValue, this);
|
|
PropertyChangedEventManager.RemoveListener(oldValue.UndoStack, this, "IsOriginalFile");
|
|
}
|
|
textArea.Document = newValue;
|
|
if (newValue != null) {
|
|
TextDocumentWeakEventManager.TextChanged.AddListener(newValue, this);
|
|
PropertyChangedEventManager.AddListener(newValue.UndoStack, this, "IsOriginalFile");
|
|
}
|
|
OnDocumentChanged(EventArgs.Empty);
|
|
OnTextChanged(EventArgs.Empty);
|
|
}
|
|
#endregion
|
|
|
|
#region Options property
|
|
/// <summary>
|
|
/// Options property.
|
|
/// </summary>
|
|
public static readonly DependencyProperty OptionsProperty
|
|
= TextView.OptionsProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(OnOptionsChanged));
|
|
|
|
/// <summary>
|
|
/// Gets/Sets the options currently used by the text editor.
|
|
/// </summary>
|
|
public TextEditorOptions Options {
|
|
get { return (TextEditorOptions)GetValue(OptionsProperty); }
|
|
set { SetValue(OptionsProperty, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when a text editor option has changed.
|
|
/// </summary>
|
|
public event PropertyChangedEventHandler OptionChanged;
|
|
|
|
/// <summary>
|
|
/// Raises the <see cref="OptionChanged"/> event.
|
|
/// </summary>
|
|
protected virtual void OnOptionChanged(PropertyChangedEventArgs e)
|
|
{
|
|
if (OptionChanged != null) {
|
|
OptionChanged(this, e);
|
|
}
|
|
}
|
|
|
|
static void OnOptionsChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
((TextEditor)dp).OnOptionsChanged((TextEditorOptions)e.OldValue, (TextEditorOptions)e.NewValue);
|
|
}
|
|
|
|
void OnOptionsChanged(TextEditorOptions oldValue, TextEditorOptions newValue)
|
|
{
|
|
if (oldValue != null) {
|
|
PropertyChangedWeakEventManager.RemoveListener(oldValue, this);
|
|
}
|
|
textArea.Options = newValue;
|
|
if (newValue != null) {
|
|
PropertyChangedWeakEventManager.AddListener(newValue, this);
|
|
}
|
|
OnOptionChanged(new PropertyChangedEventArgs(null));
|
|
}
|
|
|
|
/// <inheritdoc cref="IWeakEventListener.ReceiveWeakEvent"/>
|
|
protected virtual bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
|
|
{
|
|
if (managerType == typeof(PropertyChangedWeakEventManager)) {
|
|
OnOptionChanged((PropertyChangedEventArgs)e);
|
|
return true;
|
|
} else if (managerType == typeof(TextDocumentWeakEventManager.TextChanged)) {
|
|
OnTextChanged(e);
|
|
return true;
|
|
} else if (managerType == typeof(PropertyChangedEventManager)) {
|
|
return HandleIsOriginalChanged((PropertyChangedEventArgs)e);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool IWeakEventListener.ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
|
|
{
|
|
return ReceiveWeakEvent(managerType, sender, e);
|
|
}
|
|
#endregion
|
|
|
|
#region Text property
|
|
/// <summary>
|
|
/// Gets/Sets the text of the current document.
|
|
/// </summary>
|
|
[Localizability(LocalizationCategory.Text), DefaultValue("")]
|
|
public string Text {
|
|
get {
|
|
TextDocument document = this.Document;
|
|
return document != null ? document.Text : string.Empty;
|
|
}
|
|
set {
|
|
TextDocument document = GetDocument();
|
|
document.Text = value ?? string.Empty;
|
|
// after replacing the full text, the caret is positioned at the end of the document
|
|
// - reset it to the beginning.
|
|
this.CaretOffset = 0;
|
|
document.UndoStack.ClearAll();
|
|
}
|
|
}
|
|
|
|
TextDocument GetDocument()
|
|
{
|
|
TextDocument document = this.Document;
|
|
if (document == null)
|
|
throw ThrowUtil.NoDocumentAssigned();
|
|
return document;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when the Text property changes.
|
|
/// </summary>
|
|
public event EventHandler TextChanged;
|
|
|
|
/// <summary>
|
|
/// Raises the <see cref="TextChanged"/> event.
|
|
/// </summary>
|
|
protected virtual void OnTextChanged(EventArgs e)
|
|
{
|
|
if (TextChanged != null) {
|
|
TextChanged(this, e);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region TextArea / ScrollViewer properties
|
|
readonly TextArea textArea;
|
|
ScrollViewer scrollViewer;
|
|
|
|
/// <summary>
|
|
/// Is called after the template was applied.
|
|
/// </summary>
|
|
public override void OnApplyTemplate()
|
|
{
|
|
base.OnApplyTemplate();
|
|
scrollViewer = (ScrollViewer)Template.FindName("PART_ScrollViewer", this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the text area.
|
|
/// </summary>
|
|
public TextArea TextArea {
|
|
get {
|
|
return textArea;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the scroll viewer used by the text editor.
|
|
/// This property can return null if the template has not been applied / does not contain a scroll viewer.
|
|
/// </summary>
|
|
internal ScrollViewer ScrollViewer {
|
|
get { return scrollViewer; }
|
|
}
|
|
|
|
bool CanExecute(RoutedUICommand command)
|
|
{
|
|
return command.CanExecute(null, textArea);
|
|
}
|
|
|
|
void Execute(RoutedUICommand command)
|
|
{
|
|
command.Execute(null, textArea);
|
|
}
|
|
#endregion
|
|
|
|
#region Syntax highlighting
|
|
/// <summary>
|
|
/// The <see cref="SyntaxHighlighting"/> property.
|
|
/// </summary>
|
|
public static readonly DependencyProperty SyntaxHighlightingProperty =
|
|
DependencyProperty.Register("SyntaxHighlighting", typeof(IHighlightingDefinition), typeof(TextEditor),
|
|
new FrameworkPropertyMetadata(OnSyntaxHighlightingChanged));
|
|
|
|
|
|
/// <summary>
|
|
/// Gets/sets the syntax highlighting definition used to colorize the text.
|
|
/// </summary>
|
|
public IHighlightingDefinition SyntaxHighlighting {
|
|
get { return (IHighlightingDefinition)GetValue(SyntaxHighlightingProperty); }
|
|
set { SetValue(SyntaxHighlightingProperty, value); }
|
|
}
|
|
|
|
IVisualLineTransformer colorizer;
|
|
|
|
static void OnSyntaxHighlightingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
((TextEditor)d).OnSyntaxHighlightingChanged(e.NewValue as IHighlightingDefinition);
|
|
}
|
|
|
|
void OnSyntaxHighlightingChanged(IHighlightingDefinition newValue)
|
|
{
|
|
if (colorizer != null) {
|
|
textArea.TextView.LineTransformers.Remove(colorizer);
|
|
colorizer = null;
|
|
}
|
|
if (newValue != null) {
|
|
colorizer = CreateColorizer(newValue);
|
|
if (colorizer != null)
|
|
textArea.TextView.LineTransformers.Insert(0, colorizer);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the highlighting colorizer for the specified highlighting definition.
|
|
/// Allows derived classes to provide custom colorizer implementations for special highlighting definitions.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected virtual IVisualLineTransformer CreateColorizer(IHighlightingDefinition highlightingDefinition)
|
|
{
|
|
if (highlightingDefinition == null)
|
|
throw new ArgumentNullException("highlightingDefinition");
|
|
return new HighlightingColorizer(highlightingDefinition);
|
|
}
|
|
#endregion
|
|
|
|
#region WordWrap
|
|
/// <summary>
|
|
/// Word wrap dependency property.
|
|
/// </summary>
|
|
public static readonly DependencyProperty WordWrapProperty =
|
|
DependencyProperty.Register("WordWrap", typeof(bool), typeof(TextEditor),
|
|
new FrameworkPropertyMetadata(Boxes.False));
|
|
|
|
/// <summary>
|
|
/// Specifies whether the text editor uses word wrapping.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Setting WordWrap=true has the same effect as setting HorizontalScrollBarVisibility=Disabled and will override the
|
|
/// HorizontalScrollBarVisibility setting.
|
|
/// </remarks>
|
|
public bool WordWrap {
|
|
get { return (bool)GetValue(WordWrapProperty); }
|
|
set { SetValue(WordWrapProperty, Boxes.Box(value)); }
|
|
}
|
|
#endregion
|
|
|
|
#region IsReadOnly
|
|
/// <summary>
|
|
/// IsReadOnly dependency property.
|
|
/// </summary>
|
|
public static readonly DependencyProperty IsReadOnlyProperty =
|
|
DependencyProperty.Register("IsReadOnly", typeof(bool), typeof(TextEditor),
|
|
new FrameworkPropertyMetadata(Boxes.False, OnIsReadOnlyChanged));
|
|
|
|
/// <summary>
|
|
/// Specifies whether the user can change the text editor content.
|
|
/// Setting this property will replace the
|
|
/// <see cref="Editing.TextArea.ReadOnlySectionProvider">TextArea.ReadOnlySectionProvider</see>.
|
|
/// </summary>
|
|
public bool IsReadOnly {
|
|
get { return (bool)GetValue(IsReadOnlyProperty); }
|
|
set { SetValue(IsReadOnlyProperty, Boxes.Box(value)); }
|
|
}
|
|
|
|
static void OnIsReadOnlyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
TextEditor editor = d as TextEditor;
|
|
if (editor != null) {
|
|
if ((bool)e.NewValue)
|
|
editor.TextArea.ReadOnlySectionProvider = ReadOnlySectionDocument.Instance;
|
|
else
|
|
editor.TextArea.ReadOnlySectionProvider = NoReadOnlySections.Instance;
|
|
|
|
TextEditorAutomationPeer peer = TextEditorAutomationPeer.FromElement(editor) as TextEditorAutomationPeer;
|
|
if (peer != null) {
|
|
peer.RaiseIsReadOnlyChanged((bool)e.OldValue, (bool)e.NewValue);
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region IsModified
|
|
/// <summary>
|
|
/// Dependency property for <see cref="IsModified"/>
|
|
/// </summary>
|
|
public static readonly DependencyProperty IsModifiedProperty =
|
|
DependencyProperty.Register("IsModified", typeof(bool), typeof(TextEditor),
|
|
new FrameworkPropertyMetadata(Boxes.False, OnIsModifiedChanged));
|
|
|
|
/// <summary>
|
|
/// Gets/Sets the 'modified' flag.
|
|
/// </summary>
|
|
public bool IsModified {
|
|
get { return (bool)GetValue(IsModifiedProperty); }
|
|
set { SetValue(IsModifiedProperty, Boxes.Box(value)); }
|
|
}
|
|
|
|
static void OnIsModifiedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
TextEditor editor = d as TextEditor;
|
|
if (editor != null) {
|
|
TextDocument document = editor.Document;
|
|
if (document != null) {
|
|
UndoStack undoStack = document.UndoStack;
|
|
if ((bool)e.NewValue) {
|
|
if (undoStack.IsOriginalFile)
|
|
undoStack.DiscardOriginalFileMarker();
|
|
} else {
|
|
undoStack.MarkAsOriginalFile();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HandleIsOriginalChanged(PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == "IsOriginalFile") {
|
|
TextDocument document = this.Document;
|
|
if (document != null) {
|
|
SetCurrentValue(IsModifiedProperty, Boxes.Box(!document.UndoStack.IsOriginalFile));
|
|
}
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region ShowLineNumbers
|
|
/// <summary>
|
|
/// ShowLineNumbers dependency property.
|
|
/// </summary>
|
|
public static readonly DependencyProperty ShowLineNumbersProperty =
|
|
DependencyProperty.Register("ShowLineNumbers", typeof(bool), typeof(TextEditor),
|
|
new FrameworkPropertyMetadata(Boxes.False, OnShowLineNumbersChanged));
|
|
|
|
/// <summary>
|
|
/// Specifies whether line numbers are shown on the left to the text view.
|
|
/// </summary>
|
|
public bool ShowLineNumbers {
|
|
get { return (bool)GetValue(ShowLineNumbersProperty); }
|
|
set { SetValue(ShowLineNumbersProperty, Boxes.Box(value)); }
|
|
}
|
|
|
|
static void OnShowLineNumbersChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
TextEditor editor = (TextEditor)d;
|
|
var leftMargins = editor.TextArea.LeftMargins;
|
|
if ((bool)e.NewValue) {
|
|
LineNumberMargin lineNumbers = new LineNumberMargin();
|
|
Line line = (Line)DottedLineMargin.Create();
|
|
leftMargins.Insert(0, lineNumbers);
|
|
leftMargins.Insert(1, line);
|
|
var lineNumbersForeground = new Binding("LineNumbersForeground") { Source = editor };
|
|
line.SetBinding(Line.StrokeProperty, lineNumbersForeground);
|
|
lineNumbers.SetBinding(Control.ForegroundProperty, lineNumbersForeground);
|
|
} else {
|
|
for (int i = 0; i < leftMargins.Count; i++) {
|
|
if (leftMargins[i] is LineNumberMargin) {
|
|
leftMargins.RemoveAt(i);
|
|
if (i < leftMargins.Count && DottedLineMargin.IsDottedLineMargin(leftMargins[i])) {
|
|
leftMargins.RemoveAt(i);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region LineNumbersForeground
|
|
/// <summary>
|
|
/// LineNumbersForeground dependency property.
|
|
/// </summary>
|
|
public static readonly DependencyProperty LineNumbersForegroundProperty =
|
|
DependencyProperty.Register("LineNumbersForeground", typeof(Brush), typeof(TextEditor),
|
|
new FrameworkPropertyMetadata(Brushes.Gray, OnLineNumbersForegroundChanged));
|
|
|
|
/// <summary>
|
|
/// Gets/sets the Brush used for displaying the foreground color of line numbers.
|
|
/// </summary>
|
|
public Brush LineNumbersForeground {
|
|
get { return (Brush)GetValue(LineNumbersForegroundProperty); }
|
|
set { SetValue(LineNumbersForegroundProperty, value); }
|
|
}
|
|
|
|
static void OnLineNumbersForegroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
TextEditor editor = (TextEditor)d;
|
|
var lineNumberMargin = editor.TextArea.LeftMargins.FirstOrDefault(margin => margin is LineNumberMargin) as LineNumberMargin; ;
|
|
|
|
if (lineNumberMargin != null) {
|
|
lineNumberMargin.SetValue(Control.ForegroundProperty, e.NewValue);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region TextBoxBase-like methods
|
|
/// <summary>
|
|
/// Appends text to the end of the document.
|
|
/// </summary>
|
|
public void AppendText(string textData)
|
|
{
|
|
var document = GetDocument();
|
|
document.Insert(document.TextLength, textData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begins a group of document changes.
|
|
/// </summary>
|
|
public void BeginChange()
|
|
{
|
|
GetDocument().BeginUpdate();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies the current selection to the clipboard.
|
|
/// </summary>
|
|
public void Copy()
|
|
{
|
|
Execute(ApplicationCommands.Copy);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the current selection and copies it to the clipboard.
|
|
/// </summary>
|
|
public void Cut()
|
|
{
|
|
Execute(ApplicationCommands.Cut);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Begins a group of document changes and returns an object that ends the group of document
|
|
/// changes when it is disposed.
|
|
/// </summary>
|
|
public IDisposable DeclareChangeBlock()
|
|
{
|
|
return GetDocument().RunUpdate();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the current selection without copying it to the clipboard.
|
|
/// </summary>
|
|
public void Delete()
|
|
{
|
|
Execute(ApplicationCommands.Delete);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ends the current group of document changes.
|
|
/// </summary>
|
|
public void EndChange()
|
|
{
|
|
GetDocument().EndUpdate();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls one line down.
|
|
/// </summary>
|
|
public void LineDown()
|
|
{
|
|
if (scrollViewer != null)
|
|
scrollViewer.LineDown();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls to the left.
|
|
/// </summary>
|
|
public void LineLeft()
|
|
{
|
|
if (scrollViewer != null)
|
|
scrollViewer.LineLeft();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls to the right.
|
|
/// </summary>
|
|
public void LineRight()
|
|
{
|
|
if (scrollViewer != null)
|
|
scrollViewer.LineRight();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls one line up.
|
|
/// </summary>
|
|
public void LineUp()
|
|
{
|
|
if (scrollViewer != null)
|
|
scrollViewer.LineUp();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls one page down.
|
|
/// </summary>
|
|
public void PageDown()
|
|
{
|
|
if (scrollViewer != null)
|
|
scrollViewer.PageDown();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls one page up.
|
|
/// </summary>
|
|
public void PageUp()
|
|
{
|
|
if (scrollViewer != null)
|
|
scrollViewer.PageUp();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls one page left.
|
|
/// </summary>
|
|
public void PageLeft()
|
|
{
|
|
if (scrollViewer != null)
|
|
scrollViewer.PageLeft();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls one page right.
|
|
/// </summary>
|
|
public void PageRight()
|
|
{
|
|
if (scrollViewer != null)
|
|
scrollViewer.PageRight();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Pastes the clipboard content.
|
|
/// </summary>
|
|
public void Paste()
|
|
{
|
|
Execute(ApplicationCommands.Paste);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Redoes the most recent undone command.
|
|
/// </summary>
|
|
/// <returns>True is the redo operation was successful, false is the redo stack is empty.</returns>
|
|
public bool Redo()
|
|
{
|
|
if (CanExecute(ApplicationCommands.Redo)) {
|
|
Execute(ApplicationCommands.Redo);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls to the end of the document.
|
|
/// </summary>
|
|
public void ScrollToEnd()
|
|
{
|
|
ApplyTemplate(); // ensure scrollViewer is created
|
|
if (scrollViewer != null)
|
|
scrollViewer.ScrollToEnd();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls to the start of the document.
|
|
/// </summary>
|
|
public void ScrollToHome()
|
|
{
|
|
ApplyTemplate(); // ensure scrollViewer is created
|
|
if (scrollViewer != null)
|
|
scrollViewer.ScrollToHome();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls to the specified position in the document.
|
|
/// </summary>
|
|
public void ScrollToHorizontalOffset(double offset)
|
|
{
|
|
ApplyTemplate(); // ensure scrollViewer is created
|
|
if (scrollViewer != null)
|
|
scrollViewer.ScrollToHorizontalOffset(offset);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls to the specified position in the document.
|
|
/// </summary>
|
|
public void ScrollToVerticalOffset(double offset)
|
|
{
|
|
ApplyTemplate(); // ensure scrollViewer is created
|
|
if (scrollViewer != null)
|
|
scrollViewer.ScrollToVerticalOffset(offset);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects the entire text.
|
|
/// </summary>
|
|
public void SelectAll()
|
|
{
|
|
Execute(ApplicationCommands.SelectAll);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Undoes the most recent command.
|
|
/// </summary>
|
|
/// <returns>True is the undo operation was successful, false is the undo stack is empty.</returns>
|
|
public bool Undo()
|
|
{
|
|
if (CanExecute(ApplicationCommands.Undo)) {
|
|
Execute(ApplicationCommands.Undo);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets if the most recent undone command can be redone.
|
|
/// </summary>
|
|
public bool CanRedo {
|
|
get { return CanExecute(ApplicationCommands.Redo); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets if the most recent command can be undone.
|
|
/// </summary>
|
|
public bool CanUndo {
|
|
get { return CanExecute(ApplicationCommands.Undo); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the vertical size of the document.
|
|
/// </summary>
|
|
public double ExtentHeight {
|
|
get {
|
|
return scrollViewer != null ? scrollViewer.ExtentHeight : 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the horizontal size of the current document region.
|
|
/// </summary>
|
|
public double ExtentWidth {
|
|
get {
|
|
return scrollViewer != null ? scrollViewer.ExtentWidth : 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the horizontal size of the viewport.
|
|
/// </summary>
|
|
public double ViewportHeight {
|
|
get {
|
|
return scrollViewer != null ? scrollViewer.ViewportHeight : 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the horizontal size of the viewport.
|
|
/// </summary>
|
|
public double ViewportWidth {
|
|
get {
|
|
return scrollViewer != null ? scrollViewer.ViewportWidth : 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the vertical scroll position.
|
|
/// </summary>
|
|
public double VerticalOffset {
|
|
get {
|
|
return scrollViewer != null ? scrollViewer.VerticalOffset : 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the horizontal scroll position.
|
|
/// </summary>
|
|
public double HorizontalOffset {
|
|
get {
|
|
return scrollViewer != null ? scrollViewer.HorizontalOffset : 0;
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region TextBox methods
|
|
/// <summary>
|
|
/// Gets/Sets the selected text.
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public string SelectedText {
|
|
get {
|
|
// We'll get the text from the whole surrounding segment.
|
|
// This is done to ensure that SelectedText.Length == SelectionLength.
|
|
if (textArea.Document != null && !textArea.Selection.IsEmpty)
|
|
return textArea.Document.GetText(textArea.Selection.SurroundingSegment);
|
|
else
|
|
return string.Empty;
|
|
}
|
|
set {
|
|
if (value == null)
|
|
throw new ArgumentNullException("value");
|
|
if (textArea.Document != null) {
|
|
int offset = this.SelectionStart;
|
|
int length = this.SelectionLength;
|
|
textArea.Document.Replace(offset, length, value);
|
|
// keep inserted text selected
|
|
textArea.Selection = SimpleSelection.Create(textArea, offset, offset + value.Length);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the caret position.
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public int CaretOffset {
|
|
get {
|
|
return textArea.Caret.Offset;
|
|
}
|
|
set {
|
|
textArea.Caret.Offset = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the start position of the selection.
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public int SelectionStart {
|
|
get {
|
|
if (textArea.Selection.IsEmpty)
|
|
return textArea.Caret.Offset;
|
|
else
|
|
return textArea.Selection.SurroundingSegment.Offset;
|
|
}
|
|
set {
|
|
Select(value, SelectionLength);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets/sets the length of the selection.
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public int SelectionLength {
|
|
get {
|
|
if (!textArea.Selection.IsEmpty)
|
|
return textArea.Selection.SurroundingSegment.Length;
|
|
else
|
|
return 0;
|
|
}
|
|
set {
|
|
Select(SelectionStart, value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Selects the specified text section.
|
|
/// </summary>
|
|
public void Select(int start, int length)
|
|
{
|
|
int documentLength = Document != null ? Document.TextLength : 0;
|
|
if (start < 0 || start > documentLength)
|
|
throw new ArgumentOutOfRangeException("start", start, "Value must be between 0 and " + documentLength);
|
|
if (length < 0 || start + length > documentLength)
|
|
throw new ArgumentOutOfRangeException("length", length, "Value must be between 0 and " + (documentLength - start));
|
|
textArea.Selection = SimpleSelection.Create(textArea, start, start + length);
|
|
textArea.Caret.Offset = start + length;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the number of lines in the document.
|
|
/// </summary>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public int LineCount {
|
|
get {
|
|
TextDocument document = this.Document;
|
|
if (document != null)
|
|
return document.LineCount;
|
|
else
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears the text.
|
|
/// </summary>
|
|
public void Clear()
|
|
{
|
|
this.Text = string.Empty;
|
|
}
|
|
#endregion
|
|
|
|
#region Loading from stream
|
|
/// <summary>
|
|
/// Loads the text from the stream, auto-detecting the encoding.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method sets <see cref="IsModified"/> to false.
|
|
/// </remarks>
|
|
public void Load(Stream stream)
|
|
{
|
|
using (StreamReader reader = FileReader.OpenStream(stream, this.Encoding ?? Encoding.UTF8)) {
|
|
this.Text = reader.ReadToEnd();
|
|
SetCurrentValue(EncodingProperty, reader.CurrentEncoding); // assign encoding after ReadToEnd() so that the StreamReader can autodetect the encoding
|
|
}
|
|
SetCurrentValue(IsModifiedProperty, Boxes.False);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the text from the stream, auto-detecting the encoding.
|
|
/// </summary>
|
|
public void Load(string fileName)
|
|
{
|
|
if (fileName == null)
|
|
throw new ArgumentNullException("fileName");
|
|
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read)) {
|
|
Load(fs);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Encoding dependency property.
|
|
/// </summary>
|
|
public static readonly DependencyProperty EncodingProperty =
|
|
DependencyProperty.Register("Encoding", typeof(Encoding), typeof(TextEditor));
|
|
|
|
/// <summary>
|
|
/// Gets/sets the encoding used when the file is saved.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The <see cref="Load(Stream)"/> method autodetects the encoding of the file and sets this property accordingly.
|
|
/// The <see cref="Save(Stream)"/> method uses the encoding specified in this property.
|
|
/// </remarks>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public Encoding Encoding {
|
|
get { return (Encoding)GetValue(EncodingProperty); }
|
|
set { SetValue(EncodingProperty, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves the text to the stream.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method sets <see cref="IsModified"/> to false.
|
|
/// </remarks>
|
|
public void Save(Stream stream)
|
|
{
|
|
if (stream == null)
|
|
throw new ArgumentNullException("stream");
|
|
var encoding = this.Encoding;
|
|
var document = this.Document;
|
|
StreamWriter writer = encoding != null ? new StreamWriter(stream, encoding) : new StreamWriter(stream);
|
|
if (document != null)
|
|
document.WriteTextTo(writer);
|
|
writer.Flush();
|
|
// do not close the stream
|
|
SetCurrentValue(IsModifiedProperty, Boxes.False);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves the text to the file.
|
|
/// </summary>
|
|
public void Save(string fileName)
|
|
{
|
|
if (fileName == null)
|
|
throw new ArgumentNullException("fileName");
|
|
using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None)) {
|
|
Save(fs);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region MouseHover events
|
|
/// <summary>
|
|
/// The PreviewMouseHover event.
|
|
/// </summary>
|
|
public static readonly RoutedEvent PreviewMouseHoverEvent =
|
|
TextView.PreviewMouseHoverEvent.AddOwner(typeof(TextEditor));
|
|
|
|
/// <summary>
|
|
/// The MouseHover event.
|
|
/// </summary>
|
|
public static readonly RoutedEvent MouseHoverEvent =
|
|
TextView.MouseHoverEvent.AddOwner(typeof(TextEditor));
|
|
|
|
|
|
/// <summary>
|
|
/// The PreviewMouseHoverStopped event.
|
|
/// </summary>
|
|
public static readonly RoutedEvent PreviewMouseHoverStoppedEvent =
|
|
TextView.PreviewMouseHoverStoppedEvent.AddOwner(typeof(TextEditor));
|
|
|
|
/// <summary>
|
|
/// The MouseHoverStopped event.
|
|
/// </summary>
|
|
public static readonly RoutedEvent MouseHoverStoppedEvent =
|
|
TextView.MouseHoverStoppedEvent.AddOwner(typeof(TextEditor));
|
|
|
|
|
|
/// <summary>
|
|
/// Occurs when the mouse has hovered over a fixed location for some time.
|
|
/// </summary>
|
|
public event MouseEventHandler PreviewMouseHover {
|
|
add { AddHandler(PreviewMouseHoverEvent, value); }
|
|
remove { RemoveHandler(PreviewMouseHoverEvent, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when the mouse has hovered over a fixed location for some time.
|
|
/// </summary>
|
|
public event MouseEventHandler MouseHover {
|
|
add { AddHandler(MouseHoverEvent, value); }
|
|
remove { RemoveHandler(MouseHoverEvent, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when the mouse had previously hovered but now started moving again.
|
|
/// </summary>
|
|
public event MouseEventHandler PreviewMouseHoverStopped {
|
|
add { AddHandler(PreviewMouseHoverStoppedEvent, value); }
|
|
remove { RemoveHandler(PreviewMouseHoverStoppedEvent, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Occurs when the mouse had previously hovered but now started moving again.
|
|
/// </summary>
|
|
public event MouseEventHandler MouseHoverStopped {
|
|
add { AddHandler(MouseHoverStoppedEvent, value); }
|
|
remove { RemoveHandler(MouseHoverStoppedEvent, value); }
|
|
}
|
|
#endregion
|
|
|
|
#region ScrollBarVisibility
|
|
/// <summary>
|
|
/// Dependency property for <see cref="HorizontalScrollBarVisibility"/>
|
|
/// </summary>
|
|
public static readonly DependencyProperty HorizontalScrollBarVisibilityProperty = ScrollViewer.HorizontalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible));
|
|
|
|
/// <summary>
|
|
/// Gets/Sets the horizontal scroll bar visibility.
|
|
/// </summary>
|
|
public ScrollBarVisibility HorizontalScrollBarVisibility {
|
|
get { return (ScrollBarVisibility)GetValue(HorizontalScrollBarVisibilityProperty); }
|
|
set { SetValue(HorizontalScrollBarVisibilityProperty, value); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Dependency property for <see cref="VerticalScrollBarVisibility"/>
|
|
/// </summary>
|
|
public static readonly DependencyProperty VerticalScrollBarVisibilityProperty = ScrollViewer.VerticalScrollBarVisibilityProperty.AddOwner(typeof(TextEditor), new FrameworkPropertyMetadata(ScrollBarVisibility.Visible));
|
|
|
|
/// <summary>
|
|
/// Gets/Sets the vertical scroll bar visibility.
|
|
/// </summary>
|
|
public ScrollBarVisibility VerticalScrollBarVisibility {
|
|
get { return (ScrollBarVisibility)GetValue(VerticalScrollBarVisibilityProperty); }
|
|
set { SetValue(VerticalScrollBarVisibilityProperty, value); }
|
|
}
|
|
#endregion
|
|
|
|
object IServiceProvider.GetService(Type serviceType)
|
|
{
|
|
return textArea.GetService(serviceType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the text view position from a point inside the editor.
|
|
/// </summary>
|
|
/// <param name="point">The position, relative to top left
|
|
/// corner of TextEditor control</param>
|
|
/// <returns>The text view position, or null if the point is outside the document.</returns>
|
|
public TextViewPosition? GetPositionFromPoint(Point point)
|
|
{
|
|
if (this.Document == null)
|
|
return null;
|
|
TextView textView = this.TextArea.TextView;
|
|
return textView.GetPosition(TranslatePoint(point, textView) + textView.ScrollOffset);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls to the specified line.
|
|
/// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior).
|
|
/// </summary>
|
|
public void ScrollToLine(int line)
|
|
{
|
|
ScrollTo(line, -1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls to the specified line/column.
|
|
/// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior).
|
|
/// </summary>
|
|
public void ScrollTo(int line, int column)
|
|
{
|
|
const double MinimumScrollFraction = 0.3;
|
|
ScrollTo(line, column, VisualYPosition.LineMiddle, null != scrollViewer ? scrollViewer.ViewportHeight / 2 : 0.0, MinimumScrollFraction);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scrolls to the specified line/column.
|
|
/// This method requires that the TextEditor was already assigned a size (WPF layout must have run prior).
|
|
/// </summary>
|
|
/// <param name="line">Line to scroll to.</param>
|
|
/// <param name="column">Column to scroll to (important if wrapping is 'on', and for the horizontal scroll position).</param>
|
|
/// <param name="yPositionMode">The mode how to reference the Y position of the line.</param>
|
|
/// <param name="referencedVerticalViewPortOffset">Offset from the top of the viewport to where the referenced line/column should be positioned.</param>
|
|
/// <param name="minimumScrollFraction">The minimum vertical and/or horizontal scroll offset, expressed as fraction of the height or width of the viewport window, respectively.</param>
|
|
public void ScrollTo(int line, int column, VisualYPosition yPositionMode, double referencedVerticalViewPortOffset, double minimumScrollFraction)
|
|
{
|
|
TextView textView = textArea.TextView;
|
|
TextDocument document = textView.Document;
|
|
if (scrollViewer != null && document != null) {
|
|
if (line < 1)
|
|
line = 1;
|
|
if (line > document.LineCount)
|
|
line = document.LineCount;
|
|
|
|
IScrollInfo scrollInfo = textView;
|
|
if (!scrollInfo.CanHorizontallyScroll) {
|
|
// Word wrap is enabled. Ensure that we have up-to-date info about line height so that we scroll
|
|
// to the correct position.
|
|
// This avoids that the user has to repeat the ScrollTo() call several times when there are very long lines.
|
|
VisualLine vl = textView.GetOrConstructVisualLine(document.GetLineByNumber(line));
|
|
double remainingHeight = referencedVerticalViewPortOffset;
|
|
|
|
while (remainingHeight > 0) {
|
|
DocumentLine prevLine = vl.FirstDocumentLine.PreviousLine;
|
|
if (prevLine == null)
|
|
break;
|
|
vl = textView.GetOrConstructVisualLine(prevLine);
|
|
remainingHeight -= vl.Height;
|
|
}
|
|
}
|
|
|
|
Point p = textArea.TextView.GetVisualPosition(new TextViewPosition(line, Math.Max(1, column)), yPositionMode);
|
|
double verticalPos = p.Y - referencedVerticalViewPortOffset;
|
|
if (Math.Abs(verticalPos - scrollViewer.VerticalOffset) > minimumScrollFraction * scrollViewer.ViewportHeight) {
|
|
scrollViewer.ScrollToVerticalOffset(Math.Max(0, verticalPos));
|
|
}
|
|
if (column > 0) {
|
|
if (p.X > scrollViewer.ViewportWidth - Caret.MinimumDistanceToViewBorder * 2) {
|
|
double horizontalPos = Math.Max(0, p.X - scrollViewer.ViewportWidth / 2);
|
|
if (Math.Abs(horizontalPos - scrollViewer.HorizontalOffset) > minimumScrollFraction * scrollViewer.ViewportWidth) {
|
|
scrollViewer.ScrollToHorizontalOffset(horizontalPos);
|
|
}
|
|
} else {
|
|
scrollViewer.ScrollToHorizontalOffset(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|