Skip to content

Commit c3c0c65

Browse files
committed
Offer enums as completion items in expressions
1 parent 34b967d commit c3c0c65

File tree

4 files changed

+152
-11
lines changed

4 files changed

+152
-11
lines changed

YarnSpinner.LanguageServer.Tests/CompletionTests.cs

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ public CompletionTests(ITestOutputHelper outputHelper) : base(outputHelper)
2020
{
2121
}
2222

23+
private static int GetNodeBodyLineNumber(Workspace workspace, string nodeName)
24+
{
25+
var projectContainingNode = workspace.Projects.Single(p => p.Nodes.Any(n => n.SourceTitle == nodeName));
26+
var node = projectContainingNode.Nodes.Single(n => n.SourceTitle == nodeName);
27+
return node.BodyStartLine;
28+
}
29+
2330
[Fact]
2431
public async Task Server_OnCompletingStartOfCommand_ReturnsValidCompletions()
2532
{
@@ -163,33 +170,104 @@ public async Task Server_OnCompletingPartialJumpCommand_ReturnsNodeNames()
163170
}
164171

165172
[Fact]
166-
public async Task Server_OnCompletionRequestedInSetStatement_OffersVariableNames()
173+
public async Task Server_OnCompletionRequestedInSetStatement_OffersVariableNamesForAssignment()
167174
{
168175
// Given
169176
// Given
170177
var (client, server) = await Initialize(ConfigureClient, ConfigureServer);
171178
var filePath = Path.Combine(TestUtility.PathToTestWorkspace, "Project1", "Test.yarn");
172179
var workspace = server.Workspace.GetService<Workspace>()!;
173180
var project = workspace.Projects.Single(p => p.Uri!.Path.Contains("Project1"));
181+
var insertionLineNumber = GetNodeBodyLineNumber(workspace, "CodeCompletionTests");
174182

175-
ChangeTextInDocument(client, filePath, new Position(48, 0), "<<set ");
183+
ChangeTextInDocument(client, filePath, new Position(insertionLineNumber, 0), "<<set ");
176184

177185
// When
178186
var completionResults = await client.RequestCompletion(new CompletionParams
179187
{
180188
TextDocument = new TextDocumentIdentifier { Uri = filePath },
181-
Position = new Position(48, "<<set ".Length)
189+
Position = new Position(insertionLineNumber, "<<set ".Length)
182190
});
183191

192+
var storedVariables = project.Variables.Where(v => v.IsInlineExpansion == false);
193+
var smartVariables = project.Variables.Where(v => v.IsInlineExpansion == true);
194+
195+
184196
// Then
185-
completionResults.Should().NotBeEmpty();
186-
completionResults.Should().AllSatisfy(item =>
197+
storedVariables.Should().AllSatisfy(v => completionResults.Should().Contain(res => res.Label == v.Name));
198+
smartVariables.Should().AllSatisfy(v => completionResults.Should().NotContain(res => res.Label == v.Name));
199+
}
200+
201+
202+
[Fact]
203+
public async Task Server_OnCompletionRequestedInSetStatement_OffersIdentifiersForValues()
204+
{
205+
// Given
206+
var (client, server) = await Initialize(ConfigureClient, ConfigureServer);
207+
var filePath = Path.Combine(TestUtility.PathToTestWorkspace, "Project1", "Test.yarn");
208+
var workspace = server.Workspace.GetService<Workspace>()!;
209+
var project = workspace.Projects.Single(p => p.Uri!.Path.Contains("Project1"));
210+
var insertionLineNumber = GetNodeBodyLineNumber(workspace, "CodeCompletionTests");
211+
212+
ChangeTextInDocument(client, filePath, new Position(insertionLineNumber, 0), "<<set $x = ");
213+
214+
// When
215+
var completionResults = await client.RequestCompletion(new CompletionParams
216+
{
217+
TextDocument = new TextDocumentIdentifier { Uri = filePath },
218+
Position = new Position(insertionLineNumber, "<<set $x = ".Length)
219+
});
220+
221+
var allFunctionsAndVariables = Enumerable.Concat(project.Variables, project.Functions.Select(a => a.Declaration));
222+
var allEnumCaseNames = project.Enums.SelectMany(e => e.EnumCases.Select(c => $"{e.Name}.{c.Key}"));
223+
224+
allFunctionsAndVariables.Should().NotBeEmpty();
225+
allFunctionsAndVariables.Should().AllSatisfy(decl => decl.Should().NotBeNull());
226+
allEnumCaseNames.Should().NotBeEmpty();
227+
228+
// Then
229+
// All functions and variables should be in the list of completions
230+
allFunctionsAndVariables.Should().AllSatisfy(decl => completionResults.Should().Contain(res => res.Label == decl!.Name));
231+
// All enum cases should be in the list of completions
232+
allEnumCaseNames.Should().AllSatisfy(caseName => completionResults.Should().Contain(res => res.Label == caseName));
233+
}
234+
235+
[InlineData(["<<if "])]
236+
[InlineData(["<<elseif "])]
237+
[InlineData(["<<myCoolCommand {"])]
238+
[Theory]
239+
public async Task Server_OnCompletionRequestedInStatement_OffersIdentifiers(string expression)
240+
{
241+
// Given
242+
var (client, server) = await Initialize(ConfigureClient, ConfigureServer);
243+
var filePath = Path.Combine(TestUtility.PathToTestWorkspace, "Project1", "Test.yarn");
244+
var workspace = server.Workspace.GetService<Workspace>()!;
245+
var project = workspace.Projects.Single(p => p.Uri!.Path.Contains("Project1"));
246+
var insertionLineNumber = GetNodeBodyLineNumber(workspace, "CodeCompletionTests");
247+
248+
ChangeTextInDocument(client, filePath, new Position(insertionLineNumber, 0), expression);
249+
250+
// When
251+
var completionResults = await client.RequestCompletion(new CompletionParams
187252
{
188-
var decl = project.Variables.Should().Contain(v => v.Name == item.Label, "the completion should be a variable name").Subject;
189-
decl.IsVariable.Should().BeTrue("the declaration should be a variable");
190-
decl.IsInlineExpansion.Should().BeFalse("the variable should be a stored variable");
191-
}, "all completion results should be stored variables");
253+
TextDocument = new TextDocumentIdentifier { Uri = filePath },
254+
Position = new Position(insertionLineNumber, expression.Length)
255+
});
256+
257+
// Then
258+
completionResults.Should().NotBeEmpty();
259+
260+
var allFunctionsAndVariables = Enumerable.Concat(project.Variables, project.Functions.Select(a => a.Declaration));
261+
var allEnumCaseNames = project.Enums.SelectMany(e => e.EnumCases.Select(c => $"{e.Name}.{c.Key}"));
262+
263+
allFunctionsAndVariables.Should().NotBeEmpty();
264+
allFunctionsAndVariables.Should().AllSatisfy(decl => decl.Should().NotBeNull());
265+
allEnumCaseNames.Should().NotBeEmpty();
266+
267+
// All functions and variables should be in the list of completions
268+
allFunctionsAndVariables.Should().AllSatisfy(decl => completionResults.Should().Contain(res => res.Label == decl!.Name));
192269

270+
allEnumCaseNames.Should().AllSatisfy(caseName => completionResults.Should().Contain(res => res.Label == caseName));
193271
}
194272

195273
}

YarnSpinner.LanguageServer.Tests/TestData/TestWorkspace/Project1/Test.yarn

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ This is a line.
1010

1111
<<jump Node2>>
1212

13+
<<enum TestEnum>>
14+
<<case Item1>>
15+
<<case Item2>>
16+
<<case Item3>>
17+
<<endenum>>
18+
1319
===
1420
title: Node2
1521
tags: wow incredible

YarnSpinner.LanguageServer/src/Server/Handlers/CompletionHandler.cs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,27 @@ public CompletionHandler(Workspace workspace)
209209
Documentation = "Set assigns the value of the expression to a variable",
210210
InsertTextFormat = InsertTextFormat.PlainText,
211211
},
212+
new CompletionItem{
213+
Label = "enum",
214+
Kind = CompletionItemKind.Keyword,
215+
InsertText = "enum",
216+
Documentation = "Enums are collections of predefined values.",
217+
InsertTextFormat = InsertTextFormat.PlainText,
218+
},
219+
new CompletionItem{
220+
Label = "case",
221+
Kind = CompletionItemKind.Keyword,
222+
InsertText = "case",
223+
Documentation = new MarkupContent{ Kind=MarkupKind.Markdown, Value= "`case` creates a new member in an enum." },
224+
InsertTextFormat = InsertTextFormat.PlainText,
225+
},
226+
new CompletionItem{
227+
Label = "endenum",
228+
Kind = CompletionItemKind.Keyword,
229+
InsertText = "endenum",
230+
Documentation = new MarkupContent{ Kind=MarkupKind.Markdown, Value= "`endenum` ends an enum declaration." },
231+
InsertTextFormat = InsertTextFormat.PlainText,
232+
}
212233
};
213234
}
214235

