55using System . Linq ;
66using System . Reactive . Linq ;
77using System . Threading . Tasks ;
8+ using EnvDTE ;
89using GitHub . Commands ;
910using GitHub . Extensions ;
1011using GitHub . Models ;
12+ using GitHub . Models . Drafts ;
13+ using GitHub . ViewModels ;
1114using GitHub . VisualStudio ;
1215using Microsoft . VisualStudio ;
1316using Microsoft . VisualStudio . Editor ;
1821using Microsoft . VisualStudio . Text . Editor ;
1922using Microsoft . VisualStudio . Text . Projection ;
2023using Microsoft . VisualStudio . TextManager . Interop ;
21- using EnvDTE ;
2224using Task = System . Threading . Tasks . Task ;
2325
2426namespace GitHub . Services
@@ -39,6 +41,8 @@ public class PullRequestEditorService : IPullRequestEditorService
3941 readonly IStatusBarNotificationService statusBar ;
4042 readonly IGoToSolutionOrPullRequestFileCommand goToSolutionOrPullRequestFileCommand ;
4143 readonly IEditorOptionsFactoryService editorOptionsFactoryService ;
44+ readonly IMessageDraftStore draftStore ;
45+ readonly IInlineCommentPeekService peekService ;
4246 readonly IUsageTracker usageTracker ;
4347
4448 [ ImportingConstructor ]
@@ -49,6 +53,8 @@ public PullRequestEditorService(
4953 IStatusBarNotificationService statusBar ,
5054 IGoToSolutionOrPullRequestFileCommand goToSolutionOrPullRequestFileCommand ,
5155 IEditorOptionsFactoryService editorOptionsFactoryService ,
56+ IMessageDraftStore draftStore ,
57+ IInlineCommentPeekService peekService ,
5258 IUsageTracker usageTracker )
5359 {
5460 Guard . ArgumentNotNull ( serviceProvider , nameof ( serviceProvider ) ) ;
@@ -58,13 +64,17 @@ public PullRequestEditorService(
5864 Guard . ArgumentNotNull ( goToSolutionOrPullRequestFileCommand , nameof ( goToSolutionOrPullRequestFileCommand ) ) ;
5965 Guard . ArgumentNotNull ( goToSolutionOrPullRequestFileCommand , nameof ( editorOptionsFactoryService ) ) ;
6066 Guard . ArgumentNotNull ( usageTracker , nameof ( usageTracker ) ) ;
67+ Guard . ArgumentNotNull ( peekService , nameof ( peekService ) ) ;
68+ Guard . ArgumentNotNull ( draftStore , nameof ( draftStore ) ) ;
6169
6270 this . serviceProvider = serviceProvider ;
6371 this . pullRequestService = pullRequestService ;
6472 this . vsEditorAdaptersFactory = vsEditorAdaptersFactory ;
6573 this . statusBar = statusBar ;
6674 this . goToSolutionOrPullRequestFileCommand = goToSolutionOrPullRequestFileCommand ;
6775 this . editorOptionsFactoryService = editorOptionsFactoryService ;
76+ this . draftStore = draftStore ;
77+ this . peekService = peekService ;
6878 this . usageTracker = usageTracker ;
6979 }
7080
@@ -129,7 +139,7 @@ public async Task<ITextView> OpenFile(
129139 }
130140
131141 /// <inheritdoc/>
132- public async Task < IDifferenceViewer > OpenDiff ( IPullRequestSession session , string relativePath , string headSha , bool scrollToFirstDiff )
142+ public async Task < IDifferenceViewer > OpenDiff ( IPullRequestSession session , string relativePath , string headSha , bool scrollToFirstDraftOrDiff )
133143 {
134144 Guard . ArgumentNotNull ( session , nameof ( session ) ) ;
135145 Guard . ArgumentNotEmptyString ( relativePath , nameof ( relativePath ) ) ;
@@ -168,12 +178,37 @@ await pullRequestService.ExtractToTempFile(
168178 var caption = $ "Diff - { Path . GetFileName ( file . RelativePath ) } ";
169179 var options = __VSDIFFSERVICEOPTIONS . VSDIFFOPT_DetectBinaryFiles |
170180 __VSDIFFSERVICEOPTIONS . VSDIFFOPT_LeftFileIsTemporary ;
181+ var openThread = ( line : - 1 , side : DiffSide . Left ) ;
182+ var scrollToFirstDiff = false ;
171183
172184 if ( ! workingDirectory )
173185 {
174186 options |= __VSDIFFSERVICEOPTIONS . VSDIFFOPT_RightFileIsTemporary ;
175187 }
176188
189+ if ( scrollToFirstDraftOrDiff )
190+ {
191+ var ( key , _) = PullRequestReviewCommentThreadViewModel . GetDraftKeys (
192+ session . LocalRepository . CloneUrl . WithOwner ( session . RepositoryOwner ) ,
193+ session . PullRequest . Number ,
194+ relativePath ,
195+ 0 ) ;
196+ var drafts = ( await draftStore . GetDrafts < PullRequestReviewCommentDraft > ( key )
197+ . ConfigureAwait ( true ) )
198+ . OrderByDescending ( x => x . data . UpdatedAt )
199+ . ToList ( ) ;
200+
201+ if ( drafts . Count > 0 && int . TryParse ( drafts [ 0 ] . secondaryKey , out var line ) )
202+ {
203+ openThread = ( line , drafts [ 0 ] . data . Side ) ;
204+ scrollToFirstDiff = false ;
205+ }
206+ else
207+ {
208+ scrollToFirstDiff = true ;
209+ }
210+ }
211+
177212 IVsWindowFrame frame ;
178213 using ( OpenWithOption ( DifferenceViewerOptions . ScrollToFirstDiffName , scrollToFirstDiff ) )
179214 using ( OpenInProvisionalTab ( ) )
@@ -228,6 +263,18 @@ await pullRequestService.ExtractToTempFile(
228263 else
229264 await usageTracker . IncrementCounter ( x => x . NumberOfPRDetailsViewChanges ) ;
230265
266+ if ( openThread . line != - 1 )
267+ {
268+ var view = diffViewer . ViewMode == DifferenceViewMode . Inline ?
269+ diffViewer . InlineView :
270+ openThread . side == DiffSide . Left ? diffViewer . LeftView : diffViewer . RightView ;
271+
272+ // HACK: We need to wait here for the view to initialize or the peek session won't appear.
273+ // There must be a better way of doing this.
274+ await Task . Delay ( 1500 ) . ConfigureAwait ( true ) ;
275+ peekService . Show ( view , openThread . side , openThread . line ) ;
276+ }
277+
231278 return diffViewer ;
232279 }
233280 catch ( Exception e )
@@ -247,7 +294,7 @@ public async Task<IDifferenceViewer> OpenDiff(
247294 Guard . ArgumentNotEmptyString ( relativePath , nameof ( relativePath ) ) ;
248295 Guard . ArgumentNotNull ( thread , nameof ( thread ) ) ;
249296
250- var diffViewer = await OpenDiff ( session , relativePath , thread . CommitSha , scrollToFirstDiff : false ) ;
297+ var diffViewer = await OpenDiff ( session , relativePath , thread . CommitSha , scrollToFirstDraftOrDiff : false ) ;
251298
252299 var param = ( object ) new InlineCommentNavigationParams
253300 {
0 commit comments