diff --git a/TypeCobol.LanguageServer.Test/RefactoringTest.cs b/TypeCobol.LanguageServer.Test/RefactoringTest.cs
index 54cbcd458..2f6674193 100644
--- a/TypeCobol.LanguageServer.Test/RefactoringTest.cs
+++ b/TypeCobol.LanguageServer.Test/RefactoringTest.cs
@@ -4,7 +4,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TypeCobol.Compiler;
using TypeCobol.Compiler.Directives;
-using TypeCobol.LanguageServer.Commands.Refactor;
+using TypeCobol.LanguageServer.Commands;
using TypeCobol.LanguageServer.Test.RefactoringTests;
using TypeCobol.LanguageServer.VsCodeProtocol;
using TypeCobol.Test.Utils;
@@ -71,6 +71,8 @@ public FixedTextEditGenerator()
public string ExpectedResult { get; }
+ public IEnvironmentVariableProvider EnvironmentVariableProvider { get; set; }
+
public TextDocumentIdentifier PrepareRefactoring(object[] arguments)
{
return new TextDocumentIdentifier(){ uri = nameof(FixedTextEditGenerator) };
@@ -123,8 +125,8 @@ private static void TestDirectory([CallerMemberName] string directoryName = null
string path = Path.GetFullPath("RefactoringTests");
path = Path.Combine(path, directoryName);
- var failedTests = new List<(string TestName, Exception Exception)>();
- foreach (var testFile in Directory.GetFiles(path))
+ var failedTests = new List<(string TestFile, Exception Exception)>();
+ foreach (var testFile in Directory.GetFiles(path, "*.*", SearchOption.AllDirectories))
{
try
{
@@ -133,8 +135,7 @@ private static void TestDirectory([CallerMemberName] string directoryName = null
}
catch (Exception exception)
{
- string testName = Path.GetFileNameWithoutExtension(testFile);
- failedTests.Add((testName, exception));
+ failedTests.Add((testFile, exception));
}
}
@@ -144,7 +145,7 @@ private static void TestDirectory([CallerMemberName] string directoryName = null
Console.WriteLine();
foreach (var failedTest in failedTests)
{
- Console.WriteLine($"[{failedTest.TestName}] failed !");
+ Console.WriteLine($"[{failedTest.TestFile}] failed !");
Console.WriteLine(failedTest.Exception.Message);
Console.WriteLine();
}
@@ -164,5 +165,8 @@ public void TestServerSideApplyEdit()
[TestMethod]
public void AdjustFillers() => TestDirectory();
+
+ [TestMethod]
+ public void InsertVariableDisplay() => TestDirectory();
}
}
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/AdjustFillers/SimpleTest.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/AdjustFillers/SimpleTest.cbl
index 4e86ecead..c32c97f4c 100644
--- a/TypeCobol.LanguageServer.Test/RefactoringTests/AdjustFillers/SimpleTest.cbl
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/AdjustFillers/SimpleTest.cbl
@@ -18,11 +18,11 @@
GOBACK
.
END PROGRAM TCOMFL06.
----------------------------------------------------------------------------
-TypeCobol.LanguageServer.Commands.Refactor.AdjustFillerRefactoringProcessor
----------------------------------------------------------------------------
+---------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.AdjustFillers.AdjustFillersRefactoringProcessor
+---------------------------------------------------------------------------------
[{"uri":"file:/test.expected.cbl"}]
----------------------------------------------------------------------------
+---------------------------------------------------------------------------------
refactoring.label=Adjust FILLERs: 3 FILLER(s) modified
refactoring.source=
IDENTIFICATION DIVISION.
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/National/SimpleTest.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/National/SimpleTest.cbl
new file mode 100644
index 000000000..e65cd627c
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/National/SimpleTest.cbl
@@ -0,0 +1,42 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 var1 PIC N(200).
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 5, "character": 26 }
+ },
+ false,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 0, "name": "var1"
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 var1 PIC N(200).
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D DISPLAY 'var1 <' FUNCTION DISPLAY-OF (var1) '>'
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/National/WithGroups.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/National/WithGroups.cbl
new file mode 100644
index 000000000..e3fc2f59f
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/National/WithGroups.cbl
@@ -0,0 +1,83 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 group1.
+ 05 group1-1.
+ 10 var1 PIC N.
+ 10 var2 PIC N.
+ 01 group2.
+ 05 group2-1.
+ 10 var3 PIC N.
+ 10 var4 PIC N.
+ 10 var5 PIC N.
+ 05 group2-2.
+ 10 var6 PIC N.
+ 10 var7 PIc N.
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 16, "character": 26 }
+ },
+ false,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 1, "name": "group1", "ch": [
+ {
+ "vm": 1, "name": "group1-1", "ch": [
+ {
+ "vm": 0, "name": "var1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "vm": 2, "name": "group2"
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 group1.
+ 05 group1-1.
+ 10 var1 PIC N.
+ 10 var2 PIC N.
+ 01 group2.
+ 05 group2-1.
+ 10 var3 PIC N.
+ 10 var4 PIC N.
+ 10 var5 PIC N.
+ 05 group2-2.
+ 10 var6 PIC N.
+ 10 var7 PIc N.
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D DISPLAY ' var1 <' FUNCTION DISPLAY-OF (var1) '>'
+ D DISPLAY 'group2'
+ D DISPLAY ' group2-1'
+ D DISPLAY ' var3 <' FUNCTION DISPLAY-OF (var3) '>'
+ D DISPLAY ' var4 <' FUNCTION DISPLAY-OF (var4) '>'
+ D DISPLAY ' var5 <' FUNCTION DISPLAY-OF (var5) '>'
+ D DISPLAY ' group2-2'
+ D DISPLAY ' var6 <' FUNCTION DISPLAY-OF (var6) '>'
+ D DISPLAY ' var7 <' FUNCTION DISPLAY-OF (var7) '>'
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/FillersInAnonymousOccurs.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/FillersInAnonymousOccurs.cbl
new file mode 100644
index 000000000..f8830e6b2
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/FillersInAnonymousOccurs.cbl
@@ -0,0 +1,88 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 three-levels.
+ 05 OCCURS 1000.
+ 10 OCCURS 200.
+ 15 OCCURS 30.
+ 20 var1 PIC X.
+ 20 FILLER PIC X(20).
+ 20 var2 PIC X.
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 11, "character": 26 }
+ },
+ false,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 1, "name": "three-levels", "ch": [
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 0, "idx": 1
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 three-levels.
+ 05 OCCURS 1000.
+ 10 OCCURS 200.
+ 15 OCCURS 30.
+ 20 var1 PIC X.
+ 20 FILLER PIC X(20).
+ 20 var2 PIC X.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D77 Idx-d4df4249-1 PIC 9(4) COMP-5.
+ D77 Idx-d4df4249-2 PIC 9(4) COMP-5.
+ D77 Idx-d4df4249-3 PIC 9(4) COMP-5.
+ *
+
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D PERFORM VARYING Idx-d4df4249-1 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-1 > 1000
+ D PERFORM VARYING Idx-d4df4249-2 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-2 > 200
+ D PERFORM VARYING Idx-d4df4249-3 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-3 > 30
+ D DISPLAY ' FILLER (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ' ' Idx-d4df4249-3 ') <' three-levels (2
+ D + (Idx-d4df4249-3 - 1) * 22 + (Idx-d4df4249-2 - 1) *
+ D 660 + (Idx-d4df4249-1 - 1) * 132000:20) '>'
+ D END-PERFORM
+ D END-PERFORM
+ D END-PERFORM
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/FillersInOccursNoDependingOn.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/FillersInOccursNoDependingOn.cbl
new file mode 100644
index 000000000..fca015c02
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/FillersInOccursNoDependingOn.cbl
@@ -0,0 +1,88 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 three-levels.
+ 05 root-table-3 OCCURS 1000.
+ 10 table-level1-2 OCCURS 200.
+ 15 table-level2-1 OCCURS 30.
+ 20 var1 PIC X.
+ 20 FILLER PIC X(20).
+ 20 var2 PIC X.
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 11, "character": 26 }
+ },
+ false,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 1, "name": "three-levels", "ch": [
+ {
+ "vm": 1, "name": "root-table-3", "ch": [
+ {
+ "vm": 1, "name": "table-level1-2", "ch": [
+ {
+ "vm": 1, "name": "table-level2-1", "ch": [
+ {
+ "vm": 0, "idx": 1
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 three-levels.
+ 05 root-table-3 OCCURS 1000.
+ 10 table-level1-2 OCCURS 200.
+ 15 table-level2-1 OCCURS 30.
+ 20 var1 PIC X.
+ 20 FILLER PIC X(20).
+ 20 var2 PIC X.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D77 Idx-d4df4249-1 PIC 9(4) COMP-5.
+ D77 Idx-d4df4249-2 PIC 9(4) COMP-5.
+ D77 Idx-d4df4249-3 PIC 9(4) COMP-5.
+ *
+
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D PERFORM VARYING Idx-d4df4249-1 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-1 > 1000
+ D PERFORM VARYING Idx-d4df4249-2 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-2 > 200
+ D PERFORM VARYING Idx-d4df4249-3 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-3 > 30
+ D DISPLAY ' FILLER (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ' ' Idx-d4df4249-3 ') (2:20) <'
+ D table-level2-1 (Idx-d4df4249-1 Idx-d4df4249-2
+ D Idx-d4df4249-3) (2:20) '>'
+ D END-PERFORM
+ D END-PERFORM
+ D END-PERFORM
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/FillersInOccursWithDependingOn.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/FillersInOccursWithDependingOn.cbl
new file mode 100644
index 000000000..ff4e784df
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/FillersInOccursWithDependingOn.cbl
@@ -0,0 +1,117 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 three-levels.
+ 05 root-table-3-count PIC S9(4) BINARY.
+ 05 table-level1-2-count PIC S9(3) BINARY.
+ 05 table-level2-1-count PIC S9(2) BINARY.
+ 05 root-table-3 OCCURS 1000
+ DEPENDING ON root-table-3-count.
+ 10 table-level1-2 OCCURS 200
+ DEPENDING ON table-level1-2-count.
+ 15 table-level2-1 OCCURS 30
+ DEPENDING ON table-level2-1-count.
+ 20 var1 PIC X.
+ 20 FILLER PIC X(20).
+ 20 var2 PIC X.
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 17, "character": 26 }
+ },
+ false,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 1, "name": "three-levels", "ch": [
+ {
+ "vm": 1, "name": "root-table-3", "ch": [
+ {
+ "vm": 1, "name": "table-level1-2", "ch": [
+ {
+ "vm": 1, "name": "table-level2-1", "ch": [
+ {
+ "vm": 0, "idx": 1
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 three-levels.
+ 05 root-table-3-count PIC S9(4) BINARY.
+ 05 table-level1-2-count PIC S9(3) BINARY.
+ 05 table-level2-1-count PIC S9(2) BINARY.
+ 05 root-table-3 OCCURS 1000
+ DEPENDING ON root-table-3-count.
+ 10 table-level1-2 OCCURS 200
+ DEPENDING ON table-level1-2-count.
+ 15 table-level2-1 OCCURS 30
+ DEPENDING ON table-level2-1-count.
+ 20 var1 PIC X.
+ 20 FILLER PIC X(20).
+ 20 var2 PIC X.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D77 Idx-d4df4249-1 PIC 9(4) COMP-5.
+ D77 Idx-d4df4249-2 PIC 9(4) COMP-5.
+ D77 Idx-d4df4249-3 PIC 9(4) COMP-5.
+ *
+
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D IF root-table-3-count >= 1 AND <= 1000
+ D PERFORM VARYING Idx-d4df4249-1 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-1 > root-table-3-count
+ D IF table-level1-2-count >= 1 AND <= 200
+ D PERFORM VARYING Idx-d4df4249-2 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-2 > table-level1-2-count
+ D IF table-level2-1-count >= 1 AND <= 30
+ D PERFORM VARYING Idx-d4df4249-3 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-3 > table-level2-1-count
+ D DISPLAY ' FILLER (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ' ' Idx-d4df4249-3 ') (2:20) <'
+ D table-level2-1 (Idx-d4df4249-1 Idx-d4df4249-2
+ D Idx-d4df4249-3) (2:20) '>'
+ D END-PERFORM
+ D ELSE
+ D DISPLAY 'Cannot DISPLAY "table-level2-1" because i'
+ D 'ts DEPENDING ON "table-level2-1-count" is'
+ D ' out of range.'
+ D END-IF
+ D END-PERFORM
+ D ELSE
+ D DISPLAY 'Cannot DISPLAY "table-level1-2" because its D'
+ D 'EPENDING ON "table-level1-2-count" is out of '
+ D 'range.'
+ D END-IF
+ D END-PERFORM
+ D ELSE
+ D DISPLAY 'Cannot DISPLAY "root-table-3" because its DEPENDI'
+ D 'NG ON "root-table-3-count" is out of range.'
+ D END-IF
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/NationalInOccursNoDependingOn.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/NationalInOccursNoDependingOn.cbl
new file mode 100644
index 000000000..4a612c1d4
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/NationalInOccursNoDependingOn.cbl
@@ -0,0 +1,88 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 three-levels.
+ 05 root-table-3 OCCURS 1000.
+ 10 table-level1-2 OCCURS 200.
+ 15 table-level2-1 OCCURS 30.
+ 20 var1 PIC X.
+ 20 var-national PIC N(20).
+ 20 var2 PIC X.
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 11, "character": 26 }
+ },
+ false,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 1, "name": "three-levels", "ch": [
+ {
+ "vm": 1, "name": "root-table-3", "ch": [
+ {
+ "vm": 1, "name": "table-level1-2", "ch": [
+ {
+ "vm": 1, "name": "table-level2-1", "ch": [
+ {
+ "vm": 0, "name": "var-national"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 three-levels.
+ 05 root-table-3 OCCURS 1000.
+ 10 table-level1-2 OCCURS 200.
+ 15 table-level2-1 OCCURS 30.
+ 20 var1 PIC X.
+ 20 var-national PIC N(20).
+ 20 var2 PIC X.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D77 Idx-d4df4249-1 PIC 9(4) COMP-5.
+ D77 Idx-d4df4249-2 PIC 9(4) COMP-5.
+ D77 Idx-d4df4249-3 PIC 9(4) COMP-5.
+ *
+
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D PERFORM VARYING Idx-d4df4249-1 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-1 > 1000
+ D PERFORM VARYING Idx-d4df4249-2 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-2 > 200
+ D PERFORM VARYING Idx-d4df4249-3 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-3 > 30
+ D DISPLAY ' var-national (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ' ' Idx-d4df4249-3 ') <' FUNCTION
+ D DISPLAY-OF (var-national (Idx-d4df4249-1 Idx-d4df4249-2
+ D Idx-d4df4249-3)) '>'
+ D END-PERFORM
+ D END-PERFORM
+ D END-PERFORM
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/NationalInOccursWithDependingOn.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/NationalInOccursWithDependingOn.cbl
new file mode 100644
index 000000000..615ce7959
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/NationalInOccursWithDependingOn.cbl
@@ -0,0 +1,117 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 three-levels.
+ 05 root-table-3-count PIC S9(4) BINARY.
+ 05 table-level1-2-count PIC S9(3) BINARY.
+ 05 table-level2-1-count PIC S9(2) BINARY.
+ 05 root-table-3 OCCURS 1000
+ DEPENDING ON root-table-3-count.
+ 10 table-level1-2 OCCURS 200
+ DEPENDING ON table-level1-2-count.
+ 15 table-level2-1 OCCURS 30
+ DEPENDING ON table-level2-1-count.
+ 20 var1 PIC X.
+ 20 var-national PIC N(20).
+ 20 var2 PIC X.
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 17, "character": 26 }
+ },
+ false,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 1, "name": "three-levels", "ch": [
+ {
+ "vm": 1, "name": "root-table-3", "ch": [
+ {
+ "vm": 1, "name": "table-level1-2", "ch": [
+ {
+ "vm": 1, "name": "table-level2-1", "ch": [
+ {
+ "vm": 0, "name": "var-national"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 three-levels.
+ 05 root-table-3-count PIC S9(4) BINARY.
+ 05 table-level1-2-count PIC S9(3) BINARY.
+ 05 table-level2-1-count PIC S9(2) BINARY.
+ 05 root-table-3 OCCURS 1000
+ DEPENDING ON root-table-3-count.
+ 10 table-level1-2 OCCURS 200
+ DEPENDING ON table-level1-2-count.
+ 15 table-level2-1 OCCURS 30
+ DEPENDING ON table-level2-1-count.
+ 20 var1 PIC X.
+ 20 var-national PIC N(20).
+ 20 var2 PIC X.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D77 Idx-d4df4249-1 PIC 9(4) COMP-5.
+ D77 Idx-d4df4249-2 PIC 9(4) COMP-5.
+ D77 Idx-d4df4249-3 PIC 9(4) COMP-5.
+ *
+
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D IF root-table-3-count >= 1 AND <= 1000
+ D PERFORM VARYING Idx-d4df4249-1 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-1 > root-table-3-count
+ D IF table-level1-2-count >= 1 AND <= 200
+ D PERFORM VARYING Idx-d4df4249-2 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-2 > table-level1-2-count
+ D IF table-level2-1-count >= 1 AND <= 30
+ D PERFORM VARYING Idx-d4df4249-3 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-3 > table-level2-1-count
+ D DISPLAY ' var-national (' Idx-d4df4249-1
+ D ' ' Idx-d4df4249-2 ' ' Idx-d4df4249-3 ') <'
+ D FUNCTION DISPLAY-OF (var-national (Idx-d4df4249-1
+ D Idx-d4df4249-2 Idx-d4df4249-3)) '>'
+ D END-PERFORM
+ D ELSE
+ D DISPLAY 'Cannot DISPLAY "table-level2-1" because i'
+ D 'ts DEPENDING ON "table-level2-1-count" is'
+ D ' out of range.'
+ D END-IF
+ D END-PERFORM
+ D ELSE
+ D DISPLAY 'Cannot DISPLAY "table-level1-2" because its D'
+ D 'EPENDING ON "table-level1-2-count" is out of '
+ D 'range.'
+ D END-IF
+ D END-PERFORM
+ D ELSE
+ D DISPLAY 'Cannot DISPLAY "root-table-3" because its DEPENDI'
+ D 'NG ON "root-table-3-count" is out of range.'
+ D END-IF
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/OccursNoDependingOn.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/OccursNoDependingOn.cbl
new file mode 100644
index 000000000..df12e80fe
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/OccursNoDependingOn.cbl
@@ -0,0 +1,125 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 one-level.
+ 05 root-table-1 OCCURS 10.
+ 10 var1 PIC X.
+ 10 var2 PIC X.
+ 01 two-levels.
+ 05 root-table-2 OCCURS 100.
+ 10 table-level1-1 OCCURS 20.
+ 15 var3 PIC X.
+ 15 var4 PIC X.
+ 01 three-levels.
+ 05 root-table-3 OCCURS 1000.
+ 10 table-level1-2 OCCURS 200.
+ 15 table-level2-1 OCCURS 30.
+ 20 var5 PIC X.
+ 20 var6 PIC X.
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 19, "character": 26 }
+ },
+ false,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 2, "name": "one-level"
+ },
+ {
+ "vm": 2, "name": "two-levels"
+ },
+ {
+ "vm": 2, "name": "three-levels"
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 one-level.
+ 05 root-table-1 OCCURS 10.
+ 10 var1 PIC X.
+ 10 var2 PIC X.
+ 01 two-levels.
+ 05 root-table-2 OCCURS 100.
+ 10 table-level1-1 OCCURS 20.
+ 15 var3 PIC X.
+ 15 var4 PIC X.
+ 01 three-levels.
+ 05 root-table-3 OCCURS 1000.
+ 10 table-level1-2 OCCURS 200.
+ 15 table-level2-1 OCCURS 30.
+ 20 var5 PIC X.
+ 20 var6 PIC X.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D77 Idx-d4df4249-1 PIC 9(4) COMP-5.
+ D77 Idx-d4df4249-2 PIC 9(4) COMP-5.
+ D77 Idx-d4df4249-3 PIC 9(4) COMP-5.
+ *
+
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D DISPLAY 'one-level'
+ D PERFORM VARYING Idx-d4df4249-1 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-1 > 10
+ D DISPLAY ' root-table-1 (' Idx-d4df4249-1 ')'
+ D DISPLAY ' var1 (' Idx-d4df4249-1 ') <' var1
+ D (Idx-d4df4249-1) '>'
+ D DISPLAY ' var2 (' Idx-d4df4249-1 ') <' var2
+ D (Idx-d4df4249-1) '>'
+ D END-PERFORM
+ D DISPLAY 'two-levels'
+ D PERFORM VARYING Idx-d4df4249-1 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-1 > 100
+ D DISPLAY ' root-table-2 (' Idx-d4df4249-1 ')'
+ D PERFORM VARYING Idx-d4df4249-2 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-2 > 20
+ D DISPLAY ' table-level1-1 (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ')'
+ D DISPLAY ' var3 (' Idx-d4df4249-1 ' ' Idx-d4df4249-2
+ D ') <' var3 (Idx-d4df4249-1 Idx-d4df4249-2) '>'
+ D DISPLAY ' var4 (' Idx-d4df4249-1 ' ' Idx-d4df4249-2
+ D ') <' var4 (Idx-d4df4249-1 Idx-d4df4249-2) '>'
+ D END-PERFORM
+ D END-PERFORM
+ D DISPLAY 'three-levels'
+ D PERFORM VARYING Idx-d4df4249-1 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-1 > 1000
+ D DISPLAY ' root-table-3 (' Idx-d4df4249-1 ')'
+ D PERFORM VARYING Idx-d4df4249-2 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-2 > 200
+ D DISPLAY ' table-level1-2 (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ')'
+ D PERFORM VARYING Idx-d4df4249-3 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-3 > 30
+ D DISPLAY ' table-level2-1 (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ' ' Idx-d4df4249-3 ')'
+ D DISPLAY ' var5 (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ' ' Idx-d4df4249-3 ') <' var5
+ D (Idx-d4df4249-1 Idx-d4df4249-2 Idx-d4df4249-3) '>'
+ D DISPLAY ' var6 (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ' ' Idx-d4df4249-3 ') <' var6
+ D (Idx-d4df4249-1 Idx-d4df4249-2 Idx-d4df4249-3) '>'
+ D END-PERFORM
+ D END-PERFORM
+ D END-PERFORM
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/OccursWithDependingOn.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/OccursWithDependingOn.cbl
new file mode 100644
index 000000000..c14c67ff6
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/Occurs/OccursWithDependingOn.cbl
@@ -0,0 +1,192 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 one-level.
+ 05 root-table-1-count PIC S9(2) BINARY.
+ 05 root-table-1 OCCURS 10
+ DEPENDING ON root-table-1-count.
+ 10 var1 PIC X.
+ 10 var2 PIC X.
+ 01 two-levels.
+ 05 root-table-2-count PIC S9(3) BINARY.
+ 05 table-level1-1-count PIC S9(2) BINARY.
+ 05 root-table-2 OCCURS 100
+ DEPENDING ON root-table-2-count.
+ 10 table-level1-1 OCCURS 20
+ DEPENDING ON table-level1-1-count.
+ 15 var3 PIC X.
+ 15 var4 PIC X.
+ 01 three-levels.
+ 05 root-table-3-count PIC S9(4) BINARY.
+ 05 table-level1-2-count PIC S9(3) BINARY.
+ 05 table-level2-1-count PIC S9(2) BINARY.
+ 05 root-table-3 OCCURS 1000
+ DEPENDING ON root-table-3-count.
+ 10 table-level1-2 OCCURS 200
+ DEPENDING ON table-level1-2-count.
+ 15 table-level2-1 OCCURS 30
+ DEPENDING ON table-level2-1-count.
+ 20 var5 PIC X.
+ 20 var6 PIC X.
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 31, "character": 26 }
+ },
+ false,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 2, "name": "one-level"
+ },
+ {
+ "vm": 2, "name": "two-levels"
+ },
+ {
+ "vm": 2, "name": "three-levels"
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 one-level.
+ 05 root-table-1-count PIC S9(2) BINARY.
+ 05 root-table-1 OCCURS 10
+ DEPENDING ON root-table-1-count.
+ 10 var1 PIC X.
+ 10 var2 PIC X.
+ 01 two-levels.
+ 05 root-table-2-count PIC S9(3) BINARY.
+ 05 table-level1-1-count PIC S9(2) BINARY.
+ 05 root-table-2 OCCURS 100
+ DEPENDING ON root-table-2-count.
+ 10 table-level1-1 OCCURS 20
+ DEPENDING ON table-level1-1-count.
+ 15 var3 PIC X.
+ 15 var4 PIC X.
+ 01 three-levels.
+ 05 root-table-3-count PIC S9(4) BINARY.
+ 05 table-level1-2-count PIC S9(3) BINARY.
+ 05 table-level2-1-count PIC S9(2) BINARY.
+ 05 root-table-3 OCCURS 1000
+ DEPENDING ON root-table-3-count.
+ 10 table-level1-2 OCCURS 200
+ DEPENDING ON table-level1-2-count.
+ 15 table-level2-1 OCCURS 30
+ DEPENDING ON table-level2-1-count.
+ 20 var5 PIC X.
+ 20 var6 PIC X.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D77 Idx-d4df4249-1 PIC 9(4) COMP-5.
+ D77 Idx-d4df4249-2 PIC 9(4) COMP-5.
+ D77 Idx-d4df4249-3 PIC 9(4) COMP-5.
+ *
+
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D DISPLAY 'one-level'
+ D DISPLAY ' root-table-1-count <' root-table-1-count '>'
+ D IF root-table-1-count >= 1 AND <= 10
+ D PERFORM VARYING Idx-d4df4249-1 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-1 > root-table-1-count
+ D DISPLAY ' root-table-1 (' Idx-d4df4249-1 ')'
+ D DISPLAY ' var1 (' Idx-d4df4249-1 ') <' var1
+ D (Idx-d4df4249-1) '>'
+ D DISPLAY ' var2 (' Idx-d4df4249-1 ') <' var2
+ D (Idx-d4df4249-1) '>'
+ D END-PERFORM
+ D ELSE
+ D DISPLAY 'Cannot DISPLAY "root-table-1" because its DEPENDI'
+ D 'NG ON "root-table-1-count" is out of range.'
+ D END-IF
+ D DISPLAY 'two-levels'
+ D DISPLAY ' root-table-2-count <' root-table-2-count '>'
+ D DISPLAY ' table-level1-1-count <' table-level1-1-count '>'
+ D IF root-table-2-count >= 1 AND <= 100
+ D PERFORM VARYING Idx-d4df4249-1 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-1 > root-table-2-count
+ D DISPLAY ' root-table-2 (' Idx-d4df4249-1 ')'
+ D IF table-level1-1-count >= 1 AND <= 20
+ D PERFORM VARYING Idx-d4df4249-2 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-2 > table-level1-1-count
+ D DISPLAY ' table-level1-1 (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ')'
+ D DISPLAY ' var3 (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ') <' var3 (Idx-d4df4249-1
+ D Idx-d4df4249-2) '>'
+ D DISPLAY ' var4 (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ') <' var4 (Idx-d4df4249-1
+ D Idx-d4df4249-2) '>'
+ D END-PERFORM
+ D ELSE
+ D DISPLAY 'Cannot DISPLAY "table-level1-1" because its D'
+ D 'EPENDING ON "table-level1-1-count" is out of '
+ D 'range.'
+ D END-IF
+ D END-PERFORM
+ D ELSE
+ D DISPLAY 'Cannot DISPLAY "root-table-2" because its DEPENDI'
+ D 'NG ON "root-table-2-count" is out of range.'
+ D END-IF
+ D DISPLAY 'three-levels'
+ D DISPLAY ' root-table-3-count <' root-table-3-count '>'
+ D DISPLAY ' table-level1-2-count <' table-level1-2-count '>'
+ D DISPLAY ' table-level2-1-count <' table-level2-1-count '>'
+ D IF root-table-3-count >= 1 AND <= 1000
+ D PERFORM VARYING Idx-d4df4249-1 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-1 > root-table-3-count
+ D DISPLAY ' root-table-3 (' Idx-d4df4249-1 ')'
+ D IF table-level1-2-count >= 1 AND <= 200
+ D PERFORM VARYING Idx-d4df4249-2 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-2 > table-level1-2-count
+ D DISPLAY ' table-level1-2 (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ')'
+ D IF table-level2-1-count >= 1 AND <= 30
+ D PERFORM VARYING Idx-d4df4249-3 FROM 1 BY 1 UNTIL
+ D Idx-d4df4249-3 > table-level2-1-count
+ D DISPLAY ' table-level2-1 (' Idx-d4df4249-1
+ D ' ' Idx-d4df4249-2 ' ' Idx-d4df4249-3 ')'
+ D DISPLAY ' var5 (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ' ' Idx-d4df4249-3 ') <' var5
+ D (Idx-d4df4249-1 Idx-d4df4249-2 Idx-d4df4249-3)
+ D '>'
+ D DISPLAY ' var6 (' Idx-d4df4249-1 ' '
+ D Idx-d4df4249-2 ' ' Idx-d4df4249-3 ') <' var6
+ D (Idx-d4df4249-1 Idx-d4df4249-2 Idx-d4df4249-3)
+ D '>'
+ D END-PERFORM
+ D ELSE
+ D DISPLAY 'Cannot DISPLAY "table-level2-1" because i'
+ D 'ts DEPENDING ON "table-level2-1-count" is'
+ D ' out of range.'
+ D END-IF
+ D END-PERFORM
+ D ELSE
+ D DISPLAY 'Cannot DISPLAY "table-level1-2" because its D'
+ D 'EPENDING ON "table-level1-2-count" is out of '
+ D 'range.'
+ D END-IF
+ D END-PERFORM
+ D ELSE
+ D DISPLAY 'Cannot DISPLAY "root-table-3" because its DEPENDI'
+ D 'NG ON "root-table-3-count" is out of range.'
+ D END-IF
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/Fillers.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/Fillers.cbl
new file mode 100644
index 000000000..ffc3204c5
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/Fillers.cbl
@@ -0,0 +1,123 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 group1.
+ 05 group1-1.
+ 10 var1 PIC X.
+ 10 FILLER PIC X(20).
+ 10 var2 PIC X.
+ 01 group2.
+ 05 group2-1.
+ 10 var3 PIC X.
+ 10 PIC X.
+ 10 var5 PIC X.
+ 01 group3.
+ 05 var6 PIC X(10).
+ 05 .
+ 10 var7 PIC X(5).
+ 10 FILLER PIC X(20).
+ 01 .
+ 05 .
+ 10 var8 PIC X.
+ 10 FILLER PIC X(20).
+ 10 var9 PIC X.
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 24, "character": 26 }
+ },
+ false,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 1, "name": "group1", "ch": [
+ {
+ "vm": 1, "name": "group1-1", "ch": [
+ {
+ "vm": 0, "idx": 1
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "vm": 1, "name": "group2", "ch": [
+ {
+ "vm": 1, "name": "group2-1", "ch": [
+ {
+ "vm": 0, "idx": 1
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "vm": 1, "name": "group3", "ch": [
+ {
+ "vm": 1, "idx": 1, "ch": [
+ {
+ "vm": 0, "idx": 1
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "vm": 1, "idx": 3, "ch": [
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 0, "idx": 1
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 group1.
+ 05 group1-1.
+ 10 var1 PIC X.
+ 10 FILLER PIC X(20).
+ 10 var2 PIC X.
+ 01 group2.
+ 05 group2-1.
+ 10 var3 PIC X.
+ 10 PIC X.
+ 10 var5 PIC X.
+ 01 group3.
+ 05 var6 PIC X(10).
+ 05 .
+ 10 var7 PIC X(5).
+ 10 FILLER PIC X(20).
+ 01 .
+ 05 .
+ 10 var8 PIC X.
+ 10 FILLER PIC X(20).
+ 10 var9 PIC X.
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D DISPLAY ' FILLER (2:20) <' group1-1 (2:20) '>'
+ D DISPLAY ' FILLER (2:1) <' group2-1 (2:1) '>'
+ D DISPLAY ' FILLER <' group3 (16:20) '>'
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/NoModification.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/NoModification.cbl
new file mode 100644
index 000000000..77c782dae
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/NoModification.cbl
@@ -0,0 +1,54 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 .
+ 05 .
+ 10 var1 PIC X.
+ 10 FILLER PIC X(20).
+ 10 var2 PIC X.
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 9, "character": 26 }
+ },
+ false,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 0, "idx": 1
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=No modification.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 .
+ 05 .
+ 10 var1 PIC X.
+ 10 FILLER PIC X(20).
+ 10 var2 PIC X.
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/OtherDataSections.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/OtherDataSections.cbl
new file mode 100644
index 000000000..7b0a45140
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/OtherDataSections.cbl
@@ -0,0 +1,54 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 var-working PIC X(200).
+ LOCAL-STORAGE SECTION.
+ 01 var-local PIC X(200).
+ LINKAGE SECTION.
+ 01 var-linkage PIC X(200).
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 9, "character": 26 }
+ },
+ false,
+ {
+ "vm": 2, "idx": 0
+ },
+ {
+ "vm": 2, "idx": 1
+ },
+ {
+ "vm": 2, "idx": 2
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 var-working PIC X(200).
+ LOCAL-STORAGE SECTION.
+ 01 var-local PIC X(200).
+ LINKAGE SECTION.
+ 01 var-linkage PIC X(200).
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D DISPLAY 'var-working <' var-working '>'
+ D DISPLAY 'var-local <' var-local '>'
+ D DISPLAY 'var-linkage <' var-linkage '>'
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/SimpleTest.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/SimpleTest.cbl
new file mode 100644
index 000000000..bbbf3c8d9
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/SimpleTest.cbl
@@ -0,0 +1,42 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 var1 PIC X(200).
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 5, "character": 26 }
+ },
+ false,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 0, "name": "var1"
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 var1 PIC X(200).
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D DISPLAY 'var1 <' var1 '>'
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/WithGroups.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/WithGroups.cbl
new file mode 100644
index 000000000..326abbe1c
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleData/WithGroups.cbl
@@ -0,0 +1,102 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 group1.
+ 05 group1-1.
+ 10 var1 PIC X.
+ 10 var2 PIC X.
+ 01 group2.
+ 05 group2-1.
+ 10 var3 PIC X.
+ 10 var4 PIC X.
+ 10 var5 PIC X.
+ 05 group2-2.
+ 10 var6 PIC X.
+ 10 var7 PIc X.
+ 01 group3.
+ 05 group3-1.
+ 10 var8 PIC X.
+ 05 group3-2.
+ 10 var9 PIC X.
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 21, "character": 26 }
+ },
+ false,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 1, "name": "group1", "ch": [
+ {
+ "vm": 1, "name": "group1-1", "ch": [
+ {
+ "vm": 0, "name": "var1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "vm": 2, "name": "group2"
+ },
+ {
+ "vm": 0, "name": "group3", "ch": [
+ {
+ "vm": 0, "name": "group3-1"
+ }
+ ]
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 group1.
+ 05 group1-1.
+ 10 var1 PIC X.
+ 10 var2 PIC X.
+ 01 group2.
+ 05 group2-1.
+ 10 var3 PIC X.
+ 10 var4 PIC X.
+ 10 var5 PIC X.
+ 05 group2-2.
+ 10 var6 PIC X.
+ 10 var7 PIc X.
+ 01 group3.
+ 05 group3-1.
+ 10 var8 PIC X.
+ 05 group3-2.
+ 10 var9 PIC X.
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D DISPLAY ' var1 <' var1 '>'
+ D DISPLAY 'group2'
+ D DISPLAY ' group2-1'
+ D DISPLAY ' var3 <' var3 '>'
+ D DISPLAY ' var4 <' var4 '>'
+ D DISPLAY ' var5 <' var5 '>'
+ D DISPLAY ' group2-2'
+ D DISPLAY ' var6 <' var6 '>'
+ D DISPLAY ' var7 <' var7 '>'
+ D DISPLAY 'group3'
+ D DISPLAY ' group3-1 <' group3-1 '>'
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleDataInsertBefore/Fillers.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleDataInsertBefore/Fillers.cbl
new file mode 100644
index 000000000..8533cb911
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleDataInsertBefore/Fillers.cbl
@@ -0,0 +1,123 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 group1.
+ 05 group1-1.
+ 10 var1 PIC X.
+ 10 FILLER PIC X(20).
+ 10 var2 PIC X.
+ 01 group2.
+ 05 group2-1.
+ 10 var3 PIC X.
+ 10 PIC X.
+ 10 var5 PIC X.
+ 01 group3.
+ 05 var6 PIC X(10).
+ 05 .
+ 10 var7 PIC X(5).
+ 10 FILLER PIC X(20).
+ 01 .
+ 05 .
+ 10 var8 PIC X.
+ 10 FILLER PIC X(20).
+ 10 var9 PIC X.
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 25, "character": 11 }
+ },
+ true,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 1, "name": "group1", "ch": [
+ {
+ "vm": 1, "name": "group1-1", "ch": [
+ {
+ "vm": 0, "idx": 1
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "vm": 1, "name": "group2", "ch": [
+ {
+ "vm": 1, "name": "group2-1", "ch": [
+ {
+ "vm": 0, "idx": 1
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "vm": 1, "name": "group3", "ch": [
+ {
+ "vm": 1, "idx": 1, "ch": [
+ {
+ "vm": 0, "idx": 1
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "vm": 1, "idx": 3, "ch": [
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 0, "idx": 1
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 group1.
+ 05 group1-1.
+ 10 var1 PIC X.
+ 10 FILLER PIC X(20).
+ 10 var2 PIC X.
+ 01 group2.
+ 05 group2-1.
+ 10 var3 PIC X.
+ 10 PIC X.
+ 10 var5 PIC X.
+ 01 group3.
+ 05 var6 PIC X(10).
+ 05 .
+ 10 var7 PIC X(5).
+ 10 FILLER PIC X(20).
+ 01 .
+ 05 .
+ 10 var8 PIC X.
+ 10 FILLER PIC X(20).
+ 10 var9 PIC X.
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D DISPLAY ' FILLER (2:20) <' group1-1 (2:20) '>'
+ D DISPLAY ' FILLER (2:1) <' group2-1 (2:1) '>'
+ D DISPLAY ' FILLER <' group3 (16:20) '>'
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleDataInsertBefore/OtherDataSections.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleDataInsertBefore/OtherDataSections.cbl
new file mode 100644
index 000000000..e3b0869aa
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleDataInsertBefore/OtherDataSections.cbl
@@ -0,0 +1,54 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 var-working PIC X(200).
+ LOCAL-STORAGE SECTION.
+ 01 var-local PIC X(200).
+ LINKAGE SECTION.
+ 01 var-linkage PIC X(200).
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 10, "character": 11 }
+ },
+ true,
+ {
+ "vm": 2, "idx": 0
+ },
+ {
+ "vm": 2, "idx": 1
+ },
+ {
+ "vm": 2, "idx": 2
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 var-working PIC X(200).
+ LOCAL-STORAGE SECTION.
+ 01 var-local PIC X(200).
+ LINKAGE SECTION.
+ 01 var-linkage PIC X(200).
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D DISPLAY 'var-working <' var-working '>'
+ D DISPLAY 'var-local <' var-local '>'
+ D DISPLAY 'var-linkage <' var-linkage '>'
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleDataInsertBefore/SimpleTest.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleDataInsertBefore/SimpleTest.cbl
new file mode 100644
index 000000000..f0d835dd7
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleDataInsertBefore/SimpleTest.cbl
@@ -0,0 +1,42 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 var1 PIC X(200).
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 6, "character": 11 }
+ },
+ true,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 0, "name": "var1"
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 var1 PIC X(200).
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D DISPLAY 'var1 <' var1 '>'
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleDataInsertBefore/WithGroups.cbl b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleDataInsertBefore/WithGroups.cbl
new file mode 100644
index 000000000..cfb3e1975
--- /dev/null
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/InsertVariableDisplay/SimpleDataInsertBefore/WithGroups.cbl
@@ -0,0 +1,102 @@
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 group1.
+ 05 group1-1.
+ 10 var1 PIC X.
+ 10 var2 PIC X.
+ 01 group2.
+ 05 group2-1.
+ 10 var3 PIC X.
+ 10 var4 PIC X.
+ 10 var5 PIC X.
+ 05 group2-2.
+ 10 var6 PIC X.
+ 10 var7 PIc X.
+ 01 group3.
+ 05 group3-1.
+ 10 var8 PIC X.
+ 05 group3-2.
+ 10 var9 PIC X.
+ PROCEDURE DIVISION.
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
+-------------------------------------------------------------------------------------------------
+TypeCobol.LanguageServer.Commands.InsertVariableDisplay.InsertVariableDisplayRefactoringProcessor
+-------------------------------------------------------------------------------------------------
+[
+ {
+ "textDocument": { "uri": "file:/test.expected.cbl" },
+ "position": { "line": 22, "character": 11 }
+ },
+ true,
+ {
+ "vm": 1, "idx": 0, "ch": [
+ {
+ "vm": 1, "name": "group1", "ch": [
+ {
+ "vm": 1, "name": "group1-1", "ch": [
+ {
+ "vm": 0, "name": "var1"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "vm": 2, "name": "group2"
+ },
+ {
+ "vm": 0, "name": "group3", "ch": [
+ {
+ "vm": 0, "name": "group3-1"
+ }
+ ]
+ }
+ ]
+ }
+]
+-------------------------------------------------------------------------------------------------
+refactoring.label=Debug instructions successfully generated.
+refactoring.source=
+ IDENTIFICATION DIVISION.
+ PROGRAM-ID. TCOMFL06.
+ DATA DIVISION.
+ WORKING-STORAGE SECTION.
+ 01 group1.
+ 05 group1-1.
+ 10 var1 PIC X.
+ 10 var2 PIC X.
+ 01 group2.
+ 05 group2-1.
+ 10 var3 PIC X.
+ 10 var4 PIC X.
+ 10 var5 PIC X.
+ 05 group2-2.
+ 10 var6 PIC X.
+ 10 var7 PIc X.
+ 01 group3.
+ 05 group3-1.
+ 10 var8 PIC X.
+ 05 group3-2.
+ 10 var9 PIC X.
+ PROCEDURE DIVISION.
+ *InsertVariableDisplay 1959/09/18 11:09 TESTUSER
+ D DISPLAY ' var1 <' var1 '>'
+ D DISPLAY 'group2'
+ D DISPLAY ' group2-1'
+ D DISPLAY ' var3 <' var3 '>'
+ D DISPLAY ' var4 <' var4 '>'
+ D DISPLAY ' var5 <' var5 '>'
+ D DISPLAY ' group2-2'
+ D DISPLAY ' var6 <' var6 '>'
+ D DISPLAY ' var7 <' var7 '>'
+ D DISPLAY 'group3'
+ D DISPLAY ' group3-1 <' group3-1 '>'
+ *
+
+ GOBACK
+ .
+ END PROGRAM TCOMFL06.
\ No newline at end of file
diff --git a/TypeCobol.LanguageServer.Test/RefactoringTests/RefactoringProcessorTest.cs b/TypeCobol.LanguageServer.Test/RefactoringTests/RefactoringProcessorTest.cs
index 4400c21c7..e99f3eb27 100644
--- a/TypeCobol.LanguageServer.Test/RefactoringTests/RefactoringProcessorTest.cs
+++ b/TypeCobol.LanguageServer.Test/RefactoringTests/RefactoringProcessorTest.cs
@@ -3,7 +3,7 @@
using Newtonsoft.Json.Linq;
using TypeCobol.Compiler;
using TypeCobol.Compiler.Directives;
-using TypeCobol.LanguageServer.Commands.Refactor;
+using TypeCobol.LanguageServer.Commands;
using TypeCobol.LanguageServer.Test.Utilities;
using TypeCobol.LanguageServer.VsCodeProtocol;
using TypeCobol.Test;
@@ -13,6 +13,24 @@ namespace TypeCobol.LanguageServer.Test.RefactoringTests
{
internal class RefactoringProcessorTest
{
+ ///
+ /// Mocks environment variables to get consistent results during unit tests.
+ ///
+ private class TestEnvironment : IEnvironmentVariableProvider
+
+ {
+ public static readonly TestEnvironment Instance = new TestEnvironment();
+
+ private TestEnvironment()
+ {
+
+ }
+
+ public DateTime Now => new DateTime(new DateOnly(1959, 9, 18), new TimeOnly(11, 9)); // It's always time to reinvent COBOL when doing unit tests !
+
+ public string UserName => "TESTUSER";
+ }
+
private static readonly Dictionary _RefactoringProcessors;
static RefactoringProcessorTest()
@@ -24,10 +42,12 @@ static RefactoringProcessorTest()
var assembly = refactoringProcessorType.Assembly; // Consider types in TypeCobol.LanguageServer assembly
foreach (var type in assembly.GetTypes())
{
- if (type.GetInterfaces().Contains(refactoringProcessorType))
+ if (!type.IsAbstract && type.GetInterfaces().Contains(refactoringProcessorType))
{
- // Found a type implementing directly IRefactoringProcessor
+ // Found a concrete type implementing IRefactoringProcessor
var instance = (IRefactoringProcessor)Activator.CreateInstance(type); // Expecting a parameterless constructor
+ Debug.Assert(instance != null);
+ instance.EnvironmentVariableProvider = TestEnvironment.Instance; // Use controlled environment variables for testing
Debug.Assert(type.FullName != null);
_RefactoringProcessors.Add(type.FullName, instance);
}
diff --git a/TypeCobol.LanguageServer.Test/Utilities/LanguageServerTestUtils.cs b/TypeCobol.LanguageServer.Test/Utilities/LanguageServerTestUtils.cs
index 318f4bfc3..e5afa4706 100644
--- a/TypeCobol.LanguageServer.Test/Utilities/LanguageServerTestUtils.cs
+++ b/TypeCobol.LanguageServer.Test/Utilities/LanguageServerTestUtils.cs
@@ -23,7 +23,7 @@ public static List ParseMultiplePartsContent(string filePath)
{
while (reader.ReadLine() is { } line) // Non-null pattern + variable definition
{
- if (line.All(c => c == '-'))
+ if (line.Length > 0 && line.All(c => c == '-'))
{
// The line is a separator, flush content read so far into a part
parts.Add(builder.ToString()[..^Environment.NewLine.Length]); // Remove trailing line break
diff --git a/TypeCobol.LanguageServer/Commands/Refactor/AbstractSingleDocumentRefactoring.cs b/TypeCobol.LanguageServer/Commands/AbstractSingleDocumentRefactoring.cs
similarity index 98%
rename from TypeCobol.LanguageServer/Commands/Refactor/AbstractSingleDocumentRefactoring.cs
rename to TypeCobol.LanguageServer/Commands/AbstractSingleDocumentRefactoring.cs
index e8d601166..1606cae4a 100644
--- a/TypeCobol.LanguageServer/Commands/Refactor/AbstractSingleDocumentRefactoring.cs
+++ b/TypeCobol.LanguageServer/Commands/AbstractSingleDocumentRefactoring.cs
@@ -1,7 +1,7 @@
using TypeCobol.Compiler;
using TypeCobol.LanguageServer.VsCodeProtocol;
-namespace TypeCobol.LanguageServer.Commands.Refactor
+namespace TypeCobol.LanguageServer.Commands
{
///
/// Base class for Cobol document refactorings. This specialization of AbstractCommand
diff --git a/TypeCobol.LanguageServer/Commands/Refactor/AdjustFillers.cs b/TypeCobol.LanguageServer/Commands/AdjustFillers/AdjustFillers.cs
similarity index 74%
rename from TypeCobol.LanguageServer/Commands/Refactor/AdjustFillers.cs
rename to TypeCobol.LanguageServer/Commands/AdjustFillers/AdjustFillers.cs
index 56b68c33d..a0e6bc8cc 100644
--- a/TypeCobol.LanguageServer/Commands/Refactor/AdjustFillers.cs
+++ b/TypeCobol.LanguageServer/Commands/AdjustFillers/AdjustFillers.cs
@@ -1,6 +1,6 @@
-namespace TypeCobol.LanguageServer.Commands.Refactor
+namespace TypeCobol.LanguageServer.Commands.AdjustFillers
{
- internal class AdjustFillers : AbstractSingleDocumentRefactoring
+ internal class AdjustFillers : AbstractSingleDocumentRefactoring
{
public static AdjustFillers Create(TypeCobolServer typeCobolServer) => new(typeCobolServer);
diff --git a/TypeCobol.LanguageServer/Commands/Refactor/AdjustFillerRefactoringProcessor.cs b/TypeCobol.LanguageServer/Commands/AdjustFillers/AdjustFillersRefactoringProcessor.cs
similarity index 93%
rename from TypeCobol.LanguageServer/Commands/Refactor/AdjustFillerRefactoringProcessor.cs
rename to TypeCobol.LanguageServer/Commands/AdjustFillers/AdjustFillersRefactoringProcessor.cs
index 42fa4b25b..ef5798e4d 100644
--- a/TypeCobol.LanguageServer/Commands/Refactor/AdjustFillerRefactoringProcessor.cs
+++ b/TypeCobol.LanguageServer/Commands/AdjustFillers/AdjustFillersRefactoringProcessor.cs
@@ -1,6 +1,5 @@
using System.Diagnostics;
using System.Text;
-using Newtonsoft.Json.Linq;
using TypeCobol.Compiler;
using TypeCobol.Compiler.CodeElements;
using TypeCobol.Compiler.Nodes;
@@ -8,12 +7,12 @@
using TypeCobol.Compiler.Text;
using TypeCobol.LanguageServer.VsCodeProtocol;
-namespace TypeCobol.LanguageServer.Commands.Refactor
+namespace TypeCobol.LanguageServer.Commands.AdjustFillers
{
///
/// Refactoring processor for AdjustFillers command
///
- public class AdjustFillerRefactoringProcessor : IRefactoringProcessor
+ public class AdjustFillersRefactoringProcessor : AbstractRefactoringProcessor
{
private class AdjustFillerVisitor : AbstractAstVisitor
{
@@ -214,18 +213,13 @@ void Remove()
}
}
- public TextDocumentIdentifier PrepareRefactoring(object[] arguments)
+ public override TextDocumentIdentifier PrepareRefactoring(object[] arguments)
{
- // TODO How to factorize for all processors ?
- if (arguments == null || arguments.Length == 0 || arguments[0] is not JObject jObject)
- {
- throw new ArgumentException("Invalid arguments for command.", nameof(arguments));
- }
-
- return jObject.ToObject();
+ // Single argument is the text document identifier and it is required
+ return Expect(arguments, 0, true);
}
- public void CheckTarget(CompilationUnit compilationUnit)
+ public override void CheckTarget(CompilationUnit compilationUnit)
{
// Require full AST
if (compilationUnit.ProgramClassDocumentSnapshot == null)
@@ -234,7 +228,7 @@ public void CheckTarget(CompilationUnit compilationUnit)
}
}
- public (string Label, List TextEdits) PerformRefactoring(CompilationUnit compilationUnit)
+ public override (string Label, List TextEdits) PerformRefactoring(CompilationUnit compilationUnit)
{
// Compute edits using a visitor
var visitor = new AdjustFillerVisitor();
diff --git a/TypeCobol.LanguageServer/Commands/ICommand.cs b/TypeCobol.LanguageServer/Commands/ICommand.cs
index bbb1038be..10ed5658c 100644
--- a/TypeCobol.LanguageServer/Commands/ICommand.cs
+++ b/TypeCobol.LanguageServer/Commands/ICommand.cs
@@ -11,7 +11,8 @@ static ICommand()
{
_Commands = new Dictionary>()
{
- { "refactor/adjustFillers", Refactor.AdjustFillers.Create }
+ { "refactor/adjustFillers", AdjustFillers.AdjustFillers.Create },
+ { "source/insertVariableDisplay", InsertVariableDisplay.InsertVariableDisplay.Create }
// Register all supported commands here
};
}
diff --git a/TypeCobol.LanguageServer/Commands/IEnvironmentVariableProvider.cs b/TypeCobol.LanguageServer/Commands/IEnvironmentVariableProvider.cs
new file mode 100644
index 000000000..eddc1876f
--- /dev/null
+++ b/TypeCobol.LanguageServer/Commands/IEnvironmentVariableProvider.cs
@@ -0,0 +1,30 @@
+namespace TypeCobol.LanguageServer.Commands
+{
+ ///
+ /// Abstraction for environment. Override in unit tests to get consistent results
+ /// across environments.
+ ///
+ public interface IEnvironmentVariableProvider
+ {
+ DateTime Now { get; }
+
+ string UserName { get; }
+ }
+
+ ///
+ /// Real implementation of the environment.
+ ///
+ public class RealEnvironment : IEnvironmentVariableProvider
+ {
+ public static readonly RealEnvironment Instance = new RealEnvironment();
+
+ private RealEnvironment()
+ {
+
+ }
+
+ public DateTime Now => DateTime.Now;
+
+ public string UserName => Environment.UserName;
+ }
+}
diff --git a/TypeCobol.LanguageServer/Commands/IRefactoringProcessor.cs b/TypeCobol.LanguageServer/Commands/IRefactoringProcessor.cs
new file mode 100644
index 000000000..776a9aceb
--- /dev/null
+++ b/TypeCobol.LanguageServer/Commands/IRefactoringProcessor.cs
@@ -0,0 +1,102 @@
+using Newtonsoft.Json.Linq;
+using TypeCobol.Compiler;
+using TypeCobol.LanguageServer.VsCodeProtocol;
+
+namespace TypeCobol.LanguageServer.Commands
+{
+ ///
+ /// A refactoring processor is responsible for implementing the refactoring logic
+ /// independently of the language server currently running. This is an abstraction
+ /// for an automated modification of source text.
+ ///
+ public interface IRefactoringProcessor
+ {
+ ///
+ /// Sets the environment variable provider for this instance.
+ ///
+ IEnvironmentVariableProvider EnvironmentVariableProvider { set; }
+
+ ///
+ /// Collect and parse refactoring arguments.
+ ///
+ /// Untyped argument array, possibly null.
+ /// Non-null TextDocumentIdentifier found in args, an exception should be thrown if no identifier could be found.
+ TextDocumentIdentifier PrepareRefactoring(object[] arguments);
+
+ ///
+ /// Perform validation of the refactoring target.
+ ///
+ /// The program that should be modified by this refactoring.
+ void CheckTarget(CompilationUnit compilationUnit);
+
+ ///
+ /// Compute text edits corresponding to this refactoring.
+ ///
+ /// Target of the refactoring.
+ /// A tuple made of a label describing the modification that has been performed and a list of TextEdits.
+ (string Label, List TextEdits) PerformRefactoring(CompilationUnit compilationUnit);
+ }
+
+ ///
+ /// Base class for refactoring processors.
+ ///
+ public abstract class AbstractRefactoringProcessor : IRefactoringProcessor
+ {
+ ///
+ /// Arguments deserialization helper. Returns an object read or parsed from
+ /// given arguments array and index.
+ ///
+ /// Expected type of the argument.
+ /// Arguments array.
+ /// Argument index.
+ /// True to consider the argument as required: when missing, an ArgumentException
+ /// will be thrown. False to consider the argument as optional: the default value for the argument type
+ /// will be returned.
+ /// Instance of T, possibly null.
+ protected static T Expect(object[] arguments, int index, bool required)
+ {
+ if (arguments == null || index > arguments.Length - 1)
+ {
+ return DefaultOrThrowWhenRequired();
+ }
+
+ object argument = arguments[index];
+ switch (argument)
+ {
+ case T result:
+ return result;
+ case JToken jToken:
+ if (jToken.Type == JTokenType.Null)
+ {
+ ThrowWhenRequired();
+ }
+
+ return jToken.ToObject();
+ }
+
+ return DefaultOrThrowWhenRequired();
+
+ T DefaultOrThrowWhenRequired()
+ {
+ ThrowWhenRequired();
+ return default;
+ }
+
+ void ThrowWhenRequired()
+ {
+ if (required)
+ {
+ throw new ArgumentException("Invalid arguments for command.", nameof(arguments));
+ }
+ }
+ }
+
+ public IEnvironmentVariableProvider EnvironmentVariableProvider { get; set; } = RealEnvironment.Instance; // Initialize with real environment
+
+ public abstract TextDocumentIdentifier PrepareRefactoring(object[] arguments);
+
+ public abstract void CheckTarget(CompilationUnit compilationUnit);
+
+ public abstract (string Label, List TextEdits) PerformRefactoring(CompilationUnit compilationUnit);
+ }
+}
diff --git a/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/CobolStringBuilder.cs b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/CobolStringBuilder.cs
new file mode 100644
index 000000000..3cb9c846d
--- /dev/null
+++ b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/CobolStringBuilder.cs
@@ -0,0 +1,217 @@
+using System.Diagnostics;
+using System.Text;
+using TypeCobol.Compiler.Text;
+
+namespace TypeCobol.LanguageServer.Commands.InsertVariableDisplay
+{
+ ///
+ /// Imitates StringBuilder but for generating Cobol code according to the RDz Reference Format.
+ /// Internally it uses 2 StringBuilder: one for the accumulated text so far and one
+ /// for the current line being built. When the current line exceeds the maximum allowed length,
+ /// its content is transferred to the text and a new line is initialized.
+ ///
+ [DebuggerDisplay("Text = {_text}, CurrentLine = {_currentLine}")] // The ToString() method may alter the object, so we use custom debug display to avoid side effects
+ internal class CobolStringBuilder
+ {
+ private const int MAX_LINE_LENGTH = (int)CobolFormatAreas.End_B; // 72
+ private const char ONE_SPACE = ' ';
+ private const char DEBUG_INDICATOR = 'D';
+ private const char COMMENT_INDICATOR = '*';
+
+ private static readonly string _SequenceNumber = new string(ONE_SPACE, CobolFormatAreas.EndNumber - CobolFormatAreas.BeginNumber + 1); // 6 spaces
+
+ private readonly char _indicator;
+ private readonly StringBuilder _text;
+ private readonly StringBuilder _currentLine;
+ private bool _currentLineIsEmpty;
+
+ public CobolStringBuilder(bool debug = false)
+ {
+ _indicator = debug ? DEBUG_INDICATOR : ONE_SPACE;
+ _text = new StringBuilder();
+ _currentLine = new StringBuilder();
+ Clear();
+ }
+
+ public void Clear()
+ {
+ _text.Clear();
+ _currentLine.Clear();
+ InitCurrentLine();
+ }
+
+ private void InitCurrentLine()
+ {
+ _currentLine.Append(_SequenceNumber);
+ _currentLine.Append(_indicator);
+ _currentLineIsEmpty = true;
+ }
+
+ private int FlushCurrentLine()
+ {
+ string currentLine = _currentLine.ToString();
+
+ // Number of leading spaces on current line, does not include sequence number and indicator
+ int previousLineIndentLength = currentLine.Skip(_SequenceNumber.Length + 1).TakeWhile(c => c == ONE_SPACE).Count();
+
+ _text.Append(currentLine);
+ _currentLine.Clear();
+ return previousLineIndentLength;
+ }
+
+ ///
+ /// Append the given number of spaces at the beginning (after indicator) of the current line.
+ /// This method expects that:
+ /// - the current line is empty
+ /// - the number of spaces does not exceed the maximum line length
+ ///
+ /// Number os leading spaces to add to current line.
+ public void AppendIndent(int length)
+ {
+ Debug.Assert(_currentLineIsEmpty);
+ Debug.Assert(_currentLine.Length + length <= MAX_LINE_LENGTH);
+ string indent = new string(ONE_SPACE, length);
+ _currentLine.Append(indent);
+ }
+
+ ///
+ /// Append the given word to the current line. The word is considered as non-breakable,
+ /// meaning a new line will be created if the word does not fit on the current line.
+ ///
+ /// Cobol text to add on current line.
+ public void AppendWord(string word)
+ {
+ AppendText(word);
+ _currentLineIsEmpty = false;
+ }
+
+ private void AppendText(string text)
+ {
+ bool addSeparator = !_currentLineIsEmpty;
+ int separatorLength = addSeparator ? 1 : 0;
+ if (_currentLine.Length + separatorLength + text.Length > MAX_LINE_LENGTH)
+ {
+ // Word is too long to fit on current line, create a new line and auto align
+ int indent = AppendLine();
+ AppendIndent(indent);
+ }
+ else if (addSeparator)
+ {
+ _currentLine.Append(ONE_SPACE);
+ }
+
+ _currentLine.Append(text);
+ }
+
+ ///
+ /// Append the given literal after a DISPLAY keyword. This is a hack as this method does not
+ /// handle literal splitting and continuation lines. Instead, the literal is broken at column 72
+ /// and the concatenation is done at runtime by the DISPLAY statement itself.
+ ///
+ /// Literal value to append, without delimiters.
+ /// Desired delimiter, by default the method will use a single quote.
+ public void AppendLiteralForDisplay(string literalValue, char delimiter = '\'')
+ {
+ Debug.Assert(!_currentLineIsEmpty);
+ Debug.Assert(_currentLine.ToString().EndsWith("DISPLAY"));
+
+ bool addSeparator = true;
+ int indent = _currentLine.Length - _SequenceNumber.Length; // If a new line is required, the text will be aligned with the beginning of the literal
+
+ string text = literalValue;
+ int remaining; // Remaining available chars on current line
+ int addedCharsCount; // Number of extra chars required, 2 for the two delimiters or 3 when we also have to add the separator after DISPLAY keyword
+ while (text.Length + (addedCharsCount = (addSeparator ? 3 : 2)) > (remaining = MAX_LINE_LENGTH - _currentLine.Length))
+ {
+ // Split literal
+ string part = text.Substring(0, remaining - addedCharsCount);
+ if (addSeparator)
+ {
+ _currentLine.Append(ONE_SPACE);
+ }
+
+ // Append beginning
+ _currentLine.Append(delimiter + part + delimiter);
+
+ // Create new line and align
+ AppendLine();
+ AppendIndent(indent);
+
+ // Compute remaining text to append and continue
+ text = text.Substring(part.Length);
+ addSeparator = false;
+ }
+
+ // Append last part(this may be the full literal itself if it did not need to be split)
+ if (text.Length > 0)
+ {
+ if (addSeparator)
+ {
+ _currentLine.Append(ONE_SPACE);
+ }
+
+ _currentLine.Append(delimiter + text + delimiter);
+ _currentLineIsEmpty = false;
+ }
+ }
+
+ ///
+ /// Break current line and initialize a new one.
+ /// Return the current line indent length to allow caller to align text with previous line
+ /// when starting writing on the newly created line.
+ ///
+ /// Current line (before break) indent length.
+ public int AppendLine()
+ {
+ int previousLineIndentLength = FlushCurrentLine();
+ _text.AppendLine();
+ InitCurrentLine();
+ return previousLineIndentLength;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void AppendCommentSingleLine(string comment)
+ {
+ comment ??= string.Empty;
+
+ // 65 chars is the maximum single line comment length, this method does not support comment splitting
+ Debug.Assert(comment.Length <= CobolFormatAreas.End_B - CobolFormatAreas.Begin_A + 1);
+
+ if (_currentLineIsEmpty)
+ {
+ // The current line only contains the sequence number and the indicator and optionally some leading spaces
+ _currentLine.Clear();
+ _currentLine.Append(_SequenceNumber);
+ _currentLine.Append(COMMENT_INDICATOR);
+ _currentLine.Append(comment);
+ AppendLine();
+ }
+ else
+ {
+ // Cannot append comment on non-empty line. Create a new one and retry.
+ AppendLine();
+ Debug.Assert(_currentLineIsEmpty);
+ AppendCommentSingleLine(comment);
+ }
+ }
+
+ ///
+ /// Return full text of this builder.
+ /// Note that the current line is being flushed.
+ ///
+ /// Full text of builder.
+ public override string ToString()
+ {
+ if (!_currentLineIsEmpty)
+ {
+ FlushCurrentLine();
+ }
+
+ return _text.ToString();
+ }
+ }
+}
diff --git a/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/DataDefinitionHelper.cs b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/DataDefinitionHelper.cs
new file mode 100644
index 000000000..cf0581cd0
--- /dev/null
+++ b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/DataDefinitionHelper.cs
@@ -0,0 +1,130 @@
+using TypeCobol.Compiler.Nodes;
+
+namespace TypeCobol.LanguageServer.Commands.InsertVariableDisplay
+{
+ ///
+ /// Helper class to compute an 'accessor' for a given DataDefinition.
+ /// An accessor captures all needed information to get access to the targeted data at runtime: if the targeted
+ /// data is named, the accessor is actually the data itself. For a FILLER or anonymous data, the accessor
+ /// will contain the first named data that can be used to access the targeted data and the associated
+ /// position and length in order to read the correct memory area. Position and length combined form the
+ /// reference modifier.
+ ///
+ internal static class DataDefinitionHelper
+ {
+ ///
+ /// Private struct used to compute the leftmost character position expression.
+ ///
+ /// Delta in bytes of the targeted data from its parent data.
+ /// Starts at 1 since the position of a data is 1-based in Cobol.
+ /// List of tuples made of the name of an index and size of an occurence.
+ /// Each tuple corresponds to an OCCURS dimension that need to be traversed to get to the targeted data,
+ /// this is used to access data enclosed in anonymous arrays.
+ private record struct LeftmostCharacterPositionInfo(long DeltaParent, List<(string Index, long OccurenceSize)> EnclosingOccurs)
+ {
+ public LeftmostCharacterPositionInfo()
+ : this(1, new List<(string Index, long OccurenceSize)>())
+ {
+
+ }
+
+ public List ToExpression()
+ {
+ // Default position: no need to create an expression. The whole data is used directly.
+ if (DeltaParent == 1 && EnclosingOccurs.Count == 0)
+ return null;
+
+ // Start with delta parent
+ List words = [ DeltaParent.ToString() ];
+
+ if (EnclosingOccurs.Count > 0)
+ {
+ // Add offset for each traversed OCCURS dimension
+ foreach (var enclosingOccurs in EnclosingOccurs)
+ {
+ // Shift from the total size of occurrences based on current index value
+ // + (Idx1 - 1) * + (Idx2 - 1) * + (Idx3 - 1) * ...
+ words.Add("+");
+ words.Add('(' + enclosingOccurs.Index);
+ words.Add("-");
+ words.Add("1)");
+ words.Add("*");
+ words.Add(enclosingOccurs.OccurenceSize.ToString());
+ }
+ }
+
+ return words;
+ }
+ }
+
+ ///
+ /// Accessor for a DataDefinition.
+ ///
+ /// DataDefinition to use to access originally targeted DataDefinition.
+ /// Number of indices to use on the accessor data. When the target data is nested under
+ /// anonymous arrays, the data used to access the target is on a different OCCURS dimension. Only some of the first
+ /// indices are used and this number indicates the count of them.
+ /// Reference modifier to apply on Data, when null it means no reference modifier is required.
+ public record struct DataAccessor(DataDefinition Data, int IndicesCount, string[] ReferenceModifier);
+
+ ///
+ /// Compute accessor for given DataDefinition and indices.
+ ///
+ /// Targeted DataDefinition.
+ /// Indices required to access the targeted data (subscripts) when it is located in one
+ /// or more nested OCCURS.
+ ///
+ public static DataAccessor GetAccessor(DataDefinition dataDefinition, string[] indices)
+ {
+ var info = new LeftmostCharacterPositionInfo();
+
+ // Find the closest named data
+ var currentDefinition = dataDefinition;
+ var parentDefinition = currentDefinition.Parent as DataDefinition;
+ int index = indices.Length - 1; // Current index used, starting from innermost as we are walking up the chain of parents
+ while (parentDefinition != null && string.IsNullOrEmpty(currentDefinition.Name))
+ {
+ info.DeltaParent += currentDefinition.StartPosition - parentDefinition.StartPosition; // Update delta
+ if (currentDefinition.IsTableOccurence)
+ {
+ // Capture enclosing OCCURS details
+ long occurenceSize = currentDefinition.PhysicalLength / currentDefinition.MaxOccurencesCount;
+ string indexName = indices[index];
+ info.EnclosingOccurs.Add((indexName, occurenceSize));
+ index--;
+ }
+
+ currentDefinition = parentDefinition;
+ parentDefinition = currentDefinition.Parent as DataDefinition;
+ }
+
+ if (string.IsNullOrEmpty(currentDefinition.Name))
+ {
+ // Could not find any named data: no accessor is available for the given DataDefinition
+ return new DataAccessor(null, 0, null);
+ }
+
+ // Compute reference modifier
+ var leftMostCharacterPositionExpression = info.ToExpression();
+ string[] referenceModifier = null;
+ if (leftMostCharacterPositionExpression != null)
+ {
+ referenceModifier = new string[leftMostCharacterPositionExpression.Count];
+ for (int i = 0; i < leftMostCharacterPositionExpression.Count; i++)
+ {
+ string part = leftMostCharacterPositionExpression[i];
+ // Add opening parenthesis on first part
+ bool isFirst = i == 0;
+ if (isFirst) part = '(' + part;
+ // Add length and closing parenthesis on last part
+ bool isLast = i == leftMostCharacterPositionExpression.Count - 1;
+ if (isLast) part = part + ':' + dataDefinition.PhysicalLength + ')';
+ referenceModifier[i] = part;
+ }
+ }
+
+ int indicesCount = index + 1; // Number of indices used to access the data
+ return new DataAccessor(currentDefinition, indicesCount, referenceModifier);
+ }
+ }
+}
diff --git a/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/DataDefinitionToDisplayVisitor.cs b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/DataDefinitionToDisplayVisitor.cs
new file mode 100644
index 000000000..3ccf2f52e
--- /dev/null
+++ b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/DataDefinitionToDisplayVisitor.cs
@@ -0,0 +1,137 @@
+using TypeCobol.Compiler.Nodes;
+
+namespace TypeCobol.LanguageServer.Commands.InsertVariableDisplay
+{
+ ///
+ /// Extends SelectedNodeVisitor to build a series of DISPLAY statements,
+ /// one for each selected node.
+ ///
+ internal class DataDefinitionToDisplayVisitor : SelectedNodeVisitor
+ {
+ private readonly IndexGenerator _indexGenerator;
+ private int _dataLogicalLevel;
+ private readonly Stack _indices;
+ private GeneratedStatement _currentStatement;
+
+ public DataDefinitionToDisplayVisitor(Selection rootSelection, IndexGenerator indexGenerator)
+ : base(rootSelection)
+ {
+ _indexGenerator = indexGenerator;
+ _dataLogicalLevel = -1; // Starts at -1 so the 01 levels have a logical level of 0 after being entered
+ _indices = new Stack();
+ GeneratedStatements = new GeneratedRoot();
+ _currentStatement = GeneratedStatements;
+ }
+
+ public GeneratedRoot GeneratedStatements { get; }
+
+ protected override IEnumerable SelectChildren(Node parent)
+ {
+ // Auto select mode: all data descriptions, except anonymous
+ foreach (var child in parent.Children.OfType())
+ {
+ if (string.IsNullOrEmpty(child.Name)) continue;
+ yield return child;
+ }
+ }
+
+ protected override void EnterNode(Node node)
+ {
+ if (node is DataDefinition dataDefinition)
+ {
+ EnterDataDefinition(dataDefinition);
+ }
+ }
+
+ protected override void ProcessNode(Node node, bool nodeHasSelectedChildren)
+ {
+ if (node is DataDefinition dataDefinition)
+ {
+ ProcessDataDefinition(dataDefinition, nodeHasSelectedChildren);
+ }
+ }
+
+ protected override void ExitNode(Node node)
+ {
+ if (node is DataDefinition dataDefinition)
+ {
+ ExitDataDefinition(dataDefinition);
+ }
+ }
+
+ private void EnterDataDefinition(DataDefinition dataDefinition)
+ {
+ _dataLogicalLevel++;
+
+ // Generate PERFORM if need be
+ if (dataDefinition.IsTableOccurence)
+ {
+ // Start with MaxOccurs as max and check whether a DEPENDING ON is declared or not
+ string max = dataDefinition.MaxOccurencesCount.ToString();
+ if (dataDefinition.OccursDependingOn != null)
+ {
+ // Generate IF IS NUMERIC AND IN RANGE. The DEPENDING ON variable becomes the max for the PERFORM
+ string maxOccurs = max;
+ max = dataDefinition.OccursDependingOn.MainSymbolReference.Name;
+
+ // Check DEPENDING ON type: we should not check class when the DEPENDING ON is binary
+ var dependingOn = dataDefinition.GetDataDefinitionFromStorageAreaDictionary(dataDefinition.OccursDependingOn.StorageArea, true);
+ var dependingOnUsage = dependingOn?.SemanticData?.Type?.Usage;
+ bool checkNumeric = dependingOnUsage != Compiler.Types.Type.UsageFormat.Comp && dependingOnUsage != Compiler.Types.Type.UsageFormat.Comp5;
+ string errorMessage = $"Cannot DISPLAY \"{dataDefinition.Name}\" because its DEPENDING ON \"{max}\" is{(checkNumeric ? " either not numeric or " : " ")}out of range.";
+
+ // Create IF statement and continue generation inside it
+ var ifNumericAndInRange = new GeneratedIfIsNumericAndInRangeElseDisplayMessage(max, checkNumeric, maxOccurs, errorMessage);
+ _currentStatement.AddChild(ifNumericAndInRange);
+ _currentStatement = ifNumericAndInRange;
+ }
+
+ // Reuse or generate new index for the current OCCURS dimension
+ string index = _indexGenerator.GetOrCreateIndex(_indices.Count + 1);
+ _indices.Push(index);
+
+ // Generate PERFORM and continue generation inside it
+ var perform = new GeneratedPerform(index, max);
+ _currentStatement.AddChild(perform);
+ _currentStatement = perform;
+ }
+ }
+
+ private void ProcessDataDefinition(DataDefinition dataDefinition, bool dataDefinitionHasSelectedChildren)
+ {
+ var indices = _indices.Reverse().ToArray();
+ var accessor = DataDefinitionHelper.GetAccessor(dataDefinition, indices);
+ if (accessor.Data == null)
+ {
+ // Cannot generate DISPLAY statement
+ return;
+ }
+
+ // Generate DISPLAY
+ bool withValue = !(dataDefinition.HasChildrenThatDeclareData() && dataDefinitionHasSelectedChildren);
+ var display = new GeneratedDisplayVariable(_dataLogicalLevel, dataDefinition, accessor, indices, withValue);
+ _currentStatement.AddChild(display);
+ }
+
+ private void ExitDataDefinition(DataDefinition dataDefinition)
+ {
+ // Mirror the EnterDataDefinition method
+ if (dataDefinition.IsTableOccurence)
+ {
+ // Exit PERFORM
+ _currentStatement = _currentStatement.Parent;
+
+ if (dataDefinition.OccursDependingOn != null)
+ {
+ // Exit IF IS NUMERIC
+ _currentStatement = _currentStatement.Parent;
+ }
+
+ // Pop innermost index as we have exited the PERFORM
+ _indices.Pop();
+ }
+
+ _dataLogicalLevel--;
+ }
+ }
+}
diff --git a/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/GeneratedStatements.cs b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/GeneratedStatements.cs
new file mode 100644
index 000000000..0c75b0524
--- /dev/null
+++ b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/GeneratedStatements.cs
@@ -0,0 +1,500 @@
+using System.Diagnostics;
+using System.Text;
+using TypeCobol.Compiler.Nodes;
+using TypeCobol.Compiler.Text;
+
+namespace TypeCobol.LanguageServer.Commands.InsertVariableDisplay
+{
+ ///
+ /// Base class for a generated statement.
+ /// Generated statement, like actual statements, are organized in a tree structure.
+ /// Some statements are considered inactive unless they get at least one active children
+ /// statement. For example PERFORM statement is useless until some statements are added
+ /// into its loop body.
+ ///
+ internal abstract class GeneratedStatement
+ {
+ protected GeneratedStatement(bool isActive)
+ {
+ IsActive = isActive;
+ Parent = null;
+ Children = new List();
+ }
+
+ ///
+ /// Indicate whether the statement is active or not, an inactive statement should not
+ /// generate any code whereas an active statement is meant to be translated to Cobol code.
+ ///
+ protected bool IsActive { get; private set; }
+
+ public GeneratedStatement Parent { get; private set; }
+
+ public List Children { get; }
+
+ private void Activate()
+ {
+ // Activate this statement and propagate to parent.
+ IsActive = true;
+ Parent?.Activate();
+ }
+
+ public void AddChild(GeneratedStatement childStatement)
+ {
+ childStatement.Parent = this;
+ Children.Add(childStatement);
+ if (childStatement.IsActive)
+ {
+ Activate();
+ }
+ }
+
+ protected internal void WriteCobolCode(int statementLevel, CobolStringBuilder builder)
+ {
+ if (!IsActive) return; // Nothing to generate
+
+ /*
+ * Generic Cobol code generation algorithm: parameter statementLevel is used to indent statement code
+ * according to its nesting level and then passed recursively to children statement.
+ * Every statement has an opening line. For composite statements, the WriteStatementEnd is called to
+ * get the closing line.
+ */
+
+ WriteIndent();
+ WriteStatementOpening(builder);
+ builder.AppendLine();
+
+ if (Children.Count > 0)
+ {
+ foreach (var child in Children)
+ {
+ child.WriteCobolCode(statementLevel + 1, builder);
+ }
+
+ WriteIndent();
+ WriteStatementEnd(builder);
+ builder.AppendLine();
+ }
+
+ void WriteIndent()
+ {
+ Debug.Assert(statementLevel >= 0);
+
+ int length = CobolFormatAreas.End_A - CobolFormatAreas.Begin_A + 1; // 4 spaces to start in AreaB
+ length += 2 * statementLevel; // 2 additional spaces for each level of nested statements
+ builder.AppendIndent(length);
+ }
+ }
+
+ ///
+ /// Derived classes must implement this method to write their own keywords
+ /// and expressions to the supplied builder.
+ ///
+ /// Target builder.
+ protected abstract void WriteStatementOpening(CobolStringBuilder builder);
+
+ ///
+ /// For composite statements only, derived classes must implement this method
+ /// to write their end element.
+ ///
+ /// Target builder.
+ protected abstract void WriteStatementEnd(CobolStringBuilder builder);
+ }
+
+ ///
+ /// Root for a tree of generated statements. This does not correspond to any code,
+ /// however the code for nested statements can be retrieved only using this class.
+ ///
+ internal class GeneratedRoot : GeneratedStatement, ICobolCodeProvider
+ {
+ public GeneratedRoot()
+ : base(false) // Initially inactive as it serves no purpose until an active nested statement is added
+ {
+
+ }
+
+ public bool HasContent => IsActive;
+
+ public void WriteCobolCode(CobolStringBuilder builder)
+ {
+ // Write all children
+ foreach (var child in Children)
+ {
+ child.WriteCobolCode(0, builder);
+ }
+ }
+
+ protected override void WriteStatementOpening(CobolStringBuilder builder)
+ {
+ throw new InvalidOperationException("Root statement has no beginning code element.");
+ }
+
+ protected override void WriteStatementEnd(CobolStringBuilder builder)
+ {
+ throw new InvalidOperationException("Root statement has no ending code element.");
+ }
+ }
+
+ ///
+ /// Class to write a PERFORM statement.
+ /// Form is:
+ /// PERFORM VARYING [Index] FROM 1 BY 1 UNTIL [Index] > [Max]
+ /// [Children]
+ /// END-PERFORM
+ ///
+ internal class GeneratedPerform : GeneratedStatement
+ {
+ public string Index { get; }
+
+ public string Max { get; }
+
+ public GeneratedPerform(string index, string max)
+ : base(false) // Initially inactive as it serves no purpose until an active nested statement is added
+ {
+ Index = index;
+ Max = max;
+ }
+
+ protected override void WriteStatementOpening(CobolStringBuilder builder)
+ {
+ builder.AppendWord("PERFORM");
+ builder.AppendWord("VARYING");
+ builder.AppendWord(Index);
+ builder.AppendWord("FROM");
+ builder.AppendWord("1");
+ builder.AppendWord("BY");
+ builder.AppendWord("1");
+ builder.AppendWord("UNTIL");
+ builder.AppendWord(Index);
+ builder.AppendWord(">");
+ builder.AppendWord(Max);
+ }
+
+ protected override void WriteStatementEnd(CobolStringBuilder builder)
+ {
+ builder.AppendWord("END-PERFORM");
+ }
+ }
+
+ ///
+ /// Class to write a DISPLAY statement for a variable.
+ /// Form varies according to the presence or absence of value, indices, logical level
+ /// of the variable, etc.
+ ///
+ internal class GeneratedDisplayVariable : GeneratedStatement
+ {
+ ///
+ /// Define indentation level of the variable name, to replicate its nesting
+ /// in the output.
+ ///
+ public int LogicalLevel { get; }
+
+ ///
+ /// Data definition to display
+ ///
+ public DataDefinition Target { get; }
+
+ ///
+ /// Accessor for the targeted data
+ ///
+ public DataDefinitionHelper.DataAccessor Accessor { get; }
+
+ ///
+ /// Table of indices to use to access the targeted data, when it is defined
+ /// under one or more OCCURS.
+ ///
+ public string[] Indices { get; }
+
+ ///
+ /// True to display the value of the variable, False to display its name only.
+ ///
+ public bool WithValue { get; }
+
+ public GeneratedDisplayVariable(int logicalLevel, DataDefinition target, DataDefinitionHelper.DataAccessor accessor, string[] indices, bool withValue)
+ : base(true) // Always active
+ {
+ LogicalLevel = logicalLevel;
+ Target = target;
+ Accessor = accessor;
+ Indices = indices;
+ WithValue = withValue;
+ }
+
+ protected override void WriteStatementOpening(CobolStringBuilder builder)
+ {
+ builder.AppendWord("DISPLAY");
+
+ var wordBuilder = new StringBuilder();
+ bool withIndices = Indices.Length > 0;
+ switch (withIndices, WithValue)
+ {
+ case (false, false):
+ // No indices, no value: DISPLAY ' var1'
+ AppendDisplayName(null);
+ break;
+ case (false, true):
+ // No indices but a value: DISPLAY ' var1 <' var1 '>'
+ AppendDisplayName('<');
+ AppendValue();
+ AppendClosingValueDelimiter();
+ break;
+ case (true, false):
+ // Indices but no value: DISPLAY ' var1 (' Idx-1 ' ' Idx-2 ')'
+ AppendDisplayName('(');
+ AppendIndicesForDisplay();
+ AppendClosingIndicesDelimiterAndReferenceModifierForDisplay();
+ break;
+ case (true, true):
+ // Indices and value: DISPLAY ' var1 (' Idx-1 ' ' Idx-2 ') <' var1 (Idx-1 Idx-2) '>'
+ AppendDisplayName('(');
+ AppendIndicesForDisplay();
+ AppendClosingIndicesDelimiterAndReferenceModifierForDisplay();
+ AppendValue();
+ AppendClosingValueDelimiter();
+ break;
+ }
+
+ void AppendDisplayName(char? openingChar)
+ {
+ /*
+ * DisplayName is a literal describing what is displayed in the output:
+ *
+ * Form is:
+ * openingDelimiter Indent Name referenceModifierPrecededBySpace? openingCharPrecededBySpace? closingDelimiter
+ * - openingDelimiter and closingDelimiter are the ' (single quote) character
+ * - Indent is a string made of spaces: 2 spaces for each level of nesting of the targeted data (01 levels start at 0)
+ * - Name is the name of the targeted data or 'FILLER' for anonymous or FILLERs
+ * - referenceModifierPrecededBySpace is an optional reference modifier (for anonymous and FILLERs)
+ * - openingCharPrecededBySpace is depending on what is expected after:
+ * - null when no indices nor value come after
+ * - ( when indices come after
+ * - < when no indices come after and a value is expected
+ */
+
+ string indent = new string(' ', 2 * LogicalLevel);
+ wordBuilder.Append(indent);
+
+ string name = string.IsNullOrEmpty(Target.Name) ? "FILLER" : Target.Name;
+ wordBuilder.Append(name);
+
+ if (Indices.Length == 0)
+ {
+ AppendReferenceModifierForDisplay();
+ }
+ // else: reference modifier will be added after indices in AppendClosingIndicesDelimiterAndReferenceModifierForDisplay
+
+ if (openingChar.HasValue)
+ {
+ wordBuilder.Append(' ');
+ wordBuilder.Append(openingChar.Value);
+ }
+
+ builder.AppendLiteralForDisplay(wordBuilder.ToString());
+ wordBuilder.Clear();
+ }
+
+ void AppendValue()
+ {
+ if (!string.IsNullOrEmpty(Target.Name) && Target.IsNationalOrNationalEdited())
+ {
+ Debug.Assert(Target == Accessor.Data); // Target is named
+
+ // Display of national items require the use of DISPLAY-OF built-in function
+ // Note that display of national fillers is not supported by Cobol as the DISPLAY-OF
+ // function does not accept modified references
+ builder.AppendWord("FUNCTION");
+ builder.AppendWord("DISPLAY-OF");
+
+ // Append name
+ wordBuilder.Append('(');
+ wordBuilder.Append(Target.Name);
+ if (Indices.Length == 0)
+ {
+ wordBuilder.Append(')');
+ }
+ builder.AppendWord(wordBuilder.ToString());
+ wordBuilder.Clear();
+
+ // Append indices (if any) and the closing parenthesis of the DISPLAY-OF call
+ AppendIndicesForAccess(true);
+ }
+ else
+ {
+ // Not a National, use accessor info
+ builder.AppendWord(Accessor.Data.Name);
+ AppendIndicesForAccess(false);
+ if (Accessor.ReferenceModifier != null)
+ {
+ foreach (var referenceModifierWord in Accessor.ReferenceModifier)
+ {
+ builder.AppendWord(referenceModifierWord);
+ }
+ }
+ }
+
+ void AppendIndicesForAccess(bool addClosingParenthesis)
+ {
+ // Append (Idx1 Idx2 [...] Idxn) and an additional closing parenthesis when requested
+
+ for (int i = 0; i < Accessor.IndicesCount; i++)
+ {
+ bool isFirst = i == 0;
+ if (isFirst)
+ {
+ wordBuilder.Append('(');
+ }
+
+ wordBuilder.Append(Indices[i]);
+
+ bool isLast = i == Accessor.IndicesCount - 1;
+ if (isLast)
+ {
+ wordBuilder.Append(')');
+ if (addClosingParenthesis)
+ {
+ wordBuilder.Append(')');
+ }
+ }
+
+ builder.AppendWord(wordBuilder.ToString());
+ wordBuilder.Clear();
+ }
+ }
+ }
+
+ void AppendClosingValueDelimiter() => builder.AppendWord("'>'");
+
+ void AppendIndicesForDisplay()
+ {
+ // Indices for display are separated by a space literal
+ for (int i = 0; i < Indices.Length; i++)
+ {
+ builder.AppendWord(Indices[i]);
+
+ bool isLast = i == Indices.Length - 1;
+ if (!isLast)
+ {
+ builder.AppendWord("' '");
+ }
+ }
+ }
+
+ void AppendClosingIndicesDelimiterAndReferenceModifierForDisplay()
+ {
+ wordBuilder.Append("')");
+
+ Debug.Assert(Indices.Length > 0);
+ AppendReferenceModifierForDisplay();
+
+ if (WithValue)
+ {
+ // For opening value
+ wordBuilder.Append(" <");
+ }
+
+ wordBuilder.Append('\'');
+ builder.AppendWord(wordBuilder.ToString());
+ wordBuilder.Clear();
+ }
+
+ void AppendReferenceModifierForDisplay()
+ {
+ // Do not attempt to represent reference modifier when it is not a direct access (access using the direct parent)
+ if (Accessor.ReferenceModifier != null && Accessor.Data == Target.Parent)
+ {
+ Debug.Assert(Accessor.ReferenceModifier.Length == 1);
+ wordBuilder.Append(' ');
+ wordBuilder.Append(Accessor.ReferenceModifier[0]);
+ }
+ }
+ }
+
+ protected override void WriteStatementEnd(CobolStringBuilder builder)
+ {
+ throw new InvalidOperationException("DISPLAY statement is not composite.");
+ }
+ }
+
+ ///
+ /// Class to write an IF statement.
+ /// Form is:
+ /// IF [Variable] IS NUMERIC AND [Variable] >=1 AND <= [MaxOccurs]
+ /// [Children]
+ /// ELSE
+ /// DISPLAY [ErrorMessage]
+ /// END-IF
+ ///
+ /// or
+ ///
+ /// IF [Variable] >=1 AND <= [MaxOccurs]
+ /// [Children]
+ /// ELSE
+ /// DISPLAY [ErrorMessage]
+ /// END-IF
+ /// (when the variable cannot be class tested)
+ ///
+ internal class GeneratedIfIsNumericAndInRangeElseDisplayMessage : GeneratedStatement
+ {
+ public string Variable { get; }
+
+ public bool CheckNumeric { get; }
+
+ public string MaxOccurs { get; }
+
+ public string ErrorMessage { get; }
+
+ public GeneratedIfIsNumericAndInRangeElseDisplayMessage(string variable, bool checkNumeric, string maxOccurs, string errorMessage)
+ : base(false) // Initially inactive as it serves no purpose until an active nested statement is added
+ {
+ Variable = variable;
+ CheckNumeric = checkNumeric;
+ MaxOccurs = maxOccurs;
+ ErrorMessage = errorMessage;
+ }
+
+ protected override void WriteStatementOpening(CobolStringBuilder builder)
+ {
+ builder.AppendWord("IF");
+
+ if (CheckNumeric)
+ {
+ builder.AppendWord(Variable);
+ builder.AppendWord("IS");
+ builder.AppendWord("NUMERIC");
+ builder.AppendWord("AND");
+ }
+
+ builder.AppendWord(Variable);
+ builder.AppendWord(">=");
+ builder.AppendWord("1");
+ builder.AppendWord("AND");
+ builder.AppendWord("<=");
+ builder.AppendWord(MaxOccurs);
+ }
+
+ protected override void WriteStatementEnd(CobolStringBuilder builder)
+ {
+ /*
+ * HACK ! To model properly an IF statement, we should have something like
+ * IF
+ * THEN
+ * [Children of THEN when the condition is true]
+ * ELSE
+ * [Children of ELSE when the condition is false]
+ * END-IF
+ *
+ * With THEN a pseudo statement as it should not produce any code and not increase statement level
+ *
+ * Instead we hack the WriteStatementEnd to write directly the ELSE and DISPLAY [ErrorMessage] part
+ * directly without using the real GeneratedDisplay class !!!
+ */
+ builder.AppendWord("ELSE");
+ int indent = builder.AppendLine();
+ builder.AppendIndent(indent + 2);
+ builder.AppendWord("DISPLAY");
+ builder.AppendLiteralForDisplay(ErrorMessage);
+ builder.AppendLine();
+ builder.AppendIndent(indent);
+ builder.AppendWord("END-IF");
+ }
+ }
+}
diff --git a/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/ICobolCodeProvider.cs b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/ICobolCodeProvider.cs
new file mode 100644
index 000000000..24b835940
--- /dev/null
+++ b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/ICobolCodeProvider.cs
@@ -0,0 +1,19 @@
+namespace TypeCobol.LanguageServer.Commands.InsertVariableDisplay
+{
+ ///
+ /// Abstraction for objects that generate Cobol code.
+ ///
+ internal interface ICobolCodeProvider
+ {
+ ///
+ /// True when this provider has content to write, False when empty.
+ ///
+ bool HasContent { get; }
+
+ ///
+ /// Write the entirety of generated code to the given builder.
+ ///
+ /// Non-null CobolStringBuilder instance.
+ void WriteCobolCode(CobolStringBuilder cobolStringBuilder);
+ }
+}
diff --git a/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/IndexGenerator.cs b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/IndexGenerator.cs
new file mode 100644
index 000000000..d4fb80429
--- /dev/null
+++ b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/IndexGenerator.cs
@@ -0,0 +1,59 @@
+using System.Diagnostics;
+
+namespace TypeCobol.LanguageServer.Commands.InsertVariableDisplay
+{
+ ///
+ /// Generate index variables required when iterating arrays.
+ ///
+ internal class IndexGenerator : ICobolCodeProvider
+ {
+ private readonly string _hash;
+ private readonly List _indices;
+
+ public IndexGenerator(string hash)
+ {
+ _hash = hash;
+ _indices = new List();
+ }
+
+ public bool HasContent => _indices.Count > 0;
+
+ ///
+ /// Get the index name for the given OCCURS dimension.
+ /// If no index exists yet for this dimension, a new index is created.
+ ///
+ /// OCCURS dimension, starts at 1.
+ /// Name of the index to use for this dimension.
+ public string GetOrCreateIndex(int occursDimension)
+ {
+ Debug.Assert(occursDimension >= 1);
+
+ string index;
+ if (occursDimension > _indices.Count)
+ {
+ // Create new index
+ Debug.Assert(occursDimension == _indices.Count + 1); // Check no gap
+ index = $"Idx-{_hash}-{occursDimension}";
+ _indices.Add(index);
+ }
+ else
+ {
+ // Reuse index
+ index = _indices[occursDimension - 1];
+ }
+
+ return index;
+ }
+
+ public void WriteCobolCode(CobolStringBuilder builder)
+ {
+ // Each index is generated as a 77 level, PIC 9(4) COMP-5. This allows to iterate arrays having less than 65535 items.
+ foreach (var index in _indices)
+ {
+ // As index name is no longer than 15 chars, the whole declaration fit on one line
+ builder.AppendWord($"77 {index} PIC 9(4) COMP-5.");
+ builder.AppendLine();
+ }
+ }
+ }
+}
diff --git a/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/InsertVariableDisplay.cs b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/InsertVariableDisplay.cs
new file mode 100644
index 000000000..5dfbe1983
--- /dev/null
+++ b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/InsertVariableDisplay.cs
@@ -0,0 +1,13 @@
+namespace TypeCobol.LanguageServer.Commands.InsertVariableDisplay
+{
+ internal class InsertVariableDisplay : AbstractSingleDocumentRefactoring
+ {
+ public static InsertVariableDisplay Create(TypeCobolServer typeCobolServer) => new(typeCobolServer);
+
+ public InsertVariableDisplay(TypeCobolServer typeCobolServer)
+ : base(typeCobolServer)
+ {
+
+ }
+ }
+}
diff --git a/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/InsertVariableDisplayRefactoringProcessor.cs b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/InsertVariableDisplayRefactoringProcessor.cs
new file mode 100644
index 000000000..971009163
--- /dev/null
+++ b/TypeCobol.LanguageServer/Commands/InsertVariableDisplay/InsertVariableDisplayRefactoringProcessor.cs
@@ -0,0 +1,165 @@
+using System.Diagnostics;
+using TypeCobol.Compiler;
+using TypeCobol.Compiler.CodeElements;
+using TypeCobol.Compiler.Nodes;
+using TypeCobol.LanguageServer.Utilities;
+using TypeCobol.LanguageServer.VsCodeProtocol;
+
+namespace TypeCobol.LanguageServer.Commands.InsertVariableDisplay
+{
+ public class InsertVariableDisplayRefactoringProcessor : AbstractRefactoringProcessor
+ {
+ // From refactoring args
+ private Position _insertAt;
+ private bool _insertBeforeStatement;
+ private Selection _workingStorageSectionSelection;
+ private Selection _localStorageSectionSelection;
+ private Selection _linkageSectionSelection;
+
+ // Generated using user and timestamp
+ private string _hash;
+
+ // Computed when checking target program
+ private (CodeElement CodeElement, Node Node) _location;
+
+ public override TextDocumentIdentifier PrepareRefactoring(object[] arguments)
+ {
+ // Get TextDocumentPosition (contains TextDocumentIdentifier and insertion position)
+ var textDocumentPosition = Expect(arguments, 0, true);
+ _insertAt = textDocumentPosition.position;
+
+ // Get insert before/after flag
+ _insertBeforeStatement = Expect(arguments, 1, true);
+
+ // Get Selection objects
+ _workingStorageSectionSelection = GetSelection(2);
+ _localStorageSectionSelection = GetSelection(3);
+ _linkageSectionSelection = GetSelection(4);
+
+ // Generate new hash for this refactoring
+ _hash = Tools.Hash.CreateCOBOLNameHash($"{EnvironmentVariableProvider.UserName}@{EnvironmentVariableProvider.Now:yyyy/MM/dd HH:mm:ss.fff}");
+
+ return textDocumentPosition.textDocument;
+
+ Selection GetSelection(int index)
+ {
+ var jsonSelection = Expect(arguments, index, false);
+ return jsonSelection?.Convert();
+ }
+ }
+
+ public override void CheckTarget(CompilationUnit compilationUnit)
+ {
+ // Require full AST
+ if (compilationUnit.ProgramClassDocumentSnapshot == null)
+ {
+ throw new InvalidOperationException($"Could not get AST for program '{compilationUnit.TextSourceInfo.Name}'.");
+ }
+
+ // Check valid insertion location
+ _location = CodeElementLocator.FindCodeElementAt(compilationUnit, _insertAt);
+ if (_location.CodeElement == null)
+ {
+ throw new InvalidOperationException("Unable to locate program to modify.");
+ }
+ }
+
+ public override (string Label, List TextEdits) PerformRefactoring(CompilationUnit compilationUnit)
+ {
+ // Get program DATA DIVISION
+ var program = _location.Node.GetProgramNode();
+ var dataDivision = program.Children.OfType().SingleOrDefault();
+ var textEdits = new List();
+ if (dataDivision != null)
+ {
+ // Generate DISPLAY statements for each data section. Required indices are tracked by IndexGenerator, shared between generations.
+ var indexGenerator = new IndexGenerator(_hash);
+ var statementsForWorkingStorageVariables = GenerateDisplayStatements(dataDivision.WorkingStorageSection, _workingStorageSectionSelection, indexGenerator);
+ var statementsForLocalStorageVariables = GenerateDisplayStatements(dataDivision.LocalStorageSection, _localStorageSectionSelection, indexGenerator);
+ var statementsForLinkageVariables = GenerateDisplayStatements(dataDivision.LinkageSection, _linkageSectionSelection, indexGenerator);
+
+ // The statements and the indices are surrounded by standardized comment
+ string openingComment = $"{nameof(InsertVariableDisplay)} {EnvironmentVariableProvider.Now:yyyy/MM/dd HH:mm} {EnvironmentVariableProvider.UserName}";
+ const string closingComment = "";
+
+ // Get COBOL code for required indices
+ var cobolStringBuilder = new CobolStringBuilder(true);
+ cobolStringBuilder.AppendCommentSingleLine(openingComment);
+ indexGenerator.WriteCobolCode(cobolStringBuilder);
+ cobolStringBuilder.AppendCommentSingleLine(closingComment);
+ bool hasCodeForIndices = indexGenerator.HasContent;
+ string cobolStringForIndices = cobolStringBuilder.ToString();
+
+ // Reset builder and get COBOL code for DISPLAY statements (from all 3 sections)
+ cobolStringBuilder.Clear();
+ cobolStringBuilder.AppendCommentSingleLine(openingComment);
+ statementsForWorkingStorageVariables.WriteCobolCode(cobolStringBuilder);
+ statementsForLocalStorageVariables.WriteCobolCode(cobolStringBuilder);
+ statementsForLinkageVariables.WriteCobolCode(cobolStringBuilder);
+ cobolStringBuilder.AppendCommentSingleLine(closingComment);
+ bool hasCodeForStatements = statementsForWorkingStorageVariables.HasContent || statementsForLocalStorageVariables.HasContent || statementsForLinkageVariables.HasContent;
+ string cobolStringForStatements = cobolStringBuilder.ToString();
+
+ if (hasCodeForIndices)
+ {
+ // Insert COBOL code for indices
+ var targetSection = (DataSection)dataDivision.WorkingStorageSection ?? dataDivision.LocalStorageSection;
+ if (targetSection == null)
+ {
+ // TODO Either move this into CheckTarget and turn it into a global refactoring requirement or generate a WORKING STORAGE ourselves ?
+ return ("Unable to generate indices: neither WORKING-STORAGE SECTION nor LOCAL-STORAGE SECTION could be found to add generated indices.", new List());
+ }
+
+ textEdits.Add(InsertAtEnd(targetSection, cobolStringForIndices));
+ }
+
+ if (hasCodeForStatements)
+ {
+ // Insert COBOL code for statements
+ if (_insertBeforeStatement)
+ {
+ textEdits.Add(InsertBefore(_location.Node, cobolStringForStatements));
+ }
+ else
+ {
+ textEdits.Add(InsertAfter(_location.Node, cobolStringForStatements));
+ }
+ }
+ }
+
+ return (textEdits.Count > 0 ? "Debug instructions successfully generated." : "No modification.", textEdits);
+ }
+
+ private static GeneratedRoot GenerateDisplayStatements(DataSection dataSection, Selection rootSelection, IndexGenerator indexGenerator)
+ {
+ if (dataSection == null || rootSelection == null)
+ {
+ // Nothing to generate
+ return new GeneratedRoot();
+ }
+
+ // Create new visitor and visit the whole data section using the corresponding selection
+ var visitor = new DataDefinitionToDisplayVisitor(rootSelection, indexGenerator);
+ visitor.Visit(dataSection);
+ return visitor.GeneratedStatements;
+ }
+
+ private static TextEdit InsertBefore(Node node, string code)
+ {
+ Debug.Assert(node.CodeElement != null);
+ int line = node.CodeElement.Line;
+ int character = 0;
+ return TextEdit.Insert(new Position() { line = line, character = character }, code + Environment.NewLine);
+ }
+
+ private static TextEdit InsertAfter(Node node, string code)
+ {
+ Debug.Assert(node.CodeElement != null);
+ int line = node.CodeElement.LineEnd;
+ int character = node.CodeElement.StopIndex + 1;
+ return TextEdit.Insert(new Position() { line = line, character = character }, Environment.NewLine + code);
+ }
+
+ private static TextEdit InsertAtEnd(Node node, string code) => InsertAfter(node.GetLastNode(), code);
+ }
+}
diff --git a/TypeCobol.LanguageServer/Commands/Refactor/IRefactoringProcessor.cs b/TypeCobol.LanguageServer/Commands/Refactor/IRefactoringProcessor.cs
deleted file mode 100644
index a66a08645..000000000
--- a/TypeCobol.LanguageServer/Commands/Refactor/IRefactoringProcessor.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using TypeCobol.Compiler;
-using TypeCobol.LanguageServer.VsCodeProtocol;
-
-namespace TypeCobol.LanguageServer.Commands.Refactor
-{
- ///
- /// A refactoring processor is responsible for implementing the refactoring logic
- /// independently of the language server currently running. This is an abstraction
- /// for an automated modification of source text.
- ///
- public interface IRefactoringProcessor
- {
- ///
- /// Collect and parse refactoring arguments.
- ///
- /// Untyped argument array, possibly null.
- /// Non-null TextDocumentIdentifier found in args, an exception should be thrown if no identifier could be found.
- TextDocumentIdentifier PrepareRefactoring(object[] arguments);
-
- ///
- /// Perform validation of the refactoring target.
- ///
- /// The program that should be modified by this refactoring.
- void CheckTarget(CompilationUnit compilationUnit);
-
- ///
- /// Compute text edits corresponding to this refactoring.
- ///
- /// Target of the refactoring.
- /// A tuple made of a label describing the modification that has been performed and a list of TextEdits.
- (string Label, List TextEdits) PerformRefactoring(CompilationUnit compilationUnit);
- }
-}
diff --git a/TypeCobol.LanguageServer/Commands/SelectedNodesVisitor.cs b/TypeCobol.LanguageServer/Commands/SelectedNodesVisitor.cs
new file mode 100644
index 000000000..27974df79
--- /dev/null
+++ b/TypeCobol.LanguageServer/Commands/SelectedNodesVisitor.cs
@@ -0,0 +1,275 @@
+using Newtonsoft.Json;
+using TypeCobol.Compiler.Nodes;
+
+namespace TypeCobol.LanguageServer.Commands
+{
+ ///
+ /// Serialization class for Selection and its derived types.
+ /// To avoid type management in JSON string, all properties from all classes are defined
+ /// in this surrogate class. The client is responsible for sending consistent sets of
+ /// properties allowing the server to decide which concrete class to instantiate.
+ ///
+ public class JsonSelection
+ {
+ [JsonProperty("vm")]
+ public NodeVisitMode VisitMode { get; set; }
+
+ [JsonProperty("ch")]
+ public JsonSelection[] SubSelections { get; set; }
+
+ [JsonProperty("name")]
+ public string Name { get; set; }
+
+ [JsonProperty("idx")]
+ public int? Index { get; set; }
+
+ ///
+ /// Instantiate actual Selection from transferred data.
+ ///
+ /// Non-null instance of Selection based on criteria set in this instance.
+ /// When no valid criteria combination could be found to select the actual Selection type.
+ internal Selection Convert()
+ {
+ Selection instance = null;
+
+ if (Name != null)
+ {
+ // Prioritize selection by name
+ instance = new SelectionByName(Name, VisitMode);
+ }
+ else if (Index.HasValue)
+ {
+ // Then by index
+ instance = new SelectionByIndex(Index.Value, VisitMode);
+ }
+
+ if (instance == null)
+ {
+ // No name, no index -> Exception
+ throw new InvalidDataException("Could not find actual Selection type, no selection criterion was provided.");
+ }
+
+ if (SubSelections != null)
+ {
+ // Recursively convert children
+ var subSelections = SubSelections.Select(subSelection => subSelection.Convert());
+ instance.SubSelections.AddRange(subSelections);
+ }
+
+ return instance;
+ }
+ }
+
+ ///
+ /// Visit mode for a Node
+ ///
+ public enum NodeVisitMode
+ {
+ ///
+ /// Visit the node and process it.
+ /// Continue visit according to the SubSelections.
+ ///
+ Default,
+
+ ///
+ /// Visit the node but do not process it.
+ /// Let the SubSelections decide whether there are children nodes to be processed or not.
+ ///
+ PassThrough,
+
+ ///
+ /// Visit the node and process it, ignore the SubSelections.
+ /// The visitor itself is solely responsible for the rest of the visit, independently of the SubSelections.
+ ///
+ Automatic
+ }
+
+ ///
+ /// Abstract base class for a Node selection.
+ ///
+ internal abstract class Selection
+ {
+ ///
+ /// Defines what to do on the selected Node when reached.
+ ///
+ public NodeVisitMode VisitMode { get; }
+
+ ///
+ /// Defines which nodes to select among the children of the current selected Node.
+ ///
+ public List SubSelections { get; }
+
+ protected Selection(NodeVisitMode visitMode)
+ {
+ VisitMode = visitMode;
+ SubSelections = new List();
+ }
+
+ ///
+ /// Implements the selection mechanism defined for this instance
+ /// among the children of the given node.
+ ///
+ /// Parent node.
+ /// Node matching the selection criterion if any, null otherwise.
+ public abstract Node SelectChild(Node parent);
+ }
+
+ internal class SelectionByName : Selection
+ {
+ ///
+ /// Name of Node to select.
+ ///
+ public string Name { get; }
+
+ public SelectionByName(string name, NodeVisitMode visitMode)
+ : base(visitMode)
+ {
+ Name = name;
+ }
+
+ public override Node SelectChild(Node parent)
+ {
+ // Optimization: use SymbolTable and filter on parent instead of iterating Children and filter by name
+ if (parent.SymbolTable.DataEntries.TryGetValue(Name, out var candidates))
+ {
+ return candidates.FirstOrDefault(candidate => candidate.Parent == parent);
+ }
+
+ return null;
+ }
+ }
+
+ internal class SelectionByIndex : Selection
+ {
+ ///
+ /// Index of Node to select.
+ ///
+ public int Index { get; }
+
+ public SelectionByIndex(int index, NodeVisitMode visitMode)
+ : base(visitMode)
+ {
+ Index = index;
+ }
+
+ public override Node SelectChild(Node parent)
+ {
+ return Index < parent.ChildrenCount ? parent.Children[Index] : null;
+ }
+ }
+
+ ///
+ /// Singleton instance of Selection to represent the automatic mode.
+ ///
+ internal class AutoSelection : Selection
+ {
+ public static AutoSelection Instance = new();
+
+ private AutoSelection()
+ : base(NodeVisitMode.Automatic)
+ {
+
+ }
+
+ public override Node SelectChild(Node parent)
+ {
+ // Should not be called as the selection has to be done by visitor itself in Automatic mode.
+ throw new InvalidOperationException("AutoSelection has no selection mechanism proper.");
+ }
+ }
+
+ ///
+ /// Base class for AST visitor with a selection of nodes to visit.
+ ///
+ internal abstract class SelectedNodeVisitor
+ {
+ private readonly Selection _rootSelection;
+
+ protected SelectedNodeVisitor(Selection rootSelection)
+ {
+ _rootSelection = rootSelection;
+ }
+
+ ///
+ /// Starts the visit on the supplied root node, using the selection
+ /// provided at construction time.
+ ///
+ /// Root node to start the visit on.
+ public void Visit(Node root)
+ {
+ // Validate initial selection
+ bool startVisit = root.Parent == null || _rootSelection.SelectChild(root.Parent) == root;
+ if (startVisit)
+ {
+ VisitNode(_rootSelection, root);
+ }
+ }
+
+ private void VisitChildren(Selection selection, Node parentNode)
+ {
+ if (selection.VisitMode == NodeVisitMode.Automatic)
+ {
+ // We are in automatic mode: use the selection mechanism from this visitor
+ foreach (var child in SelectChildren(parentNode))
+ {
+ // Keep the automatic mode active
+ VisitNode(AutoSelection.Instance, child);
+ }
+ }
+ else
+ {
+ // Use selection mechanism from sub-selections
+ foreach (var subSelection in selection.SubSelections)
+ {
+ var child = subSelection.SelectChild(parentNode);
+ if (child != null)
+ {
+ VisitNode(subSelection, child);
+ }
+ // else: inconsistent selection from client ? Ignore.
+ }
+ }
+ }
+
+ ///
+ /// Custom selection mechanism for this visitor, used when in Automatic mode.
+ ///
+ /// Parent node from which a set of children must be selected.
+ /// Non-null enumeration of selected children.
+ protected abstract IEnumerable SelectChildren(Node parent);
+
+ private void VisitNode(Selection selection, Node node)
+ {
+ EnterNode(node);
+ if (selection.VisitMode != NodeVisitMode.PassThrough)
+ {
+ // Default or Automatic mode -> the node must be processed.
+ bool nodeHasSelectedChildren = selection.SubSelections.Count > 0 || selection.VisitMode == NodeVisitMode.Automatic;
+ ProcessNode(node, nodeHasSelectedChildren);
+ }
+ VisitChildren(selection, node);
+ ExitNode(node);
+ }
+
+ ///
+ /// Called when entering a new node during the visit.
+ /// This method is called no matter the current visit mode.
+ ///
+ /// Newly entered node.
+ protected abstract void EnterNode(Node node);
+
+ ///
+ /// Called when a Node has been selected for processing.
+ ///
+ /// The node to process.
+ /// Boolean indicating whether the node has selected children or not.
+ protected abstract void ProcessNode(Node node, bool nodeHasSelectedChildren);
+
+ ///
+ /// Called when exiting a Node.
+ /// This method is called on every node previously entered, no matter the current visit mode.
+ ///
+ /// Exited node.
+ protected abstract void ExitNode(Node node);
+ }
+}
diff --git a/TypeCobol.LanguageServer/Utilities/CodeElementLocator.cs b/TypeCobol.LanguageServer/Utilities/CodeElementLocator.cs
index 7d799d761..d88bdb14a 100644
--- a/TypeCobol.LanguageServer/Utilities/CodeElementLocator.cs
+++ b/TypeCobol.LanguageServer/Utilities/CodeElementLocator.cs
@@ -1,4 +1,4 @@
-using System.Diagnostics;
+using System.Diagnostics;
using TypeCobol.Compiler;
using TypeCobol.Compiler.CodeElements;
using TypeCobol.Compiler.Nodes;
diff --git a/TypeCobol/Compiler/Diagnostics/CrossChecker.cs b/TypeCobol/Compiler/Diagnostics/CrossChecker.cs
index d26f1982a..cd61d69a7 100644
--- a/TypeCobol/Compiler/Diagnostics/CrossChecker.cs
+++ b/TypeCobol/Compiler/Diagnostics/CrossChecker.cs
@@ -807,7 +807,7 @@ public override bool Visit(DataDefinition dataDefinition)
}
}
- if (HasChildrenThatDeclareData(dataDefinition))//Check if group is valid
+ if (dataDefinition.HasChildrenThatDeclareData())//Check if group is valid
{
if (dataDefinition.Picture != null)
{
@@ -947,42 +947,6 @@ public override bool Visit(FunctionEnd functionEnd)
return true;
}
- ///
- /// Test if the received DataDefinition has other children than DataConditionEntry or DataRenamesEntry
- ///
- /// Item to check
- /// True if there are only DataConditionEntry or DataRenamesEntry children
- private static bool HasChildrenThatDeclareData([NotNull] DataDefinition dataDefinition)
- {
- //We only need to check the last children:
- //DataConditionEntry is a level 88, DataRenamesEntry is level 66 and they cannot have children
- //DataDescription and DataRedefines are level between 1 and 49 inclusive.
- //As the level number drives the positioning of Node inside the Children:
- //- DataConditionEntry will always be positioned before other dataDescription
- //- DataRenamesEntry will always be positioned after other dataDescription
- if (dataDefinition.ChildrenCount > 0)
- {
- var lastChild = dataDefinition.Children[dataDefinition.ChildrenCount - 1];
-
- if (lastChild.CodeElement == null)
- {
- Debug.Assert(lastChild is IndexDefinition);
- //Last child is an Index in an OCCURS: it is not a declaration
- return false;
- }
-
- if (lastChild.CodeElement.Type == CodeElementType.DataRenamesEntry)
- {
- //Last child is a DataRenamesEntry: we need to loop on the other children to find a possible DataDescription before
- return dataDefinition.Children.Any(c => c is DataDescription);
- }
-
- return lastChild.CodeElement.Type != CodeElementType.DataConditionEntry;
- }
-
- return false;
- }
-
public override bool Visit(IndexDefinition indexDefinition)
{
var found =
diff --git a/TypeCobol/Compiler/Nodes/Data.cs b/TypeCobol/Compiler/Nodes/Data.cs
index 81e87d3e3..c07fd83db 100644
--- a/TypeCobol/Compiler/Nodes/Data.cs
+++ b/TypeCobol/Compiler/Nodes/Data.cs
@@ -1,18 +1,14 @@
+using System.Diagnostics;
using System.Text;
using JetBrains.Annotations;
+using TypeCobol.Compiler.CodeElements;
using TypeCobol.Compiler.Parser;
+using TypeCobol.Compiler.Scanner;
using TypeCobol.Compiler.Text;
using TypeCobol.Compiler.Types;
namespace TypeCobol.Compiler.Nodes {
- using System;
- using System.Collections.Generic;
- using Scanner;
- using TypeCobol.Compiler.CodeElements;
-
-
-
public class DataDivision: GenericNode, Parent {
public const string NODE_ID = "data-division";
@@ -29,7 +25,7 @@ private enum SectionIndex
LinkageSection
}
// The 5 (optional) data sections
- private DataSection[] _sections = new DataSection[5];
+ private readonly DataSection[] _sections = new DataSection[5];
public FileSection FileSection => (FileSection) _sections[(int)SectionIndex.FileSection];
public GlobalStorageSection GlobalStorageSection => (GlobalStorageSection)_sections[(int)SectionIndex.GlobalStorageSection];
public WorkingStorageSection WorkingStorageSection => (WorkingStorageSection)_sections[(int)SectionIndex.WorkingStorageSection];
@@ -1127,6 +1123,69 @@ public static bool IsFiller([CanBeNull] this DataDefinition dataDefinition)
&& dataDefinition.ChildrenCount == 0 // No children
&& dataDefinition.CodeElement.IsFiller(); // Anonymous or FILLER (by using keyword or FILLER-like name)
}
+
+ ///
+ /// Test if the received DataDefinition has other children than DataConditionEntry or DataRenamesEntry
+ ///
+ /// Non-null item to check
+ /// True if there are only DataConditionEntry or DataRenamesEntry children
+ public static bool HasChildrenThatDeclareData([NotNull] this DataDefinition dataDefinition)
+ {
+ //We only need to check the last children:
+ //DataConditionEntry is a level 88, DataRenamesEntry is level 66 and they cannot have children
+ //DataDescription and DataRedefines are level between 1 and 49 inclusive.
+ //As the level number drives the positioning of Node inside the Children:
+ //- DataConditionEntry will always be positioned before other dataDescription
+ //- DataRenamesEntry will always be positioned after other dataDescription
+ if (dataDefinition.ChildrenCount > 0)
+ {
+ var lastChild = dataDefinition.Children[dataDefinition.ChildrenCount - 1];
+
+ if (lastChild.CodeElement == null)
+ {
+ Debug.Assert(lastChild is IndexDefinition);
+ //Last child is an Index in an OCCURS: it is not a declaration
+ return false;
+ }
+
+ if (lastChild.CodeElement.Type == CodeElementType.DataRenamesEntry)
+ {
+ //Last child is a DataRenamesEntry: we need to loop on the other children to find a possible DataDescription before
+ return dataDefinition.Children.Any(c => c is DataDescription);
+ }
+
+ return lastChild.CodeElement.Type != CodeElementType.DataConditionEntry;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Test the given data to check whether its PICTURE is among the given Picture categories.
+ ///
+ /// Non-null data to test.
+ /// Enumeration of Picture Categories to test, passing no categories will simply test whether
+ /// the data has a PICTURE type or not.
+ /// True when the actual picture category of the data is among the given categories.
+ public static bool HasPictureCategory([NotNull] this DataDefinition dataDefinition, params PictureCategory[] pictureCategories)
+ {
+ var type = dataDefinition.SemanticData?.Type;
+ bool hasPicture = type?.Tag == Types.Type.Tags.Picture;
+ if (hasPicture)
+ {
+ if (pictureCategories.Length == 0)
+ return true; // No categories to test, the method will just indicate that the data has a PICTURE
+
+ var pictureType = (PictureType)type;
+ return pictureCategories.Contains(pictureType.Category); // We could use HashSet, but we're assuming a small array of PictureCategory without duplicates
+ }
+
+ // No PICTURE
+ return false;
+ }
+
+ public static bool IsNationalOrNationalEdited([NotNull] this DataDefinition dataDefinition)
+ => dataDefinition.HasPictureCategory(PictureCategory.National, PictureCategory.NationalEdited);
}
} // end of namespace TypeCobol.Compiler.Nodes