Welcome to mirror list, hosted at ThFree Co, Russian Federation.

DebuggerQuickInfoSource.cs « QuickInfo « MonoDevelop.Debugger.VSTextView « MonoDevelop.Debugger « addins « src « main - github.com/mono/monodevelop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 50e798271223b516daeaddd8e756ca3bbaa7b5db (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using MonoDevelop.Core;
using Microsoft.VisualStudio.Text.Editor;
using Gtk;
using MonoDevelop.Ide.Gui.Documents;

namespace MonoDevelop.Debugger.VSTextView.QuickInfo
{
	class DebuggerQuickInfoSource : IAsyncQuickInfoSource
	{
		readonly DebuggerQuickInfoSourceProvider provider;
		readonly ITextBuffer textBuffer;
		DebugValueWindow window;
		ITextView lastView;
		DocumentView lastDocumentView;

		public DebuggerQuickInfoSource (DebuggerQuickInfoSourceProvider provider, ITextBuffer textBuffer)
		{
			this.provider = provider;
			this.textBuffer = textBuffer;
			DebuggingService.CurrentFrameChanged += CurrentFrameChanged;
			DebuggingService.StoppedEvent += TargetProcessExited;

		}

		void CurrentFrameChanged (object sender, EventArgs e)
		{
			if (window != null) {
				DestroyWindow ();
			}
		}

		void TargetProcessExited (object sender, EventArgs e)
		{
			if (window == null)
				return;

			var debuggerSession = window.GetDebuggerSession ();
			if (debuggerSession == null || debuggerSession == sender) {
				DestroyWindow ();
			}
		}

		public void Dispose ()
		{
			DebuggingService.CurrentFrameChanged -= CurrentFrameChanged;
			DebuggingService.StoppedEvent -= TargetProcessExited;
			Runtime.RunInMainThread (DestroyWindow).Ignore ();
		}

		static async Task<bool> WaitOneAsync (WaitHandle handle, CancellationToken cancellationToken)
		{
			RegisteredWaitHandle registeredHandle = null;
			var tokenRegistration = default (CancellationTokenRegistration);
			try {
				var tcs = new TaskCompletionSource<bool> ();
				registeredHandle = ThreadPool.RegisterWaitForSingleObject (
					handle,
					(state, timedOut) => ((TaskCompletionSource<bool>)state).TrySetResult (!timedOut),
					tcs,
					int.MaxValue,
					true);
				tokenRegistration = cancellationToken.Register (
					state => ((TaskCompletionSource<bool>)state).TrySetCanceled (),
					tcs);
				return await tcs.Task;
			} finally {
				if (registeredHandle != null)
					registeredHandle.Unregister (null);
				tokenRegistration.Dispose ();
			}
		}

		public async Task<QuickInfoItem> GetQuickInfoItemAsync (IAsyncQuickInfoSession session, CancellationToken cancellationToken)
		{
			if (DebuggingService.CurrentFrame == null)
				return null;
			if (window != null)
				await Runtime.RunInMainThread (DestroyWindow);
			var view = session.TextView;

			var textViewLines = view.TextViewLines;
			var snapshot = textViewLines.FormattedSpan.Snapshot;
			var triggerPoint = session.GetTriggerPoint (textBuffer);
			var point = triggerPoint.GetPoint (snapshot);

			foreach (var debugInfoProvider in provider.debugInfoProviders) {
				var debugInfo = await debugInfoProvider.Value.GetDebugInfoAsync (point, cancellationToken);
				if (debugInfo.Text == null) {
					continue;
				}

				var options = DebuggingService.DebuggerSession.EvaluationOptions.Clone ();
				options.AllowMethodEvaluation = true;
				options.AllowTargetInvoke = true;

				var val = DebuggingService.CurrentFrame.GetExpressionValue (debugInfo.Text, options);

				if (val.IsEvaluating)
					await WaitOneAsync (val.WaitHandle, cancellationToken);
				if (cancellationToken.IsCancellationRequested)
					return null;
				if (val == null || val.IsUnknown || val.IsNotSupported || val.IsError)
					return null;

				if (!view.Properties.TryGetProperty (typeof (Gtk.Widget), out Gtk.Widget gtkParent))
					return null;
				provider.textDocumentFactoryService.TryGetTextDocument (textBuffer, out var textDocument);

				// This is a bit hacky, since AsyncQuickInfo is designed to display multiple elements if multiple sources
				// return value, we don't want that for debugger value hovering, hence we dismiss AsyncQuickInfo
				// and do our own thing, notice VS does same thing
				await session.DismissAsync ();
				await provider.joinableTaskContext.Factory.SwitchToMainThreadAsync ();
				this.lastView = view;
				val.Name = debugInfo.Text;
				window = new DebugValueWindow ((Gtk.Window)gtkParent.Toplevel, textDocument?.FilePath, textBuffer.CurrentSnapshot.GetLineNumberFromPosition (debugInfo.Span.GetStartPoint (textBuffer.CurrentSnapshot)), DebuggingService.CurrentFrame, val, null);
				Ide.IdeApp.CommandService.RegisterTopWindow (window);
				var bounds = view.TextViewLines.GetCharacterBounds (point);
				view.LayoutChanged += LayoutChanged;
#if CLOSE_ON_FOCUS_LOST
				view.LostAggregateFocus += View_LostAggregateFocus;
#endif
				RegisterForHiddenAsync (view).Ignore ();
				window.LeaveNotifyEvent += LeaveNotifyEvent;
#if MAC
				var cocoaView = ((ICocoaTextView)view);
				var cgPoint = cocoaView.VisualElement.ConvertPointToView (new CoreGraphics.CGPoint (bounds.Left - view.ViewportLeft, bounds.Top - view.ViewportTop), cocoaView.VisualElement.Superview);
				cgPoint.Y = cocoaView.VisualElement.Superview.Frame.Height - cgPoint.Y;
				window.ShowPopup (gtkParent, new Gdk.Rectangle ((int)cgPoint.X, (int)cgPoint.Y, (int)bounds.Width, (int)bounds.Height), Components.PopupPosition.TopLeft);
#else
				throw new NotImplementedException ();
#endif
				return null;
			}
			return null;
		}

		private async Task RegisterForHiddenAsync (ITextView view)
		{
			if (view.Properties.TryGetProperty<FileDocumentController> (typeof (DocumentController), out var documentController)) {
				lastDocumentView = await documentController.GetDocumentView ();
				lastDocumentView.ContentHidden += DocumentView_ContentHidden;
			}
		}

		private void DocumentView_ContentHidden (object sender, EventArgs e)
		{
			DestroyWindow ();
		}
#if CLOSE_ON_FOCUS_LOST
		private void View_LostAggregateFocus (object sender, EventArgs e)
		{
#if MAC
			var nsWindow = MacInterop.GtkQuartz.GetWindow (window);
			if (nsWindow == AppKit.NSApplication.SharedApplication.KeyWindow)
				return;
			DestroyWindow ();
#else
			throw new NotImplementedException ();
#endif
		}
#endif
		private void LayoutChanged (object sender, TextViewLayoutChangedEventArgs e)
		{
			if (e.OldViewState.ViewportLeft != e.NewViewState.ViewportLeft ||
				e.OldViewState.ViewportWidth != e.NewViewState.ViewportWidth ||
				e.OldViewState.ViewportTop != e.NewViewState.ViewportTop ||
				e.OldViewState.ViewportHeight != e.NewViewState.ViewportHeight)
				DestroyWindow ();
		}

		private void LeaveNotifyEvent (object o, LeaveNotifyEventArgs args)
		{
			if(args.Event.Detail != Gdk.NotifyType.Nonlinear)
				return;
			DestroyWindow ();
		}

		void DestroyWindow ()
		{
			Runtime.AssertMainThread ();
			if (window != null) {
				window.Destroy ();
				window.LeaveNotifyEvent -= LeaveNotifyEvent;
				window = null;
			}
			if (lastView != null) {
				lastView.LayoutChanged -= LayoutChanged;
#if CLOSE_ON_FOCUS_LOST
				lastView.LostAggregateFocus -= View_LostAggregateFocus;
#endif
				lastView = null;
			}
			if (lastDocumentView != null) {
				lastDocumentView.ContentHidden -= DocumentView_ContentHidden;
				lastDocumentView = null;
			}
		}
	}
}