From 71993014c910924fe0db9881570ede7ce10f421b Mon Sep 17 00:00:00 2001
From: Daniel Grunwald <daniel@danielgrunwald.de>
Date: Tue, 24 Mar 2009 21:28:26 +0000
Subject: [PATCH] AvalonEdit: tooltips.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@3913 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
---
 .../AvalonEdit.AddIn/Src/CodeEditor.cs        |  41 ++++-
 .../Src/SharpDevelopCompletionWindow.cs       |   4 +-
 .../Src/ToolTips/ResourceToolTipProvider.cs   |  24 ++-
 .../CodeCompletion/CompletionList.cs          |   9 ++
 .../CodeCompletion/CompletionWindow.cs        |  46 +++++-
 .../Document/LineNode.cs                      |   3 +
 .../Document/TextAnchor.cs                    |   2 +-
 .../Gui/CaretNavigationCommandHandler.cs      |   1 +
 .../Gui/TextAreaDefaultInputHandlers.cs       |  24 ++-
 .../ICSharpCode.AvalonEdit/Gui/TextEditor.cs  |  72 +++++++++
 .../ICSharpCode.AvalonEdit/Gui/TextView.cs    | 153 ++++++++++++++++++
 .../documentation/Introduction.xml            |   6 +-
 .../documentation/SyntaxHighlighting.xml      |   4 +-
 .../documentation/TextRendering.xml           |  18 ++-
 .../Project/ICSharpCode.SharpDevelop.csproj   |   1 +
 .../Src/Services/Debugger/DebuggerService.cs  | 120 +++-----------
 .../Gui/Editor/ITextAreaToolTipProvider.cs    |  48 +-----
 .../Project/Src/TextEditor/ToolTipService.cs  | 103 ++++++++++++
 18 files changed, 499 insertions(+), 180 deletions(-)
 create mode 100644 src/Main/Base/Project/Src/TextEditor/ToolTipService.cs

diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs
index 25c9d281a7..d4958808c8 100644
--- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs
+++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/CodeEditor.cs
@@ -5,12 +5,16 @@
 //     <version>$Revision$</version>
 // </file>
 
-using ICSharpCode.AvalonEdit.CodeCompletion;
 using System;
 using System.Collections.ObjectModel;
+using System.Windows;
+using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Media;
+
+using ICSharpCode.AvalonEdit.CodeCompletion;
 using ICSharpCode.Core;
+using ICSharpCode.SharpDevelop;
 using ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor;
 
 namespace ICSharpCode.AvalonEdit.AddIn
@@ -30,6 +34,41 @@ namespace ICSharpCode.AvalonEdit.AddIn
 			this.FontFamily = new FontFamily("Consolas");
 			this.FontSize = 13;
 			this.TextArea.TextEntered += TextArea_TextInput;
+			this.MouseHover += CodeEditor_MouseHover;
+			this.MouseHoverStopped += CodeEditor_MouseHoverStopped;
+		}
+		
+		ToolTip toolTip;
+
+		void CodeEditor_MouseHover(object sender, MouseEventArgs e)
+		{
+			ToolTipRequestEventArgs args = new ToolTipRequestEventArgs(textEditorAdapter);
+			var pos = GetPositionFromPoint(e.GetPosition(this));
+			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;
+			}
+		}
+		
+		void CodeEditor_MouseHoverStopped(object sender, MouseEventArgs e)
+		{
+			if (toolTip != null) {
+				toolTip.IsOpen = false;
+			}
+		}
+
+		void toolTip_Closed(object sender, RoutedEventArgs e)
+		{
+			toolTip = null;
 		}
 		
 		volatile static ReadOnlyCollection<ICodeCompletionBinding> codeCompletionBindings;
