Skip to content

Commit

Permalink
Blame: Display avatars
Browse files Browse the repository at this point in the history
  • Loading branch information
pmiossec committed Jul 1, 2019
1 parent 453cf2f commit eff6cc0
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 7 deletions.
2 changes: 1 addition & 1 deletion GitUI/Avatars/AvatarService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace GitUI.Avatars
{
public static class AvatarService
{
private static InitialsAvatarGenerator AvatarGenerator = new InitialsAvatarGenerator();
private static readonly InitialsAvatarGenerator AvatarGenerator = new InitialsAvatarGenerator();
public static IAvatarProvider Default { get; }
= new BackupAvatarProvider(new AvatarMemoryCache(new AvatarPersistentCache(new AvatarDownloader(AvatarGenerator), AvatarGenerator)),
Images.User80);
Expand Down
78 changes: 78 additions & 0 deletions GitUI/Editor/BlameAuthorMargin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using GitExtUtils.GitUI;
using ICSharpCode.TextEditor;
using Microsoft.VisualStudio.Threading;

namespace GitUI.Editor
{
/// <summary>
/// This class display avatars in the gutter in a blame.
/// </summary>
public class BlameAuthorMargin : AbstractMargin
{
private List<Image> _avatars;
private readonly int _lineHeight;
private readonly Color _backgroundColor;

public BlameAuthorMargin(TextArea textArea) : base(textArea)
{
_lineHeight = textArea.Font.Height + 1;
_backgroundColor = ((SolidBrush)SystemBrushes.Window).Color;
}

public void SetAvatars(List<JoinableTask<Image>> avatars)
{
_avatars = avatars.Select(a => ThreadHelper.JoinableTaskFactory.Run(async () => await a)).ToList();

// Update the resolution otherwise the image is not drawn at the good size :(
foreach (var avatar in _avatars)
{
if (avatar is Bitmap bitmapAvatar)
{
bitmapAvatar.SetResolution(DpiUtil.DpiX, DpiUtil.DpiY);
}
}
}

public override int Width => _lineHeight;

public override bool IsVisible => true;

public override void Paint(Graphics g, Rectangle rect)
{
if (rect.Width <= 0 || rect.Height <= 0)
{
return;
}

g.Clear(_backgroundColor);

if (_avatars == null || _avatars.Count == 0)
{
return;
}

var verticalOffset = textArea.VirtualTop.Y;
var lineStart = verticalOffset / _lineHeight;
var negativeOffset = (lineStart * _lineHeight) - verticalOffset;
var lineCount = (int)rect.Height / _lineHeight;

for (int i = 0; i < lineCount; i++)
{
if (lineStart + i >= _avatars.Count)
{
break;
}

if (_avatars[lineStart + i] != null)
{
g.DrawImage(_avatars[lineStart + i], new Point(0, negativeOffset + (i * _lineHeight)));
}
}

base.Paint(g, rect);
}
}
}
8 changes: 8 additions & 0 deletions GitUI/Editor/FileViewer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
Expand All @@ -17,7 +18,9 @@
using GitUI.Hotkey;
using GitUI.Properties;
using GitUIPluginInterfaces;
using ICSharpCode.TextEditor;
using JetBrains.Annotations;
using Microsoft.VisualStudio.Threading;
using ResourceManager;

namespace GitUI.Editor
Expand Down Expand Up @@ -1517,5 +1520,10 @@ public IgnoreWhitespaceKind IgnoreWhitespace
public ToolStripButton IgnoreAllWhitespacesButton => _fileViewer.ignoreAllWhitespaces;
public ToolStripMenuItem IgnoreAllWhitespacesMenuItem => _fileViewer.ignoreAllWhitespaceChangesToolStripMenuItem;
}

public void SetGutterAvatars(List<JoinableTask<Image>> avatars)
{
internalFileViewer.SetGutterAvatars(avatars);
}
}
}
15 changes: 15 additions & 0 deletions GitUI/Editor/FileViewerInternal.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Forms;
using GitCommands;
using GitExtUtils.GitUI;
using GitUI.Editor.Diff;
using GitUI.Properties;
using ICSharpCode.TextEditor;
using ICSharpCode.TextEditor.Document;
using Microsoft.VisualStudio.Threading;

namespace GitUI.Editor
{
Expand All @@ -28,6 +31,7 @@ public partial class FileViewerInternal : GitModuleControl, IFileViewer
private readonly CurrentViewPositionCache _currentViewPositionCache;
private DiffViewerLineNumberControl _lineNumbersControl;
private DiffHighlightService _diffHighlightService = DiffHighlightService.Instance;
private BlameAuthorMargin _authorsAvatarMargin;

public FileViewerInternal()
{
Expand Down Expand Up @@ -547,5 +551,16 @@ public TestAccessor(FileViewerInternal control)

public TextEditorControl TextEditor => _control.TextEditor;
}

public void SetGutterAvatars(List<JoinableTask<Image>> avatars)
{
if (_authorsAvatarMargin == null)
{
_authorsAvatarMargin = new BlameAuthorMargin(TextEditor.ActiveTextAreaControl.TextArea);
TextEditor.ActiveTextAreaControl.TextArea.InsertLeftMargin(0, _authorsAvatarMargin);
}

_authorsAvatarMargin.SetAvatars(avatars);
}
}
}
1 change: 1 addition & 0 deletions GitUI/GitUI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@
<Compile Include="Editor\Diff\DiffLineInfo.cs" />
<Compile Include="Editor\Diff\DiffLinesInfo.cs" />
<Compile Include="Editor\Diff\DiffLineType.cs" />
<Compile Include="Editor\BlameAuthorMargin.cs" />
<Compile Include="FontUtil.cs" />
<Compile Include="Editor\GitHighlightingStrategyBase.cs" />
<Compile Include="Editor\RebaseTodoHighlightingStrategy.cs" />
Expand Down
34 changes: 30 additions & 4 deletions GitUI/UserControls/BlameControl.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using GitCommands;
using GitExtUtils;
using GitUI.Avatars;
using GitUI.BranchTreePanel;
using GitUI.CommitInfo;
using GitUI.Editor;
using GitUI.HelperDialogs;
using GitUIPluginInterfaces;
using ICSharpCode.TextEditor;
using JetBrains.Annotations;
using Microsoft.VisualStudio.Threading;

