async Task Initialize() { needsInitialize = false; var bufferInfo = sessionManager.GetTextBufferInfo(buffer); if (bufferInfo != null) { session = bufferInfo.Session; relativePath = bufferInfo.RelativePath; file = await session.GetFile(relativePath); fileSubscription = file.LinesChanged.Subscribe(LinesChanged); side = bufferInfo.Side ?? DiffSide.Right; NotifyTagsChanged(); } else { await InitializeLiveFile(); sessionManagerSubscription = sessionManager .WhenAnyValue(x => x.CurrentSession) .Skip(1) .Subscribe(_ => ForgetWithLogging(InitializeLiveFile())); } }
async Task Initialize() { needsInitialize = false; var bufferInfo = sessionManager.GetTextBufferInfo(buffer); if (bufferInfo != null) { session = bufferInfo.Session; relativePath = bufferInfo.RelativePath; file = await session.GetFile(relativePath); leftHandSide = bufferInfo.IsLeftComparisonBuffer; NotifyTagsChanged(); } else { await InitializeLiveFile(); sessionManagerSubscription = sessionManager .WhenAnyValue(x => x.CurrentSession) .Skip(1) .Subscribe(_ => ForgetWithLogging(InitializeLiveFile())); } }
/// <inheritdoc/> public async Task InitializeAsync( IPullRequestSession session, Func <IInlineCommentThreadModel, bool> filter = null) { Guard.ArgumentNotNull(session, nameof(session)); subscriptions?.Dispose(); this.pullRequestSession = session; this.commentFilter = filter; subscriptions = new CompositeDisposable(); subscriptions.Add(session.WhenAnyValue(x => x.IsCheckedOut).Subscribe(isBranchCheckedOut)); var dirs = new Dictionary <string, PullRequestDirectoryNode> { { string.Empty, new PullRequestDirectoryNode(string.Empty) } }; using (var changes = await service.GetTreeChanges(session.LocalRepository, session.PullRequest)) { foreach (var changedFile in session.PullRequest.ChangedFiles) { var node = new PullRequestFileNode( session.LocalRepository.LocalPath, changedFile.FileName, changedFile.Sha, changedFile.Status, GetOldFileName(changedFile, changes)); var file = await session.GetFile(changedFile.FileName); if (file != null) { subscriptions.Add(file.WhenAnyValue(x => x.InlineCommentThreads) .Subscribe(x => node.CommentCount = CountComments(x, filter))); subscriptions.Add(file.WhenAnyValue(x => x.InlineAnnotations) .Subscribe(x => { var noticeCount = x.Count(model => model.AnnotationLevel == CheckAnnotationLevel.Notice); var warningCount = x.Count(model => model.AnnotationLevel == CheckAnnotationLevel.Warning); var failureCount = x.Count(model => model.AnnotationLevel == CheckAnnotationLevel.Failure); node.AnnotationNoticeCount = noticeCount; node.AnnotationWarningCount = warningCount; node.AnnotationFailureCount = failureCount; })); } var dir = GetDirectory(Path.GetDirectoryName(node.RelativePath), dirs); dir.Files.Add(node); } } ChangedFilesCount = session.PullRequest.ChangedFiles.Count; Items = dirs[string.Empty].Children.ToList(); }
async Task SessionChanged(IPullRequestSession session) { sessionSubscription?.Dispose(); this.session = session; if (file != null) { file = null; NotifyTagsChanged(); } if (session == null) { return; } relativePath = session.GetRelativePath(fullPath); if (relativePath == null) { return; } var snapshot = buffer.CurrentSnapshot; if (leftHandSide) { // If we're tagging the LHS of a diff, then the snapshot will be the base commit // (as you'd expect) but that means that the diff will be empty, so get the RHS // snapshot from the view for the comparison. var projection = view.TextSnapshot as IProjectionSnapshot; snapshot = projection?.SourceSnapshots.Count == 2 ? projection.SourceSnapshots[1] : null; } if (snapshot == null) { return; } var repository = gitService.GetRepository(session.LocalRepository.LocalPath); var isContentSource = !leftHandSide && !(buffer is IProjectionBuffer); file = await session.GetFile(relativePath, isContentSource?this : null); if (file == null) { return; } sessionSubscription = file.WhenAnyValue(x => x.InlineCommentThreads) .Subscribe(_ => NotifyTagsChanged()); NotifyTagsChanged(); }
async Task <IInlineCommentThreadModel> GetFirstCommentThread(IPullRequestFileNode file) { var sessionFile = await pullRequestSession.GetFile(file.RelativePath); var threads = sessionFile.InlineCommentThreads.AsEnumerable(); if (commentFilter != null) { threads = threads.Where(commentFilter); } return(threads.FirstOrDefault()); }
async Task DoOpen() { try { if (thread == null) { if (model.Thread.IsOutdated) { var file = await session.GetFile(RelativePath, model.Thread.OriginalCommitSha); thread = file.InlineCommentThreads.FirstOrDefault(t => t.Comments.Any(c => c.Comment.Id == model.Id)); } else { var file = await session.GetFile(RelativePath, model.Thread.CommitSha); thread = file.InlineCommentThreads.FirstOrDefault(t => t.Comments.Any(c => c.Comment.Id == model.Id)); if (thread?.LineNumber == -1) { log.Warning("Couldn't find line number for comment on {RelativePath} @ {CommitSha}", RelativePath, model.Thread.CommitSha); // Fall back to opening outdated file if we can't find a line number for the comment file = await session.GetFile(RelativePath, model.Thread.OriginalCommitSha); thread = file.InlineCommentThreads.FirstOrDefault(t => t.Comments.Any(c => c.Comment.Id == model.Id)); } } } if (thread != null && thread.LineNumber != -1) { await editorService.OpenDiff(session, RelativePath, thread); } } catch (Exception e) { log.Error(e, nameof(DoOpen)); } }
IPullRequestSessionManager CreateSessionManager( string commitSha = "COMMIT", string relativePath = RelativePath, IPullRequestSession session = null, PullRequestTextBufferInfo textBufferInfo = null) { var thread = CreateThread(10, "Existing comment"); var diff = new DiffChunk { DiffLine = 10, OldLineNumber = 1, NewLineNumber = 1, }; for (var i = 0; i < 10; ++i) { diff.Lines.Add(new DiffLine { NewLineNumber = i, DiffLineNumber = i + 10, Type = i < 5 ? DiffChangeType.Delete : DiffChangeType.Add, }); } var file = Substitute.For <IPullRequestSessionFile>(); file.CommitSha.Returns(commitSha); file.Diff.Returns(new[] { diff }); file.InlineCommentThreads.Returns(new[] { thread }); file.LinesChanged.Returns(new Subject <IReadOnlyList <Tuple <int, DiffSide> > >()); session = session ?? CreateSession(); if (textBufferInfo != null) { session.GetFile(textBufferInfo.RelativePath, textBufferInfo.CommitSha).Returns(file); } var result = Substitute.For <IPullRequestSessionManager>(); result.CurrentSession.Returns(session); result.GetLiveFile(relativePath, Arg.Any <ITextView>(), Arg.Any <ITextBuffer>()).Returns(file); result.GetRelativePath(Arg.Any <ITextBuffer>()).Returns(relativePath); result.GetTextBufferInfo(Arg.Any <ITextBuffer>()).Returns(textBufferInfo); return(result); }
async Task SessionChanged(IPullRequestSession session) { this.session = session; fileSubscription?.Dispose(); if (session == null) { Thread = null; threadSubscription?.Dispose(); return; } var relativePath = session.GetRelativePath(fullPath); file = await session.GetFile(relativePath); fileSubscription = file.WhenAnyValue(x => x.InlineCommentThreads).Subscribe(_ => UpdateThread().Forget()); }
async Task DoOpen() { try { if (thread == null) { var file = await session.GetFile(RelativePath, model.Thread.CommitSha); thread = file.InlineCommentThreads.FirstOrDefault(t => t.Comments.Any(c => c.Comment.Id == model.Id)); } if (thread != null && thread.LineNumber != -1) { await editorService.OpenDiff(session, RelativePath, thread); } } catch (Exception) { // TODO: Show error. } }
async Task DoOpen(object o) { try { if (thread == null) { var commit = model.Position.HasValue ? model.CommitId : model.OriginalCommitId; var file = await session.GetFile(RelativePath, commit); thread = file.InlineCommentThreads.FirstOrDefault(t => t.Comments.Any(c => c.Id == model.Id)); } if (thread != null && thread.LineNumber != -1) { await editorService.OpenDiff(session, RelativePath, thread); } } catch (Exception) { // TODO: Show error. } }
/// <inheritdoc/> public async Task <ITextView> OpenFile( IPullRequestSession session, string relativePath, bool workingDirectory) { Guard.ArgumentNotNull(session, nameof(session)); Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); try { var fullPath = GetAbsolutePath(session.LocalRepository, relativePath); string fileName; string commitSha; if (workingDirectory) { fileName = fullPath; commitSha = null; } else { var file = await session.GetFile(relativePath); fileName = await pullRequestService.ExtractToTempFile( session.LocalRepository, session.PullRequest, file.RelativePath, file.CommitSha, pullRequestService.GetEncoding(session.LocalRepository, file.RelativePath)); commitSha = file.CommitSha; } IVsTextView textView; IWpfTextView wpfTextView; using (workingDirectory ? null : OpenInProvisionalTab()) { var readOnly = !workingDirectory; textView = OpenDocument(fileName, readOnly, out wpfTextView); if (!workingDirectory) { AddBufferTag(wpfTextView.TextBuffer, session, fullPath, commitSha, null); EnableNavigateToEditor(textView, session); } } if (workingDirectory) { await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsOpenFileInSolution); } else { await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsViewFile); } return(wpfTextView); } catch (Exception e) { ShowErrorInStatusBar("Error opening file", e); return(null); } }
/// <inheritdoc/> public async Task <IDifferenceViewer> OpenDiff(IPullRequestSession session, string relativePath, string headSha, bool scrollToFirstDiff) { Guard.ArgumentNotNull(session, nameof(session)); Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); try { var workingDirectory = headSha == null; var file = await session.GetFile(relativePath, headSha ?? "HEAD"); var mergeBase = await pullRequestService.GetMergeBase(session.LocalRepository, session.PullRequest); var encoding = pullRequestService.GetEncoding(session.LocalRepository, file.RelativePath); var rightFile = workingDirectory ? Path.Combine(session.LocalRepository.LocalPath, relativePath) : await pullRequestService.ExtractToTempFile( session.LocalRepository, session.PullRequest, relativePath, file.CommitSha, encoding); var diffViewer = FocusExistingDiffViewer(session, mergeBase, rightFile); if (diffViewer != null) { return(diffViewer); } var leftFile = await pullRequestService.ExtractToTempFile( session.LocalRepository, session.PullRequest, relativePath, mergeBase, encoding); var leftPath = await GetBaseFileName(session, file); var rightPath = file.RelativePath; var leftLabel = $"{leftPath};{session.GetBaseBranchDisplay()}"; var rightLabel = workingDirectory ? rightPath : $"{rightPath};PR {session.PullRequest.Number}"; var caption = $"Diff - {Path.GetFileName(file.RelativePath)}"; var options = __VSDIFFSERVICEOPTIONS.VSDIFFOPT_DetectBinaryFiles | __VSDIFFSERVICEOPTIONS.VSDIFFOPT_LeftFileIsTemporary; if (!workingDirectory) { options |= __VSDIFFSERVICEOPTIONS.VSDIFFOPT_RightFileIsTemporary; } IVsWindowFrame frame; using (OpenWithOption(DifferenceViewerOptions.ScrollToFirstDiffName, scrollToFirstDiff)) using (OpenInProvisionalTab()) { var tooltip = $"{leftLabel}\nvs.\n{rightLabel}"; // Diff window will open in provisional (right hand) tab until document is touched. frame = VisualStudio.Services.DifferenceService.OpenComparisonWindow2( leftFile, rightFile, caption, tooltip, leftLabel, rightLabel, string.Empty, string.Empty, (uint)options); } diffViewer = GetDiffViewer(frame); var leftText = diffViewer.LeftView.TextBuffer.CurrentSnapshot.GetText(); var rightText = diffViewer.RightView.TextBuffer.CurrentSnapshot.GetText(); if (leftText == string.Empty) { // Don't show LeftView when empty. diffViewer.ViewMode = DifferenceViewMode.RightViewOnly; } else if (rightText == string.Empty) { // Don't show RightView when empty. diffViewer.ViewMode = DifferenceViewMode.LeftViewOnly; } else if (leftText == rightText) { // Don't show LeftView when no changes. diffViewer.ViewMode = DifferenceViewMode.RightViewOnly; } AddBufferTag(diffViewer.LeftView.TextBuffer, session, leftPath, mergeBase, DiffSide.Left); if (!workingDirectory) { AddBufferTag(diffViewer.RightView.TextBuffer, session, rightPath, file.CommitSha, DiffSide.Right); EnableNavigateToEditor(diffViewer.LeftView, session); EnableNavigateToEditor(diffViewer.RightView, session); EnableNavigateToEditor(diffViewer.InlineView, session); } if (workingDirectory) { await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsCompareWithSolution); } else { await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsViewChanges); } return(diffViewer); } catch (Exception e) { ShowErrorInStatusBar("Error opening file", e); return(null); } }
/// <inheritdoc/> public async Task <IDifferenceViewer> OpenDiff(IPullRequestSession session, string relativePath, string headSha, bool scrollToFirstDraftOrDiff) { Guard.ArgumentNotNull(session, nameof(session)); Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); try { var workingDirectory = headSha == null; var file = await session.GetFile(relativePath, headSha ?? "HEAD"); var mergeBase = await pullRequestService.GetMergeBase(session.LocalRepository, session.PullRequest); var encoding = pullRequestService.GetEncoding(session.LocalRepository, file.RelativePath); var rightFile = workingDirectory ? Path.Combine(session.LocalRepository.LocalPath, relativePath) : await pullRequestService.ExtractToTempFile( session.LocalRepository, session.PullRequest, relativePath, file.CommitSha, encoding); var diffViewer = FocusExistingDiffViewer(session, mergeBase, rightFile); if (diffViewer != null) { return(diffViewer); } var leftFile = await pullRequestService.ExtractToTempFile( session.LocalRepository, session.PullRequest, relativePath, mergeBase, encoding); var leftPath = await GetBaseFileName(session, file); var rightPath = file.RelativePath; var leftLabel = $"{leftPath};{session.GetBaseBranchDisplay()}"; var rightLabel = workingDirectory ? rightPath : $"{rightPath};PR {session.PullRequest.Number}"; var caption = $"Diff - {Path.GetFileName(file.RelativePath)}"; var options = __VSDIFFSERVICEOPTIONS.VSDIFFOPT_DetectBinaryFiles | __VSDIFFSERVICEOPTIONS.VSDIFFOPT_LeftFileIsTemporary; var openThread = (line : -1, side : DiffSide.Left); var scrollToFirstDiff = false; if (!workingDirectory) { options |= __VSDIFFSERVICEOPTIONS.VSDIFFOPT_RightFileIsTemporary; } if (scrollToFirstDraftOrDiff) { var(key, _) = PullRequestReviewCommentThreadViewModel.GetDraftKeys( session.LocalRepository.CloneUrl.WithOwner(session.RepositoryOwner), session.PullRequest.Number, relativePath, 0); var drafts = (await draftStore.GetDrafts <PullRequestReviewCommentDraft>(key) .ConfigureAwait(true)) .OrderByDescending(x => x.data.UpdatedAt) .ToList(); if (drafts.Count > 0 && int.TryParse(drafts[0].secondaryKey, out var line)) { openThread = (line, drafts[0].data.Side);
/// <inheritdoc/> public async Task OpenFile( IPullRequestSession session, string relativePath, bool workingDirectory) { Guard.ArgumentNotNull(session, nameof(session)); Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); try { var fullPath = Path.Combine(session.LocalRepository.LocalPath, relativePath); string fileName; string commitSha; if (workingDirectory) { fileName = fullPath; commitSha = null; } else { var file = await session.GetFile(relativePath); fileName = await pullRequestService.ExtractToTempFile( session.LocalRepository, session.PullRequest, file.RelativePath, file.CommitSha, pullRequestService.GetEncoding(session.LocalRepository, file.RelativePath)); commitSha = file.CommitSha; } using (workingDirectory ? null : OpenInProvisionalTab()) { var window = VisualStudio.Services.Dte.ItemOperations.OpenFile(fileName); window.Document.ReadOnly = !workingDirectory; var buffer = GetBufferAt(fileName); if (!workingDirectory) { AddBufferTag(buffer, session, fullPath, commitSha, null); var textView = FindActiveView(); var file = await session.GetFile(relativePath); EnableNavigateToEditor(textView, session, file); } } if (workingDirectory) { await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsOpenFileInSolution); } else { await usageTracker.IncrementCounter(x => x.NumberOfPRDetailsViewFile); } } catch (Exception e) { ShowErrorInStatusBar("Error opening file", e); } }