diff --git a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/SharpDevelopCompletionWindow.cs b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/SharpDevelopCompletionWindow.cs
index 9edf23dac9..357303d0c6 100644
--- a/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/SharpDevelopCompletionWindow.cs
+++ b/src/AddIns/DisplayBindings/AvalonEdit.AddIn/Src/SharpDevelopCompletionWindow.cs
@@ -71,7 +71,9 @@ namespace ICSharpCode.AvalonEdit.AddIn
 		}
 		
 		public object Description {
-			get { return item.Description; }
+			get {
+				return item.Description;
+			}
 		}
 		
 		public ImageSource Image {
diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs
index 257ae4f2ba..e611f632dc 100644
--- a/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs
+++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/ToolTips/ResourceToolTipProvider.cs
@@ -5,13 +5,14 @@
 //     <version>$Revision$</version>
 // </file>
 
-using ICSharpCode.SharpDevelop.Refactoring;
+using ICSharpCode.SharpDevelop.Dom.Refactoring;
 using System;
 using System.Drawing;
 using Hornung.ResourceToolkit.Resolver;
+using ICSharpCode.NRefactory;
+using ICSharpCode.SharpDevelop;
 using ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor;
-using ICSharpCode.TextEditor;
-using ICSharpCode.TextEditor.Document;
+using ICSharpCode.SharpDevelop.Refactoring;
 
 namespace Hornung.ResourceToolkit.ToolTips
 {
@@ -20,22 +21,19 @@ namespace Hornung.ResourceToolkit.ToolTips
 	/// </summary>
 	public class ResourceToolTipProvider : ITextAreaToolTipProvider
 	{
-		
-		public ToolTipInfo GetToolTipInfo(TextArea textArea, ToolTipRequestEventArgs e)
+		public void HandleToolTipRequest(ToolTipRequestEventArgs e)
 		{
-			TextLocation logicPos = e.LogicalPosition;
-			IDocument doc = textArea.Document;
-			if (logicPos.X > doc.GetLineSegment(logicPos.Y).Length - 1) {
-				return null;
+			Location logicPos = e.LogicalPosition;
+			IDocument doc = e.Editor.Document;
+			if (logicPos.X > doc.GetLine(logicPos.Y).Length) {
+				return;
 			}
 			
-			ResourceResolveResult result = ResourceResolverService.Resolve(textArea.MotherTextEditorControl.FileName, new TextEditorDocument(doc), logicPos.Y, logicPos.X, null);
+			ResourceResolveResult result = ResourceResolverService.Resolve(e.Editor.FileName, doc, logicPos.Y - 1, logicPos.X - 1, null);
 			
 			if (result != null && result.ResourceFileContent != null) {
-				return new ToolTipInfo(ResourceResolverService.FormatResourceDescription(result.ResourceFileContent, result.Key));
+				e.ShowToolTip(ResourceResolverService.FormatResourceDescription(result.ResourceFileContent, result.Key));
 			}
-			
-			return null;
 		}
 		
 	}
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs
index 3844497bdb..eff46a9cf3 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionList.cs
@@ -10,6 +10,7 @@ using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
 using System.Windows.Documents;
 using System.Windows.Input;
 
@@ -140,6 +141,14 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
 			}
 		}
 		
+		/// <summary>
+		/// Occurs when the SelectedItem property changes.
+		/// </summary>
+		public event SelectionChangedEventHandler SelectionChanged {
+			add { AddHandler(Selector.SelectionChangedEvent, value); }
+			remove { RemoveHandler(Selector.SelectionChangedEvent, value); }
+		}
+		
 		/// <summary>
 		/// Selects the item that starts with the specified text.
 		/// </summary>
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs
index 3621775b38..cf71707520 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/CodeCompletion/CompletionWindow.cs
@@ -9,6 +9,7 @@ using System;
 using System.Diagnostics;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
 using System.Windows.Documents;
 using System.Windows.Input;
 using System.Windows.Media;
@@ -27,6 +28,7 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
 		int startOffset;
 		int endOffset;
 		readonly CompletionList completionList = new CompletionList();
+		ToolTip toolTip = new ToolTip();
 		
 		/// <summary>
 		/// Creates a new code completion window.
@@ -38,11 +40,41 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
 			this.Width = 200;
 			this.Content = completionList;
 			
+			toolTip.PlacementTarget = this;
+			toolTip.Placement = PlacementMode.Right;
+			toolTip.Closed += toolTip_Closed;
+			
 			startOffset = endOffset = this.TextArea.Caret.Offset;
 			document = textArea.TextView.Document;
 			completionList.InsertionRequested += completionList_InsertionRequested;
+			completionList.SelectionChanged += completionList_SelectionChanged;
 		}
-
+		
+		#region ToolTip handling
+		void toolTip_Closed(object sender, RoutedEventArgs e)
+		{
+			// Clear content after tooltip is closed.
+			// We cannot clear is immediately when setting IsOpen=false
+			// because the tooltip uses an animation for closing.
+			if (toolTip != null)
+				toolTip.Content = null;
+		}
+		
+		void completionList_SelectionChanged(object sender, SelectionChangedEventArgs e)
+		{
+			var item = completionList.SelectedItem;
+			if (item == null)
+				return;
+			object description = item.Description;
+			if (description != null) {
+				toolTip.Content = description;
+				toolTip.IsOpen = true;
+			} else {
+				toolTip.IsOpen = false;
+			}
+		}
+		#endregion
+		
 		void completionList_InsertionRequested(object sender, EventArgs e)
 		{
 			var item = completionList.SelectedItem;
@@ -134,6 +166,16 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
 		}
 		#endregion
 		