namespace GitUI.Blame
{
Expand Down Expand Up @@ -268,7 +273,10 @@ private void BlameFile_VScrollPositionChanged(object sender, EventArgs e)

private void ProcessBlame(string filename, GitRevision revision, IReadOnlyList<ObjectId> children, Control controlToMask, int lineNumber, int scrollpos)
{
var (gutter, body) = BuildBlameContents(filename);
var avatarSize = BlameAuthor.Font.Height + 1;
var (gutter, body, avatars) = BuildBlameContents(filename, avatarSize);

BlameAuthor.SetGutterAvatars(avatars);

ThreadHelper.JoinableTaskFactory.RunAsync(
() => BlameAuthor.ViewTextAsync("committer.txt", gutter));
Expand All @@ -293,7 +301,8 @@ private void ProcessBlame(string filename, GitRevision revision, IReadOnlyList<O
HighlightFollowingHistory();
}

private (string gutter, string body) BuildBlameContents(string filename)
private (string gutter, string body, List<JoinableTask<Image>> avatars) BuildBlameContents(string filename,
int avatarSize)
{
var body = new StringBuilder(capacity: 4096);

Expand All @@ -320,14 +329,31 @@ private void ProcessBlame(string filename, GitRevision revision, IReadOnlyList<O
var lineBuilder = new StringBuilder(lineLength + 2);
var gutter = new StringBuilder(capacity: lineBuilder.Capacity * _blame.Lines.Count);
var emptyLine = new string(' ', lineLength);
List<JoinableTask<Image>> avatars = new List<JoinableTask<Image>>();
Dictionary<string, JoinableTask<Image>> cacheAvatars = new Dictionary<string, JoinableTask<Image>>();
var noAvatar = ThreadHelper.JoinableTaskFactory.RunAsync(() => Task.FromResult<Image>(null));
foreach (var line in _blame.Lines)
{
if (line.Commit == lastCommit)
{
avatars.Add(noAvatar);
gutter.AppendLine(emptyLine);
}
else
{
var authorEmail = line.Commit.AuthorMail.Trim('<', '>');
if (cacheAvatars.ContainsKey(authorEmail))
{
avatars.Add(cacheAvatars[authorEmail]);
}
else
{
var avatar = ThreadHelper.JoinableTaskFactory.RunAsync(async () => await AvatarService.Default.GetAvatarAsync(authorEmail, line.Commit.Author,
avatarSize));
cacheAvatars.Add(authorEmail, avatar);
avatars.Add(avatar);
}

BuildAuthorLine(line, lineBuilder, dateTimeFormat, filename, AppSettings.BlameShowAuthor, AppSettings.BlameShowAuthorDate, AppSettings.BlameShowOriginalFilePath, AppSettings.BlameDisplayAuthorFirst);

gutter.Append(lineBuilder);
Expand All @@ -340,7 +366,7 @@ private void ProcessBlame(string filename, GitRevision revision, IReadOnlyList<O
lastCommit = line.Commit;
}

return (gutter.ToString(), body.ToString());
return (gutter.ToString(), body.ToString(), avatars);
}

private void BuildAuthorLine(GitBlameLine line, StringBuilder lineBuilder, string dateTimeFormat,
Expand Down Expand Up @@ -591,7 +617,7 @@ public GitBlame Blame
public void BuildAuthorLine(GitBlameLine line, StringBuilder lineBuilder, string dateTimeFormat, string filename, bool showAuthor, bool showAuthorDate, bool showOriginalFilePath, bool displayAuthorFirst)
=> _control.BuildAuthorLine(line, lineBuilder, dateTimeFormat, filename, showAuthor, showAuthorDate, showOriginalFilePath, displayAuthorFirst);

public (string gutter, string body) BuildBlameContents(string filename) => _control.BuildBlameContents(filename);
public (string gutter, string body, List<JoinableTask<Image>> avatars) BuildBlameContents(string filename) => _control.BuildBlameContents(filename, 10);
}
}
}
4 changes: 2 additions & 2 deletions UnitTests/GitUITests/UserControls/BlameControlTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public void BuildBlameContents_WithDateAndTime()
{
AppSettings.BlameShowAuthorTime = true;

var (gutter, content) = _sut.BuildBlameContents("fileName.txt");
var (gutter, content, _) = _sut.BuildBlameContents("fileName.txt");

content.Should().Be($"line1{Environment.NewLine}line2{Environment.NewLine}line3{Environment.NewLine}line4{Environment.NewLine}");
var gutterLines = gutter.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
Expand Down Expand Up @@ -123,7 +123,7 @@ public void BuildBlameContents_WithDateButNotTime()
AppSettings.BlameShowAuthorTime = false;

// When
var (gutter, content) = _sut.BuildBlameContents("fileName.txt");
var (gutter, content, _) = _sut.BuildBlameContents("fileName.txt");

// Then
content.Should().Be($"line1{Environment.NewLine}line2{Environment.NewLine}line3{Environment.NewLine}line4{Environment.NewLine}");
Expand Down

0 comments on commit eff6cc0

Please sign in to comment.