Skip to content

Commit 9530e0c

Browse files
sdubovabrooksv
authored andcommitted
Add completion for DotNet Lambda handler field in run configuration editor (aws#1247)
* Add completion for Lambda handler text field in Lambda run configuration editor * Add backend logic to find all possible lambda handlers in a solution to provide them in a completion lookup * Move logic to get Amazon Lambda handlers into a static Util class * Add docs for Lambda protocol model * Add tests to verify completion completion provider
1 parent c9112e4 commit 9530e0c

File tree

19 files changed

+1099
-3
lines changed

19 files changed

+1099
-3
lines changed

jetbrains-core/resources/META-INF/ext-rider.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@
2121
<runtimeGroup runtimeGroup="DOTNET" implementation="software.aws.toolkits.jetbrains.services.lambda.dotnet.DotNetRuntimeGroup"/>
2222
<builder runtimeGroup="DOTNET" implementation="software.aws.toolkits.jetbrains.services.lambda.dotnet.DotNetLambdaBuilder"/>
2323
<handlerResolver runtimeGroup="DOTNET" implementation="software.aws.toolkits.jetbrains.services.lambda.dotnet.DotNetLambdaHandlerResolver"/>
24-
<sam.projectWizard runtimeGroup="DOTNET" implementation="software.aws.toolkits.jetbrains.services.lambda.dotnet.DotNetSamProjectWizard"/>
24+
<handlerCompletion runtimeGroup="DOTNET" implementation="software.aws.toolkits.jetbrains.services.lambda.completion.DotNetHandlerCompletion"/>
2525
<sam.debugSupport runtimeGroup="DOTNET" implementation="software.aws.toolkits.jetbrains.services.lambda.dotnet.DotNetSamDebugSupport"/>
26+
<sam.projectWizard runtimeGroup="DOTNET" implementation="software.aws.toolkits.jetbrains.services.lambda.dotnet.DotNetSamProjectWizard"/>
2627
</extensions>
2728

2829
</idea-plugin>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.lambda.completion
5+
6+
import com.intellij.testFramework.ProjectRule
7+
import org.junit.Rule
8+
import org.junit.Test
9+
import software.amazon.awssdk.services.lambda.model.Runtime
10+
import kotlin.test.assertFalse
11+
12+
class HandlerCompletionProviderTest {
13+
14+
@JvmField
15+
@Rule
16+
val projectRule = ProjectRule()
17+
18+
@Test
19+
fun completionIsNotSupportedJava() {
20+
val provider = HandlerCompletionProvider(projectRule.project, Runtime.JAVA8)
21+
assertFalse(provider.isCompletionSupported)
22+
}
23+
24+
@Test
25+
fun completionIsNotSupportedPython() {
26+
val provider = HandlerCompletionProvider(projectRule.project, Runtime.PYTHON3_7)
27+
assertFalse(provider.isCompletionSupported)
28+
}
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using JetBrains.Annotations;
2+
using JetBrains.ProjectModel;
3+
using JetBrains.ReSharper.Psi;
4+
using JetBrains.ReSharper.Psi.Modules;
5+
using JetBrains.RiderTutorials.Utils;
6+
7+
namespace ReSharper.AWS.Lambda
8+
{
9+
public static class LambdaHandlerUtils
10+
{
11+
[NotNull]
12+
public static string ComposeHandlerString([NotNull] IMethod method)
13+
{
14+
if (!(method.Module is IProjectPsiModule projectPsiModule)) return "";
15+
16+
var project = projectPsiModule.Project;
17+
var assemblyName = project.GetOutputAssemblyName(project.GetCurrentTargetFrameworkId());
18+
19+
var containingType = method.GetContainingType();
20+
if (containingType == null) return "";
21+
22+
var typeString = containingType.GetFullClrName();
23+
24+
var methodName = method.ShortName;
25+
26+
return $"{assemblyName}::{typeString}::{methodName}";
27+
}
28+
}
29+
}

jetbrains-rider/ReSharper.AWS/src/AWS.Daemon/Lambda/LambdaHost.cs

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
13
using JetBrains.ProjectModel;
4+
using JetBrains.Rd.Tasks;
25
using JetBrains.ReSharper.Host.Features;
6+
using JetBrains.ReSharper.Host.Platform.Icons;
7+
using JetBrains.ReSharper.Psi;
8+
using JetBrains.ReSharper.Psi.Caches;
9+
using JetBrains.ReSharper.Psi.Modules;
10+
using JetBrains.ReSharper.Resources.Shell;
311
using JetBrains.Rider.Model;
412

513
namespace ReSharper.AWS.Lambda
@@ -8,10 +16,23 @@ namespace ReSharper.AWS.Lambda
816
public class LambdaHost
917
{
1018
private readonly LambdaModel myModel;
19+
private readonly ISymbolCache mySymbolCache;
20+
private readonly PsiIconManager myPsiIconManager;
21+
private readonly IconHost myIconHost;
1122

12-
public LambdaHost(ISolution solution)
23+
public LambdaHost(ISolution solution, ISymbolCache symbolCache, PsiIconManager psiIconManager, IconHost iconHost)
1324
{
1425
myModel = solution.GetProtocolSolution().GetLambdaModel();
26+
mySymbolCache = symbolCache;
27+
myPsiIconManager = psiIconManager;
28+
myIconHost = iconHost;
29+
30+
myModel.DetermineHandlers.Set((lifetime, unit) =>
31+
{
32+
var task = new RdTask<List<HandlerCompletionItem>>();
33+
task.Set(DetermineHandlers(solution));
34+
return task;
35+
});
1536
}
1637

1738
public void RunLambda(string methodName, string handler)
@@ -28,5 +49,80 @@ public void CreateNewLambda(string methodName, string handler)
2849
{
2950
myModel.CreateNewLambda(new LambdaRequest(methodName, handler));
3051
}
52+
53+
private List<HandlerCompletionItem> DetermineHandlers(ISolution solution)
54+
{
55+
var handlers = new List<HandlerCompletionItem>();
56+
57+
using (ReadLockCookie.Create())
58+
{
59+
var projects = solution.GetAllProjects();
60+
61+
foreach (var project in projects)
62+
{
63+
if (!LambdaFinder.IsLambdaProjectType(project)) continue;
64+
var psiModules = project.GetPsiModules();
65+
66+
foreach (var psiModule in psiModules)
67+
{
68+
using (CompilationContextCookie.OverrideOrCreate(psiModule.GetContextFromModule()))
69+
{
70+
var scope = mySymbolCache.GetSymbolScope(psiModule, false, true);
71+
var namespaces = scope.GlobalNamespace.GetNestedNamespaces(scope);
72+
73+
foreach (var @namespace in namespaces)
74+
{
75+
ProcessNamespace(@namespace, scope, handlers);
76+
}
77+
}
78+
}
79+
}
80+
}
81+
82+
return handlers;
83+
}
84+
85+
private void ProcessNamespace(INamespace element, ISymbolScope scope,
86+
ICollection<HandlerCompletionItem> handlers)
87+
{
88+
var nestedNamespaces = element.GetNestedNamespaces(scope);
89+
90+
foreach (var @namespace in nestedNamespaces)
91+
{
92+
ProcessNamespace(@namespace, scope, handlers);
93+
}
94+
95+
var classes = element.GetNestedElements(scope).OfType<IClass>();
96+
foreach (var @class in classes)
97+
{
98+
ProcessClass(@class, handlers);
99+
}
100+
}
101+
102+
private void ProcessClass(ITypeElement element, ICollection<HandlerCompletionItem> handlers)
103+
{
104+
var nestedClasses = element.GetMembers().OfType<IClass>();
105+
106+
foreach (var @class in nestedClasses)
107+
{
108+
ProcessClass(@class, handlers);
109+
}
110+
111+
var methods = element.Methods;
112+
foreach (var method in methods)
113+
{
114+
GetHandlers(method, handlers);
115+
}
116+
}
117+
118+
private void GetHandlers(IMethod method, ICollection<HandlerCompletionItem> handlers)
119+
{
120+
if (!LambdaFinder.IsSuitableLambdaMethod(method)) return;
121+
122+
var handlerString = LambdaHandlerUtils.ComposeHandlerString(method);
123+
var iconId = myPsiIconManager.GetImage(method.GetElementType());
124+
var iconModel = myIconHost.Transform(iconId);
125+
handlers.Add(new HandlerCompletionItem(handlerString, iconModel));
126+
}
31127
}
32128
}

jetbrains-rider/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ test {
8484
// Please see https://youtrack.jetbrains.com/issue/RIDER-19506.
8585
def testClassToExclude = [ "DotNetLambdaHandlerResolverTest",
8686
"LambdaGutterMarkHighlightingTest",
87-
"DotNetLocalLambdaRunConfigurationTest" ]
87+
"DotNetLocalLambdaRunConfigurationTest",
88+
"DotNetHandlerCompletionTest" ]
8889

8990
testClassToExclude.forEach { classToExclude ->
9091
exclude "**/${classToExclude}.class"

jetbrains-rider/protocol/model/daemon/LambdaModel.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@
44
package protocol.model.daemon
55

66
import com.jetbrains.rd.generator.nova.Ext
7+
import com.jetbrains.rd.generator.nova.async
78
import com.jetbrains.rd.generator.nova.doc
89
import com.jetbrains.rd.generator.nova.field
10+
import com.jetbrains.rd.generator.nova.immutableList
11+
import com.jetbrains.rd.generator.nova.nullable
912
import com.jetbrains.rd.generator.nova.sink
13+
import com.jetbrains.rd.generator.nova.call
1014
import com.jetbrains.rd.generator.nova.PredefinedType.string
15+
import com.jetbrains.rd.generator.nova.PredefinedType.void
1116
import com.jetbrains.rider.model.nova.ide.SolutionModel
17+
import com.jetbrains.rider.model.nova.ide.ShellModel.IconModel
1218

1319
@Suppress("unused")
1420
object LambdaModel : Ext(SolutionModel.Solution) {
@@ -18,7 +24,15 @@ object LambdaModel : Ext(SolutionModel.Solution) {
1824
field("handler", string)
1925
}
2026

27+
private val HandlerCompletionItem = structdef {
28+
field("handler", string)
29+
field("iconId", IconModel.nullable)
30+
}
31+
2132
init {
33+
call("determineHandlers", void, immutableList(HandlerCompletionItem)).async
34+
.doc("Get collection of HandlerCompletionItems with available handler functions for a solution")
35+
2236
sink("runLambda", LambdaRequest)
2337
.doc("Signal from backend to run lambda on local environment")
2438

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package software.aws.toolkits.jetbrains.services.lambda.completion
5+
6+
import com.intellij.codeInsight.completion.PrefixMatcher
7+
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher
8+
import com.intellij.codeInsight.lookup.LookupElement
9+
import com.intellij.codeInsight.lookup.LookupElementBuilder
10+
import com.intellij.openapi.project.Project
11+
import com.jetbrains.rd.framework.impl.RpcTimeouts
12+
import com.jetbrains.rdclient.icons.FrontendIconHost
13+
import com.jetbrains.rider.model.lambdaModel
14+
import com.jetbrains.rider.projectView.solution
15+
16+
class DotNetHandlerCompletion : HandlerCompletion {
17+
18+
override fun getPrefixMatcher(prefix: String): PrefixMatcher =
19+
CamelHumpMatcher(prefix)
20+
21+
override fun getLookupElements(project: Project): Collection<LookupElement> {
22+
val completionItems = project.solution.lambdaModel.determineHandlers.sync(Unit, RpcTimeouts.default)
23+
return completionItems.map { completionItem ->
24+
LookupElementBuilder.create(completionItem.handler).let {
25+
if (completionItem.iconId != null) it.withIcon(FrontendIconHost.getInstance(project).toIdeaIcon(completionItem.iconId))
26+
else it
27+
}
28+
}
29+
}
30+
}

0 commit comments

Comments
 (0)