+		/// <inheritdoc/>
+		protected override void OnClosed(EventArgs e)
+		{
+			base.OnClosed(e);
+			if (toolTip != null) {
+				toolTip.IsOpen = false;
+				toolTip = null;
+			}
+		}
+		
 		/// <inheritdoc/>
 		protected override void OnKeyDown(KeyEventArgs e)
 		{
@@ -167,6 +209,8 @@ namespace ICSharpCode.AvalonEdit.CodeCompletion
 		/// Gets/sets whether the completion window should expect text insertion at the start offset,
 		/// which not go into the completion region, but before it.
 		/// </summary>
+		/// <remarks>This property allows only a single insertion, it is reset to false
+		/// when that insertion has occurred.</remarks>
 		public bool ExpectInsertionBeforeStart { get; set; }
 		
 		void textArea_Document_Changing(object sender, DocumentChangeEventArgs e)
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineNode.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineNode.cs
index e43eb464c9..c4ed992444 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineNode.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/LineNode.cs
@@ -31,6 +31,9 @@ namespace ICSharpCode.AvalonEdit.Document
 		// apparently the JIT only optimizes for memory when there are at least three small fields.
 		// Currently, DocumentLine takes 40 bytes on x86 (8 byte object overhead, 4 pointers, 3 ints, and another DWORD
 		// for the small fields).
+		// TODO: a possible optimization would be to combine 'totalLength' and the small fields into a single uint.
+		// delimiterSize takes only two bits, the two bools take another two bits; so there's still 
+		// 28 bits left for totalLength. 268435455 characters per line should be enough for everyone :)
 		
 		/// <summary>
 		/// Resets the line to enable its reuse after a document rebuild.
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchor.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchor.cs
index ee46778532..15356e9173 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchor.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextAnchor.cs
@@ -115,7 +115,7 @@ namespace ICSharpCode.AvalonEdit.Document
 		/// </summary>
 		public TextLocation Location {
 			get {
-				return new TextLocation(Line, Column);
+				return document.GetLocation(this.Offset);
 			}
 		}
 		
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/CaretNavigationCommandHandler.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/CaretNavigationCommandHandler.cs
index 5bc31815d6..0b3bb5f077 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/CaretNavigationCommandHandler.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/CaretNavigationCommandHandler.cs
@@ -108,6 +108,7 @@ namespace ICSharpCode.AvalonEdit.Gui
 		{
 			TextArea textArea = GetTextArea(target);
 			if (textArea != null && textArea.Document != null) {
+			args.Handled = true;
 				textArea.Caret.Offset = textArea.Document.TextLength;
 				textArea.Selection = new SimpleSelection(0, textArea.Document.TextLength);
 				textArea.Caret.BringCaretToView();
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextAreaDefaultInputHandlers.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextAreaDefaultInputHandlers.cs
index 17c7252df0..240934101f 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextAreaDefaultInputHandlers.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextAreaDefaultInputHandlers.cs
@@ -57,27 +57,39 @@ namespace ICSharpCode.AvalonEdit.Gui
 		void ExecuteUndo(object sender, ExecutedRoutedEventArgs e)
 		{
 			var undoStack = GetUndoStack();
-			if (undoStack != null && undoStack.CanUndo)
-				undoStack.Undo();
+			if (undoStack != null) {
+				if (undoStack.CanUndo)
+					undoStack.Undo();
+				e.Handled = true;
+			}
 		}
 		
 		void CanExecuteUndo(object sender, CanExecuteRoutedEventArgs e)
 		{
 			var undoStack = GetUndoStack();
-			e.CanExecute = undoStack != null && undoStack.CanUndo;
+			if (undoStack != null) {
+				e.Handled = true;
+				e.CanExecute = undoStack.CanUndo;
+			}
 		}
 		
 		void ExecuteRedo(object sender, ExecutedRoutedEventArgs e)
 		{
 			var undoStack = GetUndoStack();
-			if (undoStack != null && undoStack.CanRedo)
-				undoStack.Redo();
+			if (undoStack != null) {
+				if (undoStack.CanRedo)
+					undoStack.Redo();
+				e.Handled = true;
+			}
 		}
 		
 		void CanExecuteRedo(object sender, CanExecuteRoutedEventArgs e)
 		{
 			var undoStack = GetUndoStack();
-			e.CanExecute = undoStack != null && undoStack.CanRedo;
+			if (undoStack != null) {
+				e.Handled = true;
+				e.CanExecute = undoStack.CanRedo;
+			}
 		}
 		#endregion
 	}
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs
index b1dedb6d70..fdc9ca14be 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextEditor.cs
@@ -731,5 +731,77 @@ namespace ICSharpCode.AvalonEdit
 			}
 		}
 		#endregion
+		
+		/// <summary>
+		/// Gets the text view position from a point inside the editor.
+		/// </summary>
+		/// <param name="p">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 p)
+		{
+			if (this.Document == null)
+				return null;
+			TextView textView = this.TextArea.TextView;
+			return textView.GetPosition(TranslatePoint(p, textView) + textView.ScrollOffset);
+		}
+		
+		/// <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); }
+		}
 	}
 }
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs
index 96c8362b72..a564faa2b2 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Gui/TextView.cs
@@ -1176,6 +1176,8 @@ namespace ICSharpCode.AvalonEdit.Gui
 		/// </summary>
 		public VisualLine GetVisualLineFromVisualTop(double visualTop)
 		{
+			// TODO: change this method to also work outside the visible range -
+			// required to make GetPosition work as expected!
 			EnsureVisualLines();
 			foreach (VisualLine vl in this.VisualLines) {
 				if (visualTop < vl.VisualTop)
@@ -1224,6 +1226,25 @@ namespace ICSharpCode.AvalonEdit.Gui
 			}
 			return visualLine.GetVisualPosition(visualColumn, yPositionMode);
 		}
+		
+		/// <summary>
+		/// Gets the text view position from the specified visual position.
+		/// </summary>
+		/// <param name="visualPosition">The position in WPF device-independent pixels relative
+		/// to the top left corner of the document.</param>
+		/// <returns>The logical position, or null if the position is outside the document.</returns>
+		public TextViewPosition? GetPosition(Point visualPosition)
+		{
+			VerifyAccess();
+			if (this.Document == null)
+				throw new InvalidOperationException("There is no document assigned to the TextView");
+			VisualLine line = GetVisualLineFromVisualTop(visualPosition.Y);
+			if (line == null)
+				return null;
+			int visualColumn = line.GetVisualColumn(visualPosition);
+			int documentOffset = line.GetRelativeOffset(visualColumn) + line.FirstDocumentLine.Offset;
+			return new TextViewPosition(document.GetLocation(documentOffset), visualColumn);
+		}
 		#endregion
 		
 		#region Service Provider
