Skip to content

Commit ddbd6c0

Browse files
committed
fix: handle unloaded projects using IVsHierarchy
1 parent fa60cf7 commit ddbd6c0

File tree

2 files changed

+90
-2
lines changed

2 files changed

+90
-2
lines changed

src/CodingWithCalvin.OpenInNotepadPlusPlus/Commands/OpenExecutableCommand.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ private static void OpenExecutable(string executablePath, string selectedFilePat
7272
{
7373
var startInfo = new ProcessStartInfo
7474
{
75-
WorkingDirectory = selectedFilePath,
7675
FileName = $"\"{executablePath}\"",
7776
Arguments = $"\"{selectedFilePath}\"",
7877
CreateNoWindow = true,

src/CodingWithCalvin.OpenInNotepadPlusPlus/Helpers/ProjectHelpers.cs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
using System;
2+
using System.IO;
3+
using System.Runtime.InteropServices;
24
using EnvDTE;
35
using EnvDTE80;
6+
using Microsoft.VisualStudio;
47
using Microsoft.VisualStudio.Shell;
8+
using Microsoft.VisualStudio.Shell.Interop;
59

610
namespace CodingWithCalvin.OpenInNotepadPlusPlus.Helpers
711
{
@@ -21,12 +25,97 @@ UIHierarchyItem selectedItem in (Array)
2125
case ProjectItem projectItem:
2226
return projectItem.FileNames[1];
2327
case Project project:
24-
return project.FullName;
28+
return GetProjectPath(project, dte.Solution);
2529
case Solution solution:
2630
return solution.FullName;
31+
default:
32+
// Handle unloaded projects - they don't expose a Project object
33+
// but we can get the path via IVsMonitorSelection and IVsHierarchy
34+
var path = GetPathFromHierarchy();
35+
if (!string.IsNullOrEmpty(path))
36+
{
37+
return path;
38+
}
39+
break;
2740
}
2841
}
2942
return null;
3043
}
44+
45+
private static string GetPathFromHierarchy()
46+
{
47+
ThreadHelper.ThrowIfNotOnUIThread();
48+
49+
// Use IVsMonitorSelection to get the current selection's IVsHierarchy
50+
// This works for unloaded projects where selectedItem.Object is not a Project
51+
var monitorSelection = Package.GetGlobalService(typeof(SVsShellMonitorSelection)) as IVsMonitorSelection;
52+
if (monitorSelection == null)
53+
{
54+
return null;
55+
}
56+
57+
var hr = monitorSelection.GetCurrentSelection(out var hierarchyPtr, out var itemId, out _, out _);
58+
if (hr != VSConstants.S_OK || hierarchyPtr == IntPtr.Zero)
59+
{
60+
return null;
61+
}
62+
63+
try
64+
{
65+
var hierarchy = Marshal.GetObjectForIUnknown(hierarchyPtr) as IVsHierarchy;
66+
if (hierarchy == null)
67+
{
68+
return null;
69+
}
70+
71+
// GetCanonicalName returns the full path for project files
72+
if (hierarchy.GetCanonicalName(itemId, out var canonicalName) == VSConstants.S_OK
73+
&& !string.IsNullOrEmpty(canonicalName)
74+
&& File.Exists(canonicalName))
75+
{
76+
return canonicalName;
77+
}
78+
}
79+
finally
80+
{
81+
Marshal.Release(hierarchyPtr);
82+
}
83+
84+
return null;
85+
}
86+
87+
private static string GetProjectPath(Project project, Solution solution)
88+
{
89+
ThreadHelper.ThrowIfNotOnUIThread();
90+
91+
// Try FullName first - works for loaded projects and sometimes unloaded ones
92+
if (!string.IsNullOrEmpty(project.FullName) && File.Exists(project.FullName))
93+
{
94+
return project.FullName;
95+
}
96+
97+
// Fallback: try UniqueName which may contain the relative or absolute path
98+
if (!string.IsNullOrEmpty(project.UniqueName))
99+
{
100+
// UniqueName might already be an absolute path
101+
if (Path.IsPathRooted(project.UniqueName) && File.Exists(project.UniqueName))
102+
{
103+
return project.UniqueName;
104+
}
105+
106+
// Try combining with solution directory
107+
if (!string.IsNullOrEmpty(solution?.FullName))
108+
{
109+
var solutionDirectory = Path.GetDirectoryName(solution.FullName);
110+
var projectPath = Path.Combine(solutionDirectory, project.UniqueName);
111+
if (File.Exists(projectPath))
112+
{
113+
return projectPath;
114+
}
115+
}
116+
}
117+
118+
return null;
119+
}
31120
}
32121
}

0 commit comments

Comments
 (0)