//
//
//
//
// $Revision$
//
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Gui;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Indentation;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Bookmarks;
using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Editor;
namespace ICSharpCode.AvalonEdit.AddIn
{
///
/// Integrates AvalonEdit with SharpDevelop.
///
public class CodeEditor : Grid
{
QuickClassBrowser quickClassBrowser;
readonly TextEditor primaryTextEditor;
readonly CodeEditorAdapter primaryTextEditorAdapter;
TextEditor secondaryTextEditor;
CodeEditorAdapter secondaryTextEditorAdapter;
readonly IconBarManager iconBarManager;
readonly TextMarkerService textMarkerService;
public TextEditor PrimaryTextEditor {
get { return primaryTextEditor; }
}
public TextEditor ActiveTextEditor {
get { return primaryTextEditor; }
}
TextDocument document;
public TextDocument Document {
get {
return document;
}
set {
if (document != value) {
document = value;
if (DocumentChanged != null) {
DocumentChanged(this, EventArgs.Empty);
}
}
}
}
public event EventHandler DocumentChanged;
public IDocument DocumentAdapter {
get { return primaryTextEditorAdapter.Document; }
}
public CodeEditorAdapter ActiveTextEditorAdapter {
get { return GetAdapter(this.ActiveTextEditor); }
}
CodeEditorAdapter GetAdapter(TextEditor editor)
{
if (editor == secondaryTextEditor)
return secondaryTextEditorAdapter;
else
return primaryTextEditorAdapter;
}
public IconBarManager IconBarManager {
get { return iconBarManager; }
}
string fileName;
public string FileName {
get { return fileName; }
set {
if (fileName != value) {
fileName = value;
FetchParseInformation();
}
}
}
public void Redraw(ISegment segment, DispatcherPriority priority)
{
primaryTextEditor.TextArea.TextView.Redraw(segment, priority);
if (secondaryTextEditor != null) {
secondaryTextEditor.TextArea.TextView.Redraw(segment, priority);
}
}
public CodeEditor()
{
this.CommandBindings.Add(new CommandBinding(SharpDevelopRoutedCommands.SplitView, OnSplitView));
textMarkerService = new TextMarkerService(this);
iconBarManager = new IconBarManager();
primaryTextEditor = CreateTextEditor();
primaryTextEditorAdapter = (CodeEditorAdapter)primaryTextEditor.TextArea.GetService(typeof(ITextEditor));
Debug.Assert(primaryTextEditorAdapter != null);
this.Document = primaryTextEditor.Document;
primaryTextEditor.SetBinding(TextEditor.DocumentProperty, new Binding("Document") { Source = this });
this.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
this.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
SetRow(primaryTextEditor, 1);
this.Children.Add(primaryTextEditor);
}
protected virtual TextEditor CreateTextEditor()
{
TextEditor textEditor = new TextEditor();
CodeEditorAdapter adapter = new CodeEditorAdapter(this, textEditor);
TextView textView = textEditor.TextArea.TextView;
textView.Services.AddService(typeof(ITextEditor), adapter);
textView.Services.AddService(typeof(CodeEditor), this);
textEditor.Background = Brushes.White;
textEditor.FontFamily = new FontFamily("Consolas");
textEditor.FontSize = 13;
textEditor.TextArea.TextEntered += TextArea_TextInput;
textEditor.MouseHover += textEditor_MouseHover;
textEditor.MouseHoverStopped += textEditor_MouseHoverStopped;
textEditor.TextArea.Caret.PositionChanged += caret_PositionChanged;
textEditor.TextArea.DefaultInputHandler.CommandBindings.Add(
new CommandBinding(CustomCommands.CtrlSpaceCompletion, OnCodeCompletion));
textEditor.TextArea.DefaultInputHandler.CommandBindings.Add(
new CommandBinding(CustomCommands.DeleteLine, OnDeleteLine));
textView.BackgroundRenderers.Add(textMarkerService);
textView.LineTransformers.Add(textMarkerService);
textView.Services.AddService(typeof(ITextMarkerService), textMarkerService);
textView.Services.AddService(typeof(IBookmarkMargin), iconBarManager);
var iconBarMargin = new IconBarMargin(iconBarManager) { TextView = textView };
textEditor.TextArea.LeftMargins.Insert(0, iconBarMargin);
return textEditor;
}
// always use primary text editor for loading/saving
// (the file encoding is stored only there)
public void Load(Stream stream)
{
primaryTextEditor.Load(stream);
}
public void Save(Stream stream)
{
primaryTextEditor.Save(stream);
}
void OnSplitView(object sender, ExecutedRoutedEventArgs e)
{
if (secondaryTextEditor == null) {
// create secondary editor
this.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
secondaryTextEditor = CreateTextEditor();
secondaryTextEditorAdapter = (CodeEditorAdapter)secondaryTextEditor.TextArea.GetService(typeof(ITextEditor));
Debug.Assert(primaryTextEditorAdapter != null);
secondaryTextEditor.SetBinding(TextEditor.DocumentProperty,
new Binding(TextEditor.DocumentProperty.Name) { Source = primaryTextEditor });
secondaryTextEditor.TextArea.SetBinding(TextArea.IndentationStrategyProperty,
new Binding(TextArea.IndentationStrategyProperty.Name) { Source = primaryTextEditor.TextArea });
secondaryTextEditor.SyntaxHighlighting = primaryTextEditor.SyntaxHighlighting;
SetRow(secondaryTextEditor, 2);
this.Children.Add(secondaryTextEditor);
} else {
// remove secondary editor
this.Children.Remove(secondaryTextEditor);
secondaryTextEditor = null;
secondaryTextEditorAdapter = null;
this.RowDefinitions.RemoveAt(this.RowDefinitions.Count - 1);
}
}
void caret_PositionChanged(object sender, EventArgs e)
{
InvalidateQuickClassBrowserCaretPosition();
}
bool quickClassBrowserCaretPositionInvalid;
///
/// Only call 'SelectItemAtCaretPosition' once when the caret position
/// changes multiple times (e.g. running refactoring which causes lots of caret changes).
///
void InvalidateQuickClassBrowserCaretPosition()
{
if (!quickClassBrowserCaretPositionInvalid) {
quickClassBrowserCaretPositionInvalid = true;
Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
new Action(
delegate {
quickClassBrowserCaretPositionInvalid = false;
if (quickClassBrowser != null) {
quickClassBrowser.SelectItemAtCaretPosition(this.ActiveTextEditorAdapter.Caret.Position);
}
}));
}
}
public void JumpTo(int line, int column)
{
// the adapter sets the caret position and takes care of scrolling
this.ActiveTextEditorAdapter.JumpTo(line, column);
this.ActiveTextEditor.Focus();
}
ToolTip toolTip;
void textEditor_MouseHover(object sender, MouseEventArgs e)
{
TextEditor textEditor = (TextEditor)sender;
ToolTipRequestEventArgs args = new ToolTipRequestEventArgs(GetAdapter(textEditor));
var pos = textEditor.GetPositionFromPoint(e.GetPosition(textEditor));
args.InDocument = pos.HasValue;
if (pos.HasValue) {
args.LogicalPosition = AvalonEditDocumentAdapter.ToLocation(pos.Value);
}
ToolTipRequestService.RequestToolTip(args);
if (args.ContentToShow != null) {
if (toolTip == null) {
toolTip = new ToolTip();
toolTip.Closed += toolTip_Closed;
}
toolTip.Content = args.ContentToShow;
toolTip.IsOpen = true;
e.Handled = true;
}
}
void textEditor_MouseHoverStopped(object sender, MouseEventArgs e)
{
if (toolTip != null) {
toolTip.IsOpen = false;
e.Handled = true;
}
}
void toolTip_Closed(object sender, RoutedEventArgs e)
{
toolTip = null;
}
volatile static ReadOnlyCollection codeCompletionBindings;
public static ReadOnlyCollection CodeCompletionBindings {
get {
if (codeCompletionBindings == null) {
codeCompletionBindings = AddInTree.BuildItems("/AddIns/DefaultTextEditor/CodeCompletion", null, false).AsReadOnly();
}
return codeCompletionBindings;
}
}
void TextArea_TextInput(object sender, TextCompositionEventArgs e)
{
// don't start new code completion if there is still a completion window open
if (completionWindow != null)
return;
TextArea textArea = (TextArea)sender;
ITextEditor adapter = (ITextEditor)textArea.GetService(typeof(ITextEditor));
Debug.Assert(adapter != null);
foreach (char c in e.Text) {
foreach (ICodeCompletionBinding cc in CodeCompletionBindings) {
CodeCompletionKeyPressResult result = cc.HandleKeyPress(adapter, c);
if (result == CodeCompletionKeyPressResult.Completed) {
if (completionWindow != null) {
// a new CompletionWindow was shown, but does not eat the input
// tell it to expect the text insertion
completionWindow.ExpectInsertionBeforeStart = true;
}
return;
} else if (result == CodeCompletionKeyPressResult.CompletedIncludeKeyInCompletion) {
if (completionWindow != null) {
if (completionWindow.StartOffset == completionWindow.EndOffset) {
completionWindow.CloseWhenCaretAtBeginning = true;
}
}
return;
} else if (result == CodeCompletionKeyPressResult.EatKey) {
e.Handled = true;
return;
}
}
}
}
void OnCodeCompletion(object sender, ExecutedRoutedEventArgs e)
{
TextEditor textEditor = (TextEditor)sender;
if (completionWindow != null)
completionWindow.Close();
foreach (ICodeCompletionBinding cc in CodeCompletionBindings) {
if (cc.CtrlSpace(GetAdapter(textEditor))) {
e.Handled = true;
break;
}
}
}
void OnDeleteLine(object sender, ExecutedRoutedEventArgs e)
{
TextEditor textEditor = (TextEditor)sender;
e.Handled = true;
using (textEditor.Document.RunUpdate()) {
DocumentLine currentLine = textEditor.Document.GetLineByNumber(textEditor.TextArea.Caret.Line);
textEditor.Select(currentLine.Offset, currentLine.TotalLength);
textEditor.SelectedText = string.Empty;
}
}
CompletionWindow completionWindow;
internal void NotifyCompletionWindowOpened(CompletionWindow window)
{
if (completionWindow != null) {
// if there already is a completion window open, close it
completionWindow.Close();
}
completionWindow = window;
window.Closed += delegate {
completionWindow = null;
};
}
IFormattingStrategy formattingStrategy;
public IFormattingStrategy FormattingStrategy {
get { return formattingStrategy; }
set {
if (formattingStrategy != value) {
formattingStrategy = value;
if (value != null)
primaryTextEditor.TextArea.IndentationStrategy = new IndentationStrategyAdapter(primaryTextEditorAdapter, value);
else
primaryTextEditor.TextArea.IndentationStrategy = new DefaultIndentationStrategy();
// no need to update the secondary text editor - its IndentationStrategy property will
// update using a binding.
}
}
}
public IHighlightingDefinition SyntaxHighlighting {
get { return primaryTextEditor.SyntaxHighlighting; }
set {
primaryTextEditor.SyntaxHighlighting = value;
if (secondaryTextEditor != null) {
secondaryTextEditor.SyntaxHighlighting = value;
}
}
}
void FetchParseInformation()
{
ParseInformationUpdated(ParserService.GetParseInformation(this.FileName));
}
public void ParseInformationUpdated(ParseInformation parseInfo)
{
if (parseInfo != null) {
if (quickClassBrowser == null) {
quickClassBrowser = new QuickClassBrowser();
quickClassBrowser.JumpAction = JumpTo;
SetRow(quickClassBrowser, 0);
this.Children.Add(quickClassBrowser);
}
quickClassBrowser.Update(parseInfo.MostRecentCompilationUnit);
InvalidateQuickClassBrowserCaretPosition();
} else {
if (quickClassBrowser != null) {
this.Children.Remove(quickClassBrowser);
quickClassBrowser = null;
}
}
}
}
}