@@ -1256,6 +1277,138 @@ namespace ICSharpCode.AvalonEdit.Gui
 		}
 		#endregion
 		
+		#region MouseHover
+		/// <summary>
+		/// The PreviewMouseHover event.
+		/// </summary>
+		public static readonly RoutedEvent PreviewMouseHoverEvent =
+			EventManager.RegisterRoutedEvent("PreviewMouseHover", RoutingStrategy.Tunnel,
+			                                 typeof(MouseEventHandler), typeof(TextView));
+		/// <summary>
+		/// The MouseHover event.
+		/// </summary>
+		public static readonly RoutedEvent MouseHoverEvent =
+			EventManager.RegisterRoutedEvent("MouseHover", RoutingStrategy.Bubble,
+			                                 typeof(MouseEventHandler), typeof(TextView));
+		
+		/// <summary>
+		/// The PreviewMouseHoverStopped event.
+		/// </summary>
+		public static readonly RoutedEvent PreviewMouseHoverStoppedEvent =
+			EventManager.RegisterRoutedEvent("PreviewMouseHoverStopped", RoutingStrategy.Tunnel,
+			                                 typeof(MouseEventHandler), typeof(TextView));
+		/// <summary>
+		/// The MouseHoverStopped event.
+		/// </summary>
+		public static readonly RoutedEvent MouseHoverStoppedEvent =
+			EventManager.RegisterRoutedEvent("MouseHoverStopped", RoutingStrategy.Bubble,
+			                                 typeof(MouseEventHandler), typeof(TextView));
+		
+		
+		/// <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); }
+		}
+		
+		DispatcherTimer mouseHoverTimer;
+		Point mouseHoverStartPoint;
+		MouseEventArgs mouseHoverLastEventArgs;
+		bool mouseHovering;
+		
+		/// <inheritdoc/>
+		protected override void OnMouseMove(MouseEventArgs e)
+		{
+			base.OnMouseMove(e);
+			Point newPosition = e.GetPosition(this);
+			Vector mouseMovement = mouseHoverStartPoint - newPosition;
+			if (Math.Abs(mouseMovement.X) > SystemParameters.MouseHoverWidth
+			    || Math.Abs(mouseMovement.Y) > SystemParameters.MouseHoverHeight)
+			{
+				StopHovering();
+				mouseHoverStartPoint = newPosition;
+				mouseHoverLastEventArgs = e;
+				mouseHoverTimer = new DispatcherTimer(SystemParameters.MouseHoverTime, DispatcherPriority.Background,
+				                                      OnMouseHoverTimerElapsed, this.Dispatcher);
+				mouseHoverTimer.Start();
+			}
+			// do not set e.Handled - allow others to also handle MouseMove
+		}
+		
+		/// <inheritdoc/>
+		protected override void OnMouseLeave(MouseEventArgs e)
+		{
+			base.OnMouseLeave(e);
+			StopHovering();
+			// do not set e.Handled - allow others to also handle MouseLeave
+		}
+		
+		void StopHovering()
+		{
+			if (mouseHoverTimer != null) {
+				mouseHoverTimer.Stop();
+				mouseHoverTimer = null;
+			}
+			if (mouseHovering) {
+				mouseHovering = false;
+				RaiseHoverEventPair(PreviewMouseHoverStoppedEvent, MouseHoverStoppedEvent);
+			}
+		}
+		
+		void OnMouseHoverTimerElapsed(object sender, EventArgs e)
+		{
+			mouseHoverTimer.Stop();
+			mouseHoverTimer = null;
+			
+			mouseHovering = true;
+			RaiseHoverEventPair(PreviewMouseHoverEvent, MouseHoverEvent);
+		}
+		
+		void RaiseHoverEventPair(RoutedEvent tunnelingEvent, RoutedEvent bubblingEvent)
+		{
+			var mouseDevice = mouseHoverLastEventArgs.MouseDevice;
+			var stylusDevice = mouseHoverLastEventArgs.StylusDevice;
+			int inputTime = Environment.TickCount;
+			var args1 = new MouseEventArgs(mouseDevice, inputTime, stylusDevice) {
+				RoutedEvent = tunnelingEvent,
+				Source = this
+			};
+			RaiseEvent(args1);
+			var args2 = new MouseEventArgs(mouseDevice, inputTime, stylusDevice) {
+				RoutedEvent = bubblingEvent,
+				Source = this,
+				Handled = args1.Handled
+			};
+			RaiseEvent(args2);
+		}
+		#endregion
+		
 		/// <summary>
 		/// Collapses lines for the purpose of scrolling. This method is meant for
 		/// <see cref="VisualLineElementGenerator"/>s that cause <see cref="VisualLine"/>s to span
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/Introduction.xml b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/Introduction.xml
index e3ef6e742f..6521aa0c05 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/Introduction.xml
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/Introduction.xml
@@ -11,10 +11,10 @@
 		<para>While the WPF RichTextBox is quite powerful, you quickly run into its limits
 		when trying to use it as a code editor: it's hard to write efficient syntax highlighting for it,
 		and you cannot really implement features like code folding with the standard RichTextBox.</para>
