Skip to content

Commit 9a566a3

Browse files
authored
WI #2735 Merge CompletionElligibleTokens with CodeElementMatchers (#2743)
1 parent 3f8357a commit 9a566a3

File tree

7 files changed

+99
-144
lines changed

7 files changed

+99
-144
lines changed

TypeCobol.LanguageServer/Completion Factory/CodeElementMatcher.cs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,31 @@ namespace TypeCobol.LanguageServer
66
{
77
public static class CodeElementMatcher
88
{
9+
/// <summary>
10+
/// Completion Tokens is a Dictionary associating a boolean for each token supported for completion operation.
11+
/// The boolean flag indicates whether the completion is allowed immediately after the token.
12+
/// For instance for PERFORM token the last position is not allowed:
13+
/// PERFORM
14+
/// ^
15+
/// that is to say if the cursor is just after the M that no completion should occurs.
16+
/// </summary>
17+
private static Dictionary<TokenType, bool> _ElligibleCompletionTokens = new()
18+
{
19+
{ TokenType.PERFORM, false },
20+
{ TokenType.CALL, false },
21+
{ TokenType.TYPE, false },
22+
{ TokenType.QualifiedNameSeparator, true },
23+
{ TokenType.INPUT, false },
24+
{ TokenType.OUTPUT, false },
25+
{ TokenType.IN_OUT, false },
26+
{ TokenType.MOVE, false },
27+
{ TokenType.TO, false },
28+
{ TokenType.SET, false },
29+
{ TokenType.OF, false },
30+
{ TokenType.INTO, false },
31+
{ TokenType.DISPLAY, false }
32+
};
33+
934
/// <summary>
1035
/// This method will try to found the best significant token that code be used fo completion. It depends on the given CodeElements and Position.
1136
/// It will also return the CodeElement that contains the significant token detected.
@@ -45,8 +70,7 @@ public static CodeElement MatchCompletionCodeElement(Position position,
4570
closestTokenToCursor.StopIndex + 1 >= position.character)
4671
//the cursor is at the end or in the middle of a token.
4772
{
48-
if (closestTokenToCursor.StopIndex + 1 == position.character &&
49-
CompletionElligibleTokens.IsCompletionElligibleToken(closestTokenToCursor) && CompletionElligibleTokens.DoesTokenAllowLastPos(closestTokenToCursor))
73+
if (closestTokenToCursor.StopIndex + 1 == position.character && DoesTokenAllowLastPos(closestTokenToCursor))
5074
//Detect if token is eligible and if the cursor is at the end of the token
5175
{
5276
//the completion has to start from this token and this codeElement
@@ -112,7 +136,7 @@ Token ReplaceClosestTokenToCursor(Token originalToken)
112136
if (finalToken.StartIndex > position.character && !(finalToken.Line < position.line + 1))
113137
break;
114138

115-
if (CompletionElligibleTokens.IsCompletionElligibleToken(finalToken) &&
139+
if (_ElligibleCompletionTokens.ContainsKey(finalToken.TokenType) &&
116140
(finalToken.StopIndex + 1 <= position.character || finalToken.Line <= position.line + 1))
117141
{
118142
lastSignificantToken = finalToken;
@@ -140,7 +164,7 @@ Token ReplaceClosestTokenToCursor(Token originalToken)
140164
{
141165
//Detect if the cursor is just after the token, in this case and if bAllowLastPos is false, set
142166
if ((lastSignificantToken != null &&
143-
(!CompletionElligibleTokens.DoesTokenAllowLastPos(lastSignificantToken) && lastSignificantToken.StopIndex + 1 == position.character &&
167+
(!DoesTokenAllowLastPos(lastSignificantToken) && lastSignificantToken.StopIndex + 1 == position.character &&
144168
lastSignificantToken.Line == position.line + 1))
145169
||
146170
(consumedTokens.Last().TokenType == TokenType.UserDefinedWord &&
@@ -174,5 +198,8 @@ Token ReplaceClosestTokenToCursor(Token originalToken)
174198

175199
return significantCodeElement;
176200
}
201+
202+
private static bool DoesTokenAllowLastPos(Token token) =>
203+
_ElligibleCompletionTokens.TryGetValue(token.TokenType, out bool allowLastPos) && allowLastPos;
177204
}
178205
}

TypeCobol.LanguageServer/Completion Factory/CompletionElligibleTokens.cs

Lines changed: 0 additions & 61 deletions
This file was deleted.

TypeCobol.LanguageServer/Completion Factory/CompletionFactoryHelpers.cs

Lines changed: 10 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using TypeCobol.Compiler;
2-
using TypeCobol.Compiler.CodeElements;
1+
using TypeCobol.Compiler.CodeElements;
32
using TypeCobol.Compiler.CodeModel;
43
using TypeCobol.Compiler.Directives;
54
using TypeCobol.Compiler.Nodes;
@@ -29,56 +28,6 @@ public static string GetProcedureNameFromTokens(List<Token> consumedTokens)
2928
.Select(t => t.Text));
3029
}
3130

32-
/// <summary>
33-
/// Get the matching node for the given CodeElement, returns null if not found.
34-
/// </summary>
35-
/// <param name="fileCompiler">Current file being compiled with its compilation results</param>
36-
/// <param name="codeElement">Target CodeElement</param>
37-
/// <returns>Corresponding Node instance, null if not found.</returns>
38-
public static Node GetMatchingNode(FileCompiler fileCompiler, CodeElement codeElement)
39-
{
40-
var codeElementToNode = fileCompiler.CompilationResultsForProgram.ProgramClassDocumentSnapshot?.NodeCodeElementLinkers;
41-
if (codeElementToNode != null && codeElementToNode.TryGetValue(codeElement, out var node))
42-
{
43-
return node;
44-
}
45-
return null;
46-
}
47-
48-
public static IEnumerable<string> AggregateTokens(IEnumerable<Token> tokensToAggregate)
49-
{
50-
var aggregatedTokens = new Stack<string>();
51-
52-
Token previousToken = null;
53-
foreach (var token in tokensToAggregate)
54-
{
55-
if (previousToken != null && previousToken.TokenType == TokenType.UserDefinedWord)
56-
{
57-
if (token.TokenType != TokenType.QualifiedNameSeparator)
58-
{
59-
aggregatedTokens.Push(token.Text);
60-
}
61-
else if (previousToken.TokenType == TokenType.UserDefinedWord)
62-
{
63-
var retainedString = aggregatedTokens.Pop();
64-
aggregatedTokens.Push(retainedString + ".");
65-
}
66-
}
67-
else if (previousToken != null && previousToken.TokenType == TokenType.QualifiedNameSeparator)
68-
{
69-
var retainedString = aggregatedTokens.Pop();
70-
aggregatedTokens.Push(retainedString + token.Text);
71-
}
72-
73-
if (previousToken == null && token.TokenType == TokenType.UserDefinedWord)
74-
aggregatedTokens.Push(token.Text);
75-
76-
previousToken = token;
77-
}
78-
79-
return aggregatedTokens.ToArray().Reverse();
80-
}
81-
8231
public static List<CompletionItem> CreateCompletionItemsForType(IEnumerable<TypeDefinition> types, Node node, bool enablePublicFlag = true)
8332
{
8433
var completionItems = new List<CompletionItem>();
@@ -126,8 +75,8 @@ public static List<CompletionItem> CreateCompletionItemsForProcedures(IEnumerabl
12675
{
12776
var completionItems = new List<CompletionItem>();
12877

129-
Case textCase = GetTextCase(node.CodeElement.ConsumedTokens.First(t => t.TokenType == TokenType.CALL).Text);
130-
Dictionary<ParameterDescription.PassingTypes, string> paramWithCase = GetParamsWithCase(textCase);
78+
Token callToken = node.CodeElement.ConsumedTokens.First(t => t.TokenType == TokenType.CALL);
79+
Dictionary<ParameterDescription.PassingTypes, string> paramWithCase = GetParamsUsingMatchingCase(callToken);
13180

13281
foreach (var proc in procedures)
13382
{
@@ -284,9 +233,12 @@ public static CompletionItem CreateCompletionItemForSingleVariable(DataDefinitio
284233
return new CompletionItem() { label = label, insertText = insertText, kind = CompletionItemKind.Variable };
285234
}
286235

287-
public static Case GetTextCase(string tokenText)
236+
private static Case GetTextCase(Token token)
288237
{
238+
string tokenText = token?.Text;
239+
289240
if (string.IsNullOrEmpty(tokenText)) return Case.Lower;
241+
290242
// check if upper case
291243
bool isUpper = true;
292244
foreach (char c in tokenText)
@@ -351,9 +303,9 @@ public static Case GetTextCase(string tokenText)
351303
{ ParameterDescription.PassingTypes.InOut, "in-out" }
352304
};
353305

354-
public static Dictionary<ParameterDescription.PassingTypes, string> GetParamsWithCase(Case textCase)
306+
public static Dictionary<ParameterDescription.PassingTypes, string> GetParamsUsingMatchingCase(Token token)
355307
{
356-
switch (textCase)
308+
switch (GetTextCase(token))
357309
{
358310
case Case.Upper:
359311
return _UpperParams;
@@ -364,7 +316,7 @@ public static Case GetTextCase(string tokenText)
364316
}
365317
}
366318

367-
public enum Case
319+
private enum Case
368320
{
369321
Lower = 0, // default value
370322
Upper,

TypeCobol.LanguageServer/Completion Factory/CompletionForProcedureParameter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,8 @@ public override List<CompletionItem> ComputeProposals(CompilationUnit compilatio
171171
var items = CompletionFactoryHelpers.CreateCompletionItemsForVariableSetAndDisambiguate(variables, compilationUnit.CompilerOptions);
172172
completionItems.AddRange(items);
173173

174-
CompletionFactoryHelpers.Case textCase = CompletionFactoryHelpers.GetTextCase(codeElement.ConsumedTokens.First(t => t.TokenType == TokenType.CALL).Text);
175-
Dictionary<ParameterDescription.PassingTypes, string> paramWithCase = CompletionFactoryHelpers.GetParamsWithCase(textCase);
174+
Token callToken = codeElement.ConsumedTokens.First(t => t.TokenType == TokenType.CALL);
175+
Dictionary<ParameterDescription.PassingTypes, string> paramWithCase = CompletionFactoryHelpers.GetParamsUsingMatchingCase(callToken);
176176
//If signature of procedure is available
177177
if (_procedureSignatureContext != null)
178178
{

TypeCobol.LanguageServer/Processor/CompletionProcessor.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ public List<CompletionItem> ComputeProposals(CompilationUnit compilationUnit, Po
3232

3333
if (lastSignificantToken != null)
3434
{
35-
3635
switch (lastSignificantToken.TokenType)
36+
/*
37+
* WARNING: when adding completion support for a new keyword, do not forget
38+
* to reference the new keyword in CodeElementMatcher static class!
39+
*/
3740
{
3841
case TokenType.PERFORM:
3942
items = new CompletionAfterPerform(userFilterToken).ComputeProposals(compilationUnit, matchingCodeElement);

TypeCobol.LanguageServer/SignatureHelper/ProcedureSignatureHelper.cs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
6-
using TypeCobol.Compiler.CodeElements.Expressions;
1+
using TypeCobol.Compiler.CodeElements.Expressions;
72
using TypeCobol.Compiler.Nodes;
83
using TypeCobol.Compiler.Scanner;
94
using TypeCobol.LanguageServer.VsCodeProtocol;
@@ -121,7 +116,6 @@ public static SignatureInformation SignatureHelperSignatureFormatter(FunctionDec
121116
return activeParameter;
122117
}
123118

124-
125119
public static int ParametersTester(IList<ParameterDescription> parameters, List<string> parameters2, Node node)
126120
{
127121
var length = Math.Min(parameters.Count, parameters2.Count);
@@ -145,5 +139,45 @@ private static bool IsParameterCompatible(ParameterDescription parameter1, DataD
145139
{
146140
return parameter1.DataType == parameter2.DataType;
147141
}
142+
143+
/// <summary>
144+
/// Re-arrange parameters tokens to get their names in URI-style.
145+
/// </summary>
146+
/// <param name="parametersTokens">Set of tokens describing the procedure parameters list for a single
147+
/// passing direction (INPUT, IN-OUT or OUTPUT).</param>
148+
/// <returns>Enumeration of parameter names.</returns>
149+
public static IEnumerable<string> CollectParameters(IEnumerable<Token> parametersTokens)
150+
{
151+
var aggregatedTokens = new Stack<string>();
152+
153+
Token previousToken = null;
154+
foreach (var token in parametersTokens)
155+
{
156+
if (previousToken != null && previousToken.TokenType == TokenType.UserDefinedWord)
157+
{
158+
if (token.TokenType != TokenType.QualifiedNameSeparator)
159+
{
160+
aggregatedTokens.Push(token.Text);
161+
}
162+
else if (previousToken.TokenType == TokenType.UserDefinedWord)
163+
{
164+
var retainedString = aggregatedTokens.Pop();
165+
aggregatedTokens.Push(retainedString + ".");
166+
}
167+
}
168+
else if (previousToken != null && previousToken.TokenType == TokenType.QualifiedNameSeparator)
169+
{
170+
var retainedString = aggregatedTokens.Pop();
171+
aggregatedTokens.Push(retainedString + token.Text);
172+
}
173+
174+
if (previousToken == null && token.TokenType == TokenType.UserDefinedWord)
175+
aggregatedTokens.Push(token.Text);
176+
177+
previousToken = token;
178+
}
179+
180+
return aggregatedTokens.Reverse();
181+
}
148182
}
149183
}

TypeCobol.LanguageServer/TypeCobolServer.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ protected override SignatureHelp OnSignatureHelp(TextDocumentPosition parameters
572572
if (wrappedCodeElement == null) //No codeelements found
573573
return null;
574574

575-
var node = CompletionFactoryHelpers.GetMatchingNode(docContext.FileCompiler, wrappedCodeElement);
575+
var node = CompletionContext.GetMatchingNode(docContext.FileCompiler.CompilationResultsForProgram, wrappedCodeElement);
576576

577577
//Get procedure name or qualified name
578578
string procedureName = CompletionFactoryHelpers.GetProcedureNameFromTokens(wrappedCodeElement.ArrangedConsumedTokens);
@@ -607,20 +607,20 @@ protected override SignatureHelp OnSignatureHelp(TextDocumentPosition parameters
607607
//Else try to find the best matching signature
608608

609609
//Get all given INPUT
610-
var givenInputParameters = CompletionFactoryHelpers.AggregateTokens(
610+
var givenInputParameters = ProcedureSignatureHelper.CollectParameters(
611611
wrappedCodeElement.ArrangedConsumedTokens.SkipWhile(t => t.TokenType != TokenType.INPUT)
612612
.Skip(1) //Ignore the INPUT Token
613613
.TakeWhile(t => !(t.TokenType == TokenType.OUTPUT || t.TokenType == TokenType.IN_OUT))).ToList();
614614
//Get all given OUTPUT
615-
var givenOutputParameters = CompletionFactoryHelpers.AggregateTokens(
616-
wrappedCodeElement.ArrangedConsumedTokens.SkipWhile(t => t.TokenType != TokenType.OUTPUT)
617-
.Skip(1) //Ignore the INPUT Token
618-
.TakeWhile(t => !(t.TokenType == TokenType.INPUT || t.TokenType == TokenType.IN_OUT))).ToList();
619-
//Get all given INOUT
620-
var givenInoutParameters = CompletionFactoryHelpers.AggregateTokens(
621-
wrappedCodeElement.ArrangedConsumedTokens.SkipWhile(t => t.TokenType != TokenType.IN_OUT)
622-
.Skip(1) //Ignore the INPUT Token
623-
.TakeWhile(t => !(t.TokenType == TokenType.OUTPUT || t.TokenType == TokenType.INPUT))).ToList();
615+
var givenOutputParameters = ProcedureSignatureHelper.CollectParameters(
616+
wrappedCodeElement.ArrangedConsumedTokens.SkipWhile(t => t.TokenType != TokenType.OUTPUT)
617+
.Skip(1) //Ignore the OUTPUT Token
618+
.TakeWhile(t => !(t.TokenType == TokenType.INPUT || t.TokenType == TokenType.IN_OUT))).ToList();
619+
//Get all given IN-OUT
620+
var givenInoutParameters = ProcedureSignatureHelper.CollectParameters(
621+
wrappedCodeElement.ArrangedConsumedTokens.SkipWhile(t => t.TokenType != TokenType.IN_OUT)
622+
.Skip(1) //Ignore the IN-OUT Token
623+
.TakeWhile(t => !(t.TokenType == TokenType.OUTPUT || t.TokenType == TokenType.INPUT))).ToList();
624624
var totalGivenParameters = givenInputParameters.Count + givenInoutParameters.Count + givenOutputParameters.Count;
625625

626626
var signatureInformation = new List<SignatureInformation>();

0 commit comments

Comments
 (0)