diff --git a/src/GitHub.App/SampleData/SampleViewModels.cs b/src/GitHub.App/SampleData/SampleViewModels.cs index 57746efe6c..1ee0298e87 100644 --- a/src/GitHub.App/SampleData/SampleViewModels.cs +++ b/src/GitHub.App/SampleData/SampleViewModels.cs @@ -409,6 +409,11 @@ public void SetIcon(bool isPrivate, bool isFork) { } + public UriString GenerateUrl(string path = null, int startLine = -1, int endLine = -1) + { + return null; + } + public string Name { get; set; } public UriString CloneUrl { get; set; } public string LocalPath { get; set; } diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index b6ea5c21a4..23d21adf55 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -112,6 +112,7 @@ + diff --git a/src/GitHub.Exports/Models/ISimpleRepositoryModel.cs b/src/GitHub.Exports/Models/ISimpleRepositoryModel.cs index 197a09378f..e1f6ece70c 100644 --- a/src/GitHub.Exports/Models/ISimpleRepositoryModel.cs +++ b/src/GitHub.Exports/Models/ISimpleRepositoryModel.cs @@ -18,5 +18,6 @@ public interface ISimpleRepositoryModel : INotifyPropertyChanged /// Updates the url information based on the local path /// void Refresh(); + UriString GenerateUrl(string path = null, int startLine = -1, int endLine = -1); } } diff --git a/src/GitHub.Exports/Models/SimpleRepositoryModel.cs b/src/GitHub.Exports/Models/SimpleRepositoryModel.cs index 9bb2672660..9e4aaa6af5 100644 --- a/src/GitHub.Exports/Models/SimpleRepositoryModel.cs +++ b/src/GitHub.Exports/Models/SimpleRepositoryModel.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; +using System.Linq; using GitHub.Primitives; using GitHub.UI; using GitHub.VisualStudio.Helpers; @@ -10,7 +11,7 @@ namespace GitHub.Models { [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class SimpleRepositoryModel : NotificationAwareObject, ISimpleRepositoryModel, INotifyPropertySource, IEquatable + public class SimpleRepositoryModel : NotificationAwareObject, ISimpleRepositoryModel, IEquatable { public SimpleRepositoryModel(string name, UriString cloneUrl, string localPath = null) { @@ -53,6 +54,60 @@ public void Refresh() CloneUrl = uri; } + /// + /// Generates a http(s) url to the repository in the remote server, optionally + /// pointing to a specific file and specific line range in it. + /// + /// The file to generate an url to. Optional. + /// A specific line, or (if specifying the as well) the start of a range + /// The end of a line range on the specified file. + /// An UriString with the generated url, or null if the repository has no remote server configured or if it can't be found locally + public UriString GenerateUrl(string path = null, int startLine = -1, int endLine = -1) + { + if (CloneUrl == null) + return null; + + var sha = HeadSha; + // this also incidentally checks whether the repo has a valid LocalPath + if (String.IsNullOrEmpty(sha)) + return CloneUrl.ToRepositoryUrl().AbsoluteUri; + + if (path != null && Path.IsPathRooted(path)) + { + // if the path root doesn't match the repository local path, then ignore it + if (!path.StartsWith(LocalPath, StringComparison.OrdinalIgnoreCase)) + { + Debug.Assert(false, String.Format(CultureInfo.CurrentCulture, "GenerateUrl: path {0} doesn't match repository {1}", path, LocalPath)); + path = null; + } + else + path = path.Substring(LocalPath.Length + 1); + } + + return new UriString(GenerateUrl(CloneUrl.ToRepositoryUrl().AbsoluteUri, sha, path, startLine, endLine)); + } + + const string CommitFormat = "{0}/commit/{1}"; + const string BlobFormat = "{0}/blob/{1}/{2}"; + const string StartLineFormat = "{0}#L{1}"; + const string EndLineFormat = "{0}-L{1}"; + static string GenerateUrl(string basePath, string sha, string path, int startLine = -1, int endLine = -1) + { + if (sha == null) + return basePath; + + if (String.IsNullOrEmpty(path)) + return String.Format(CultureInfo.InvariantCulture, CommitFormat, basePath, sha); + + var ret = String.Format(CultureInfo.InvariantCulture, BlobFormat, basePath, sha, path.Replace(@"\", "/")); + if (startLine < 0) + return ret; + ret = String.Format(CultureInfo.InvariantCulture, StartLineFormat, ret, startLine); + if (endLine < 0) + return ret; + return String.Format(CultureInfo.InvariantCulture, EndLineFormat, ret, endLine); + } + public string Name { get; } UriString cloneUrl; public UriString CloneUrl { get { return cloneUrl; } set { cloneUrl = value; this.RaisePropertyChange(); } } @@ -60,6 +115,15 @@ public void Refresh() Octicon icon; public Octicon Icon { get { return icon; } set { icon = value; this.RaisePropertyChange(); } } + public string HeadSha + { + get + { + var repo = GitService.GitServiceHelper.GetRepo(LocalPath); + return repo?.Commits.FirstOrDefault()?.Sha ?? String.Empty; + } + } + /// /// Note: We don't consider CloneUrl a part of the hash code because it can change during the lifetime /// of a repository. Equals takes care of any hash collisions because of this diff --git a/src/GitHub.Exports/Services/IActiveDocumentSnapshot.cs b/src/GitHub.Exports/Services/IActiveDocumentSnapshot.cs new file mode 100644 index 0000000000..60fcdca44b --- /dev/null +++ b/src/GitHub.Exports/Services/IActiveDocumentSnapshot.cs @@ -0,0 +1,9 @@ +namespace GitHub.VisualStudio +{ + public interface IActiveDocumentSnapshot + { + string Name { get; } + int StartLine { get; } + int EndLine { get; } + } +} diff --git a/src/GitHub.VisualStudio/Base/MenuBase.cs b/src/GitHub.VisualStudio/Base/MenuBase.cs index aa9be9181a..40fa6cd259 100644 --- a/src/GitHub.VisualStudio/Base/MenuBase.cs +++ b/src/GitHub.VisualStudio/Base/MenuBase.cs @@ -1,4 +1,8 @@ using System; +using System.Globalization; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Services; namespace GitHub.VisualStudio { @@ -7,6 +11,8 @@ public abstract class MenuBase readonly IServiceProvider serviceProvider; protected IServiceProvider ServiceProvider { get { return serviceProvider; } } + protected ISimpleRepositoryModel ActiveRepo { get; private set; } + protected MenuBase() { } @@ -15,5 +21,30 @@ protected MenuBase(IServiceProvider serviceProvider) { this.serviceProvider = serviceProvider; } + + void RefreshRepo() + { + ActiveRepo = ServiceProvider.GetExportedValue().ActiveRepo; + + if (ActiveRepo == null) + { + var vsservices = ServiceProvider.GetExportedValue(); + string path = vsservices?.GetActiveRepoPath() ?? String.Empty; + try + { + ActiveRepo = !String.IsNullOrEmpty(path) ? new SimpleRepositoryModel(path) : null; + } + catch (Exception ex) + { + VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "{0}: Error loading the repository from '{1}'. {2}", GetType(), path, ex)); + } + } + } + + protected bool IsGitHubRepo() + { + RefreshRepo(); + return ActiveRepo?.CloneUrl?.RepositoryName != null; + } } } diff --git a/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs b/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs index 1a1493dd24..b57d4b145b 100644 --- a/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs +++ b/src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs @@ -45,11 +45,9 @@ void OnThemeChanged() { try { - var color = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowTextColorKey); - var brightness = color.GetBrightness(); - var dark = brightness > 0.5f; - - Icon = SharedResources.GetDrawingForIcon(octicon, dark ? Colors.DarkThemeNavigationItem : Colors.LightThemeNavigationItem, dark ? "dark" : "light"); + var theme = Colors.DetectTheme(); + var dark = theme == "Dark"; + Icon = SharedResources.GetDrawingForIcon(octicon, dark ? Colors.DarkThemeNavigationItem : Colors.LightThemeNavigationItem, theme); } catch (ArgumentNullException) { diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj index 214d4a2917..c6f641b380 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.csproj @@ -212,8 +212,12 @@ + + + + @@ -340,6 +344,14 @@ + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile Designer @@ -360,6 +372,18 @@ Designer MSBuild:Compile + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.imagemanifest b/src/GitHub.VisualStudio/GitHub.VisualStudio.imagemanifest index c2b9f7a2a2..f798fa065b 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.imagemanifest +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.imagemanifest @@ -8,6 +8,8 @@ + + @@ -26,5 +28,11 @@ + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio/GitHub.VisualStudio.vsct b/src/GitHub.VisualStudio/GitHub.VisualStudio.vsct index 1b642a837a..934c23106f 100644 --- a/src/GitHub.VisualStudio/GitHub.VisualStudio.vsct +++ b/src/GitHub.VisualStudio/GitHub.VisualStudio.vsct @@ -38,7 +38,11 @@ - + + + + + @@ -51,22 +55,20 @@ + + + + GitHub + GitHub + + + - - - + + - - - - - - - - + + + + - + + + - + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -164,6 +221,8 @@ + + @@ -177,7 +236,11 @@ - + + + + + diff --git a/src/GitHub.VisualStudio/GitHubPackage.cs b/src/GitHub.VisualStudio/GitHubPackage.cs index cbf0caf69a..130cf8227d 100644 --- a/src/GitHub.VisualStudio/GitHubPackage.cs +++ b/src/GitHub.VisualStudio/GitHubPackage.cs @@ -1,6 +1,8 @@ using System; +using System.Globalization; using System.Runtime.InteropServices; using GitHub.Extensions; +using GitHub.Models; using GitHub.Services; using GitHub.UI; using GitHub.VisualStudio.Base; diff --git a/src/GitHub.VisualStudio/Helpers/ActiveDocumentSnapshot.cs b/src/GitHub.VisualStudio/Helpers/ActiveDocumentSnapshot.cs new file mode 100644 index 0000000000..b9bd68df0f --- /dev/null +++ b/src/GitHub.VisualStudio/Helpers/ActiveDocumentSnapshot.cs @@ -0,0 +1,38 @@ +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.TextManager.Interop; +using System; +using System.ComponentModel.Composition; +using System.Diagnostics; + +namespace GitHub.VisualStudio +{ + [Export(typeof(IActiveDocumentSnapshot))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class ActiveDocumentSnapshot : IActiveDocumentSnapshot + { + public string Name { get; private set; } + public int StartLine { get; private set; } + public int EndLine { get; private set; } + + [ImportingConstructor] + public ActiveDocumentSnapshot([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) + { + StartLine = EndLine = -1; + Name = Services.Dte2?.ActiveDocument?.FullName; + + var textManager = serviceProvider.GetService(typeof(SVsTextManager)) as IVsTextManager; + Debug.Assert(textManager != null, "No SVsTextManager service available"); + if (textManager == null) + return; + IVsTextView view; + int anchorLine, anchorCol, endLine, endCol; + if (ErrorHandler.Succeeded(textManager.GetActiveView(0, null, out view)) && + ErrorHandler.Succeeded(view.GetSelection(out anchorLine, out anchorCol, out endLine, out endCol))) + { + StartLine = anchorLine + 1; + EndLine = endLine + 1; + } + } + } +} diff --git a/src/GitHub.VisualStudio/Helpers/Colors.cs b/src/GitHub.VisualStudio/Helpers/Colors.cs index f3f33f3c54..930aa64513 100644 --- a/src/GitHub.VisualStudio/Helpers/Colors.cs +++ b/src/GitHub.VisualStudio/Helpers/Colors.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.VisualStudio.PlatformUI; +using System; using System.Windows.Media; namespace GitHub.VisualStudio.Helpers @@ -25,5 +26,25 @@ public static Color ToColor(this System.Drawing.Color color) { return Color.FromArgb(color.A, color.R, color.G, color.B); } + + + static Color AccentMediumDarkTheme = Color.FromRgb(45, 45, 48); + static Color AccentMediumLightTheme = Color.FromRgb(238, 238, 242); + static Color AccentMediumBlueTheme = Color.FromRgb(255, 236, 181); + + public static string DetectTheme() + { + var color = VSColorTheme.GetThemedColor(EnvironmentColors.AccentMediumColorKey); + var cc = color.ToColor(); + if (cc == AccentMediumBlueTheme) + return "Blue"; + if (cc == AccentMediumLightTheme) + return "Light"; + if (cc == AccentMediumDarkTheme) + return "Dark"; + var brightness = color.GetBrightness(); + var dark = brightness > 0.5f; + return dark ? "Dark" : "Light"; + } } } diff --git a/src/GitHub.VisualStudio/Helpers/SharedDictionaryManager.cs b/src/GitHub.VisualStudio/Helpers/SharedDictionaryManager.cs index 80424db8f7..fa997573de 100644 --- a/src/GitHub.VisualStudio/Helpers/SharedDictionaryManager.cs +++ b/src/GitHub.VisualStudio/Helpers/SharedDictionaryManager.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.IO; using System.Linq; +using Microsoft.VisualStudio.PlatformUI; namespace GitHub.VisualStudio.Helpers { @@ -29,6 +30,11 @@ static SharedDictionaryManager() AppDomain.CurrentDomain.AssemblyResolve += LoadAssemblyFromRunDir; } + public SharedDictionaryManager() + { + currentTheme = Colors.DetectTheme(); + } + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] static Assembly LoadAssemblyFromRunDir(object sender, ResolveEventArgs e) { @@ -53,24 +59,51 @@ static Assembly LoadAssemblyFromRunDir(object sender, ResolveEventArgs e) #region ResourceDictionaryImplementation static readonly Dictionary resourceDicts = new Dictionary(); + static string baseThemeUri = "pack://application:,,,/GitHub.VisualStudio;component/Styles/"; Uri sourceUri; + string currentTheme; + bool themed = false; public new Uri Source { get { return sourceUri; } set { + if (value.ToString() == "Theme.xaml") + { + if (!themed) + { + themed = true; + VSColorTheme.ThemeChanged += OnThemeChange; + } + value = new Uri(baseThemeUri + "Theme" + currentTheme + ".xaml"); + } + sourceUri = value; ResourceDictionary ret; if (resourceDicts.TryGetValue(value, out ret)) { - MergedDictionaries.Add(ret); - return; + if (ret != this) + { + MergedDictionaries.Add(ret); + return; + } } base.Source = value; - resourceDicts.Add(value, this); + if (ret == null) + resourceDicts.Add(value, this); } } -#endregion + + void OnThemeChange(ThemeChangedEventArgs e) + { + var uri = new Uri(baseThemeUri + "Theme" + currentTheme + ".xaml"); + ResourceDictionary ret; + if (resourceDicts.TryGetValue(uri, out ret)) + MergedDictionaries.Remove(ret); + currentTheme = Colors.DetectTheme(); + Source = uri; + } + #endregion } } \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Menus/AddConnection.cs b/src/GitHub.VisualStudio/Menus/AddConnection.cs index 7ed0dad0ee..c9e397f7c3 100644 --- a/src/GitHub.VisualStudio/Menus/AddConnection.cs +++ b/src/GitHub.VisualStudio/Menus/AddConnection.cs @@ -17,8 +17,8 @@ public AddConnection([Import(typeof(SVsServiceProvider))] IServiceProvider servi { } - public Guid Guid { get { return GuidList.guidGitHubCmdSet; } } - public int CmdId { get { return PkgCmdIDList.addConnectionCommand; } } + public Guid Guid => GuidList.guidGitHubCmdSet; + public int CmdId => PkgCmdIDList.addConnectionCommand; public void Activate() { diff --git a/src/GitHub.VisualStudio/Menus/CopyLink.cs b/src/GitHub.VisualStudio/Menus/CopyLink.cs new file mode 100644 index 0000000000..547b1cbcbf --- /dev/null +++ b/src/GitHub.VisualStudio/Menus/CopyLink.cs @@ -0,0 +1,49 @@ +using Microsoft.VisualStudio.Shell; +using System; +using System.ComponentModel.Composition; +using System.Windows; +using GitHub.Extensions; +using GitHub.Services; + +namespace GitHub.VisualStudio.Menus +{ + [Export(typeof(IDynamicMenuHandler))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class CopyLink : LinkMenuBase, IDynamicMenuHandler + { + [ImportingConstructor] + public CopyLink([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) + : base(serviceProvider) + { + } + + public Guid Guid => GuidList.guidContextMenuSet; + public int CmdId => PkgCmdIDList.copyLinkCommand; + + public void Activate() + { + if (!IsGitHubRepo()) + return; + + var link = GenerateLink(); + if (link == null) + return; + try + { + Clipboard.SetText(link); + var ns = ServiceProvider.GetExportedValue(); + ns?.ShowMessage(Resources.LinkCopiedToClipboardMessage); + } + catch + { + var ns = ServiceProvider.GetExportedValue(); + ns?.ShowMessage(Resources.Error_FailedToCopyToClipboard); + } + } + + public bool CanShow() + { + return IsGitHubRepo(); + } + } +} diff --git a/src/GitHub.VisualStudio/Menus/LinkMenuBase.cs b/src/GitHub.VisualStudio/Menus/LinkMenuBase.cs new file mode 100644 index 0000000000..49c2931580 --- /dev/null +++ b/src/GitHub.VisualStudio/Menus/LinkMenuBase.cs @@ -0,0 +1,23 @@ +using GitHub.Extensions; +using GitHub.Primitives; +using System; + +namespace GitHub.VisualStudio.Menus +{ + public class LinkMenuBase: MenuBase + { + public LinkMenuBase(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } + + protected UriString GenerateLink() + { + var repo = ActiveRepo; + var activeDocument = ServiceProvider.GetExportedValue(); + if (activeDocument == null) + return null; + return repo.GenerateUrl(activeDocument.Name, activeDocument.StartLine, activeDocument.EndLine); + } + } +} diff --git a/src/GitHub.VisualStudio/Menus/MenuProvider.cs b/src/GitHub.VisualStudio/Menus/MenuProvider.cs index 49d0bddcf2..082130dde6 100644 --- a/src/GitHub.VisualStudio/Menus/MenuProvider.cs +++ b/src/GitHub.VisualStudio/Menus/MenuProvider.cs @@ -9,9 +9,9 @@ namespace GitHub.VisualStudio.Menus [PartCreationPolicy(CreationPolicy.Shared)] public class MenuProvider : IMenuProvider { - public IReadOnlyCollection Menus { get; private set; } + public IReadOnlyCollection Menus { get; } - public IReadOnlyCollection DynamicMenus { get; private set; } + public IReadOnlyCollection DynamicMenus { get; } [ImportingConstructor] public MenuProvider([ImportMany] IEnumerable menus, [ImportMany] IEnumerable dynamicMenus) diff --git a/src/GitHub.VisualStudio/Menus/OpenLink.cs b/src/GitHub.VisualStudio/Menus/OpenLink.cs new file mode 100644 index 0000000000..03071fba52 --- /dev/null +++ b/src/GitHub.VisualStudio/Menus/OpenLink.cs @@ -0,0 +1,39 @@ +using GitHub.Extensions; +using GitHub.Services; +using Microsoft.VisualStudio.Shell; +using System; +using System.ComponentModel.Composition; + +namespace GitHub.VisualStudio.Menus +{ + [Export(typeof(IDynamicMenuHandler))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class OpenLink: LinkMenuBase, IDynamicMenuHandler + { + [ImportingConstructor] + public OpenLink([Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider) + : base(serviceProvider) + { + } + + public Guid Guid => GuidList.guidContextMenuSet; + public int CmdId => PkgCmdIDList.openLinkCommand; + + public void Activate() + { + if (!IsGitHubRepo()) + return; + + var link = GenerateLink(); + if (link == null) + return; + var browser = ServiceProvider.GetExportedValue(); + browser?.OpenUrl(link.ToUri()); + } + + public bool CanShow() + { + return IsGitHubRepo(); + } + } +} diff --git a/src/GitHub.VisualStudio/Menus/ShowGitHubPane.cs b/src/GitHub.VisualStudio/Menus/ShowGitHubPane.cs index 7160d8c08d..e579e936de 100644 --- a/src/GitHub.VisualStudio/Menus/ShowGitHubPane.cs +++ b/src/GitHub.VisualStudio/Menus/ShowGitHubPane.cs @@ -2,14 +2,14 @@ using System; using System.ComponentModel.Composition; -namespace GitHub.VisualStudio +namespace GitHub.VisualStudio.Menus { [Export(typeof(IMenuHandler))] [PartCreationPolicy(CreationPolicy.Shared)] public class ShowGitHubPane: MenuBase, IMenuHandler { - public Guid Guid { get { return GuidList.guidGitHubCmdSet; } } - public int CmdId { get { return PkgCmdIDList.showGitHubPaneCommand; } } + public Guid Guid => GuidList.guidGitHubCmdSet; + public int CmdId => PkgCmdIDList.showGitHubPaneCommand; public void Activate() { diff --git a/src/GitHub.VisualStudio/PkgCmdID.cs b/src/GitHub.VisualStudio/PkgCmdID.cs index 1b20104b15..b86fe1c67e 100644 --- a/src/GitHub.VisualStudio/PkgCmdID.cs +++ b/src/GitHub.VisualStudio/PkgCmdID.cs @@ -11,5 +11,7 @@ static class PkgCmdIDList public const int forwardCommand = 0x301; public const int refreshCommand = 0x302; public const int pullRequestCommand = 0x310; + public const int openLinkCommand = 0x100; + public const int copyLinkCommand = 0x101; }; } \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Resources.Designer.cs b/src/GitHub.VisualStudio/Resources.Designer.cs index 959c260bf9..5702488628 100644 --- a/src/GitHub.VisualStudio/Resources.Designer.cs +++ b/src/GitHub.VisualStudio/Resources.Designer.cs @@ -213,6 +213,15 @@ public static string enterpriseUrlPromptText { } } + /// + /// Looks up a localized string similar to Could not copy to the clipboard. Please try again.. + /// + public static string Error_FailedToCopyToClipboard { + get { + return ResourceManager.GetString("Error_FailedToCopyToClipboard", resourceCulture); + } + } + /// /// Looks up a localized string similar to Search repositories. /// @@ -303,6 +312,15 @@ public static string licenseListText { } } + /// + /// Looks up a localized string similar to Link copied to clipboard. + /// + public static string LinkCopiedToClipboardMessage { + get { + return ResourceManager.GetString("LinkCopiedToClipboardMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Some or all repositories may not have loaded. Close the dialog and try again.. /// diff --git a/src/GitHub.VisualStudio/Resources.resx b/src/GitHub.VisualStudio/Resources.resx index 4f0f18e23e..b56eab92e0 100644 --- a/src/GitHub.VisualStudio/Resources.resx +++ b/src/GitHub.VisualStudio/Resources.resx @@ -282,4 +282,10 @@ Repository created successfully. + + Link copied to clipboard + + + Could not copy to the clipboard. Please try again. + \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Resources/icons/clippy.xaml b/src/GitHub.VisualStudio/Resources/icons/clippy.xaml new file mode 100644 index 0000000000..f7147a392d --- /dev/null +++ b/src/GitHub.VisualStudio/Resources/icons/clippy.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Resources/icons/link_external.xaml b/src/GitHub.VisualStudio/Resources/icons/link_external.xaml new file mode 100644 index 0000000000..23b58dadc9 --- /dev/null +++ b/src/GitHub.VisualStudio/Resources/icons/link_external.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Resources/icons/mark_github.xaml b/src/GitHub.VisualStudio/Resources/icons/mark_github.xaml index 0e48f1ceb3..502e182916 100644 --- a/src/GitHub.VisualStudio/Resources/icons/mark_github.xaml +++ b/src/GitHub.VisualStudio/Resources/icons/mark_github.xaml @@ -1,18 +1,13 @@ - - + + - - - \ No newline at end of file + + diff --git a/src/GitHub.VisualStudio/Services/SharedResources.cs b/src/GitHub.VisualStudio/Services/SharedResources.cs index 0c128a3435..b7dff21cb7 100644 --- a/src/GitHub.VisualStudio/Services/SharedResources.cs +++ b/src/GitHub.VisualStudio/Services/SharedResources.cs @@ -27,7 +27,6 @@ public static DrawingBrush GetDrawingForIcon(Octicon icon, Brush colorBrush, str Drawing = new GeometryDrawing() { Brush = colorBrush, - Pen = new Pen(colorBrush, 1.0).FreezeThis(), Geometry = OcticonPath.GetGeometryForIcon(icon).FreezeThis() } .FreezeThis(), diff --git a/src/GitHub.VisualStudio/Settings.cs b/src/GitHub.VisualStudio/Settings.cs index 4f395054ca..7ad5047270 100644 --- a/src/GitHub.VisualStudio/Settings.cs +++ b/src/GitHub.VisualStudio/Settings.cs @@ -15,7 +15,6 @@ static class GuidList public static readonly Guid guidGitHubCmdSet = new Guid(guidGitHubCmdSetString); public static readonly Guid guidGitHubToolbarCmdSet = new Guid(guidGitHubToolbarCmdSetString); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Already used in https://github.com/github/VisualStudio/pull/156")] public static readonly Guid guidContextMenuSet = new Guid(guidContextMenuSetString); } diff --git a/src/GitHub.VisualStudio/Styles/ThemeBlue.xaml b/src/GitHub.VisualStudio/Styles/ThemeBlue.xaml new file mode 100644 index 0000000000..9235ec2083 --- /dev/null +++ b/src/GitHub.VisualStudio/Styles/ThemeBlue.xaml @@ -0,0 +1,9 @@ + + + #424242 + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Styles/ThemeDark.xaml b/src/GitHub.VisualStudio/Styles/ThemeDark.xaml new file mode 100644 index 0000000000..9235ec2083 --- /dev/null +++ b/src/GitHub.VisualStudio/Styles/ThemeDark.xaml @@ -0,0 +1,9 @@ + + + #424242 + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio/Styles/ThemeLight.xaml b/src/GitHub.VisualStudio/Styles/ThemeLight.xaml new file mode 100644 index 0000000000..9235ec2083 --- /dev/null +++ b/src/GitHub.VisualStudio/Styles/ThemeLight.xaml @@ -0,0 +1,9 @@ + + + #424242 + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubInvitationSection.cs b/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubInvitationSection.cs index 53a1275333..f43787d91d 100644 --- a/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubInvitationSection.cs +++ b/src/GitHub.VisualStudio/TeamExplorer/Connect/GitHubInvitationSection.cs @@ -64,11 +64,9 @@ void OnThemeChanged() { try { - var color = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowTextColorKey); - var brightness = color.GetBrightness(); - var dark = brightness > 0.5f; - - Icon = SharedResources.GetDrawingForIcon(Octicon.mark_github, dark ? Helpers.Colors.DarkThemeNavigationItem : Helpers.Colors.LightThemeNavigationItem, dark ? "dark" : "light"); + var theme = Helpers.Colors.DetectTheme(); + var dark = theme == "Dark"; + Icon = SharedResources.GetDrawingForIcon(Octicon.mark_github, dark ? Colors.White : Helpers.Colors.LightThemeNavigationItem, theme); } catch (ArgumentNullException) { diff --git a/src/UnitTests/GitHub.App/Models/RepositoryModelTests.cs b/src/UnitTests/GitHub.App/Models/RepositoryModelTests.cs index 64577d00b8..aa02a388dc 100644 --- a/src/UnitTests/GitHub.App/Models/RepositoryModelTests.cs +++ b/src/UnitTests/GitHub.App/Models/RepositoryModelTests.cs @@ -53,6 +53,7 @@ public void DifferentContentEqualsFalse(string name1, string url1, string name2, } } + [Collection("PackageServiceProvider global data tests")] public class PathConstructorTests : TempFileBaseClass { [Fact] diff --git a/src/UnitTests/GitHub.Exports/SimpleRepositoryModelTests.cs b/src/UnitTests/GitHub.Exports/SimpleRepositoryModelTests.cs new file mode 100644 index 0000000000..5d441ef163 --- /dev/null +++ b/src/UnitTests/GitHub.Exports/SimpleRepositoryModelTests.cs @@ -0,0 +1,59 @@ +using System; +using GitHub.Models; +using GitHub.VisualStudio; +using LibGit2Sharp; +using NSubstitute; +using UnitTests; +using Xunit; +using GitHub.Primitives; + +[Collection("PackageServiceProvider global data tests")] +public class SimpleRepositoryModelTests : TempFileBaseClass +{ + void SetupRepository(string sha) + { + var provider = Substitutes.ServiceProvider; + Services.PackageServiceProvider = provider; + var gitservice = provider.GetGitService(); + var repo = Substitute.For(); + gitservice.GetRepo(Args.String).Returns(repo); + if (!String.IsNullOrEmpty(sha)) + { + var commit = Substitute.For(); + commit.Sha.Returns(sha); + repo.Commits.Returns(new FakeCommitLog { commit }); + } + } + + [Theory] + [InlineData(false, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", -1, -1, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs")] + [InlineData(false, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", 1, -1, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs#L1")] + [InlineData(false, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", 1, 2, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs#L1-L2")] + [InlineData(false, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", -1, 2, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs")] + [InlineData(false, "https://github.com/foo/bar", "123123", "", 1, 2, "https://github.com/foo/bar/commit/123123")] + [InlineData(false, "https://github.com/foo/bar", "", @"src\dir\file1.cs", -1, 2, "https://github.com/foo/bar")] + [InlineData(false, "https://github.com/foo/bar", null, null, -1, -1, "https://github.com/foo/bar")] + [InlineData(false, null, "123123", @"src\dir\file1.cs", 1, 2, null)] + [InlineData(true, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", -1, -1, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs")] + [InlineData(true, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", 1, -1, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs#L1")] + [InlineData(true, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", 1, 2, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs#L1-L2")] + [InlineData(true, "https://github.com/foo/bar", "123123", @"src\dir\file1.cs", -1, 2, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs")] + [InlineData(true, "https://github.com/foo/bar", "", @"src\dir\file1.cs", -1, 2, "https://github.com/foo/bar")] + [InlineData(true, null, "123123", @"src\dir\file1.cs", 1, 2, null)] + [InlineData(false, "git@github.com/foo/bar", "123123", @"src\dir\file1.cs", -1, -1, "https://github.com/foo/bar/blob/123123/src/dir/file1.cs")] + public void GenerateUrl(bool createRootedPath, string baseUrl, string sha, string path, int startLine, int endLine, string expected) + { + SetupRepository(sha); + + var basePath = Directory.CreateSubdirectory("generate-url-test1"); + if (createRootedPath && path != null) + path = System.IO.Path.Combine(basePath.FullName, path); + ISimpleRepositoryModel model = null; + if (!String.IsNullOrEmpty(baseUrl)) + model = new SimpleRepositoryModel("bar", new UriString(baseUrl), basePath.FullName); + else + model = new SimpleRepositoryModel(basePath.FullName); + var result = model.GenerateUrl(path, startLine, endLine); + Assert.Equal(expected, result?.ToString()); + } +} diff --git a/src/UnitTests/TestDoubles/FakeCommitLog.cs b/src/UnitTests/TestDoubles/FakeCommitLog.cs index b580159d92..9be25c5a62 100644 --- a/src/UnitTests/TestDoubles/FakeCommitLog.cs +++ b/src/UnitTests/TestDoubles/FakeCommitLog.cs @@ -1,7 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using LibGit2Sharp; -public class FakeCommitLog : List, ICommitLog +public class FakeCommitLog : List, IQueryableCommitLog { public CommitSortStrategies SortedBy { @@ -10,4 +11,29 @@ public CommitSortStrategies SortedBy return CommitSortStrategies.Topological; } } + + public Commit FindMergeBase(IEnumerable commits, MergeBaseFindingStrategy strategy) + { + throw new NotImplementedException(); + } + + public Commit FindMergeBase(Commit first, Commit second) + { + throw new NotImplementedException(); + } + + public IEnumerable QueryBy(string path) + { + throw new NotImplementedException(); + } + + public ICommitLog QueryBy(CommitFilter filter) + { + throw new NotImplementedException(); + } + + public IEnumerable QueryBy(string path, FollowFilter filter) + { + throw new NotImplementedException(); + } } diff --git a/src/UnitTests/UnitTests.csproj b/src/UnitTests/UnitTests.csproj index 455a47a11c..081547429e 100644 --- a/src/UnitTests/UnitTests.csproj +++ b/src/UnitTests/UnitTests.csproj @@ -156,6 +156,7 @@ +