-		<para>The issue is that in the RichTextBox edits a rich document. In contrast, AvalonEdit
-		just edits text. However, AvalonEdit offers lots of possibilities on how the text document is
+		<para>The issue is simple: the RichTextBox edits a rich document. In contrast, AvalonEdit
+		simply edits text. However, AvalonEdit offers lots of possibilities on how the text document is
 		displayed - so it is much more suitable for a code editor where things like the text color
-		are not controlled by the user, but simply depend on the text (syntax highlighting).</para>
+		are not controlled by the user, but instead depend on the text (syntax highlighting).</para>
 	</introduction>
 	
 	<!-- TODO: screenshot-->
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/SyntaxHighlighting.xml b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/SyntaxHighlighting.xml
index b8f85b7733..42c9f55b5e 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/SyntaxHighlighting.xml
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/SyntaxHighlighting.xml
@@ -9,8 +9,8 @@
 		Text Rendering extension points is the support for "visual line transformers" that
 		can change the display of a visual line after it has been constructed by the "visual element generators".
 		A useful base class implementing IVisualLineTransformer for the purpose of syntax highlighting
-		is DocumentColorizingTransformer. But please take a look at that class' documentation to see
-		how to write fully custom syntax highlighters. This article discusses the XML-driven built-in
+		is DocumentColorizingTransformer. Take a look at that class' documentation to see
+		how to write fully custom syntax highlighters. This article only discusses the XML-driven built-in
 		highlighting engine.
 		</para>
 	</introduction>
diff --git a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/TextRendering.xml b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/TextRendering.xml
index 6e666bb793..124bf61a3c 100644
--- a/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/TextRendering.xml
+++ b/src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/documentation/TextRendering.xml
@@ -22,8 +22,9 @@
 		<content>
 			<para>
 			VisualLines are only created for the visible part of the document.
-			This usually happens in the MeasureOverride of TextView:
-			when the TextView is measured, it uses the height tree to determine the first
+			Lots of actions can trigger their creation, but most commonly the creation will be
+			caused by the MeasureOverride method of TextView.
+			When the TextView is measured, it uses the height tree to determine the first
 			document line in the visible region. Then, it constructs and measures a VisualLine
 			for that first line, and repeats that with the following lines
 			until the visible region is filled.
@@ -52,10 +53,10 @@
 			one redraw for multiple input actions.
 			</para>
 			<para>
-			ALERT: this means you need to ensure that if you access VisualLine, you can cope
-			with the case that the VisualLines you're interested in are not available.
+			ALERT: this means that if you access VisualLine, you must be able to handle
+			the case that your requested VisualLines are not available.
 			You can use GetOrConstructVisualLine to make the text editor re-build the visual line
-			if it does not exist anymore; or you can call EnsureVisualLines() to make the text
+			if it does not exist anymore; or you can call EnsureVisualLines() to make the text view
 			create all VisualLines in the visible region.
 			</para>
 		</content>
@@ -68,6 +69,13 @@
 			room in between the elements returned from the generators is filled with text elements.
 			Then, the VisualLine assigns the VisualColumn and RelativeTextOffset properties of the line elements.
 			</para>
+			<para>
+			For example, a line contains the text "Hello, World". 
+			The user has enabled "ShowSpaces", so the text editor should show a little dot instead of the space.
+			In this case, the SingleCharacterElementGenerator, which is responsible for ShowSpaces, will produce
+			a "SpaceTextElement" for the space character. Because no other generators are interested in the line,
+			the remaining strings "Hello," and "World" will be represented by VisualLineText elements.
+			</para>
 		</content>
 	</section>
 	<section>
diff --git a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
index 1224ae5de7..d58c00d46e 100644
--- a/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
+++ b/src/Main/Base/Project/ICSharpCode.SharpDevelop.csproj
@@ -558,6 +558,7 @@
     <Compile Include="Src\TextEditor\ICompletionItem.cs" />
     <Compile Include="Src\TextEditor\ICompletionItemList.cs" />
     <Compile Include="Src\TextEditor\ITextEditor.cs" />
+    <Compile Include="Src\TextEditor\ToolTipService.cs" />
     <Compile Include="Src\TextEditor\XmlFormattingStrategy.cs" />
     <Compile Include="Src\Services\Tasks\TaskEventHandler.cs" />
     <Compile Include="Src\TextEditor\Commands\NavigationCommands.cs" />
diff --git a/src/Main/Base/Project/Src/Services/Debugger/DebuggerService.cs b/src/Main/Base/Project/Src/Services/Debugger/DebuggerService.cs
index b00f742831..07a6e14cd5 100644
--- a/src/Main/Base/Project/Src/Services/Debugger/DebuggerService.cs
+++ b/src/Main/Base/Project/Src/Services/Debugger/DebuggerService.cs
@@ -5,13 +5,13 @@
 //     <version>$Revision$</version>
 // </file>
 
