11using System ;
22using System . Composition ;
3+ using System . Linq ;
34using System . Threading ;
45using System . Threading . Tasks ;
56using Microsoft . CodeAnalysis ;
910using Microsoft . CodeAnalysis . Options ;
1011using Microsoft . CodeAnalysis . Text ;
1112using Microsoft . VisualStudio . Text ;
13+ using Microsoft . VisualStudio . Text . Projection ;
1214using MonoDevelop . Core ;
15+ using MonoDevelop . Ide . Composition ;
16+ using MonoDevelop . Ide . Editor . Projection ;
1317using MonoDevelop . Ide . TypeSystem ;
1418
1519namespace MonoDevelop . Ide . RoslynServices
@@ -19,11 +23,14 @@ internal sealed class VisualStudioDocumentNavigationServiceFactory : IWorkspaceS
1923 {
2024 private readonly IDocumentNavigationService _singleton ;
2125
26+ [ Import ]
27+ public IBufferGraphFactoryService BufferGraphFactoryService { get ; set ; }
28+
2229 [ ImportingConstructor ]
2330 [ Obsolete ( MefConstruction . ImportingConstructorMessage , error : true ) ]
2431 private VisualStudioDocumentNavigationServiceFactory ( )
2532 {
26- _singleton = new MonoDevelopDocumentNavigationService ( ) ;
33+ _singleton = new MonoDevelopDocumentNavigationService ( this ) ;
2734 }
2835
2936 public IWorkspaceService CreateService ( HostWorkspaceServices workspaceServices )
@@ -34,6 +41,13 @@ public IWorkspaceService CreateService (HostWorkspaceServices workspaceServices)
3441
3542 class MonoDevelopDocumentNavigationService : IDocumentNavigationService
3643 {
44+ private VisualStudioDocumentNavigationServiceFactory factory ;
45+
46+ public MonoDevelopDocumentNavigationService ( VisualStudioDocumentNavigationServiceFactory visualStudioDocumentNavigationServiceFactory )
47+ {
48+ this . factory = visualStudioDocumentNavigationServiceFactory ;
49+ }
50+
3751 public bool CanNavigateToSpan ( Workspace workspace , DocumentId documentId , TextSpan textSpan )
3852 {
3953 // Navigation should not change the context of linked files and Shared Projects.
@@ -99,7 +113,7 @@ public bool TryNavigateToSpan (Workspace workspace, DocumentId documentId, TextS
99113
100114 Runtime . AssertMainThread ( ) ;
101115
102- var document = OpenDocument ( workspace , documentId , options ) ;
116+ var document = workspace . CurrentSolution . GetDocument ( documentId ) ;
103117 if ( document == null ) {
104118 return false ;
105119 }
@@ -127,7 +141,7 @@ public bool TryNavigateToLineAndOffset (Workspace workspace, DocumentId document
127141
128142 Runtime . AssertMainThread ( ) ;
129143
130- var document = OpenDocument ( workspace , documentId , options ) ;
144+ var document = workspace . CurrentSolution . GetDocument ( documentId ) ;
131145 if ( document == null ) {
132146 return false ;
133147 }
@@ -149,7 +163,7 @@ public bool TryNavigateToPosition (Workspace workspace, DocumentId documentId, i
149163
150164 Runtime . AssertMainThread ( ) ;
151165
152- var document = OpenDocument ( workspace , documentId , options ) ;
166+ var document = workspace . CurrentSolution . GetDocument ( documentId ) ;
153167 if ( document == null ) {
154168 return false ;
155169 }
@@ -222,13 +236,41 @@ private static Document OpenDocument (Workspace workspace, DocumentId documentId
222236
223237 private bool NavigateTo ( Document document , TextSpan span )
224238 {
239+ string filePath = document . FilePath ;
240+ filePath = GetActualFilePathToOpen ( filePath ) ;
225241 var proj = ( document . Project . Solution . Workspace as MonoDevelopWorkspace ) ? . GetMonoProject ( document . Project ) ;
226- var task = IdeApp . Workbench . OpenDocument ( new Gui . FileOpenInformation ( document . FilePath , proj ) {
242+ var task = IdeApp . Workbench . OpenDocument ( new Gui . FileOpenInformation ( filePath , proj ) {
227243 Offset = span . Start
228244 } ) ;
229245 return true ;
230246 }
231247
248+ /// <summary>
249+ /// Razor: Strip the .g.cs since we want to open the corresponding .cshtml or .razor document.
250+ ///
251+ /// In Visual Studio for Windows the underlying C# buffer is added to the workspace with the
252+ /// .cshtml or .razor extension (without the .g.cs) part, so they don't have to worry about
253+ /// this. In our case we have an assumption somewhere that all C# documents in the workspace
254+ /// have the .cs extension, so we're adding the .g.cs part that we need to strip here.
255+ ///
256+ /// This is not great to hardcode application-specific logic here, but we don't anticipate
257+ /// more scenarios where we want to open a different file than requested, so it doesn't
258+ /// warrant an extension point at this time.
259+ /// </summary>
260+ string GetActualFilePathToOpen ( string filePath )
261+ {
262+ if ( filePath == null ) {
263+ return null ;
264+ }
265+
266+ if ( filePath . EndsWith ( ".cshtml.g.cs" , StringComparison . OrdinalIgnoreCase ) ||
267+ filePath . EndsWith ( ".razor.g.cs" , StringComparison . OrdinalIgnoreCase ) ) {
268+ filePath = filePath . Substring ( 0 , filePath . Length - ".g.cs" . Length ) ;
269+ }
270+
271+ return filePath ;
272+ }
273+
232274 private bool IsSecondaryBuffer ( Workspace workspace , Document document )
233275 {
234276 var containedDocument = MonoDevelopHostDocumentRegistration . FromDocument ( document ) ;
@@ -239,23 +281,33 @@ private bool IsSecondaryBuffer (Workspace workspace, Document document)
239281 return true ;
240282 }
241283
242- public static bool TryMapSpanFromSecondaryBufferToPrimaryBuffer ( TextSpan spanInSecondaryBuffer , Microsoft . CodeAnalysis . Workspace workspace , Document document , out TextSpan spanInPrimaryBuffer )
284+ public bool TryMapSpanFromSecondaryBufferToPrimaryBuffer ( TextSpan spanInSecondaryBuffer , Microsoft . CodeAnalysis . Workspace workspace , Document document , out TextSpan spanInPrimaryBuffer )
243285 {
244286 spanInPrimaryBuffer = default ;
245287
246288 var containedDocument = MonoDevelopHostDocumentRegistration . FromDocument ( document ) ;
247289 if ( containedDocument == null ) {
248290 return false ;
249291 }
250- throw new NotImplementedException ( ) ;
251- //var bufferCoordinator = containedDocument.BufferCoordinator;
252292
253- //var primary = new VsTextSpan [1];
254- //var hresult = bufferCoordinator.MapSecondaryToPrimarySpan (spanInSecondaryBuffer, primary);
293+ var projectionBuffer = containedDocument . TopBuffer ;
294+
295+ var bufferGraph = factory . BufferGraphFactoryService . CreateBufferGraph ( projectionBuffer ) ;
255296
256- //spanInPrimaryBuffer = primary [0];
297+ if ( document . TryGetText ( out var sourceText ) && sourceText . Container . TryGetTextBuffer ( ) is ITextBuffer languageBuffer ) {
298+ var secondarySnapshot = languageBuffer . CurrentSnapshot ;
299+ var snapshotSpanInSecondaryBuffer = new SnapshotSpan ( secondarySnapshot , new Span ( spanInSecondaryBuffer . Start , spanInSecondaryBuffer . Length ) ) ;
300+ var topBufferSnapshotSpan = bufferGraph . MapUpToSnapshot (
301+ snapshotSpanInSecondaryBuffer ,
302+ SpanTrackingMode . EdgeExclusive ,
303+ projectionBuffer . CurrentSnapshot ) . FirstOrDefault ( ) ;
304+ if ( topBufferSnapshotSpan != default ) {
305+ spanInPrimaryBuffer = new TextSpan ( topBufferSnapshotSpan . Start , topBufferSnapshotSpan . Length ) ;
306+ return true ;
307+ }
308+ }
257309
258- // return ErrorHandler.Succeeded (hresult) ;
310+ return false ;
259311 }
260312
261313 private bool CanMapFromSecondaryBufferToPrimaryBuffer ( Workspace workspace , Document document , TextSpan spanInSecondaryBuffer )
0 commit comments