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

Commit cadea92

Browse files
committed
Merge pull request #191 from github/feature/link-to-vs
Feature: Link from code to GitHub via context menus
2 parents 70a6a13 + 808e859 commit cadea92

36 files changed

+646
-70
lines changed

src/GitHub.App/SampleData/SampleViewModels.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,11 @@ public void SetIcon(bool isPrivate, bool isFork)
409409
{
410410
}
411411

412+
public UriString GenerateUrl(string path = null, int startLine = -1, int endLine = -1)
413+
{
414+
return null;
415+
}
416+
412417
public string Name { get; set; }
413418
public UriString CloneUrl { get; set; }
414419
public string LocalPath { get; set; }

src/GitHub.Exports/GitHub.Exports.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
<Compile Include="Helpers\NotificationAwareObject.cs" />
113113
<Compile Include="Services\Connection.cs" />
114114
<Compile Include="Services\GitService.cs" />
115+
<Compile Include="Services\IActiveDocumentSnapshot.cs" />
115116
<Compile Include="Services\IGitService.cs" />
116117
<Compile Include="Services\IMenuHandler.cs" />
117118
<Compile Include="Services\IMenuProvider.cs" />

src/GitHub.Exports/Models/ISimpleRepositoryModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ public interface ISimpleRepositoryModel : INotifyPropertyChanged
1818
/// Updates the url information based on the local path
1919
/// </summary>
2020
void Refresh();
21+
UriString GenerateUrl(string path = null, int startLine = -1, int endLine = -1);
2122
}
2223
}