+using ICSharpCode.NRefactory;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
 using System.Drawing;
 using System.Text;
 using System.Windows.Forms;
-
 using ICSharpCode.Core;
 using ICSharpCode.SharpDevelop.Dom;
 using ICSharpCode.SharpDevelop.Gui;
@@ -21,7 +21,6 @@ using ICSharpCode.TextEditor.Document;
 using BM = ICSharpCode.SharpDevelop.Bookmarks;
 using ITextAreaToolTipProvider = ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.ITextAreaToolTipProvider;
 using ITextEditorControlProvider = ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.ITextEditorControlProvider;
-using ToolTipInfo = ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor.ToolTipInfo;
 
 namespace ICSharpCode.SharpDevelop.Debugging
 {
@@ -37,7 +36,6 @@ namespace ICSharpCode.SharpDevelop.Debugging
 				ClearDebugMessages();
 			};
 			
-			WorkbenchSingleton.WorkbenchCreated += new EventHandler(WorkspaceCreated);
 			BM.BookmarkManager.Added   += BookmarkAdded;
 			BM.BookmarkManager.Removed += BookmarkRemoved;
 		}
@@ -240,33 +238,13 @@ namespace ICSharpCode.SharpDevelop.Debugging
 			}
 		}
 		
-		static void WorkspaceCreated(object sender, EventArgs args)
-		{
-			WorkbenchSingleton.Workbench.ViewOpened += new ViewContentEventHandler(ViewContentOpened);
-			WorkbenchSingleton.Workbench.ViewClosed += new ViewContentEventHandler(ViewContentClosed);
-		}
-		
+		/* TODO: reimplement this stuff
 		static void ViewContentOpened(object sender, ViewContentEventArgs e)
 		{
-			if (e.Content is ITextEditorControlProvider) {
-				TextArea textArea = ((ITextEditorControlProvider)e.Content).TextEditorControl.ActiveTextAreaControl.TextArea;
-				
 				textArea.IconBarMargin.MouseDown += IconBarMouseDown;
 				textArea.ToolTipRequest          += TextAreaToolTipRequest;
 				textArea.MouseLeave              += TextAreaMouseLeave;
-			}
-		}
-		
-		static void ViewContentClosed(object sender, ViewContentEventArgs e)
-		{
-			if (e.Content is ITextEditorControlProvider) {
-				TextArea textArea = ((ITextEditorControlProvider)e.Content).TextEditorControl.ActiveTextAreaControl.TextArea;
-				
-				textArea.IconBarMargin.MouseDown -= IconBarMouseDown;
-				textArea.ToolTipRequest          -= TextAreaToolTipRequest;
-				textArea.MouseLeave              -= TextAreaMouseLeave;
-			}
-		}
+		}*/
 		
 		public static void RemoveCurrentLineMarker()
 		{
@@ -292,59 +270,8 @@ namespace ICSharpCode.SharpDevelop.Debugging
 		}
 		
 		#region Tool tips
-		const string ToolTipProviderAddInTreePath = "/SharpDevelop/ViewContent/DefaultTextEditor/ToolTips";
-		
 		static DebuggerGridControl oldToolTipControl;
 		
-		/// <summary>
-		/// This function shows variable values as tooltips
-		/// </summary>
-		static void TextAreaToolTipRequest(object sender, ToolTipRequestEventArgs e)
-		{
-			DebuggerGridControl toolTipControl = null;
-			try {
-				TextArea textArea = (TextArea)sender;
-				if (e.ToolTipShown) return;
-				if (oldToolTipControl != null && !oldToolTipControl.AllowClose) return;
-				if (!CodeCompletionOptions.EnableCodeCompletion) return;
-				if (!CodeCompletionOptions.TooltipsEnabled) return;
-				
-				if (CodeCompletionOptions.TooltipsOnlyWhenDebugging) {
-					if (currentDebugger == null) return;
-					if (!currentDebugger.IsDebugging) return;
-				}
-				
-				if (e.InDocument) {
-					// Query all registered tooltip providers using the AddInTree.
-					// The first one that does not return null will be used.
-					ToolTipInfo ti = null;
-					foreach (ITextAreaToolTipProvider toolTipProvider in AddInTree.BuildItems<ITextAreaToolTipProvider>(ToolTipProviderAddInTreePath, null, false)) {
-						if ((ti = toolTipProvider.GetToolTipInfo(textArea, e)) != null) {
-							break;
-						}
-					}
-					
-					if (ti != null) {
-						toolTipControl = ti.ToolTipControl as DebuggerGridControl;
-						if (ti.ToolTipText != null) {
-							e.ShowToolTip(ti.ToolTipText);
-						}
-					}
-					CloseOldToolTip();
-					if (toolTipControl != null) {
-						toolTipControl.ShowForm(textArea, e.LogicalPosition);
-					}
-					oldToolTipControl = toolTipControl;
-					
-				}
-			} catch (Exception ex) {
-				ICSharpCode.Core.MessageService.ShowError(ex, "Error while requesting tooltip for location " + e.LogicalPosition);
-			} finally {
-				if (toolTipControl == null && CanCloseOldToolTip)
-					CloseOldToolTip();
-			}
-		}
-		
 		static bool CanCloseOldToolTip {
 			get {
 				return oldToolTipControl != null && oldToolTipControl.AllowClose;
@@ -360,35 +287,28 @@ namespace ICSharpCode.SharpDevelop.Debugging
 			}
 		}
 		