@@ -520,6 +541,7 @@ enum IdentifierTypes
520541
Function = 1,
521542
StoredVariable = 2,
522543
SmartVariable = 4,
544+
EnumCases = 8,
523545
All = ~0,
524546
}
525547

@@ -529,8 +551,6 @@ private static void GetIdentifierCompletions(Project project, Range indexTokenRa
529551

530552
if (identifierTypes.HasFlag(IdentifierTypes.Function))
531553
{
532-
533-
534554
foreach (var function in project.Functions.DistinctBy(f => f.YarnName))
535555
{
536556
builder.Append(function.YarnName);
@@ -594,6 +614,29 @@ private static void GetIdentifierCompletions(Project project, Range indexTokenRa
594614
});
595615
}
596616
}
617+
618+
if (identifierTypes.HasFlag(IdentifierTypes.EnumCases))
619+
{
620+
foreach (var userEnum in project.Enums)
621+
{
622+
foreach (var enumCase in userEnum.EnumCases)
623+
{
624+
var fullName = userEnum.Name + "." + enumCase.Key;
625+
results.Add(new CompletionItem
626+
{
627+
Label = fullName,
628+
Kind = CompletionItemKind.EnumMember,
629+
Documentation = enumCase.Value.Description,
630+
TextEdit = new TextEditOrInsertReplaceEdit(new TextEdit
631+
{
632+
NewText = fullName,
633+
Range = indexTokenRange.CollapseToEnd()
634+
}),
635+
InsertTextFormat = InsertTextFormat.PlainText,
636+
});
637+
}
638+
}
639+
}
597640
}
598641

599642
private void GetCommandCompletions(CompletionParams request, Range indexTokenRange, List<CompletionItem> results)

YarnSpinner.LanguageServer/src/Server/Workspace/Project.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ internal IEnumerable<Yarn.Compiler.Declaration> Variables
3333
}
3434
}
3535

36+
internal IEnumerable<Yarn.EnumType> Enums
37+
{
38+
get
39+
{
40+
if (LastCompilationResult == null)
41+
{
42+
return Enumerable.Empty<Yarn.EnumType>();
43+
}
44+
45+
var enums = LastCompilationResult.UserDefinedTypes.OfType<Yarn.EnumType>();
46+
return enums;
47+
}
48+
}
49+
3650
internal IEnumerable<Yarn.Compiler.Diagnostic> Diagnostics
3751
{
3852
get

0 commit comments

Comments
 (0)