Skip to content

Commit 80f8dd7

Browse files
authored
Add auto completion implementation for debug console. (microsoft#169)
* Add auto completion implementation for debug console. * Fix review comment. * Use source container to resolve types.
1 parent 8935dc4 commit 80f8dd7

File tree

7 files changed

+201
-76
lines changed

7 files changed

+201
-76
lines changed

com.microsoft.java.debug.core/src/main/java/com/microsoft/java/debug/core/adapter/DebugAdapter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import com.microsoft.java.debug.core.Configuration;
2323
import com.microsoft.java.debug.core.adapter.handler.AttachRequestHandler;
24+
import com.microsoft.java.debug.core.adapter.handler.CompletionsHandler;
2425
import com.microsoft.java.debug.core.adapter.handler.ConfigurationDoneRequestHandler;
2526
import com.microsoft.java.debug.core.adapter.handler.DisconnectRequestHandler;
2627
import com.microsoft.java.debug.core.adapter.handler.EvaluateRequestHandler;
@@ -108,6 +109,7 @@ private void initialize() {
108109
registerHandler(new EvaluateRequestHandler());
109110
registerHandler(new HotCodeReplaceHandler());
110111
registerHandler(new RestartFrameHandler());
112+
registerHandler(new CompletionsHandler());
111113
}
112114

113115
private void registerHandler(IDebugRequestHandler handler) {

com.microsoft.java.debug.plugin/META-INF/MANIFEST.MF

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ Require-Bundle: org.eclipse.core.runtime,
1515
org.eclipse.jdt.core,
1616
org.eclipse.jdt.ls.core,
1717
org.eclipse.jdt.launching,
18-
com.google.gson;bundle-version="2.7.0",
19-
org.apache.commons.lang3;bundle-version="3.1.0"
18+
com.google.gson,
19+
org.apache.commons.lang3,
20+
org.eclipse.lsp4j
2021
Bundle-ClassPath: lib/commons-io-2.5.jar,
2122
.,
2223
lib/rxjava-2.1.1.jar,
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2018 Microsoft Corporation and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Microsoft Corporation - initial API and implementation
10+
*******************************************************************************/
11+
12+
package com.microsoft.java.debug.plugin.internal;
13+
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.logging.Level;
18+
import java.util.logging.Logger;
19+
20+
import org.eclipse.core.runtime.CoreException;
21+
import org.eclipse.jdt.core.CompletionProposal;
22+
import org.eclipse.jdt.core.IJavaProject;
23+
import org.eclipse.jdt.core.IType;
24+
import org.eclipse.jdt.ls.core.internal.contentassist.CompletionProposalRequestor;
25+
import org.eclipse.jdt.ls.core.internal.handlers.JsonRpcHelpers;
26+
27+
import com.microsoft.java.debug.core.Configuration;
28+
import com.microsoft.java.debug.core.DebugException;
29+
import com.microsoft.java.debug.core.adapter.ICompletionsProvider;
30+
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext;
31+
import com.microsoft.java.debug.core.adapter.ISourceLookUpProvider;
32+
import com.microsoft.java.debug.core.protocol.Types.CompletionItem;
33+
import com.sun.jdi.StackFrame;
34+
35+
public class CompletionsProvider implements ICompletionsProvider {
36+
37+
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
38+
39+
private IDebugAdapterContext context;
40+
41+
@Override
42+
public void initialize(IDebugAdapterContext context, Map<String, Object> options) {
43+
this.context = context;
44+
}
45+
46+
@Override
47+
public List<CompletionItem> codeComplete(StackFrame frame, String snippet, int line, int column) {
48+
List<CompletionItem> res = new ArrayList<CompletionItem>();
49+
50+
try {
51+
IType type = resolveType(frame);
52+
if (type != null) {
53+
final int offset = JsonRpcHelpers.toOffset(type.getCompilationUnit().getBuffer(), frame.location().lineNumber(), 0);
54+
CompletionProposalRequestor collector = new CompletionProposalRequestor(type.getCompilationUnit(), offset);
55+
56+
collector.setAllowsRequiredProposals(CompletionProposal.FIELD_REF, CompletionProposal.TYPE_REF, true);
57+
collector.setAllowsRequiredProposals(CompletionProposal.FIELD_REF, CompletionProposal.TYPE_IMPORT, true);
58+
collector.setAllowsRequiredProposals(CompletionProposal.FIELD_REF, CompletionProposal.FIELD_IMPORT, true);
59+
60+
collector.setAllowsRequiredProposals(CompletionProposal.METHOD_REF, CompletionProposal.TYPE_REF, true);
61+
collector.setAllowsRequiredProposals(CompletionProposal.METHOD_REF, CompletionProposal.TYPE_IMPORT, true);
62+
collector.setAllowsRequiredProposals(CompletionProposal.METHOD_REF, CompletionProposal.METHOD_IMPORT, true);
63+
64+
collector.setAllowsRequiredProposals(CompletionProposal.CONSTRUCTOR_INVOCATION, CompletionProposal.TYPE_REF, true);
65+
66+
collector.setAllowsRequiredProposals(CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION, CompletionProposal.TYPE_REF, true);
67+
collector.setAllowsRequiredProposals(CompletionProposal.ANONYMOUS_CLASS_DECLARATION, CompletionProposal.TYPE_REF, true);
68+
69+
collector.setAllowsRequiredProposals(CompletionProposal.TYPE_REF, CompletionProposal.TYPE_REF, true);
70+
71+
type.codeComplete(snippet.toCharArray(), offset, snippet.length(), null, null, null, frame.location().method().isStatic(), collector);
72+
73+
List<org.eclipse.lsp4j.CompletionItem> items = collector.getCompletionItems();
74+
75+
if (items != null) {
76+
for (org.eclipse.lsp4j.CompletionItem lspItem : items) {
77+
res.add(convertFromLsp(lspItem));
78+
}
79+
}
80+
}
81+
82+
} catch (DebugException | CoreException e) {
83+
logger.log(Level.WARNING, String.format("Failed to code complete because of %s", e.toString()), e);
84+
}
85+
86+
return res;
87+
}
88+
89+
private IType resolveType(StackFrame frame) throws CoreException, DebugException {
90+
ISourceLookUpProvider sourceProvider = context.getProvider(ISourceLookUpProvider.class);
91+
if (sourceProvider instanceof JdtSourceLookUpProvider) {
92+
IJavaProject project = JdtUtils.findProject(frame, ((JdtSourceLookUpProvider) sourceProvider).getSourceContainers());
93+
if (project != null) {
94+
return project.findType(JdtUtils.getDeclaringTypeName(frame));
95+
}
96+
}
97+
return null;
98+
}
99+
100+
private CompletionItem convertFromLsp(org.eclipse.lsp4j.CompletionItem lspItem) {
101+
CompletionItem item = new CompletionItem(lspItem.getLabel(), lspItem.getInsertText());
102+
if (lspItem.getKind() != null) {
103+
item.type = lspItem.getKind().name().toLowerCase();
104+
}
105+
return item;
106+
}
107+
}

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaHotCodeReplaceProvider.java

Lines changed: 1 addition & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
import org.eclipse.jdt.core.IJavaModelMarker;
5050
import org.eclipse.jdt.core.IJavaProject;
5151
import org.eclipse.jdt.core.JavaCore;
52-
import org.eclipse.jdt.core.Signature;
5352
import org.eclipse.jdt.core.ToolFactory;
5453
import org.eclipse.jdt.core.util.IClassFileReader;
5554
import org.eclipse.jdt.core.util.ISourceAttribute;
@@ -66,13 +65,10 @@
6665
import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider;
6766
import com.microsoft.java.debug.core.protocol.Events;
6867

69-
import com.sun.jdi.ArrayType;
70-
import com.sun.jdi.ClassNotLoadedException;
7168
import com.sun.jdi.IncompatibleThreadStateException;
7269
import com.sun.jdi.ReferenceType;
7370
import com.sun.jdi.StackFrame;
7471
import com.sun.jdi.ThreadReference;
75-
import com.sun.jdi.Type;
7672
import com.sun.jdi.VMDisconnectedException;
7773
import com.sun.jdi.VirtualMachine;
7874
import com.sun.jdi.request.StepRequest;
@@ -514,7 +510,7 @@ private StackFrame getAffectedFrame(ThreadReference thread, List<String> replace
514510
}
515511

516512
private boolean containsChangedType(StackFrame frame, List<String> replacedClassNames) throws DebugException {
517-
String declaringTypeName = getDeclaringTypeName(frame);
513+
String declaringTypeName = JdtUtils.getDeclaringTypeName(frame);
518514
// Check if the frame's declaring type was changed
519515
if (replacedClassNames.contains(declaringTypeName)) {
520516
return true;
@@ -530,69 +526,6 @@ private boolean containsChangedType(StackFrame frame, List<String> replacedClass
530526
return false;
531527
}
532528

533-
private String getDeclaringTypeName(StackFrame frame) throws DebugException {
534-
return getGenericName(StackFrameUtility.getDeclaringType(frame));
535-
}
536-
537-
private String getGenericName(ReferenceType type) throws DebugException {
538-
if (type instanceof ArrayType) {
539-
try {
540-
Type componentType;
541-
componentType = ((ArrayType) type).componentType();
542-
if (componentType instanceof ReferenceType) {
543-
return getGenericName((ReferenceType) componentType) + "[]"; //$NON-NLS-1$
544-
}
545-
return type.name();
546-
} catch (ClassNotLoadedException e) {
547-
// we cannot create the generic name using the component type,
548-
// just try to create one with the information
549-
}
550-
}
551-
String signature = type.signature();
552-
StringBuffer res = new StringBuffer(getTypeName(signature));
553-
String genericSignature = type.genericSignature();
554-
if (genericSignature != null) {
555-
String[] typeParameters = Signature.getTypeParameters(genericSignature);
556-
if (typeParameters.length > 0) {
557-
res.append('<').append(Signature.getTypeVariable(typeParameters[0]));
558-
for (int i = 1; i < typeParameters.length; i++) {
559-
res.append(',').append(Signature.getTypeVariable(typeParameters[i]));
560-
}
561-
res.append('>');
562-
}
563-
}
564-
return res.toString();
565-
}
566-
567-
private String getTypeName(String genericTypeSignature) {
568-
int arrayDimension = 0;
569-
while (genericTypeSignature.charAt(arrayDimension) == '[') {
570-
arrayDimension++;
571-
}
572-
int parameterStart = genericTypeSignature.indexOf('<');
573-
StringBuffer name = new StringBuffer();
574-
if (parameterStart < 0) {
575-
name.append(genericTypeSignature.substring(arrayDimension + 1, genericTypeSignature.length() - 1)
576-
.replace('/', '.'));
577-
} else {
578-
if (parameterStart != 0) {
579-
name.append(genericTypeSignature.substring(arrayDimension + 1, parameterStart).replace('/', '.'));
580-
}
581-
try {
582-
String sig = Signature.toString(genericTypeSignature)
583-
.substring(Math.max(parameterStart - 1, 0) - arrayDimension);
584-
name.append(sig.replace('/', '.'));
585-
} catch (IllegalArgumentException iae) {
586-
// do nothing
587-
name.append(genericTypeSignature);
588-
}
589-
}
590-
for (int i = 0; i < arrayDimension; i++) {
591-
name.append("[]"); //$NON-NLS-1$
592-
}
593-
return name.toString();
594-
}
595-
596529
private boolean supportsDropToFrame(ThreadReference thread, StackFrame frame) {
597530
List<StackFrame> frames = getStackFrames(thread, false);
598531
for (int i = 0; i < frames.size(); i++) {

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtProviderContextFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
package com.microsoft.java.debug.plugin.internal;
1313

14+
import com.microsoft.java.debug.core.adapter.ICompletionsProvider;
1415
import com.microsoft.java.debug.core.adapter.IEvaluationProvider;
1516
import com.microsoft.java.debug.core.adapter.IHotCodeReplaceProvider;
1617
import com.microsoft.java.debug.core.adapter.IProviderContext;
@@ -33,6 +34,7 @@ public static IProviderContext createProviderContext() {
3334
context.registerProvider(IVirtualMachineManagerProvider.class, new JdtVirtualMachineManagerProvider());
3435
context.registerProvider(IHotCodeReplaceProvider.class, new JavaHotCodeReplaceProvider());
3536
context.registerProvider(IEvaluationProvider.class, new JdtEvaluationProvider());
37+
context.registerProvider(ICompletionsProvider.class, new CompletionsProvider());
3638

3739
return context;
3840
}

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtSourceLookUpProvider.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,11 @@ public String getSourceFileURI(String fullyQualifiedName, String sourcePath) {
181181
return null;
182182
}
183183

184-
private synchronized ISourceContainer[] getSourceContainers() {
184+
/**
185+
* Get the project associated source containers.
186+
* @return the initialized source container list
187+
*/
188+
public synchronized ISourceContainer[] getSourceContainers() {
185189
if (sourceContainers == null) {
186190
sourceContainers = JdtUtils.getSourceContainers((String) options.get(Constants.PROJECT_NAME));
187191
}

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JdtUtils.java

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,21 @@
3030
import org.eclipse.jdt.core.IPackageFragmentRoot;
3131
import org.eclipse.jdt.core.JavaCore;
3232
import org.eclipse.jdt.core.JavaModelException;
33+
import org.eclipse.jdt.core.Signature;
3334
import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
3435
import org.eclipse.jdt.launching.JavaRuntime;
3536
import org.eclipse.jdt.launching.sourcelookup.containers.JavaProjectSourceContainer;
3637
import org.eclipse.jdt.launching.sourcelookup.containers.PackageFragmentRootSourceContainer;
3738

39+
import com.microsoft.java.debug.core.DebugException;
40+
import com.microsoft.java.debug.core.StackFrameUtility;
3841
import com.sun.jdi.AbsentInformationException;
42+
import com.sun.jdi.ArrayType;
43+
import com.sun.jdi.ClassNotLoadedException;
3944
import com.sun.jdi.Location;
45+
import com.sun.jdi.ReferenceType;
4046
import com.sun.jdi.StackFrame;
47+
import com.sun.jdi.Type;
4148

4249
public class JdtUtils {
4350

@@ -235,29 +242,98 @@ public static Object findSourceElement(String sourcePath, ISourceContainer[] con
235242
}
236243

237244
/**
238-
* Given a stack frame, find the target project that the associated source file belongs to.
245+
* Given a stack frame, find the target java project that the associated source file belongs to.
239246
*
240247
* @param stackFrame
241248
* the stack frame.
242249
* @param containers
243250
* the source container list.
244-
* @return the context project.
251+
* @return the java project.
245252
*/
246-
public static IProject findProject(StackFrame stackFrame, ISourceContainer[] containers) {
253+
public static IJavaProject findProject(StackFrame stackFrame, ISourceContainer[] containers) {
247254
Location location = stackFrame.location();
248255
try {
249256
Object sourceElement = findSourceElement(location.sourcePath(), containers);
250257
if (sourceElement instanceof IResource) {
251-
return ((IResource) sourceElement).getProject();
258+
return JavaCore.create(((IResource) sourceElement).getProject());
252259
} else if (sourceElement instanceof IClassFile) {
253260
IJavaProject javaProject = ((IClassFile) sourceElement).getJavaProject();
254261
if (javaProject != null) {
255-
return javaProject.getProject();
262+
return javaProject;
256263
}
257264
}
258265
} catch (AbsentInformationException e) {
259266
// When the compiled .class file doesn't contain debug source information, return null.
260267
}
261268
return null;
262269
}
270+
271+
/**
272+
* Given a stack frame, get the fully qualified type name that associated with the frame.
273+
* @param frame the stack frame
274+
* @return the fully qualified type name
275+
* @throws DebugException debug exception
276+
*/
277+
public static String getDeclaringTypeName(StackFrame frame) throws DebugException {
278+
return getGenericName(StackFrameUtility.getDeclaringType(frame));
279+
}
280+
281+
private static String getGenericName(ReferenceType type) throws DebugException {
282+
if (type instanceof ArrayType) {
283+
try {
284+
Type componentType;
285+
componentType = ((ArrayType) type).componentType();
286+
if (componentType instanceof ReferenceType) {
287+
return getGenericName((ReferenceType) componentType) + "[]"; //$NON-NLS-1$
288+
}
289+
return type.name();
290+
} catch (ClassNotLoadedException e) {
291+
// we cannot create the generic name using the component type,
292+
// just try to create one with the information
293+
}
294+
}
295+
String signature = type.signature();
296+
StringBuffer res = new StringBuffer(getTypeName(signature));
297+
String genericSignature = type.genericSignature();
298+
if (genericSignature != null) {
299+
String[] typeParameters = Signature.getTypeParameters(genericSignature);
300+
if (typeParameters.length > 0) {
301+
res.append('<').append(Signature.getTypeVariable(typeParameters[0]));
302+
for (int i = 1; i < typeParameters.length; i++) {
303+
res.append(',').append(Signature.getTypeVariable(typeParameters[i]));
304+
}
305+
res.append('>');
306+
}
307+
}
308+
return res.toString();
309+
}
310+
311+
private static String getTypeName(String genericTypeSignature) {
312+
int arrayDimension = 0;
313+
while (genericTypeSignature.charAt(arrayDimension) == '[') {
314+
arrayDimension++;
315+
}
316+
int parameterStart = genericTypeSignature.indexOf('<');
317+
StringBuffer name = new StringBuffer();
318+
if (parameterStart < 0) {
319+
name.append(genericTypeSignature.substring(arrayDimension + 1, genericTypeSignature.length() - 1)
320+
.replace('/', '.'));
321+
} else {
322+
if (parameterStart != 0) {
323+
name.append(genericTypeSignature.substring(arrayDimension + 1, parameterStart).replace('/', '.'));
324+
}
325+
try {
326+
String sig = Signature.toString(genericTypeSignature)
327+
.substring(Math.max(parameterStart - 1, 0) - arrayDimension);
328+
name.append(sig.replace('/', '.'));
329+
} catch (IllegalArgumentException iae) {
330+
// do nothing
331+
name.append(genericTypeSignature);
332+
}
333+
}
334+
for (int i = 0; i < arrayDimension; i++) {
335+
name.append("[]"); //$NON-NLS-1$
336+
}
337+
return name.toString();
338+
}
263339
}

0 commit comments

Comments
 (0)