diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml
index e5954d1..9a011d0 100644
--- a/.github/workflows/workflow.yml
+++ b/.github/workflows/workflow.yml
@@ -10,7 +10,7 @@ permissions:
contents: read
env:
- version: '10.10.${{ github.run_number }}'
+ version: '10.11.${{ github.run_number }}'
dotnetVersion: '8'
repoUrl: ${{ github.server_url }}/${{ github.repository }}
vsixPath: src/CodeNav/bin/Release/net472/CodeNav.vsix
@@ -142,20 +142,20 @@ jobs:
uses: actions/download-artifact@v8
- name: Publish to Marketplace
- uses: cezarypiatek/VsixPublisherAction@1.2
+ uses: madskristensen/publish-marketplace@v2
with:
extension-file: ${{ github.workspace }}/CodeNav/${{ env.vsixPath }}
publish-manifest-file: CodeNav/publish-manifest.json
personal-access-code: ${{ secrets.VS_PUBLISHER_ACCESS_TOKEN }}
- name: Publish to Marketplace 2022
- uses: cezarypiatek/VsixPublisherAction@1.2
+ uses: madskristensen/publish-marketplace@v2
with:
extension-file: ${{ github.workspace }}/CodeNav2022/${{ env.vsixPath }}
publish-manifest-file: CodeNav2022/publish-manifest-2022.json
personal-access-code: ${{ secrets.VS_PUBLISHER_ACCESS_TOKEN }}
- name: Publish to Open VSIX Gallery
- uses: timheuer/openvsixpublish@v1
+ uses: madskristensen/publish-vsixgallery@v1
with:
vsix-file: ${{ github.workspace }}/CodeNav/${{ env.vsixPath }}
\ No newline at end of file
diff --git a/README.md b/README.md
index 1e9c389..404df21 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@ Visual Studio extension to show the code structure of your current document
# 🔤 Language support
- C#
+- Visual Basic (enable it in the CodeNav settings)
# ⬇️ Installing
[Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=SamirBoulema.CodeNav2026)
@@ -60,4 +61,4 @@ The CodeNav tool window can be opened via the entry in the `Extensions` menu.

