Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Conversation

@grokys
Copy link
Contributor

@grokys grokys commented Jun 5, 2017

This PR adds inline comments for Pull Requests to editor and compare views in Visual Studio.

This PR implements the MVP for this feature, namely:

  • Number of comments next to files in the tree
    • When # of comments is clicked on, file is opened and first inline comment thread navigated to
  • A margin displaying icons where comments are located, and showing where new comments can be left
  • Comments displayed in peek view
  • Existing comments can be replied to
  • New comments can be left where allowed
  • Navigating next/previous comment in file
    • Menu items
    • Keyboard shortcuts
    • Toolbar buttons for text editor don't think this is necessary, toolbar buttons aren't very discoverable
    • Toolbar buttons already exist in the diff view - can we hook into them? can't find a way to do this
  • Buttons for next/previous comment in peek view
  • Existing comments displayed as marks on scrollbars punting to a later date
  • When a file has local changes, display a message to commit/push before comments can be left
  • Refreshing the PR in the PR details view updates the inline comments

grokys and others added 30 commits April 20, 2017 21:28
 Conflicts:
	.ncrunch/GitHub.UI.UnitTests.v3.ncrunchproject
	src/GitHub.Exports/Settings/Guids.cs
Use a heuristic to recalculate comment positions when opening a file in
response to any changes that may be present locally.
When editing occurs, recalculate comment positions on a background
thread.
One of the files might not exist of the file is added or deleted.
`IVsDifferenceService.OpenComparisonWindow2` doesn't allow us to pass
extra information to the tagger, such as the path to the file being
tagged (the left file path is always passed, which may be a temp file).
Hack around this by storing global state in the pull request review
session object (ugh).
Previously it was exposing an `AccountCacheItem` instead of an
`IAccount` which wasn't that useful.
Doesn't actually do anything yet mind.
Make margin visible on Left, Right and Inline views.
The glyphs may be in the wrong place, and they're displayed erroneously
on the RHS too, but they're there.
This required a rewrite of our diff handling - now using VS' inbuilt
differ rather than DiffPlex.
There's a distinction - reviews are made of comments.
GitHub.VisualStudio was previously referencing it directly meaning that
it wasn't found on a machine without VS2015 installed.
It was causing a crash on VS2017 for some reason.
}
else
{
j = 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could do second, more forgiving pass that still matches if lines have been added? That would address the inconsistency where you can add lines above the chunk, but if you happen to add a line within the chunk the comment dissapears (for me this is the most disconcerting bit).

I'm sure there are fancier things we could do later, but this would be simple, consistent and predictable. What do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, one really simple thing we could do is treat / as white space when matching lines. That way a developer could // out lines and not immediately loose their inline comments.

Copy link
Collaborator

@jcansdale jcansdale Jul 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simply ignoring white-space and comments actually works pretty well. It means you can comment out the line with an inline-comment and add stuff below. For example:

image

You can see I've added j = 777, but the inline-comment is still there for reference.

return Task.Factory.StartNew(() =>
{
var commit = repository.Lookup<Commit>(commitSha);
if(commit == null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Space missing!

var tempFile = Path.Combine(tempDir, tempFileName);

Directory.CreateDirectory(tempDir);
File.WriteAllText(tempFile, contents, encoding);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which thread is this happening in? If it's on the main thread, IO is slow for some reason and the file is large, this may block the UI. All IO should be off on a side thread to avoid this.

if (mergeBase == null)
{
throw new FileNotFoundException($"Could not retrieve {fileName}@{pullRequest.Base.Sha}");
throw new FileNotFoundException($"Couldn't find merge base between {baseSha} and {headSha}.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception type doesn't match the actual exception, this isn't an IO operation. Perhaps a custom exception type could be useful here for conveying meaning?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was reluctant to create a new exception type for something that really shouldn't happen. I've changed it to InvalidOperationException which we're using elsewhere in this class. Still not ideal, but at least it isn't an IO exception.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the heads up. I'll revert my other commit. Did you fix a test for that as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I changed the test to expect NotFoundException too.

static string CreateTempFile(string fileName, string commitSha, string contents, Encoding encoding)
{
var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
var tempFileName = $"{Path.GetFileNameWithoutExtension(fileName)}@{commitSha}{Path.GetExtension(fileName)}";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't be trusting that the filename coming from the PR is valid under the current locale and encoding or even valid to Windows. There are certain filenames that can be created in other OSs that the Windows fs can't deal with, and there are certain characters and encodings that might not match the local. In this case, ignoring the string formatting warning is not a good option, and using the original filename to create a temp file is probably not a good option either.

We're already creating a GUID in the directory name, if we use the GUID instead for the filename we avoid both issues entirely. Alternatively, if you want to reuse the cached temp file, you could encode the filename with Convert.ToBase64String(Encoding.UTF8.GetBytes(filename)) and put it in a known temp directory.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment I don't think the file path is surfaced anywhere, but it is visible in the #1036 PR (which isn't ideal).

image

Maybe we should find a way to sanitize the file name and not include the commitSha? Would URL-encoding the filename work? I think we definitely need to need to keep the file extension for automatic syntax highlighting.

var tempFileName = $"{Path.GetFileNameWithoutExtension(fileName)}@{commitSha}{Path.GetExtension(fileName)}";
var tempFile = Path.Combine(tempDir, tempFileName);

Directory.CreateDirectory(tempDir);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have an OS facade to abstract the filesystem out for these things. We should use it everywhere, for consistency.

return sb.ToString();
}

public static bool EqualsIgnoringLineEndings(this string a, string b)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't being used anywhere, is it still needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope!

}
}

private void ViewCommentsClick(object sender, RoutedEventArgs e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 private 👀

null);
}

private async void ViewFileCommentsClick(object sender, RoutedEventArgs e)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 private 👀

{
foreach (var b in encoding.GetPreamble())
{
if(b != stream.ReadByte())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if space!

glyphs = dictionary;
}
}
static ITextViewLine GetStartingLine(IList<ITextViewLine> lines, Span span)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing newline

}
}

public FrameworkElement MarginVisual => glyphMarginGrid;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Properties should be defined after methods


public event EventHandler Disposed;

public void Dispose()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs implementing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there's actually anything we need to dispose here. The reason this method appears is that IPeekResult implements IDisposable so we need to implement it too.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've marked it as sealed because it isn't intended to be extended.

public ReactiveCommand<object> CancelEdit { get; }

/// <inheritdoc/>
public ReactiveCommand<Unit> CommitEdit { get; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these properties should be after methods

/// <summary>
/// Gets the thread of comments to display.
/// </summary>
public ICommentThreadViewModel Thread
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably called something other than Thread, since there's a Thread type that can easily be confused with it, and makes the code harder to read. We call a series of related comments a Discussion on .com.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd not considered this; I've used Thread in the names of quite a few types as well.

nextCommentCommand,
previousCommentCommand);
viewModel.Initialize().Forget();
peekableItems.Add(new InlineCommentPeekableItem(viewModel));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The viewmodel may not be finished initializing by the time it's added to this list. What is the impact of that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be fine - the view model will update itself in-place. The reason I did it like this was to make clear that there's async work to be done - the other options were to do the async work in the ctor or make Initialize an async void method.

IEnumerable<string> FilePaths
{
get { return PullRequest.ChangedFiles.Select(x => x.FileName); }
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Properties after methods

catch (FileNotFoundException)
{
var pullHeadRef = $"refs/pull/{pullRequestNumber}/head";
await gitClient.Fetch(repo, "origin", sha, pullHeadRef);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to use an https remote, so it needs to do var remote = await gitClient.GetHttpRemote(repo); and call fetch with the returned remote.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already fixed this in the work associated with the PR details right-click menu.

Copy link
Contributor

@shana shana left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a bunch of comments and there are things to address in subsequent PRs. Been using this branch to review, it works nicely! 😄

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants