Skip to content
This repository was archived by the owner on Oct 4, 2021. It is now read-only.

Commit 554ce92

Browse files
authored
Merge pull request #9166 from mono/jstedfast-debugger-expression-code-completion
[Debugger] Implemented code-completion for the new MacObjectValueTree…
2 parents a1995a3 + b4a3753 commit 554ce92

File tree

8 files changed

+544
-9
lines changed

8 files changed

+544
-9
lines changed

main/src/addins/MonoDevelop.Debugger/MonoDevelop.Debugger.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,8 @@
208208
<Compile Include="MonoDevelop.Debugger\ObjectValue\Mac\MacDebuggerObjectPinView.cs" />
209209
<Compile Include="MonoDevelop.Debugger\ObjectValue\ExpressionEventArgs.cs" />
210210
<Compile Include="MonoDevelop.Debugger\ObjectValue\Mac\MacDebuggerTextField.cs" />
211+
<Compile Include="MonoDevelop.Debugger\DebuggerAsyncCompletionSource.cs" />
212+
<Compile Include="MonoDevelop.Debugger\DebuggerCompletionCommandHandler.cs" />
211213
</ItemGroup>
212214
<ItemGroup Condition="$(OS) != 'Windows_NT'">
213215
<Compile Include="MonoDevelop.Debugger.VSTextView\ExceptionCaught\ExceptionCaughtProvider.cs" />
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
//
2+
// DebuggerAsyncCompletionSource.cs
3+
//
4+
// Author:
5+
// David Karlas <[email protected]>
6+
//
7+
// Copyright (c) 2019 Microsoft Corp.
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in
17+
// all copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
// THE SOFTWARE.
26+
27+
using System;
28+
using System.Threading;
29+
using System.Threading.Tasks;
30+
using System.Collections.Generic;
31+
using System.Collections.Immutable;
32+
using System.ComponentModel.Composition;
33+
34+
using Microsoft.VisualStudio.Text;
35+
using Microsoft.VisualStudio.Utilities;
36+
using Microsoft.VisualStudio.Text.Editor;
37+
using Microsoft.VisualStudio.Text.Adornments;
38+
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
39+
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data;
40+
41+
using ObjectValueFlags = Mono.Debugging.Client.ObjectValueFlags;
42+
43+
namespace MonoDevelop.Debugger
44+
{
45+
static class DebuggerCompletion
46+
{
47+
public const string ContentType = "DebuggerCompletion";
48+
}
49+
50+
[Export (typeof (IAsyncCompletionSourceProvider))]
51+
[Name ("Debugger Completion Source Provider")]
52+
[ContentType (DebuggerCompletion.ContentType)]
53+
sealed class DebuggerAsyncCompletionSourceProvider : IAsyncCompletionSourceProvider
54+
{
55+
public IAsyncCompletionSource GetOrCreate (ITextView textView)
56+
{
57+
return new DebuggerAsyncCompletionSource ();
58+
}
59+
}
60+
61+
sealed class DebuggerAsyncCompletionSource : IAsyncCompletionSource
62+
{
63+
static readonly Task<object> EmptyDescription = Task.FromResult<object> (null);
64+
65+
public async Task<CompletionContext> GetCompletionContextAsync (IAsyncCompletionSession session, CompletionTrigger trigger, SnapshotPoint triggerLocation, SnapshotSpan applicableToSpan, CancellationToken token)
66+
{
67+
var text = triggerLocation.Snapshot.GetText (0, triggerLocation.Position);
68+
var data = await DebuggingService.GetCompletionDataAsync (DebuggingService.CurrentFrame, text, token);
69+
70+
if (data == null)
71+
return new CompletionContext (ImmutableArray<CompletionItem>.Empty);
72+
73+
var builder = ImmutableArray.CreateBuilder<CompletionItem> (data.Items.Count);
74+
75+
foreach (var item in data.Items) {
76+
var image = new ImageElement (ObjectValueTreeViewController.GetImageId (item.Flags));
77+
78+
builder.Add (new CompletionItem (item.Name, this, image));
79+
}
80+
81+
return new CompletionContext (builder.MoveToImmutable ());
82+
}
83+
84+
public Task<object> GetDescriptionAsync (IAsyncCompletionSession session, CompletionItem item, CancellationToken token)
85+
{
86+
return EmptyDescription;
87+
}
88+
89+
public CompletionStartData InitializeCompletion (CompletionTrigger trigger, SnapshotPoint triggerLocation, CancellationToken token)
90+
{
91+
switch (trigger.Character) {
92+
case '.': // we want member completion for this
93+
case '<': // we want type completion for this
94+
case '(': // we want parameter completion for this
95+
break;
96+
default:
97+
if (!char.IsLetterOrDigit (trigger.Character))
98+
return new CompletionStartData (CompletionParticipation.DoesNotProvideItems);
99+
break;
100+
}
101+
102+
var text = triggerLocation.Snapshot.GetText (0, triggerLocation.Position);
103+
104+
if (IsInsideQuotedString (text, triggerLocation.Position))
105+
return new CompletionStartData (CompletionParticipation.DoesNotProvideItems);
106+
107+
var span = GetWordSpan (text, triggerLocation.Position);
108+
109+
return new CompletionStartData (CompletionParticipation.ProvidesItems, new SnapshotSpan (triggerLocation.Snapshot, span));
110+
}
111+
112+
static bool IsInsideQuotedString (string text, int position)
113+
{
114+
bool quoted = false;
115+
int index = 0;
116+
117+
do {
118+
while (index < position && text[index] != '"')
119+
index++;
120+
121+
if (index == position)
122+
break;
123+
124+
if (index > 0 && text[index - 1] == '\'') {
125+
// char literal
126+
index++;
127+
} else {
128+
// quoted string
129+
var literal = index > 0 && text[index - 1] == '@';
130+
var escaped = false;
131+
quoted = true;
132+
index++;
133+
134+
while (index < position) {
135+
if (text[index] == '\\') {
136+
escaped = !escaped;
137+
} else if (text[index] == '"') {
138+
if (escaped) {
139+
escaped = false;
140+
} else {
141+
quoted = false;
142+
index++;
143+
144+
if (literal && index < position && text[index] == '"') {
145+
quoted = true;
146+
} else {
147+
break;
148+
}
149+
}
150+
}
151+
152+
index++;
153+
}
154+
}
155+
} while (index < position);
156+
157+
return quoted;
158+
}
159+
160+
public static Span GetWordSpan (string text, int position)
161+
{
162+
var start = position;
163+
while (start > 0 && char.IsLetterOrDigit (text[start - 1]))
164+
start--;
165+
166+
// If we're brought up in the middle of a word, extend to the end of the word as well.
167+
// This means that if a user brings up the completion list at the start of the word they
168+
// will "insert" the text before what's already there (useful for qualifying existing
169+
// text). However, if they bring up completion in the "middle" of a word, then they will
170+
// "overwrite" the text. Useful for correcting misspellings or just replacing unwanted
171+
// code with new code.
172+
var end = position;
173+
if (start != position) {
174+
while (end < text.Length && char.IsLetterOrDigit (text[end]))
175+
end++;
176+
}
177+
178+
return Span.FromBounds (start, end);
179+
}
180+
}
181+
182+
[Export (typeof (IAsyncCompletionCommitManagerProvider))]
183+
[Name ("Debugger Completion Commit Manager")]
184+
[ContentType (DebuggerCompletion.ContentType)]
185+
sealed class DebuggerAsyncCompletionCommitManagerProvider : IAsyncCompletionCommitManagerProvider
186+
{
187+
public IAsyncCompletionCommitManager GetOrCreate (ITextView textView)
188+
{
189+
return new DebuggerAsyncCompletionCommitManager ();
190+
}
191+
}
192+
193+
sealed class DebuggerAsyncCompletionCommitManager : IAsyncCompletionCommitManager
194+
{
195+
static readonly char[] CommitCharacters = new char[] { ' ', '\t', '\n', '.', ',', '<', '>', '(', ')', '[', ']', '\'', '"' };
196+
197+
public IEnumerable<char> PotentialCommitCharacters {
198+
get { return CommitCharacters; }
199+
}
200+
201+
public bool ShouldCommitCompletion (IAsyncCompletionSession session, SnapshotPoint location, char typedChar, CancellationToken token)
202+
{
203+
return true;
204+
}
205+
206+
public CommitResult TryCommit (IAsyncCompletionSession session, ITextBuffer buffer, CompletionItem item, char typedChar, CancellationToken token)
207+
{
208+
if (typedChar == '\'' || typedChar == '"') {
209+
// User is entering a char or string, dismiss the completion window.
210+
return new CommitResult (true, CommitBehavior.None);
211+
}
212+
213+
// Note: Hitting Return should *always* complete the current selection, but other typed chars require examining context...
214+
if (typedChar != '\0' && typedChar != '\n' && typedChar != '\t') {
215+
var text = buffer.CurrentSnapshot.GetText ();
216+
var span = DebuggerAsyncCompletionSource.GetWordSpan (text, text.Length);
217+
218+
if (span.Length == 0)
219+
return new CommitResult (true, CommitBehavior.None);
220+
221+
var typedWord = text.AsSpan (span.Start, span.Length);
222+
223+
if (!item.InsertText.AsSpan ().Contains (typedWord, StringComparison.Ordinal))
224+
return new CommitResult (true, CommitBehavior.None);
225+
}
226+
227+
return CommitResult.Unhandled;
228+
}
229+
}
230+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//
2+
// MacDebuggerCompletionCommandHandler.cs
3+
//
4+
// Author:
5+
// Jeffrey Stedfast <[email protected]>
6+
//
7+
// Copyright (c) 2019 Microsoft Corp.
8+
//
9+
// Permission is hereby granted, free of charge, to any person obtaining a copy
10+
// of this software and associated documentation files (the "Software"), to deal
11+
// in the Software without restriction, including without limitation the rights
12+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
// copies of the Software, and to permit persons to whom the Software is
14+
// furnished to do so, subject to the following conditions:
15+
//
16+
// The above copyright notice and this permission notice shall be included in
17+
// all copies or substantial portions of the Software.
18+
//
19+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
// THE SOFTWARE.
26+
27+
using System.ComponentModel.Composition;
28+
29+
using Microsoft.VisualStudio.Utilities;
30+
using Microsoft.VisualStudio.Commanding;
31+
using Microsoft.VisualStudio.Text.Editor;
32+
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
33+
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;
34+
35+
namespace MonoDevelop.Debugger
36+
{
37+
[Name ("Debbugger Completion CommandHandler")]
38+
[ContentType (DebuggerCompletion.ContentType)]
39+
[TextViewRole (PredefinedTextViewRoles.Interactive)]
40+
[Export (typeof (ICommandHandler))]
41+
[Order (After = PredefinedCompletionNames.CompletionCommandHandler)]
42+
sealed class DebuggerCompletionCommandHandler :
43+
ICommandHandler<EscapeKeyCommandArgs>,
44+
ICommandHandler<ReturnKeyCommandArgs>,
45+
ICommandHandler<TabKeyCommandArgs>
46+
{
47+
public string DisplayName => nameof (DebuggerCompletionCommandHandler);
48+
49+
#region EscapeKey
50+
51+
public CommandState GetCommandState (EscapeKeyCommandArgs args)
52+
{
53+
return CommandState.Available;
54+
}
55+
56+
public bool ExecuteCommand (EscapeKeyCommandArgs args, CommandExecutionContext executionContext)
57+
{
58+
var cocoaTextView = (ICocoaTextView) args.TextView;
59+
var bgView = cocoaTextView.VisualElement.Superview; // the NSView that draws the background color
60+
var superview = bgView?.Superview;
61+
62+
if (superview is MacDebuggerObjectNameView nameView)
63+
nameView.CancelEdit ();
64+
else
65+
System.Console.WriteLine ("superview is {0}", superview.GetType ().FullName);
66+
67+
return true;
68+
}
69+
70+
#endregion // EscapeKey
71+
72+
#region ReturnKey
73+
74+
public CommandState GetCommandState (ReturnKeyCommandArgs args)
75+
{
76+
return CommandState.Available;
77+
}
78+
79+
public bool ExecuteCommand (ReturnKeyCommandArgs args, CommandExecutionContext executionContext)
80+
{
81+
var cocoaTextView = (ICocoaTextView) args.TextView;
82+
83+
cocoaTextView.VisualElement.ResignFirstResponder ();
84+
85+
return true;
86+
}
87+
88+
#endregion // ReturnKey
89+
90+
#region TabKey
91+
92+
public CommandState GetCommandState (TabKeyCommandArgs args)
93+
{
94+
return CommandState.Available;
95+
}
96+
97+
public bool ExecuteCommand (TabKeyCommandArgs args, CommandExecutionContext executionContext)
98+
{
99+
var cocoaTextView = (ICocoaTextView) args.TextView;
100+
101+
cocoaTextView.VisualElement.ResignFirstResponder ();
102+
103+
return true;
104+
}
105+
106+
#endregion // TabKey
107+
}
108+
}

0 commit comments

Comments
 (0)