11using System ;
2+ using System . IO ;
3+ using System . Runtime . InteropServices ;
24using EnvDTE ;
35using EnvDTE80 ;
6+ using Microsoft . VisualStudio ;
47using Microsoft . VisualStudio . Shell ;
8+ using Microsoft . VisualStudio . Shell . Interop ;
59
610namespace 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