-		static void TextAreaMouseLeave(object source, EventArgs e)
-		{
-			if (CanCloseOldToolTip && !oldToolTipControl.IsMouseOver)
-				CloseOldToolTip();
-		}
-		
 		/// <summary>
 		/// Gets debugger tooltip information for the specified position.
 		/// A descriptive text for the element or a DebuggerGridControl
 		/// showing its current value (when in debugging mode) can be returned
 		/// through the ToolTipInfo object.
-		/// Returns <c>null</c>, if no tooltip information is available.
 		/// </summary>
-		internal static ToolTipInfo GetToolTipInfo(TextArea textArea, ToolTipRequestEventArgs e)
+		internal static void HandleToolTipRequest(ToolTipRequestEventArgs e)
 		{
-			TextLocation logicPos = e.LogicalPosition;
-			IDocument doc = textArea.Document;
-			IExpressionFinder expressionFinder = ParserService.GetExpressionFinder(textArea.MotherTextEditorControl.FileName);
+			Location logicPos = e.LogicalPosition;
+			var doc = e.Editor.Document;
+			IExpressionFinder expressionFinder = ParserService.GetExpressionFinder(e.Editor.FileName);
 			if (expressionFinder == null)
-				return null;
-			LineSegment seg = doc.GetLineSegment(logicPos.Y);
-			if (logicPos.X > seg.Length - 1)
-				return null;
-			string textContent = doc.TextContent;
-			ExpressionResult expressionResult = expressionFinder.FindFullExpression(textContent, seg.Offset + logicPos.X);
+				return;
+			var currentLine = doc.GetLine(logicPos.Y);
+			if (logicPos.X > currentLine.Length)
+				return;
+			string textContent = doc.Text;
+			ExpressionResult expressionResult = expressionFinder.FindFullExpression(textContent, doc.PositionToOffset(logicPos.Line, logicPos.Column));
 			string expression = (expressionResult.Expression ?? "").Trim();
 			if (expression.Length > 0) {
 				// Look if it is variable
-				ResolveResult result = ParserService.Resolve(expressionResult, logicPos.Y + 1, logicPos.X + 1, textArea.MotherTextEditorControl.FileName, textContent);
+				ResolveResult result = ParserService.Resolve(expressionResult, logicPos.Y, logicPos.X, e.Editor.FileName, textContent);
 				bool debuggerCanShowValue;
 				string toolTipText = GetText(result, expression, out debuggerCanShowValue);
 				if (Control.ModifierKeys == Keys.Control) {
@@ -397,18 +317,18 @@ namespace ICSharpCode.SharpDevelop.Debugging
 				}
 				if (toolTipText != null) {
 					if (debuggerCanShowValue && currentDebugger != null) {
-						return new ToolTipInfo(currentDebugger.GetTooltipControl(expressionResult.Expression));
+						e.ShowToolTip(currentDebugger.GetTooltipControl(expressionResult.Expression));
+					} else {
+						e.ShowToolTip(toolTipText);
 					}
-					return new ToolTipInfo(toolTipText);
 				}
 			} else {
 				#if DEBUG
 				if (Control.ModifierKeys == Keys.Control) {
-					return new ToolTipInfo("no expr: " + expressionResult.ToString());
+					e.ShowToolTip("no expr: " + expressionResult.ToString());
 				}
 				#endif
 			}
-			return null;
 		}
 		
 		static string GetText(ResolveResult result, string expression, out bool debuggerCanShowValue)
@@ -523,9 +443,9 @@ namespace ICSharpCode.SharpDevelop.Debugging
 	/// </remarks>
 	public class DebuggerTextAreaToolTipProvider : ITextAreaToolTipProvider
 	{
-		public ToolTipInfo GetToolTipInfo(TextArea textArea, ToolTipRequestEventArgs e)
+		public void HandleToolTipRequest(ToolTipRequestEventArgs e)
 		{
-			return DebuggerService.GetToolTipInfo(textArea, e);
+			DebuggerService.HandleToolTipRequest(e);
 		}
 	}
 }
diff --git a/src/Main/Base/Project/Src/TextEditor/Gui/Editor/ITextAreaToolTipProvider.cs b/src/Main/Base/Project/Src/TextEditor/Gui/Editor/ITextAreaToolTipProvider.cs
index 2356917e16..74adb56087 100644
--- a/src/Main/Base/Project/Src/TextEditor/Gui/Editor/ITextAreaToolTipProvider.cs
+++ b/src/Main/Base/Project/Src/TextEditor/Gui/Editor/ITextAreaToolTipProvider.cs
@@ -22,52 +22,6 @@ namespace ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor
 		/// if available.
 		/// </summary>
 		/// <returns><c>null</c>, if no tooltip information is available at this position, otherwise a ToolTipInfo object containing the tooltip information to be displayed.</returns>
