Skip to content

Commit f52ac3b

Browse files
committed
fix: Outlining for regions and namespaces
1 parent eaecfc7 commit f52ac3b

4 files changed

Lines changed: 98 additions & 30 deletions

File tree

docs/spans.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ The main span is used when determining if a code item is part of a region or imp
1212
And when selecting the code from the code item context menu.
1313

1414
<pre>
15-
<font color="green">namespace CodeNav.Test.Files;
15+
<b>namespace CodeNav.Test.Files;
1616

1717
internal class TestSpans
1818
{
1919
private int Counter = 0;
20-
}</font>
20+
}</b>
2121
</pre>
2222

2323
## Identifier span
@@ -31,7 +31,7 @@ member in the text view.
3131
<pre>
3232
namespace CodeNav.Test.Files;
3333

34-
internal class <font color="green">TestSpans</font>
34+
internal class <b>TestSpans</b>
3535
{
3636
private int Counter = 0;
3737
}
@@ -47,8 +47,28 @@ to find the matching outline region in the text view.
4747
<pre>
4848
namespace CodeNav.Test.Files;
4949

50-
internal class TestSpans<font color="green">
50+
internal class TestSpans<b>
5151
{
5252
private int Counter = 0;
53-
}</font>
54-
</pre>
53+
}</b>
54+
</pre>
55+
56+
<pre>
57+
namespace CodeNav.Test.Files;
58+
59+
<b>#region Classes
60+
internal class TestSpans
61+
{
62+
private int Counter = 0;
63+
}
64+
#endregion</b>
65+
</pre>
66+
67+
namespace CodeNav.Test.Files;
68+
69+
$\color{green}{\textsf{#region Classes}}$<br/>
70+
$\color{green}{\textsf{internal class TestSpans}}$<br/>
71+
$\color{green}{\textsf{{}}$<br/>
72+
$\color{green}{\textsf{private int Counter = 0;}}$<br/>
73+
$\color{green}{\textsf{}}}$<br/>
74+
$\color{green}{\textsf{#endregion}}$<br/>

src/CodeNav.OutOfProc/Languages/CSharp/Mappers/BaseMapper.cs

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using CodeNav.OutOfProc.ViewModels;
33
using Microsoft.CodeAnalysis;
44
using Microsoft.CodeAnalysis.CSharp;
5+
using Microsoft.CodeAnalysis.CSharp.Syntax;
56
using Microsoft.CodeAnalysis.Text;
67

78
namespace CodeNav.OutOfProc.Languages.CSharp.Mappers;
@@ -15,7 +16,8 @@ public static class BaseMapper
1516
/// <param name="source">Syntax node of the code member</param>
1617
/// <param name="semanticModel">Semantic model used during compilation</param>
1718
/// <param name="codeDocumentViewModel">Code document view model used in the CodeNav tool window</param>
18-
/// <param name="identifier">Syntax token of the code identifier</param>
19+
/// <param name="identifier">Syntax token of the code member identifier</param>
20+
/// <param name="nameSyntax">Syntax token of the code member name</param>
1921
/// <param name="name">Name of the code member</param>
2022
/// <param name="modifiers">Accessibility modifiers of the code member</param>
2123
/// <returns>Code item class or othe code class derived from code item</returns>
@@ -24,12 +26,13 @@ public static T MapBase<T>(
2426
SemanticModel semanticModel,
2527
CodeDocumentViewModel codeDocumentViewModel,
2628
SyntaxToken? identifier = null,
29+
NameSyntax? nameSyntax = null,
2730
string name = "",
2831
SyntaxTokenList? modifiers = null) where T : CodeItem
2932
{
3033
var codeItem = Activator.CreateInstance<T>();
3134

32-
var codeItemName = MapName(identifier, name);
35+
var codeItemName = MapName(identifier, nameSyntax, name);
3336

3437
codeItem.Name = codeItemName;
3538
codeItem.FullName = MapFullName(source, codeItemName, semanticModel);
@@ -43,20 +46,43 @@ public static T MapBase<T>(
4346

4447
codeItem.Span = source.Span;
4548
codeItem.IdentifierSpan = identifier?.Span;
46-
codeItem.OutlineSpan = MapOutlineSpan(codeItem.Span, codeItem.IdentifierSpan, name);
49+
codeItem.OutlineSpan = MapOutlineSpan(codeItem.Span, codeItem.IdentifierSpan, nameSyntax?.Span);
4750

4851
return codeItem;
4952
}
5053

51-
private static TextSpan MapOutlineSpan(TextSpan span, TextSpan? identifierSpan, string name)
54+
/// <summary>
55+
/// Map the span that is used for expanding/collapsing outline regions
56+
/// </summary>
57+
/// <param name="span">Normal span of the syntax node</param>
58+
/// <param name="identifierSpan">Identifier span of the syntax node</param>
59+
/// <param name="name">Name of the syntax node</param>
60+
/// <returns>TextSpan usable for outlining</returns>
61+
private static TextSpan MapOutlineSpan(TextSpan span, TextSpan? identifierSpan, TextSpan? nameSpan)
5262
{
53-
var outlineSpanStart = identifierSpan != null
54-
? identifierSpan.Value.End
55-
: span.Start + name.Length;
63+
var outlineSpanStart = 0;
64+
65+
if (nameSpan != null)
66+
{
67+
outlineSpanStart = nameSpan.Value.End;
68+
}
69+
70+
if (identifierSpan != null)
71+
{
72+
outlineSpanStart = identifierSpan.Value.End;
73+
}
5674

5775
return new TextSpan(outlineSpanStart, span.End - outlineSpanStart);
5876
}
5977

78+
/// <summary>
79+
/// Map the full name of a code item
80+
/// </summary>
81+
/// <remarks>Used to create a unique id for the code item</remarks>
82+
/// <param name="source">Syntax node of the code item</param>
83+
/// <param name="name">Display name of the code item</param>
84+
/// <param name="semanticModel">Semantic model used during compilation</param>
85+
/// <returns>String full name</returns>
6086
private static string MapFullName(SyntaxNode source, string name, SemanticModel semanticModel)
6187
{
6288
try
@@ -70,8 +96,26 @@ private static string MapFullName(SyntaxNode source, string name, SemanticModel
7096
}
7197
}
7298

73-
private static string MapName(SyntaxToken? identifier, string name)
74-
=> identifier != null ? identifier.Value.Text : name;
99+
/// <summary>
100+
/// Map the display name of a code item
101+
/// </summary>
102+
/// <param name="identifier">Identifier syntax token of the code item</param>
103+
/// <param name="nameSyntax">Name syntax token of the code item</param>
104+
/// <returns>String display name</returns>
105+
private static string MapName(SyntaxToken? identifier, NameSyntax? nameSyntax, string name = "")
106+
{
107+
if (identifier != null)
108+
{
109+
return identifier.Value.Text;
110+
}
111+
112+
if (nameSyntax != null)
113+
{
114+
return nameSyntax.ToString();
115+
}
116+
117+
return name;
118+
}
75119

76120
private static CodeItemAccessEnum MapAccess(SyntaxTokenList? modifiers, SyntaxNode source)
77121
{

src/CodeNav.OutOfProc/Languages/CSharp/Mappers/NamespaceMapper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public static class NamespaceMapper
1212
public static CodeNamespaceItem MapNamespace(BaseNamespaceDeclarationSyntax member,
1313
SemanticModel semanticModel, SyntaxTree tree, CodeDocumentViewModel codeDocumentViewModel)
1414
{
15-
var codeItem = BaseMapper.MapBase<CodeNamespaceItem>(member, semanticModel, codeDocumentViewModel, name: member.Name.ToString());
15+
var codeItem = BaseMapper.MapBase<CodeNamespaceItem>(member, semanticModel, codeDocumentViewModel, nameSyntax: member.Name);
1616
codeItem.Kind = CodeItemKindEnum.Namespace;
1717
codeItem.Moniker = IconMapper.MapMoniker(codeItem.Kind, codeItem.Access);
1818
codeItem.Tooltip = TooltipMapper.Map(member, codeItem.Access, string.Empty, codeItem.Name, codeItem.Parameters);

src/CodeNav.OutOfProc/Languages/CSharp/Mappers/RegionMapper.cs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public static List<CodeRegionItem> MapRegions(SyntaxTree tree, TextSpan span, Co
4545
.Where(syntaxTrivia => syntaxTrivia.IsKind(SyntaxKind.RegionDirectiveTrivia))
4646
.Where(syntaxTrivia => span.Contains(syntaxTrivia.Span));
4747

48-
regionList.AddRange(regionStarts.Select(MapRegion));
48+
regionList.AddRange(regionStarts.Select(regionStart => MapRegion(regionStart, codeDocumentViewModel)));
4949

5050
// Find all region end trivia
5151
var regionEnds = root
@@ -66,6 +66,7 @@ public static List<CodeRegionItem> MapRegions(SyntaxTree tree, TextSpan span, Co
6666
}
6767

6868
region.Span = new(region.Span.Start, regionEnd.Span.End - region.Span.Start);
69+
region.OutlineSpan = new(region.OutlineSpan.Start, regionEnd.Span.End - region.Span.Start);
6970
}
7071

7172
var regions = ToHierarchy(regionList, span);
@@ -100,7 +101,7 @@ private static List<CodeRegionItem> ToHierarchy(List<CodeRegionItem> regionList,
100101
return nestedRegions;
101102
}
102103

103-
private static CodeRegionItem MapRegion(SyntaxTrivia regionStart)
104+
private static CodeRegionItem MapRegion(SyntaxTrivia regionStart, CodeDocumentViewModel codeDocumentViewModel)
104105
{
105106
var name = MapRegionName(regionStart);
106107

@@ -112,13 +113,16 @@ private static CodeRegionItem MapRegion(SyntaxTrivia regionStart)
112113
Tooltip = name,
113114
Kind = CodeItemKindEnum.Region,
114115
Span = new(regionStart.Span.Start, 0),
116+
OutlineSpan = new(regionStart.Span.Start, 0),
115117
Moniker = IconMapper.MapMoniker(CodeItemKindEnum.Region, CodeItemAccessEnum.Unknown),
118+
CodeDocumentViewModel = codeDocumentViewModel,
116119
};
117120
}
118121

119122
private static string MapRegionName(SyntaxTrivia source)
120123
{
121124
const string defaultRegionName = "Region";
125+
122126
var syntaxNode = source.GetStructure();
123127
var name = "#";
124128

@@ -145,11 +149,11 @@ private static string MapRegionName(SyntaxTrivia source)
145149
/// Add a CodeItem to the list of Regions, will recursively find the right region to add the item
146150
/// </summary>
147151
/// <param name="regions"></param>
148-
/// <param name="item"></param>
152+
/// <param name="codeItem"></param>
149153
/// <returns></returns>
150-
public static bool AddToRegion(List<CodeRegionItem> regions, CodeItem item)
154+
public static bool AddToRegion(List<CodeRegionItem> regions, CodeItem codeItem)
151155
{
152-
if (item?.Span == null)
156+
if (codeItem?.Span == null)
153157
{
154158
return false;
155159
}
@@ -161,15 +165,15 @@ public static bool AddToRegion(List<CodeRegionItem> regions, CodeItem item)
161165
continue;
162166
}
163167

164-
if (AddToRegion(region.Members, item))
168+
if (AddToRegion(region.Members, codeItem))
165169
{
166170
return true;
167171
}
168172

169-
if (item.Span.Start >= region.Span.Start &&
170-
item.Span.Start <= region.Span.End)
173+
if (codeItem.Span.Start >= region.Span.Start &&
174+
codeItem.Span.Start <= region.Span.End)
171175
{
172-
region.Members.Add(item);
176+
region.Members.Add(codeItem);
173177
return true;
174178
}
175179
}
@@ -181,9 +185,9 @@ public static bool AddToRegion(List<CodeRegionItem> regions, CodeItem item)
181185
/// Help add a CodeItem to an inner Region structure
182186
/// </summary>
183187
/// <param name="members"></param>
184-
/// <param name="item"></param>
188+
/// <param name="codeItem"></param>
185189
/// <returns></returns>
186-
private static bool AddToRegion(List<CodeItem> members, CodeItem item)
190+
private static bool AddToRegion(List<CodeItem> members, CodeItem codeItem)
187191
{
188192
foreach (var member in members)
189193
{
@@ -192,15 +196,15 @@ private static bool AddToRegion(List<CodeItem> members, CodeItem item)
192196
continue;
193197
}
194198

195-
if (member is IMembers memberItem && AddToRegion(memberItem.Members, item))
199+
if (member is IMembers memberItem && AddToRegion(memberItem.Members, codeItem))
196200
{
197201
return true;
198202
}
199203

200204
if (member is CodeRegionItem regionItem &&
201-
member.Span.Contains(item.Span))
205+
member.Span.Contains(codeItem.Span))
202206
{
203-
regionItem.Members.Add(item);
207+
regionItem.Members.Add(codeItem);
204208
return true;
205209
}
206210
}

0 commit comments

Comments
 (0)