# 📄 Documentation
-Looking for more info? Read the full [Documentation](https://github.com/sboulema/CodeNav/blob/main/docs/index.md) with links, guides, troubleshooting, and tips.
\ No newline at end of file
+Looking for more info? Read the full [Documentation](https://github.com/sboulema/CodeNav/blob/main/docs/index.md) with links, guides, troubleshooting, and tips.
diff --git a/src/CodeNav.OutOfProc/CodeNav.OutOfProc.csproj b/src/CodeNav.OutOfProc/CodeNav.OutOfProc.csproj
index 94a6565..1f77323 100644
--- a/src/CodeNav.OutOfProc/CodeNav.OutOfProc.csproj
+++ b/src/CodeNav.OutOfProc/CodeNav.OutOfProc.csproj
@@ -19,7 +19,8 @@
-
+
+
diff --git a/src/CodeNav.OutOfProc/Dialogs/SettingsDialog/SettingsDialogControl.xaml b/src/CodeNav.OutOfProc/Dialogs/SettingsDialog/SettingsDialogControl.xaml
index 0201876..aa05bce 100644
--- a/src/CodeNav.OutOfProc/Dialogs/SettingsDialog/SettingsDialogControl.xaml
+++ b/src/CodeNav.OutOfProc/Dialogs/SettingsDialog/SettingsDialogControl.xaml
@@ -42,6 +42,21 @@
+
+
+
+ C#
+
+
+
+ Visual Basic
+
+
+
+
-
\ No newline at end of file
+
diff --git a/src/CodeNav.OutOfProc/Dialogs/SettingsDialog/SettingsDialogData.cs b/src/CodeNav.OutOfProc/Dialogs/SettingsDialog/SettingsDialogData.cs
index 2c30e7f..eb925ed 100644
--- a/src/CodeNav.OutOfProc/Dialogs/SettingsDialog/SettingsDialogData.cs
+++ b/src/CodeNav.OutOfProc/Dialogs/SettingsDialog/SettingsDialogData.cs
@@ -1,4 +1,5 @@
-using System.Runtime.Serialization;
+using CodeNav.OutOfProc.Constants;
+using System.Runtime.Serialization;
namespace CodeNav.OutOfProc.Dialogs.SettingsDialog;
@@ -52,4 +53,16 @@ public class SettingsDialogData
///
[DataMember]
public bool UseCompactMode { get; set; }
+
+ ///
+ /// Setting which languages are enabled for CodeNav.
+ ///
+ [DataMember]
+ public bool EnableCSharp { get; set; } = true;
+
+ ///
+ /// Setting which languages are enabled for CodeNav.
+ ///
+ [DataMember]
+ public bool EnableVisualBasic { get; set; } = false;
}
diff --git a/src/CodeNav.OutOfProc/Interfaces/IDocumentMapper.cs b/src/CodeNav.OutOfProc/Interfaces/IDocumentMapper.cs
new file mode 100644
index 0000000..04ac3a4
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Interfaces/IDocumentMapper.cs
@@ -0,0 +1,19 @@
+using CodeNav.OutOfProc.Models;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.VisualStudio.Extensibility;
+
+namespace CodeNav.OutOfProc.Interfaces;
+
+public interface IDocumentMapper
+{
+ bool CanMapDocument(
+ string filePath,
+ GlobalSettings settings);
+
+ Task> MapDocument(
+ string text,
+ string? excludeFilePath,
+ CodeDocumentViewModel codeDocumentViewModel,
+ VisualStudioExtensibility extensibility,
+ CancellationToken cancellationToken);
+}
\ No newline at end of file
diff --git a/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/DocumentMapper.cs b/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/DocumentMapper.cs
index e873f01..e0b7989 100644
--- a/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/DocumentMapper.cs
+++ b/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/DocumentMapper.cs
@@ -1,3 +1,5 @@
+using CodeNav.OutOfProc.Interfaces;
+using CodeNav.OutOfProc.Models;
using CodeNav.OutOfProc.ViewModels;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
@@ -7,7 +9,7 @@
namespace CodeNav.OutOfProc.Languages.CSharp.Mappers;
-public class DocumentMapper
+public class DocumentMapper : IDocumentMapper
{
///
/// Map text document to list of code items.
@@ -18,7 +20,7 @@ public class DocumentMapper
/// Visual Studio extensibility used to retrieve all solution files for compilation
/// Cancellation token
/// List of code items
- public static async Task> MapDocument(
+ public async Task> MapDocument(
string text,
string? excludeFilePath,
CodeDocumentViewModel codeDocumentViewModel,
@@ -138,6 +140,9 @@ SyntaxKind.ExtensionBlockDeclaration when member is ExtensionBlockDeclarationSyn
_ => null,
};
- public static bool CanMapDocument(string filePath)
- => filePath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase);
+ public bool CanMapDocument(
+ string filePath,
+ GlobalSettings settings)
+ => settings.EnableCSharp &&
+ filePath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase);
}
\ No newline at end of file
diff --git a/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/RegionMapper.cs b/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/RegionMapper.cs
index 9fa8ffa..ecb3d42 100644
--- a/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/RegionMapper.cs
+++ b/src/CodeNav.OutOfProc/Languages/CSharp/Mappers/RegionMapper.cs
@@ -8,7 +8,6 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
-using System.Drawing;
namespace CodeNav.OutOfProc.Languages.CSharp.Mappers;
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/BaseMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/BaseMapper.cs
new file mode 100644
index 0000000..5a1852e
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/BaseMapper.cs
@@ -0,0 +1,166 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+using Microsoft.CodeAnalysis.VisualBasic;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class BaseMapper
+{
+ public static T MapBase(
+ SyntaxNode source,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel,
+ SyntaxToken? identifier = null,
+ NameSyntax? nameSyntax = null,
+ string name = "",
+ SyntaxTokenList? modifiers = null) where T : CodeItem
+ {
+ var codeItem = Activator.CreateInstance();
+
+ var codeItemName = MapName(identifier, nameSyntax, name);
+
+ codeItem.Name = codeItemName;
+ codeItem.FullName = MapFullName(
+ source,
+ codeItemName,
+ semanticModel);
+
+ codeItem.FilePath =
+ string.IsNullOrEmpty(source.SyntaxTree.FilePath)
+ ? null
+ : new Uri(source.SyntaxTree.FilePath);
+
+ codeItem.Id = codeItem.FullName;
+ codeItem.Tooltip = codeItemName;
+ codeItem.Access = MapAccess(modifiers, source);
+ codeItem.CodeDocumentViewModel = codeDocumentViewModel;
+
+ codeItem.Span = source.Span;
+ codeItem.IdentifierSpan = identifier?.Span;
+ codeItem.OutlineSpan = MapOutlineSpan(
+ codeItem.Span,
+ codeItem.IdentifierSpan,
+ nameSyntax?.Span);
+
+ return codeItem;
+ }
+
+ private static TextSpan MapOutlineSpan(
+ TextSpan span,
+ TextSpan? identifierSpan,
+ TextSpan? nameSpan)
+ {
+ var outlineSpanStart = 0;
+
+ if (nameSpan != null)
+ {
+ outlineSpanStart = nameSpan.Value.End;
+ }
+
+ if (identifierSpan != null)
+ {
+ outlineSpanStart = identifierSpan.Value.End;
+ }
+
+ return new TextSpan(
+ outlineSpanStart,
+ span.End - outlineSpanStart);
+ }
+
+ private static string MapFullName(
+ SyntaxNode source,
+ string name,
+ SemanticModel semanticModel)
+ {
+ try
+ {
+ var symbol = semanticModel.GetDeclaredSymbol(source);
+
+ return symbol?.ToString() ?? name;
+ }
+ catch (Exception)
+ {
+ return name;
+ }
+ }
+
+ private static string MapName(
+ SyntaxToken? identifier,
+ NameSyntax? nameSyntax,
+ string name = "")
+ {
+ if (identifier != null &&
+ !identifier.Value.IsKind(SyntaxKind.None))
+ {
+ return identifier.Value.Text;
+ }
+
+ if (nameSyntax != null)
+ {
+ return nameSyntax.ToString();
+ }
+
+ return name;
+ }
+
+ private static CodeItemAccessEnum MapAccess(
+ SyntaxTokenList? modifiers,
+ SyntaxNode source)
+ {
+ if (modifiers == null)
+ {
+ return MapDefaultAccess(source);
+ }
+
+ if (modifiers.Value.Any(m =>
+ m.IsKind(SyntaxKind.PublicKeyword)))
+ {
+ return CodeItemAccessEnum.Public;
+ }
+
+ if (modifiers.Value.Any(m =>
+ m.IsKind(SyntaxKind.PrivateKeyword)))
+ {
+ return CodeItemAccessEnum.Private;
+ }
+
+ if (modifiers.Value.Any(m =>
+ m.IsKind(SyntaxKind.ProtectedKeyword)))
+ {
+ return CodeItemAccessEnum.Protected;
+ }
+
+ if (modifiers.Value.Any(m =>
+ m.IsKind(SyntaxKind.FriendKeyword)))
+ {
+ return CodeItemAccessEnum.Internal;
+ }
+
+ return MapDefaultAccess(source);
+ }
+
+ private static CodeItemAccessEnum MapDefaultAccess(
+ SyntaxNode source)
+ {
+ if (source.Parent is CompilationUnitSyntax)
+ {
+ return source switch
+ {
+ EnumBlockSyntax => CodeItemAccessEnum.Public,
+ NamespaceBlockSyntax => CodeItemAccessEnum.Public,
+ _ => CodeItemAccessEnum.Internal,
+ };
+ }
+
+ return source switch
+ {
+ NamespaceBlockSyntax => CodeItemAccessEnum.Public,
+ EnumBlockSyntax => CodeItemAccessEnum.Public,
+ InterfaceBlockSyntax => CodeItemAccessEnum.Public,
+ _ => CodeItemAccessEnum.Private,
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/ClassMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/ClassMapper.cs
new file mode 100644
index 0000000..1a44a55
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/ClassMapper.cs
@@ -0,0 +1,89 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.Mappers;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class ClassMapper
+{
+ public static CodeClassItem MapClass(
+ ClassBlockSyntax member,
+ SemanticModel semanticModel,
+ SyntaxTree tree,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ member.ClassStatement.Identifier,
+ modifiers: member.ClassStatement.Modifiers);
+
+ codeItem.Kind = CodeItemKindEnum.Class;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ codeItem.Parameters = MapInheritance(member);
+
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ string.Empty,
+ codeItem.Name,
+ codeItem.Parameters);
+
+ var regions = RegionMapper.MapRegions(
+ tree,
+ member.Span,
+ codeDocumentViewModel);
+
+ foreach (var classMember in member.Members)
+ {
+ foreach (var memberItem in DocumentMapper.MapMembers(
+ classMember,
+ tree,
+ semanticModel,
+ codeDocumentViewModel))
+ {
+ if (RegionMapper.AddToRegion(regions, memberItem))
+ {
+ continue;
+ }
+
+ codeItem.Members.Add(memberItem);
+ }
+ }
+
+ RegionMapper.AddRegionsIfNotPresent(
+ codeItem.Members,
+ regions);
+
+ return codeItem;
+ }
+
+ private static string MapInheritance(
+ ClassBlockSyntax member)
+ {
+ var inheritance = new List();
+
+ foreach (var inheritsStatement in member.Inherits)
+ {
+ inheritance.AddRange(
+ inheritsStatement.Types.Select(type => type.ToString()));
+ }
+
+ foreach (var implementsStatement in member.Implements)
+ {
+ inheritance.AddRange(
+ implementsStatement.Types.Select(
+ interfaceType => interfaceType.ToString()));
+ }
+
+ return inheritance.Any()
+ ? $" : {string.Join(", ", inheritance)}"
+ : string.Empty;
+ }
+}
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/DeclareMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/DeclareMapper.cs
new file mode 100644
index 0000000..20404a0
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/DeclareMapper.cs
@@ -0,0 +1,42 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.Mappers;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class DeclareMapper
+{
+ public static CodeItem MapDeclare(
+ DeclareStatementSyntax member,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ member.Identifier,
+ modifiers: member.Modifiers);
+
+ codeItem.Kind = CodeItemKindEnum.Method;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ codeItem.Parameters =
+ member.ParameterList?.ToString() ?? string.Empty;
+
+ codeItem.ReturnType = TypeMapper.Map(member.AsClause);
+
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ codeItem.ReturnType,
+ codeItem.Name,
+ codeItem.Parameters);
+
+ return codeItem;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/DelegateMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/DelegateMapper.cs
new file mode 100644
index 0000000..e1a9b0e
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/DelegateMapper.cs
@@ -0,0 +1,42 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.Mappers;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class DelegateMapper
+{
+ public static CodeItem MapDelegate(
+ DelegateStatementSyntax member,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ member.Identifier,
+ modifiers: member.Modifiers);
+
+ codeItem.Kind = CodeItemKindEnum.Delegate;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ codeItem.Parameters =
+ member.ParameterList?.ToString() ?? string.Empty;
+
+ codeItem.ReturnType = TypeMapper.Map(member.AsClause);
+
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ codeItem.ReturnType,
+ codeItem.Name,
+ codeItem.Parameters);
+
+ return codeItem;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/DocumentMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/DocumentMapper.cs
new file mode 100644
index 0000000..0b04b4e
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/DocumentMapper.cs
@@ -0,0 +1,265 @@
+using CodeNav.OutOfProc.Interfaces;
+using CodeNav.OutOfProc.Models;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+using Microsoft.VisualStudio.Extensibility;
+using Microsoft.VisualStudio.ProjectSystem.Query;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public class DocumentMapper : IDocumentMapper
+{
+ ///
+ /// Map text document to list of code items.
+ ///
+ public async Task> MapDocument(
+ string text,
+ string? excludeFilePath,
+ CodeDocumentViewModel codeDocumentViewModel,
+ VisualStudioExtensibility extensibility,
+ CancellationToken cancellationToken)
+ {
+ var projects = await extensibility
+ .Workspaces()
+ .QueryProjectsAsync(project
+ => project.With(project
+ => project.Files
+ .Where(file => file.Extension == ".vb")
+ .Where(file => file.Path != excludeFilePath)
+ .With(file => file.Path)), cancellationToken);
+
+ var filePaths = projects
+ .SelectMany(project => project.Files)
+ .Select(file => file.Path);
+
+ return await MapDocument(
+ text,
+ codeDocumentViewModel,
+ filePaths,
+ cancellationToken);
+ }
+
+ ///
+ /// Map text document to list of code items.
+ ///
+ public static async Task> MapDocument(
+ string text,
+ CodeDocumentViewModel codeDocumentViewModel,
+ IEnumerable filePaths,
+ CancellationToken cancellationToken)
+ {
+ var syntaxTree = VisualBasicSyntaxTree.ParseText(
+ text,
+ cancellationToken: cancellationToken);
+
+ var semanticModel = await GetVisualBasicSemanticModel(
+ filePaths,
+ syntaxTree,
+ cancellationToken);
+
+ var root =
+ (CompilationUnitSyntax)
+ await syntaxTree.GetRootAsync(cancellationToken);
+
+ if (semanticModel == null)
+ {
+ return [];
+ }
+
+ return [.. root.Members
+ .Where(member => member != null)
+ .SelectMany(member =>
+ MapMembers(
+ member,
+ syntaxTree,
+ semanticModel,
+ codeDocumentViewModel))
+ ];
+ }
+
+ public static async Task GetVisualBasicSemanticModel(
+ IEnumerable filePaths,
+ SyntaxTree syntaxTree,
+ CancellationToken cancellationToken)
+ {
+ var syntaxTrees = await Task.WhenAll(
+ filePaths.Select(async filePath =>
+ {
+ var fileText = await File.ReadAllTextAsync(
+ filePath,
+ cancellationToken);
+
+ return VisualBasicSyntaxTree
+ .ParseText(
+ fileText,
+ cancellationToken: cancellationToken)
+ .WithFilePath(filePath);
+ }));
+
+ var compilation = VisualBasicCompilation
+ .Create("CodeNavCompilation")
+ .AddReferences(
+ MetadataReference.CreateFromFile(
+ typeof(object).Assembly.Location))
+ .AddSyntaxTrees(syntaxTrees)
+ .AddSyntaxTrees(syntaxTree);
+
+ return compilation.GetSemanticModel(syntaxTree);
+ }
+
+ public static CodeItem? MapMember(
+ SyntaxNode member,
+ SyntaxTree tree,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ => member switch
+ {
+ NamespaceBlockSyntax namespaceSyntax
+ => NamespaceMapper.MapNamespace(
+ namespaceSyntax,
+ semanticModel,
+ tree,
+ codeDocumentViewModel),
+
+ ClassBlockSyntax classSyntax
+ => ClassMapper.MapClass(
+ classSyntax,
+ semanticModel,
+ tree,
+ codeDocumentViewModel),
+
+ StructureBlockSyntax structureSyntax
+ => StructureMapper.MapStructure(
+ structureSyntax,
+ semanticModel,
+ tree,
+ codeDocumentViewModel),
+
+ InterfaceBlockSyntax interfaceSyntax
+ => InterfaceMapper.MapInterface(
+ interfaceSyntax,
+ semanticModel,
+ tree,
+ codeDocumentViewModel),
+
+ MethodBlockSyntax methodSyntax
+ => MethodMapper.MapMethod(
+ methodSyntax,
+ semanticModel,
+ codeDocumentViewModel),
+
+ MethodStatementSyntax methodSyntax
+ => MethodMapper.MapMethod(
+ methodSyntax,
+ semanticModel,
+ codeDocumentViewModel),
+
+ ConstructorBlockSyntax constructorSyntax
+ => MethodMapper.MapConstructor(
+ constructorSyntax,
+ semanticModel,
+ codeDocumentViewModel),
+
+ PropertyBlockSyntax propertySyntax
+ => PropertyMapper.MapProperty(
+ propertySyntax,
+ semanticModel,
+ codeDocumentViewModel),
+
+ PropertyStatementSyntax propertySyntax
+ => PropertyMapper.MapProperty(
+ propertySyntax,
+ semanticModel,
+ codeDocumentViewModel),
+
+ FieldDeclarationSyntax fieldSyntax
+ => FieldMapper.MapFields(
+ fieldSyntax,
+ semanticModel,
+ codeDocumentViewModel)
+ .FirstOrDefault(),
+
+ EventBlockSyntax eventSyntax
+ => EventMapper.MapEvent(
+ eventSyntax,
+ semanticModel,
+ codeDocumentViewModel),
+
+ EventStatementSyntax eventSyntax
+ => EventMapper.MapEvent(
+ eventSyntax,
+ semanticModel,
+ codeDocumentViewModel),
+
+ EnumBlockSyntax enumSyntax
+ => EnumMapper.MapEnum(
+ enumSyntax,
+ semanticModel,
+ tree,
+ codeDocumentViewModel),
+
+ DelegateStatementSyntax delegateSyntax
+ => DelegateMapper.MapDelegate(
+ delegateSyntax,
+ semanticModel,
+ codeDocumentViewModel),
+
+ ModuleBlockSyntax moduleSyntax
+ => ModuleMapper.MapModule(
+ moduleSyntax,
+ semanticModel,
+ tree,
+ codeDocumentViewModel),
+
+ OperatorStatementSyntax operatorSyntax
+ => OperatorMapper.MapOperator(
+ operatorSyntax,
+ semanticModel,
+ codeDocumentViewModel),
+
+ OperatorBlockSyntax operatorSyntax
+ => OperatorMapper.MapOperator(
+ operatorSyntax,
+ semanticModel,
+ codeDocumentViewModel),
+
+ DeclareStatementSyntax declareSyntax
+ => DeclareMapper.MapDeclare(
+ declareSyntax,
+ semanticModel,
+ codeDocumentViewModel),
+
+ _ => null,
+ };
+
+ public static IEnumerable MapMembers(
+ SyntaxNode member,
+ SyntaxTree tree,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ if (member is FieldDeclarationSyntax fieldSyntax)
+ {
+ return FieldMapper.MapFields(
+ fieldSyntax,
+ semanticModel,
+ codeDocumentViewModel);
+ }
+
+ var codeItem = MapMember(
+ member,
+ tree,
+ semanticModel,
+ codeDocumentViewModel);
+
+ return codeItem == null ? [] : [codeItem];
+ }
+
+ public bool CanMapDocument(
+ string filePath,
+ GlobalSettings settings)
+ => settings.EnableVisualBasic &&
+ filePath.EndsWith(".vb", StringComparison.OrdinalIgnoreCase);
+}
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/EnumMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/EnumMapper.cs
new file mode 100644
index 0000000..db2e2f3
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/EnumMapper.cs
@@ -0,0 +1,83 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.Mappers;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class EnumMapper
+{
+ public static CodeItem MapEnum(
+ EnumBlockSyntax member,
+ SemanticModel semanticModel,
+ SyntaxTree tree,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var statement = member.EnumStatement;
+
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ statement.Identifier,
+ modifiers: statement.Modifiers);
+
+ codeItem.Kind = CodeItemKindEnum.Enum;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ var regions = RegionMapper.MapRegions(
+ tree,
+ member.Span,
+ codeDocumentViewModel);
+
+ foreach (var enumMember in member.Members.OfType())
+ {
+ var memberItem = MapEnumMember(
+ enumMember,
+ semanticModel,
+ codeDocumentViewModel);
+
+ if (RegionMapper.AddToRegion(regions, memberItem))
+ {
+ continue;
+ }
+
+ codeItem.Members.Add(memberItem);
+ }
+
+ RegionMapper.AddRegionsIfNotPresent(
+ codeItem.Members,
+ regions);
+
+ return codeItem;
+ }
+
+ private static CodeItem MapEnumMember(
+ EnumMemberDeclarationSyntax member,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ member.Identifier);
+
+ codeItem.Kind = CodeItemKindEnum.EnumMember;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ string.Empty,
+ codeItem.Name,
+ string.Empty);
+
+ return codeItem;
+ }
+}
\ No newline at end of file
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/EventMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/EventMapper.cs
new file mode 100644
index 0000000..d6bce9b
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/EventMapper.cs
@@ -0,0 +1,65 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.Mappers;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class EventMapper
+{
+ public static CodeItem MapEvent(
+ EventBlockSyntax member,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var statement = member.EventStatement;
+
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ statement.Identifier,
+ modifiers: statement.Modifiers);
+
+ codeItem.Kind = CodeItemKindEnum.Event;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ string.Empty,
+ codeItem.Name,
+ string.Empty);
+
+ return codeItem;
+ }
+
+ public static CodeItem MapEvent(
+ EventStatementSyntax member,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ member.Identifier,
+ modifiers: member.Modifiers);
+
+ codeItem.Kind = CodeItemKindEnum.Event;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ string.Empty,
+ codeItem.Name,
+ string.Empty);
+
+ return codeItem;
+ }
+}
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/FieldMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/FieldMapper.cs
new file mode 100644
index 0000000..7cac81d
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/FieldMapper.cs
@@ -0,0 +1,59 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.Mappers;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class FieldMapper
+{
+ public static IEnumerable MapFields(
+ FieldDeclarationSyntax member,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ => member.Declarators
+ .SelectMany(declarator => declarator.Names)
+ .Select(variable => MapField(
+ member,
+ variable,
+ semanticModel,
+ codeDocumentViewModel));
+
+ private static CodeItem MapField(
+ FieldDeclarationSyntax declaration,
+ ModifiedIdentifierSyntax variable,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var codeItem = BaseMapper.MapBase(
+ variable,
+ semanticModel,
+ codeDocumentViewModel,
+ variable.Identifier,
+ modifiers: declaration.Modifiers);
+
+ codeItem.Kind = IsConstant(declaration.Modifiers)
+ ? CodeItemKindEnum.Constant
+ : CodeItemKindEnum.Variable;
+
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ codeItem.Tooltip = TooltipMapper.Map(
+ variable,
+ codeItem.Access,
+ string.Empty,
+ codeItem.Name,
+ string.Empty);
+
+ return codeItem;
+ }
+
+ private static bool IsConstant(
+ SyntaxTokenList modifiers)
+ => modifiers.Any(
+ m => m.RawKind == (int)SyntaxKind.ConstKeyword);
+}
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/InterfaceMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/InterfaceMapper.cs
new file mode 100644
index 0000000..e574a8b
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/InterfaceMapper.cs
@@ -0,0 +1,83 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.Mappers;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class InterfaceMapper
+{
+ public static CodeInterfaceItem MapInterface(
+ InterfaceBlockSyntax member,
+ SemanticModel semanticModel,
+ SyntaxTree tree,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ member.InterfaceStatement.Identifier,
+ modifiers: member.InterfaceStatement.Modifiers);
+
+ codeItem.Kind = CodeItemKindEnum.Interface;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ codeItem.Parameters = MapInheritance(member);
+
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ string.Empty,
+ codeItem.Name,
+ codeItem.Parameters);
+
+ var regions = RegionMapper.MapRegions(
+ tree,
+ member.Span,
+ codeDocumentViewModel);
+
+ foreach (var interfaceMember in member.Members)
+ {
+ foreach (var memberItem in DocumentMapper.MapMembers(
+ interfaceMember,
+ tree,
+ semanticModel,
+ codeDocumentViewModel))
+ {
+ if (RegionMapper.AddToRegion(regions, memberItem))
+ {
+ continue;
+ }
+
+ codeItem.Members.Add(memberItem);
+ }
+ }
+
+ RegionMapper.AddRegionsIfNotPresent(
+ codeItem.Members,
+ regions);
+
+ return codeItem;
+ }
+
+ private static string MapInheritance(
+ InterfaceBlockSyntax member)
+ {
+ var inheritance = new List();
+
+ foreach (var inheritsStatement in member.Inherits)
+ {
+ inheritance.AddRange(
+ inheritsStatement.Types.Select(
+ type => type.ToString()));
+ }
+
+ return inheritance.Any()
+ ? $" : {string.Join(", ", inheritance)}"
+ : string.Empty;
+ }
+}
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/MethodMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/MethodMapper.cs
new file mode 100644
index 0000000..607ebac
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/MethodMapper.cs
@@ -0,0 +1,116 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.Mappers;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class MethodMapper
+{
+ public static CodeItem MapMethod(
+ MethodBlockSyntax member,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var statement = member.SubOrFunctionStatement;
+
+ var identifier = statement.Identifier;
+ var modifiers = statement.Modifiers;
+
+ var returnType = statement.AsClause;
+ var parameters = statement.ParameterList;
+
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ identifier,
+ modifiers: modifiers);
+
+ var functionItem = codeItem;
+
+ functionItem.ReturnType = TypeMapper.Map(returnType);
+ functionItem.Parameters = parameters?.ToString() ?? string.Empty;
+
+ codeItem.IdentifierSpan = identifier.Span;
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ functionItem.ReturnType,
+ codeItem.Name,
+ functionItem.Parameters);
+
+ codeItem.Kind = CodeItemKindEnum.Method;
+
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ return codeItem;
+ }
+
+ public static CodeItem MapMethod(
+ MethodStatementSyntax member,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ member.Identifier,
+ modifiers: member.Modifiers);
+
+ codeItem.ReturnType = TypeMapper.Map(member.AsClause);
+ codeItem.Parameters = member.ParameterList?.ToString() ?? string.Empty;
+ codeItem.IdentifierSpan = member.Identifier.Span;
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ codeItem.ReturnType,
+ codeItem.Name,
+ codeItem.Parameters);
+ codeItem.Kind = CodeItemKindEnum.Method;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ return codeItem;
+ }
+
+ public static CodeItem MapConstructor(
+ ConstructorBlockSyntax member,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var statement = member.SubNewStatement;
+
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ name: "New",
+ modifiers: statement.Modifiers);
+
+ var functionItem = codeItem;
+
+ functionItem.Parameters =
+ statement.ParameterList?.ToString() ?? string.Empty;
+
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ string.Empty,
+ codeItem.Name,
+ functionItem.Parameters);
+
+ codeItem.Kind = CodeItemKindEnum.Constructor;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ return codeItem;
+ }
+}
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/ModuleMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/ModuleMapper.cs
new file mode 100644
index 0000000..c9c1580
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/ModuleMapper.cs
@@ -0,0 +1,66 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.Mappers;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class ModuleMapper
+{
+ public static CodeItem MapModule(
+ ModuleBlockSyntax member,
+ SemanticModel semanticModel,
+ SyntaxTree tree,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var statement = member.ModuleStatement;
+
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ statement.Identifier,
+ modifiers: statement.Modifiers);
+
+ codeItem.Kind = CodeItemKindEnum.Class;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ string.Empty,
+ codeItem.Name,
+ string.Empty);
+
+ var regions = RegionMapper.MapRegions(
+ tree,
+ member.Span,
+ codeDocumentViewModel);
+
+ foreach (var moduleMember in member.Members)
+ {
+ foreach (var memberItem in DocumentMapper.MapMembers(
+ moduleMember,
+ tree,
+ semanticModel,
+ codeDocumentViewModel))
+ {
+ if (RegionMapper.AddToRegion(regions, memberItem))
+ {
+ continue;
+ }
+
+ codeItem.Members.Add(memberItem);
+ }
+ }
+
+ RegionMapper.AddRegionsIfNotPresent(
+ codeItem.Members,
+ regions);
+
+ return codeItem;
+ }
+}
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/NamespaceMember.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/NamespaceMember.cs
new file mode 100644
index 0000000..d0ad152
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/NamespaceMember.cs
@@ -0,0 +1,62 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.Extensions;
+using CodeNav.OutOfProc.Mappers;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class NamespaceMapper
+{
+ public static CodeNamespaceItem MapNamespace(
+ NamespaceBlockSyntax member,
+ SemanticModel semanticModel,
+ SyntaxTree tree,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ name: member.NamespaceStatement.Name.ToString());
+
+ codeItem.Kind = CodeItemKindEnum.Namespace;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ string.Empty,
+ codeItem.Name,
+ string.Empty);
+
+ var regions = RegionMapper.MapRegions(
+ tree,
+ member.Span,
+ codeDocumentViewModel);
+
+ foreach (var namespaceMember in member.Members)
+ {
+ foreach (var memberItem in DocumentMapper.MapMembers(
+ namespaceMember,
+ tree,
+ semanticModel,
+ codeDocumentViewModel))
+ {
+ if (!RegionMapper.AddToRegion(regions, memberItem))
+ {
+ codeItem.Members.AddIfNotNull(memberItem);
+ }
+ }
+ }
+
+ RegionMapper.AddRegionsIfNotPresent(
+ codeItem.Members,
+ regions);
+
+ return codeItem;
+ }
+}
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/OperatorMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/OperatorMapper.cs
new file mode 100644
index 0000000..b523a78
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/OperatorMapper.cs
@@ -0,0 +1,63 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.Mappers;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class OperatorMapper
+{
+ public static CodeItem MapOperator(
+ OperatorStatementSyntax member,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ => MapOperator(
+ member,
+ member,
+ semanticModel,
+ codeDocumentViewModel);
+
+ public static CodeItem MapOperator(
+ OperatorBlockSyntax member,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ => MapOperator(
+ member,
+ member.OperatorStatement,
+ semanticModel,
+ codeDocumentViewModel);
+
+ private static CodeItem MapOperator(
+ SyntaxNode source,
+ OperatorStatementSyntax member,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var codeItem = BaseMapper.MapBase(
+ source,
+ semanticModel,
+ codeDocumentViewModel,
+ name: member.OperatorToken.Text,
+ modifiers: member.Modifiers);
+
+ codeItem.Kind = CodeItemKindEnum.Method;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ codeItem.Parameters =
+ member.ParameterList?.ToString() ?? string.Empty;
+
+ codeItem.ReturnType = TypeMapper.Map(member.AsClause);
+
+ codeItem.Tooltip = TooltipMapper.Map(
+ source,
+ codeItem.Access,
+ codeItem.ReturnType,
+ codeItem.Name,
+ codeItem.Parameters);
+
+ return codeItem;
+ }
+}
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/PropertyMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/PropertyMapper.cs
new file mode 100644
index 0000000..dd20a30
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/PropertyMapper.cs
@@ -0,0 +1,94 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.Mappers;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class PropertyMapper
+{
+ public static CodePropertyItem MapProperty(
+ PropertyBlockSyntax member,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var statement = member.PropertyStatement;
+
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ statement.Identifier,
+ modifiers: statement.Modifiers);
+
+ codeItem.IdentifierSpan = statement.Identifier.Span;
+
+ codeItem.ReturnType = TypeMapper.Map(statement.AsClause);
+ codeItem.Parameters = statement.ParameterList?.ToString() ?? string.Empty;
+
+ var accessors = new List();
+
+ if (member.Accessors.Any(
+ accessor => accessor.Kind() == SyntaxKind.GetAccessorBlock))
+ {
+ accessors.Add("Get");
+ }
+
+ if (member.Accessors.Any(
+ accessor => accessor.Kind() == SyntaxKind.SetAccessorBlock))
+ {
+ accessors.Add("Set");
+ }
+
+ if (accessors.Any())
+ {
+ codeItem.Parameters +=
+ $" {{{string.Join(",", accessors)}}}";
+ }
+
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ codeItem.ReturnType,
+ codeItem.Name,
+ codeItem.Parameters);
+
+ codeItem.Kind = CodeItemKindEnum.Property;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ return codeItem;
+ }
+
+ public static CodePropertyItem MapProperty(
+ PropertyStatementSyntax member,
+ SemanticModel semanticModel,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ member.Identifier,
+ modifiers: member.Modifiers);
+
+ codeItem.IdentifierSpan = member.Identifier.Span;
+ codeItem.ReturnType = TypeMapper.Map(member.AsClause);
+ codeItem.Parameters = member.ParameterList?.ToString() ?? string.Empty;
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ codeItem.ReturnType,
+ codeItem.Name,
+ codeItem.Parameters);
+ codeItem.Kind = CodeItemKindEnum.Property;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ return codeItem;
+ }
+}
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/RegionMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/RegionMapper.cs
new file mode 100644
index 0000000..05f7eef
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/RegionMapper.cs
@@ -0,0 +1,326 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.Extensions;
+using CodeNav.OutOfProc.Helpers;
+using CodeNav.OutOfProc.Interfaces;
+using CodeNav.OutOfProc.Mappers;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+using Microsoft.CodeAnalysis.Text;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class RegionMapper
+{
+ ///
+ /// Find all regions in a file and get their start and end span.
+ ///
+ /// Syntax tree for the given file.
+ /// Span in which to search for regions.
+ /// Code document view model.
+ /// Flat list of regions.
+ public static List MapRegions(
+ SyntaxTree tree,
+ TextSpan span,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var regionList = new List();
+
+ if (tree == null)
+ {
+ return regionList;
+ }
+
+ // Check if regions should be ignored based on the filter rules.
+ var filterRule = FilterRuleHelper.GetFilterRule(
+ codeDocumentViewModel,
+ CodeItemKindEnum.Region);
+
+ if (filterRule?.Ignore == true)
+ {
+ return regionList;
+ }
+
+ var root = tree.GetRoot();
+
+ // Find all region start trivia.
+ var regionStarts = root
+ .DescendantTrivia()
+ .Where(syntaxTrivia =>
+ syntaxTrivia.IsKind(SyntaxKind.RegionDirectiveTrivia))
+ .Where(syntaxTrivia =>
+ span.Contains(syntaxTrivia.Span));
+
+ regionList.AddRange(
+ regionStarts.Select(regionStart =>
+ MapRegion(regionStart, codeDocumentViewModel)));
+
+ // Find all region end trivia.
+ var regionEnds = root
+ .DescendantTrivia()
+ .Where(syntaxTrivia =>
+ syntaxTrivia.IsKind(SyntaxKind.EndRegionDirectiveTrivia))
+ .Where(syntaxTrivia =>
+ span.Contains(syntaxTrivia.Span));
+
+ // Match a region start with the closest region end.
+ foreach (var regionEnd in regionEnds)
+ {
+ var region = regionList
+ .LastOrDefault(regionStart =>
+ regionStart.Span.Start < regionEnd.Span.Start &&
+ regionStart.Span.End == regionStart.Span.Start);
+
+ if (region == null)
+ {
+ continue;
+ }
+
+ region.Span = new(
+ region.Span.Start,
+ regionEnd.Span.End - region.Span.Start);
+
+ region.OutlineSpan = new(
+ region.OutlineSpan.Start,
+ regionEnd.Span.End - region.Span.Start);
+ }
+
+ return ToHierarchy(regionList, span);
+ }
+
+ ///
+ /// Transform the flat list of regions into a nested hierarchy.
+ ///
+ private static List ToHierarchy(
+ List regionList,
+ TextSpan textSpan)
+ {
+ var nestedRegions = new List();
+
+ foreach (var region in regionList)
+ {
+ if (!StrictContains(textSpan, region.Span) ||
+ regionList.Any(otherBiggerRegion =>
+ StrictContains(
+ otherBiggerRegion.Span,
+ region.Span) &&
+ otherBiggerRegion.Span.Length < textSpan.Length))
+ {
+ continue;
+ }
+
+ region.Members =
+ [.. ToHierarchy(regionList, region.Span).Cast()];
+
+ nestedRegions.Add(region);
+ }
+
+ return nestedRegions;
+ }
+
+ private static CodeRegionItem MapRegion(
+ SyntaxTrivia regionStart,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var name = MapRegionName(regionStart);
+
+ return new()
+ {
+ Name = name,
+ FullName = name,
+ Id = name,
+ Tooltip = name,
+ Kind = CodeItemKindEnum.Region,
+ Span = new(
+ regionStart.Span.Start,
+ 0),
+ OutlineSpan = new(
+ regionStart.Span.Start,
+ 0),
+ IdentifierSpan = MapIdentifierSpan(regionStart),
+ Moniker = IconMapper.MapMoniker(
+ CodeItemKindEnum.Region,
+ CodeItemAccessEnum.Unknown),
+ CodeDocumentViewModel = codeDocumentViewModel,
+ };
+ }
+
+ private static TextSpan MapIdentifierSpan(
+ SyntaxTrivia regionStart)
+ {
+ return regionStart.Span;
+ }
+
+ private static string MapRegionName(
+ SyntaxTrivia regionStart)
+ {
+ const string defaultRegionName = "Region";
+ const string regionKeyword = "#Region";
+
+ var syntaxNode = regionStart.GetStructure();
+
+ if (syntaxNode is not RegionDirectiveTriviaSyntax regionSyntax)
+ {
+ return defaultRegionName;
+ }
+
+ var text = regionSyntax.ToString();
+
+ if (!text.StartsWith(
+ regionKeyword,
+ StringComparison.OrdinalIgnoreCase))
+ {
+ return defaultRegionName;
+ }
+
+ var name = text[regionKeyword.Length..]
+ .Trim()
+ .Trim('"');
+
+ return string.IsNullOrEmpty(name)
+ ? defaultRegionName
+ : name;
+ }
+
+ ///
+ /// Add a code item to the list of regions, recursively finding
+ /// the correct region.
+ ///
+ public static bool AddToRegion(
+ List regions,
+ CodeItem codeItem)
+ {
+ if (codeItem?.Span == null)
+ {
+ return false;
+ }
+
+ foreach (var region in regions)
+ {
+ if (region?.Kind != CodeItemKindEnum.Region)
+ {
+ continue;
+ }
+
+ if (AddToRegion(region.Members, codeItem))
+ {
+ return true;
+ }
+
+ if (codeItem.Span.Start >= region.Span.Start &&
+ codeItem.Span.Start <= region.Span.End)
+ {
+ region.Members.Add(codeItem);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Help add a code item to an inner region structure.
+ ///
+ private static bool AddToRegion(
+ List members,
+ CodeItem codeItem)
+ {
+ foreach (var member in members)
+ {
+ if (member == null)
+ {
+ continue;
+ }
+
+ if (member is IMembers memberItem &&
+ AddToRegion(memberItem.Members, codeItem))
+ {
+ return true;
+ }
+
+ if (member is CodeRegionItem regionItem &&
+ member.Span.Contains(codeItem.Span))
+ {
+ regionItem.Members.Add(codeItem);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Determines whether a small span falls completely within
+ /// a bigger span without touching its boundaries.
+ ///
+ public static bool StrictContains(
+ TextSpan bigSpan,
+ TextSpan smallSpan)
+ => smallSpan.Start > bigSpan.Start &&
+ smallSpan.End < bigSpan.End;
+
+ ///
+ /// Check if a code item is part of any region.
+ ///
+ public static bool IsPartOfRegion(
+ IEnumerable regions,
+ CodeItem codeItem)
+ => regions.Any(region =>
+ region.Span.Contains(codeItem.Span));
+
+ public static CodeRegionItem? GetRegion(
+ IEnumerable regions,
+ CodeItem codeItem)
+ {
+ foreach (var region in regions)
+ {
+ if (region.Kind != CodeItemKindEnum.Region)
+ {
+ continue;
+ }
+
+ if (!region.Span.Contains(codeItem.Span))
+ {
+ continue;
+ }
+
+ // Try to find a more specific nested region first.
+ if (region is IMembers regionMembersItem)
+ {
+ var nestedMatch = GetRegion(
+ regionMembersItem.Members,
+ codeItem);
+
+ return nestedMatch ??
+ region as CodeRegionItem;
+ }
+
+ return region as CodeRegionItem;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Add regions to the given list of members, but only if a region
+ /// with the same ID is not already present somewhere within those members.
+ ///
+ public static void AddRegionsIfNotPresent(
+ List members,
+ List regions)
+ {
+ foreach (var region in regions)
+ {
+ var alreadyPresent = members
+ .Flatten()
+ .FilterNull()
+ .Any(item => item.Id == region?.Id);
+
+ if (!alreadyPresent)
+ {
+ members.AddIfNotNull(region);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/StructureMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/StructureMapper.cs
new file mode 100644
index 0000000..4d79c7e
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/StructureMapper.cs
@@ -0,0 +1,90 @@
+using CodeNav.OutOfProc.Constants;
+using CodeNav.OutOfProc.Mappers;
+using CodeNav.OutOfProc.ViewModels;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class StructureMapper
+{
+ public static CodeClassItem MapStructure(
+ StructureBlockSyntax member,
+ SemanticModel semanticModel,
+ SyntaxTree tree,
+ CodeDocumentViewModel codeDocumentViewModel)
+ {
+ var codeItem = BaseMapper.MapBase(
+ member,
+ semanticModel,
+ codeDocumentViewModel,
+ member.StructureStatement.Identifier,
+ modifiers: member.StructureStatement.Modifiers);
+
+ codeItem.Kind = CodeItemKindEnum.Struct;
+ codeItem.Moniker = IconMapper.MapMoniker(
+ codeItem.Kind,
+ codeItem.Access);
+
+ codeItem.Parameters = MapInheritance(member);
+
+ codeItem.Tooltip = TooltipMapper.Map(
+ member,
+ codeItem.Access,
+ string.Empty,
+ codeItem.Name,
+ codeItem.Parameters);
+
+ var regions = RegionMapper.MapRegions(
+ tree,
+ member.Span,
+ codeDocumentViewModel);
+
+ foreach (var structureMember in member.Members)
+ {
+ foreach (var memberItem in DocumentMapper.MapMembers(
+ structureMember,
+ tree,
+ semanticModel,
+ codeDocumentViewModel))
+ {
+ if (RegionMapper.AddToRegion(regions, memberItem))
+ {
+ continue;
+ }
+
+ codeItem.Members.Add(memberItem);
+ }
+ }
+
+ RegionMapper.AddRegionsIfNotPresent(
+ codeItem.Members,
+ regions);
+
+ return codeItem;
+ }
+
+ private static string MapInheritance(
+ StructureBlockSyntax member)
+ {
+ var inheritance = new List();
+
+ foreach (var inheritsStatement in member.Inherits)
+ {
+ inheritance.AddRange(
+ inheritsStatement.Types.Select(
+ type => type.ToString()));
+ }
+
+ foreach (var implementsStatement in member.Implements)
+ {
+ inheritance.AddRange(
+ implementsStatement.Types.Select(
+ type => type.ToString()));
+ }
+
+ return inheritance.Any()
+ ? $" : {string.Join(", ", inheritance)}"
+ : string.Empty;
+ }
+}
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/TooltipMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/TooltipMapper.cs
new file mode 100644
index 0000000..4ea087d
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/TooltipMapper.cs
@@ -0,0 +1,48 @@
+using CodeNav.OutOfProc.Constants;
+using Microsoft.CodeAnalysis;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class TooltipMapper
+{
+ public static string Map(
+ SyntaxNode node,
+ CodeItemAccessEnum access,
+ string returnType,
+ string name,
+ string parameters)
+ {
+ var accessText = access switch
+ {
+ CodeItemAccessEnum.Public => "Public",
+ CodeItemAccessEnum.Private => "Private",
+ CodeItemAccessEnum.Protected => "Protected",
+ CodeItemAccessEnum.Internal => "Friend",
+ _ => string.Empty
+ };
+
+ var parts = new List();
+
+ if (!string.IsNullOrEmpty(accessText))
+ {
+ parts.Add(accessText);
+ }
+
+ if (!string.IsNullOrEmpty(name))
+ {
+ parts.Add(name);
+ }
+
+ if (!string.IsNullOrEmpty(parameters))
+ {
+ parts.Add(parameters);
+ }
+
+ if (!string.IsNullOrEmpty(returnType))
+ {
+ parts.Add($"As {returnType}");
+ }
+
+ return string.Join(" ", parts);
+ }
+}
\ No newline at end of file
diff --git a/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/TypeMapper.cs b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/TypeMapper.cs
new file mode 100644
index 0000000..2228bef
--- /dev/null
+++ b/src/CodeNav.OutOfProc/Languages/VisualBasic/Mappers/TypeMapper.cs
@@ -0,0 +1,14 @@
+using Microsoft.CodeAnalysis.VisualBasic.Syntax;
+
+namespace CodeNav.OutOfProc.Languages.VisualBasic.Mappers;
+
+public static class TypeMapper
+{
+ public static string Map(SimpleAsClauseSyntax? asClause)
+ => asClause?.Type?.ToString() ?? string.Empty;
+
+ public static string Map(AsClauseSyntax? asClause)
+ => asClause?.ToString()
+ .Replace("As ", string.Empty, StringComparison.OrdinalIgnoreCase)
+ .Trim() ?? string.Empty;
+}
\ No newline at end of file
diff --git a/src/CodeNav.OutOfProc/Models/GlobalSettings.cs b/src/CodeNav.OutOfProc/Models/GlobalSettings.cs
index 33ee6cc..28d33db 100644
--- a/src/CodeNav.OutOfProc/Models/GlobalSettings.cs
+++ b/src/CodeNav.OutOfProc/Models/GlobalSettings.cs
@@ -44,6 +44,13 @@ public class GlobalSettings
///
public bool UseCompactMode { get; set; }
+ ///
+ /// Settings dialog - Languages enabled for CodeNav.
+ ///
+ public bool EnableCSharp { get; set; } = true;
+
+ public bool EnableVisualBasic { get; set; } = false;
+
///
/// Main toolbar - Setting to store the selected sort order for code items
///
diff --git a/src/CodeNav.OutOfProc/Services/CodeDocumentService.cs b/src/CodeNav.OutOfProc/Services/CodeDocumentService.cs
index d593013..971ae3c 100644
--- a/src/CodeNav.OutOfProc/Services/CodeDocumentService.cs
+++ b/src/CodeNav.OutOfProc/Services/CodeDocumentService.cs
@@ -1,13 +1,15 @@
using CodeNav.OutOfProc.Dialogs.FilterDialog;
using CodeNav.OutOfProc.Dialogs.SettingsDialog;
using CodeNav.OutOfProc.Helpers;
-using CodeNav.OutOfProc.Languages.CSharp.Mappers;
+using CodeNav.OutOfProc.Interfaces;
using CodeNav.OutOfProc.Models;
using CodeNav.OutOfProc.ViewModels;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.ToolWindows;
using Microsoft.VisualStudio.Extensibility.UI;
using System.Windows;
+using CSharpDocumentMapper = CodeNav.OutOfProc.Languages.CSharp.Mappers.DocumentMapper;
+using VisualBasicDocumentMapper = CodeNav.OutOfProc.Languages.VisualBasic.Mappers.DocumentMapper;
namespace CodeNav.OutOfProc.Services;
@@ -16,6 +18,12 @@ public class CodeDocumentService(
OutliningService outliningService,
WindowFrameService windowFrameService)
{
+ private readonly List documentMappers =
+ [
+ new CSharpDocumentMapper(),
+ new VisualBasicDocumentMapper(),
+ ];
+
///
/// DataContext for the tool window.
///
@@ -62,9 +70,14 @@ public async Task UpdateCodeDocumentViewModel(
return CodeDocumentViewModel;
}
- if (!DocumentMapper.CanMapDocument(filePath))
+ var documentMapper = documentMappers
+ .FirstOrDefault(mapper =>
+ mapper.CanMapDocument(filePath, GlobalSettings!));
+
+ if (documentMapper == null)
{
- CodeDocumentViewModel.CodeItems = PlaceholderHelper.CreateNoCodeItemsFound();
+ CodeDocumentViewModel.CodeItems =
+ PlaceholderHelper.CreateNoCodeItemsFound();
// No code items found, hide the tool window after showing the "No code items found" message
await HideToolWindow(cancellationToken);
@@ -82,7 +95,7 @@ public async Task UpdateCodeDocumentViewModel(
loadingCancellationToken);
// Get the new list of code items
- var codeItems = await DocumentMapper.MapDocument(
+ var codeItems = await documentMapper.MapDocument(
text,
filePath,
CodeDocumentViewModel,
@@ -185,6 +198,8 @@ public async Task LoadGlobalSettings(bool readFromDisk = false)
EnableCrashAnalytics = GlobalSettings.EnableCrashAnalytics,
ShowToolWindowForUnsupportedFiles = GlobalSettings.ShowToolWindowForUnsupportedFiles,
UseCompactMode = GlobalSettings.UseCompactMode,
+ EnableCSharp = GlobalSettings.EnableCSharp,
+ EnableVisualBasic = GlobalSettings.EnableVisualBasic,
};
var filterRules = GlobalSettings
diff --git a/src/CodeNav.OutOfProc/TextViewEventListener.cs b/src/CodeNav.OutOfProc/TextViewEventListener.cs
index 4b12d61..77e9d47 100644
--- a/src/CodeNav.OutOfProc/TextViewEventListener.cs
+++ b/src/CodeNav.OutOfProc/TextViewEventListener.cs
@@ -32,8 +32,7 @@ internal class TextViewEventListener(
{
AppliesTo =
[
- DocumentFilter.FromDocumentType("CSharp"),
- DocumentFilter.FromGlobPattern("**/*.cs", true),
+ DocumentFilter.FromGlobPattern("**/*.{cs,vb}", true),
],
};
@@ -140,4 +139,4 @@ await codeDocumentService.UpdateCodeDocumentViewModel(
await LogHelper.LogException(codeDocumentService, "Error listening to TextViewOpened", e);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/CodeNav.OutOfProc/ViewModels/CodeDocumentViewModel.cs b/src/CodeNav.OutOfProc/ViewModels/CodeDocumentViewModel.cs
index c29693c..22e5e49 100644
--- a/src/CodeNav.OutOfProc/ViewModels/CodeDocumentViewModel.cs
+++ b/src/CodeNav.OutOfProc/ViewModels/CodeDocumentViewModel.cs
@@ -255,11 +255,18 @@ private async Task Settings(object? commandParameter, IClientContext clientConte
CodeDocumentService.GlobalSettings.EnableCrashAnalytics = CodeDocumentService.SettingsDialogData.EnableCrashAnalytics;
CodeDocumentService.GlobalSettings.ShowToolWindowForUnsupportedFiles = CodeDocumentService.SettingsDialogData.ShowToolWindowForUnsupportedFiles;
CodeDocumentService.GlobalSettings.UseCompactMode = CodeDocumentService.SettingsDialogData.UseCompactMode;
+ CodeDocumentService.GlobalSettings.EnableCSharp = CodeDocumentService.SettingsDialogData.EnableCSharp;
+ CodeDocumentService.GlobalSettings.EnableVisualBasic = CodeDocumentService.SettingsDialogData.EnableVisualBasic;
await SettingsHelper.SaveGlobalSettings(CodeDocumentService);
// Refresh the tool window with the new settings
await CodeDocumentService.LoadGlobalSettings(readFromDisk: true);
+
+ await Refresh(
+ commandParameter,
+ clientContext,
+ cancellationToken);
}
[DataMember]
diff --git a/test/Files/NonCSharp/VisualBasicSmokeTest.vb b/test/Files/NonCSharp/VisualBasicSmokeTest.vb
new file mode 100644
index 0000000..0276a22
--- /dev/null
+++ b/test/Files/NonCSharp/VisualBasicSmokeTest.vb
@@ -0,0 +1,94 @@
+Imports System
+
+Namespace CodeNav.SmokeTest
+
+ Public Interface IWorker
+ Sub Execute(value As Integer)
+ Property Name As String
+ Event Completed As EventHandler
+ End Interface
+
+ Public MustInherit Class WorkerBase
+ Public MustOverride Sub Run()
+ End Class
+
+ Public Class SampleWorker
+ Inherits WorkerBase
+ Implements IWorker
+
+ Private _first, _second As Integer
+
+ Public Const Maximum As Integer = 10
+
+ Public Sub New(value As Integer)
+ _first = value
+ End Sub
+
+ Public Property Name As String Implements IWorker.Name
+
+ Public Event Completed As EventHandler Implements IWorker.Completed
+
+ Public Overrides Sub Run()
+ Execute(_first)
+ End Sub
+
+#Region "Worker members"
+#Region "Execution"
+ Public Sub Execute(value As Integer) Implements IWorker.Execute
+ _second = value
+ RaiseEvent Completed(Me, EventArgs.Empty)
+ End Sub
+#End Region
+
+ Public Function Calculate(value As Integer) As String
+ Return (value + _second).ToString()
+ End Function
+#End Region
+
+ Public Default Property Item(index As Integer) As Integer
+ Get
+ Return If(index = 0, _first, _second)
+ End Get
+ Set(value As Integer)
+ If index = 0 Then
+ _first = value
+ Else
+ _second = value
+ End If
+ End Set
+ End Property
+
+ Public Custom Event Changed As EventHandler
+ AddHandler(value As EventHandler)
+ End AddHandler
+ RemoveHandler(value As EventHandler)
+ End RemoveHandler
+ RaiseEvent()
+ End RaiseEvent
+ End Event
+ End Class
+
+ Public Structure Point
+ Public X As Integer
+ Public Y As Integer
+
+ Public Shared Operator +(left As Point, right As Point) As Point
+ Return New Point With {
+ .X = left.X + right.X,
+ .Y = left.Y + right.Y
+ }
+ End Operator
+ End Structure
+
+ Public Enum WorkState
+ NotStarted
+ Running
+ Complete
+ End Enum
+
+ Public Delegate Function Transformer(value As Integer) As String
+
+ Public Module NativeMethods
+ Public Declare Function GetTickCount Lib "kernel32" () As UInteger
+ End Module
+End Namespace
diff --git a/test/VisualBasicDocumentMapperTests.cs b/test/VisualBasicDocumentMapperTests.cs
new file mode 100644
index 0000000..4d331a1
--- /dev/null
+++ b/test/VisualBasicDocumentMapperTests.cs
@@ -0,0 +1,256 @@
+using CodeNav.OutOfProc.Models;
+using VisualBasicDocumentMapper = CodeNav.OutOfProc.Languages.VisualBasic.Mappers.DocumentMapper;
+
+namespace CodeNav.Test;
+
+[TestFixture]
+internal class VisualBasicDocumentMapperTests
+{
+ [Test]
+ public async Task MapsStatementMembersInInterfacesAndClasses()
+ {
+ const string source = """
+ Namespace Example
+ Public Interface IExample
+ Sub Execute(value As Integer)
+ Property Name As String
+ Event Changed As EventHandler
+ End Interface
+
+ Public MustInherit Class ExampleClass
+ Public MustOverride Sub Run()
+ Public Property Count As Integer
+ Public Event Updated As EventHandler
+ End Class
+ End Namespace
+ """;
+
+ var codeItems = await VisualBasicDocumentMapper.MapDocument(
+ source,
+ new CodeDocumentViewModel(),
+ filePaths: [],
+ cancellationToken: default);
+
+ var namespaceItem = codeItems.Single() as CodeNamespaceItem;
+ Assert.That(namespaceItem, Is.Not.Null);
+
+ var interfaceItem = namespaceItem!.Members
+ .OfType()
+ .Single(item => item.Name == "IExample");
+ var classItem = namespaceItem.Members
+ .OfType()
+ .Single(item => item.Name == "ExampleClass");
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(interfaceItem.Members.Select(item => item.Name),
+ Is.EquivalentTo(["Execute", "Name", "Changed"]));
+ Assert.That(classItem.Members.Select(item => item.Name),
+ Is.EquivalentTo(["Run", "Count", "Updated"]));
+ Assert.That(interfaceItem.Members.Select(item => item.Kind),
+ Is.EquivalentTo([
+ CodeItemKindEnum.Method,
+ CodeItemKindEnum.Property,
+ CodeItemKindEnum.Event,
+ ]));
+ }
+ }
+
+ [Test]
+ public async Task MapsClassMemberBlocksAndTheirDetails()
+ {
+ const string source = """
+ Namespace Example
+ Public Class Sample
+ Private _first, _second As Integer
+ Public Const Maximum As Integer = 10
+
+ Public Sub New(value As Integer)
+ End Sub
+
+ Public Function Calculate(value As Integer) As String
+ Return value.ToString()
+ End Function
+
+ Public Default Property Value(index As Integer) As Integer
+ Get
+ Return _first
+ End Get
+ Set(value As Integer)
+ _first = value
+ End Set
+ End Property
+
+ Public Custom Event Changed As EventHandler
+ AddHandler(value As EventHandler)
+ End AddHandler
+ RemoveHandler(value As EventHandler)
+ End RemoveHandler
+ RaiseEvent()
+ End RaiseEvent
+ End Event
+ End Class
+ End Namespace
+ """;
+
+ var classItem = GetNamespace(await Map(source)).Members
+ .OfType()
+ .Single();
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(classItem.Members.Select(item => item.Name), Is.EquivalentTo([
+ "_first", "_second", "Maximum", "New", "Calculate", "Value", "Changed",
+ ]));
+ Assert.That(classItem.Members.Select(item => item.Kind), Is.EquivalentTo([
+ CodeItemKindEnum.Variable,
+ CodeItemKindEnum.Variable,
+ CodeItemKindEnum.Constant,
+ CodeItemKindEnum.Constructor,
+ CodeItemKindEnum.Method,
+ CodeItemKindEnum.Property,
+ CodeItemKindEnum.Event,
+ ]));
+ }
+
+ var calculate = classItem.Members
+ .OfType()
+ .Single(item => item.Name == "Calculate");
+ var property = classItem.Members
+ .OfType()
+ .Single();
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(calculate.Parameters, Is.EqualTo("(value As Integer)"));
+ Assert.That(calculate.ReturnType, Is.EqualTo("String"));
+ Assert.That(property.Parameters, Is.EqualTo("(index As Integer) {Get,Set}"));
+ Assert.That(property.ReturnType, Is.EqualTo("Integer"));
+ }
+ }
+
+ [Test]
+ public async Task MapsNamespacesAndTopLevelDeclarationKinds()
+ {
+ const string source = """
+ Namespace Example.Outer
+ Public Delegate Function Transformer(value As Integer) As String
+
+ Public Enum Choice
+ First
+ Second
+ End Enum
+
+ Public Structure Point
+ Public X As Integer
+ Public Shared Operator +(left As Point, right As Point) As Point
+ Return left
+ End Operator
+ End Structure
+
+ Public Module Utilities
+ Public Declare Function GetTickCount Lib "kernel32" () As Integer
+ End Module
+ End Namespace
+ """;
+
+ var namespaceItem = GetNamespace(await Map(source));
+ var enumItem = namespaceItem.Members
+ .OfType()
+ .Single(item => item.Name == "Choice");
+ var structureItem = namespaceItem.Members
+ .OfType()
+ .Single(item => item.Name == "Point");
+ var moduleItem = namespaceItem.Members
+ .OfType()
+ .Single(item => item.Name == "Utilities");
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(namespaceItem.Name, Is.EqualTo("Example.Outer"));
+ Assert.That(namespaceItem.Members.Select(item => item.Kind), Is.EquivalentTo([
+ CodeItemKindEnum.Delegate,
+ CodeItemKindEnum.Enum,
+ CodeItemKindEnum.Struct,
+ CodeItemKindEnum.Class,
+ ]));
+ Assert.That(enumItem.Members.Select(item => item.Name),
+ Is.EquivalentTo(["First", "Second"]));
+ Assert.That(enumItem.Members.Select(item => item.Kind),
+ Is.All.EqualTo(CodeItemKindEnum.EnumMember));
+ Assert.That(structureItem.Members.Select(item => item.Name),
+ Is.EquivalentTo(["X", "+"]));
+ Assert.That(moduleItem.Members.Single().Name, Is.EqualTo("GetTickCount"));
+ Assert.That(moduleItem.Members.Single().Kind, Is.EqualTo(CodeItemKindEnum.Method));
+ }
+ }
+
+ [Test]
+ public async Task MapsNestedRegionsAndPlacesMembersInTheInnermostRegion()
+ {
+ const string source = """
+ Namespace Example
+ Public Class Sample
+ #Region "Outer"
+ #Region "Inner"
+ Public Sub Execute()
+ End Sub
+ #End Region
+ #End Region
+ End Class
+ End Namespace
+ """;
+
+ var classItem = GetNamespace(await Map(source)).Members
+ .OfType()
+ .Single();
+ var outerRegion = classItem.Members.OfType().Single();
+ var innerRegion = outerRegion.Members.OfType().Single();
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(outerRegion.Name, Is.EqualTo("Outer"));
+ Assert.That(innerRegion.Name, Is.EqualTo("Inner"));
+ Assert.That(innerRegion.Members.Single().Name, Is.EqualTo("Execute"));
+ Assert.That(innerRegion.Members.Single().Kind, Is.EqualTo(CodeItemKindEnum.Method));
+ }
+ }
+
+ [Test]
+ public void OnlyMapsVisualBasicFilesWhenVisualBasicIsEnabled()
+ {
+ var mapper = new VisualBasicDocumentMapper();
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(mapper.CanMapDocument("Example.vb", new GlobalSettings
+ {
+ EnableVisualBasic = true,
+ }), Is.True);
+ Assert.That(mapper.CanMapDocument("Example.VB", new GlobalSettings
+ {
+ EnableVisualBasic = true,
+ }), Is.True);
+ Assert.That(mapper.CanMapDocument("Example.vb", new GlobalSettings()), Is.False);
+ Assert.That(mapper.CanMapDocument("Example.cs", new GlobalSettings
+ {
+ EnableVisualBasic = true,
+ }), Is.False);
+ }
+ }
+
+ private static async Task> Map(string source)
+ => await VisualBasicDocumentMapper.MapDocument(
+ source,
+ new CodeDocumentViewModel(),
+ filePaths: [],
+ cancellationToken: default);
+
+ private static CodeNamespaceItem GetNamespace(IEnumerable codeItems)
+ {
+ var namespaceItem = codeItems.Single() as CodeNamespaceItem;
+ Assert.That(namespaceItem, Is.Not.Null);
+
+ return namespaceItem!;
+ }
+}