-		ToolTipInfo GetToolTipInfo(TextArea textArea, ToolTipRequestEventArgs e);
-	}
-	
-	/// <summary>
-	/// Contains information about a tooltip to be shown on the text area.
-	/// </summary>
-	public class ToolTipInfo
-	{
-		object toolTipObject;
-		
-		/// <summary>
-		/// Gets the tool tip text to be displayed.
-		/// May be <c>null</c>.
-		/// </summary>
-		public string ToolTipText {
-			get {
-				return this.toolTipObject as string;
-			}
-		}
-		
-		/// <summary>
-		/// Gets the DebuggerGridControl to be shown as tooltip.
-		/// May be <c>null</c>.
-		/// </summary>
-		public Control ToolTipControl {
-			get {
-				return this.toolTipObject as Control;
-			}
-		}
-		
-		/// <summary>
-		/// Initializes a new instance of the <see cref="ToolTipInfo"/> class.
-		/// </summary>
-		/// <param name="toolTipText">The tooltip text to be displayed.</param>
-		public ToolTipInfo(string toolTipText)
-		{
-			this.toolTipObject = toolTipText;
-		}
-		
-		/// <summary>
-		/// Initializes a new instance of the <see cref="ToolTipInfo"/> class.
-		/// </summary>
-		/// <param name="toolTipControl">The DebuggerGridControl to be shown as tooltip.</param>
-		public ToolTipInfo(Control toolTipControl)
-		{
-			this.toolTipObject = toolTipControl;
-		}
+		void HandleToolTipRequest(ToolTipRequestEventArgs e);
 	}
 }
diff --git a/src/Main/Base/Project/Src/TextEditor/ToolTipService.cs b/src/Main/Base/Project/Src/TextEditor/ToolTipService.cs
new file mode 100644
index 0000000000..4f40f45941
--- /dev/null
+++ b/src/Main/Base/Project/Src/TextEditor/ToolTipService.cs
@@ -0,0 +1,103 @@
+// <file>
+//     <copyright see="prj:///doc/copyright.txt"/>
+//     <license see="prj:///doc/license.txt"/>
+//     <author name="Daniel Grunwald"/>
+//     <version>$Revision$</version>
+// </file>
+
+using ICSharpCode.SharpDevelop.Debugging;
+using System;
+using ICSharpCode.Core;
+using ICSharpCode.NRefactory;
+using ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor;
+
+namespace ICSharpCode.SharpDevelop
+{
+	/// <summary>
+	/// Static class for the ToolTipRequested event.
+	/// </summary>
+	public static class ToolTipRequestService
+	{
+		const string ToolTipProviderAddInTreePath = "/SharpDevelop/ViewContent/DefaultTextEditor/ToolTips";
+		
+		/// <summary>
+		/// This event occurs on a tool tip request,
+		/// after any <see cref="ITextAreaToolTipProvider"/> registered in the addintree have run.
+		/// This event is still raised if AddIns handled it, so please check the Handled property.
+		/// </summary>
+		public static event EventHandler<ToolTipRequestEventArgs> ToolTipRequested;
+		
+		public static void RequestToolTip(ToolTipRequestEventArgs e)
+		{
+			if (e == null)
+				throw new ArgumentNullException("e");
+			
+			if (!CodeCompletionOptions.EnableCodeCompletion) return;
+			if (!CodeCompletionOptions.TooltipsEnabled) return;
+			
+			if (CodeCompletionOptions.TooltipsOnlyWhenDebugging) {
+				if (!DebuggerService.IsDebuggerLoaded) return;
+				if (!DebuggerService.CurrentDebugger.IsDebugging) return;
+			}
+			
+			// Query all registered tooltip providers using the AddInTree.
+			// The first one that does not return null will be used.
+			foreach (ITextAreaToolTipProvider toolTipProvider in AddInTree.BuildItems<ITextAreaToolTipProvider>(ToolTipProviderAddInTreePath, null, false)) {
+				toolTipProvider.HandleToolTipRequest(e);
+				if (e.Handled)
+					break;
+			}
+			
+			EventHandler<ToolTipRequestEventArgs> eh = ToolTipRequested;
+			if (eh != null)
+				eh(null, e);
+		}
+	}
+	
+	public class ToolTipRequestEventArgs : EventArgs
+	{
+		/// <summary>
+		/// Gets whether the tool tip request was handled.
+		/// </summary>
+		public bool Handled { get; set; }
+		
+		/// <summary>
+		/// Gets the editor causing the request.
+		/// </summary>
+		public ITextEditor Editor { get; private set; }
+		
+		/// <summary>
+		/// Gets whether the mouse was inside the document bounds.
+		/// </summary>
+		public bool InDocument { get; set; }
+		
+		/// <summary>
+		/// The mouse position, in document coordinates.
+		/// </summary>
+		public Location LogicalPosition { get; set; }
+		
+		/// <summary>
+		/// Gets/Sets the content to show as a tooltip.
+		/// </summary>
+		public object ContentToShow { get; set; }
+		
+		/// <summary>
+		/// Shows the tool tip.
+		/// </summary>
+		public void ShowToolTip(object content)
+		{
+			if (content == null)
+				throw new ArgumentNullException("content");
+			this.Handled = true;
+			this.ContentToShow = content;
+		}
+		
+		public ToolTipRequestEventArgs(ITextEditor editor)
+		{
+			if (editor == null)
+				throw new ArgumentNullException("editor");
+			this.Editor = editor;
+			this.InDocument = true;
+		}
+	}
+}