src/GitHub.Exports/Models/SimpleRepositoryModel.cs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Diagnostics;
33
using System.Globalization;
44
using System.IO;
5+
using System.Linq;
56
using GitHub.Primitives;
67
using GitHub.UI;
78
using GitHub.VisualStudio.Helpers;
@@ -10,7 +11,7 @@
1011
namespace GitHub.Models
1112
{
1213
[DebuggerDisplay("{DebuggerDisplay,nq}")]
13-
public class SimpleRepositoryModel : NotificationAwareObject, ISimpleRepositoryModel, INotifyPropertySource, IEquatable<SimpleRepositoryModel>
14+
public class SimpleRepositoryModel : NotificationAwareObject, ISimpleRepositoryModel, IEquatable<SimpleRepositoryModel>
1415
{
1516
public SimpleRepositoryModel(string name, UriString cloneUrl, string localPath = null)
1617
{
@@ -53,13 +54,76 @@ public void Refresh()
5354
CloneUrl = uri;
5455
}
5556

57+
/// <summary>
58+
/// Generates a http(s) url to the repository in the remote server, optionally
59+
/// pointing to a specific file and specific line range in it.
60+
/// </summary>
61+
/// <param name="path">The file to generate an url to. Optional.</param>
62+
/// <param name="startLine">A specific line, or (if specifying the <paramref name="endLine"/> as well) the start of a range</param>
63+
/// <param name="endLine">The end of a line range on the specified file.</param>
64+
/// <returns>An UriString with the generated url, or null if the repository has no remote server configured or if it can't be found locally</returns>
65+
public UriString GenerateUrl(string path = null, int startLine = -1, int endLine = -1)
66+
{
67+
if (CloneUrl == null)
68+
return null;
69+
70+
var sha = HeadSha;
71+
// this also incidentally checks whether the repo has a valid LocalPath
72+
if (String.IsNullOrEmpty(sha))
73+
return CloneUrl.ToRepositoryUrl().AbsoluteUri;
74+
75+
if (path != null && Path.IsPathRooted(path))
76+
{
77+
// if the path root doesn't match the repository local path, then ignore it
78+
if (!path.StartsWith(LocalPath, StringComparison.OrdinalIgnoreCase))
79+
{
80+
Debug.Assert(false, String.Format(CultureInfo.CurrentCulture, "GenerateUrl: path {0} doesn't match repository {1}", path, LocalPath));
81+
path = null;
82+
}
83+
else
84+
path = path.Substring(LocalPath.Length + 1);
85+
}
86+
87+
return new UriString(GenerateUrl(CloneUrl.ToRepositoryUrl().AbsoluteUri, sha, path, startLine, endLine));
88+
}
89+
90+
const string CommitFormat = "{0}/commit/{1}";
91+
const string BlobFormat = "{0}/blob/{1}/{2}";
92+
const string StartLineFormat = "{0}#L{1}";
93+
const string EndLineFormat = "{0}-L{1}";
94+
static string GenerateUrl(string basePath, string sha, string path, int startLine = -1, int endLine = -1)
95+
{
96+
if (sha == null)
97+
return basePath;
98+
99+
if (String.IsNullOrEmpty(path))
100+
return String.Format(CultureInfo.InvariantCulture, CommitFormat, basePath, sha);
101+
102+
var ret = String.Format(CultureInfo.InvariantCulture, BlobFormat, basePath, sha, path.Replace(@"\", "/"));
103+
if (startLine < 0)
104+
return ret;
105+
ret = String.Format(CultureInfo.InvariantCulture, StartLineFormat, ret, startLine);
106+
if (endLine < 0)
107+
return ret;
108+
return String.Format(CultureInfo.InvariantCulture, EndLineFormat, ret, endLine);
109+
}
110+
56111
public string Name { get; }
57112
UriString cloneUrl;
58113
public UriString CloneUrl { get { return cloneUrl; } set { cloneUrl = value; this.RaisePropertyChange(); } }
59114
public string LocalPath { get; }
60115
Octicon icon;
61116
public Octicon Icon { get { return icon; } set { icon = value; this.RaisePropertyChange(); } }
62117

118+
public string HeadSha
119+
{
120+
get
121+
{
122+
var repo = GitService.GitServiceHelper.GetRepo(LocalPath);
123+
return repo?.Commits.FirstOrDefault()?.Sha ?? String.Empty;
124+
}
125+
}
126+
63127
/// <summary>
64128
/// Note: We don't consider CloneUrl a part of the hash code because it can change during the lifetime
65129
/// of a repository. Equals takes care of any hash collisions because of this
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace GitHub.VisualStudio
2+
{
3+
public interface IActiveDocumentSnapshot
4+
{
5+
string Name { get; }
6+
int StartLine { get; }
7+
int EndLine { get; }
8+
}
9+
}

src/GitHub.VisualStudio/Base/MenuBase.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
using System;
2+
using System.Globalization;
3+
using GitHub.Extensions;
4+
using GitHub.Models;
5+
using GitHub.Services;
26

37
namespace GitHub.VisualStudio
48
{
@@ -7,6 +11,8 @@ public abstract class MenuBase
711
readonly IServiceProvider serviceProvider;
812
protected IServiceProvider ServiceProvider { get { return serviceProvider; } }
913

14+
protected ISimpleRepositoryModel ActiveRepo { get; private set; }
15+
1016
protected MenuBase()
1117
{
1218
}
@@ -15,5 +21,30 @@ protected MenuBase(IServiceProvider serviceProvider)
1521
{
1622
this.serviceProvider = serviceProvider;
1723
}
24+
25+
void RefreshRepo()
26+
{
27+
ActiveRepo = ServiceProvider.GetExportedValue<ITeamExplorerServiceHolder>().ActiveRepo;
28+
29+
if (ActiveRepo == null)
30+
{
31+
var vsservices = ServiceProvider.GetExportedValue<IVSServices>();
32+
string path = vsservices?.GetActiveRepoPath() ?? String.Empty;
33+
try
34+
{
35+
ActiveRepo = !String.IsNullOrEmpty(path) ? new SimpleRepositoryModel(path) : null;
36+
}
37+
catch (Exception ex)
38+
{
39+
VsOutputLogger.WriteLine(string.Format(CultureInfo.CurrentCulture, "{0}: Error loading the repository from '{1}'. {2}", GetType(), path, ex));
40+
}
41+
}
42+
}
43+
44+
protected bool IsGitHubRepo()
45+
{
46+
RefreshRepo();
47+
return ActiveRepo?.CloneUrl?.RepositoryName != null;
48+
}
1849
}
1950
}

src/GitHub.VisualStudio/Base/TeamExplorerNavigationItemBase.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,9 @@ void OnThemeChanged()
4545
{
4646
try
4747
{
48-
var color = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowTextColorKey);
49-
var brightness = color.GetBrightness();
50-
var dark = brightness > 0.5f;
51-
52-
Icon = SharedResources.GetDrawingForIcon(octicon, dark ? Colors.DarkThemeNavigationItem : Colors.LightThemeNavigationItem, dark ? "dark" : "light");
48+
var theme = Colors.DetectTheme();
49+
var dark = theme == "Dark";
50+
Icon = SharedResources.GetDrawingForIcon(octicon, dark ? Colors.DarkThemeNavigationItem : Colors.LightThemeNavigationItem, theme);
5351
}
5452
catch (ArgumentNullException)
5553
{

src/GitHub.VisualStudio/GitHub.VisualStudio.csproj

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,12 @@
212212
<Compile Include="Base\TeamExplorerServiceHolder.cs" />
213213
<Compile Include="Converters\CountToVisibilityConverter.cs" />
214214
<Compile Include="Helpers\SharedDictionaryManager.cs" />
215+
<Compile Include="Helpers\ActiveDocumentSnapshot.cs" />
216+
<Compile Include="Menus\OpenLink.cs" />
217+
<Compile Include="Menus\LinkMenuBase.cs" />
215218
<Compile Include="Menus\ShowGitHubPane.cs" />
216219
<Compile Include="Menus\AddConnection.cs" />
220+
<Compile Include="Menus\CopyLink.cs" />
217221
<Compile Include="Base\MenuBase.cs" />
218222
<Compile Include="Menus\MenuProvider.cs" />
219223
<Compile Include="Properties\AssemblyInfo.cs" />
@@ -340,6 +344,14 @@
340344
<Resource Include="Resources\default_user_avatar.png" />
341345
</ItemGroup>
342346
<ItemGroup>
347+
<Page Include="Resources\icons\clippy.xaml">
348+
<Generator>MSBuild:Compile</Generator>
349+
<SubType>Designer</SubType>
350+
</Page>
351+
<Page Include="Resources\icons\link_external.xaml">
352+
<Generator>MSBuild:Compile</Generator>
353+
<SubType>Designer</SubType>
354+
</Page>
343355
<Page Include="Resources\icons\refresh.xaml">
344356
<Generator>MSBuild:Compile</Generator>
345357
<SubType>Designer</SubType>
@@ -360,6 +372,18 @@
360372
<SubType>Designer</SubType>
361373
<Generator>MSBuild:Compile</Generator>
362374
</Page>
375+
<Page Include="Styles\ThemeBlue.xaml">
376+
<Generator>MSBuild:Compile</Generator>
377+
<SubType>Designer</SubType>
378+
</Page>
379+
<Page Include="Styles\ThemeLight.xaml">
380+
<Generator>MSBuild:Compile</Generator>
381+
<SubType>Designer</SubType>
382+
</Page>
383+
<Page Include="Styles\ThemeDark.xaml">
384+
<Generator>MSBuild:Compile</Generator>
385+
<SubType>Designer</SubType>
386+
</Page>
363387
<Page Include="SharedDictionary.xaml">
364388
<Generator>MSBuild:Compile</Generator>
365389
</Page>

src/GitHub.VisualStudio/GitHub.VisualStudio.imagemanifest

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
<ID Name="arrow_right" Value="3" />
99
<ID Name="refresh" Value="4" />
1010
<ID Name="pullrequest" Value="5" />
11+
<ID Name="link_external" Value="6"/>
12+
<ID Name="clippy" Value="7"/>
1113
</Symbols>
1214

1315
<Images>
@@ -26,5 +28,11 @@
2628
<Image Guid="$(guidImages)" ID="$(pullrequest)">
2729
<Source Uri="$(Resources)/git_pull_request.xaml" />
2830
</Image>
31+
<Image Guid="$(guidImages)" ID="$(link_external)">
32+
<Source Uri="$(Resources)/link_external.xaml" />
33+
</Image>
34+
<Image Guid="$(guidImages)" ID="$(clippy)">
35+
<Source Uri="$(Resources)/clippy.xaml" />
36+
</Image>
2937
</Images>
3038
</ImageManifest>

0 commit comments

Comments
 (0)