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

Commit 2dd3ce5

Browse files
authored
Merge pull request #1407 from github/feature/navigate-diff-to-editor
[Feature] Enable navigation from diff view to editor
2 parents 9d5580e + c3da975 commit 2dd3ce5

File tree

14 files changed

+529
-3
lines changed

14 files changed

+529
-3
lines changed

src/GitHub.App/GitHub.App.csproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161
<HintPath>..\..\packages\Microsoft.VisualStudio.ComponentModelHost.14.0.25424\lib\net45\Microsoft.VisualStudio.ComponentModelHost.dll</HintPath>
6262
<Private>True</Private>
6363
</Reference>
64+
<Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
65+
<HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath>
66+
<Private>True</Private>
67+
</Reference>
6468
<Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
6569
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath>
6670
<Private>True</Private>
@@ -69,10 +73,18 @@
6973
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath>
7074
<Private>True</Private>
7175
</Reference>
76+
<Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
77+
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath>
78+
<Private>True</Private>
79+
</Reference>
7280
<Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
7381
<HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath>
7482
<Private>True</Private>
7583
</Reference>
84+
<Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
85+
<HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath>
86+
<Private>True</Private>
87+
</Reference>
7688
<Reference Include="Microsoft.VisualStudio.Threading, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
7789
<SpecificVersion>False</SpecificVersion>
7890
<HintPath>..\..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath>

src/GitHub.App/packages.config

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
<package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" />
44
<package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" />
55
<package id="Microsoft.VisualStudio.ComponentModelHost" version="14.0.25424" targetFramework="net461" />
6+
<package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net461" />
67
<package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" />
78
<package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" />
9+
<package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net461" />
810
<package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net461" />
11+
<package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net461" />
912
<package id="Newtonsoft.Json" version="6.0.8" targetFramework="net45" />
1013
<package id="Rothko" version="0.0.3-ghfvs" targetFramework="net461" />
1114
<package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" />

src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,46 @@
5757
<HintPath>..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll</HintPath>
5858
<Private>True</Private>
5959
</Reference>
60+
<Reference Include="Microsoft.VisualStudio.Imaging, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
61+
<HintPath>..\..\packages\Microsoft.VisualStudio.Imaging.14.3.25407\lib\net45\Microsoft.VisualStudio.Imaging.dll</HintPath>
62+
<Private>True</Private>
63+
</Reference>
64+
<Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
65+
<HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath>
66+
<Private>True</Private>
67+
</Reference>
68+
<Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
69+
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath>
70+
<Private>True</Private>
71+
</Reference>
72+
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
73+
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath>
74+
<Private>True</Private>
75+
</Reference>
76+
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
77+
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath>
78+
<Private>True</Private>
79+
</Reference>
80+
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
81+
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.12.0.12.0.21003\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll</HintPath>
82+
<Private>True</Private>
83+
</Reference>
84+
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
85+
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll</HintPath>
86+
<Private>True</Private>
87+
</Reference>
88+
<Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
89+
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath>
90+
<Private>True</Private>
91+
</Reference>
92+
<Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
93+
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath>
94+
<Private>True</Private>
95+
</Reference>
96+
<Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
97+
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll</HintPath>
98+
<Private>True</Private>
99+
</Reference>
60100
<Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
61101
<HintPath>..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll</HintPath>
62102
<Private>True</Private>
@@ -69,6 +109,26 @@
69109
<HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll</HintPath>
70110
<Private>True</Private>
71111
</Reference>
112+
<Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
113+
<HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath>
114+
<Private>True</Private>
115+
</Reference>
116+
<Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
117+
<HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath>
118+
<Private>True</Private>
119+
</Reference>
120+
<Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
121+
<HintPath>..\..\packages\Microsoft.VisualStudio.Threading.14.1.111\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath>
122+
<Private>True</Private>
123+
</Reference>
124+
<Reference Include="Microsoft.VisualStudio.Utilities, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
125+
<HintPath>..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll</HintPath>
126+
<Private>True</Private>
127+
</Reference>
128+
<Reference Include="Microsoft.VisualStudio.Validation, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
129+
<HintPath>..\..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath>
130+
<Private>True</Private>
131+
</Reference>
72132
<Reference Include="PresentationCore" />
73133
<Reference Include="System" />
74134
<Reference Include="System.ComponentModel.Composition" />
@@ -115,12 +175,14 @@
115175
<Compile Include="Models\PullRequestTextBufferInfo.cs" />
116176
<Compile Include="Services\IModelService.cs" />
117177
<Compile Include="Services\IGistPublishService.cs" />
178+
<Compile Include="Services\IPullRequestEditorService.cs" />
118179
<Compile Include="Services\IPullRequestSession.cs" />
119180
<Compile Include="Services\IPullRequestService.cs" />
120181
<Compile Include="Services\IPullRequestSessionManager.cs" />
121182
<Compile Include="Services\IShowDialogService.cs" />
122183
<Compile Include="Services\LocalRepositoriesExtensions.cs" />
123184
<Compile Include="Services\ModelServiceExtensions.cs" />
185+
<Compile Include="Services\PullRequestEditorService.cs" />
124186
<Compile Include="ViewModels\Dialog\IDialogContentViewModel.cs" />
125187
<Compile Include="ViewModels\Dialog\IGitHubDialogWindowViewModel.cs" />
126188
<Compile Include="ViewModels\Dialog\IGistCreationViewModel.cs" />
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using Microsoft.VisualStudio.TextManager.Interop;
2+
3+
namespace GitHub.Services
4+
{
5+
public interface IPullRequestEditorService
6+
{
7+
/// <summary>
8+
/// Find the active text view.
9+
/// </summary>
10+
/// <returns>The active view or null if view can't be found.</returns>
11+
IVsTextView FindActiveView();
12+
13+
/// <summary>
14+
/// Navigate to and place the caret at the best guess equivalent position in <see cref="targetFile"/>.
15+
/// </summary>
16+
/// <param name="sourceView">The text view to navigate from.</param>
17+
/// <param name="targetFile">The text view to open and navigate to.</param>
18+
/// <returns>The opened text view.</returns>
19+
IVsTextView NavigateToEquivalentPosition(IVsTextView sourceView, string targetFile);
20+
}
21+
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
using System.Collections.Generic;
2+
using System.ComponentModel.Composition;
3+
using Microsoft.VisualStudio;
4+
using Microsoft.VisualStudio.Shell;
5+
using Microsoft.VisualStudio.Shell.Interop;
6+
using Microsoft.VisualStudio.TextManager.Interop;
7+
using GitHub.Models;
8+
9+
namespace GitHub.Services
10+
{
11+
[Export(typeof(IPullRequestEditorService))]
12+
public class PullRequestEditorService : IPullRequestEditorService
13+
{
14+
readonly IGitHubServiceProvider serviceProvider;
15+
16+
// If the target line doesn't have a unique match, search this number of lines above looking for a match.
17+
public const int MatchLinesAboveTarget = 4;
18+
19+
[ImportingConstructor]
20+
public PullRequestEditorService(IGitHubServiceProvider serviceProvider)
21+
{
22+
this.serviceProvider = serviceProvider;
23+
}
24+
25+
public IVsTextView NavigateToEquivalentPosition(IVsTextView sourceView, string targetFile)
26+
{
27+
int line;
28+
int column;
29+
ErrorHandler.ThrowOnFailure(sourceView.GetCaretPos(out line, out column));
30+
var text1 = GetText(sourceView);
31+
32+
var view = OpenDocument(targetFile);
33+
var text2 = VsShellUtilities.GetRunningDocumentContents(serviceProvider, targetFile);
34+
35+
var fromLines = ReadLines(text1);
36+
var toLines = ReadLines(text2);
37+
var matchingLine = FindMatchingLine(fromLines, toLines, line, matchLinesAbove: MatchLinesAboveTarget);
38+
if (matchingLine == -1)
39+
{
40+
// If we can't match line use orignal as best guess.
41+
matchingLine = line < toLines.Count ? line : toLines.Count - 1;
42+
column = 0;
43+
}
44+
45+
ErrorHandler.ThrowOnFailure(view.SetCaretPos(matchingLine, column));
46+
ErrorHandler.ThrowOnFailure(view.CenterLines(matchingLine, 1));
47+
48+
return view;
49+
}
50+
51+
public IVsTextView FindActiveView()
52+
{
53+
var textManager = serviceProvider.GetService<SVsTextManager, IVsTextManager2>();
54+
IVsTextView view;
55+
var hresult = textManager.GetActiveView2(1, null, (uint)_VIEWFRAMETYPE.vftCodeWindow, out view);
56+
return hresult == VSConstants.S_OK ? view : null;
57+
}
58+
59+
/// <summary>
60+
/// Find the closest matching line in <see cref="toLines"/>.
61+
/// </summary>
62+
/// <remarks>
63+
/// When matching we prioritize unique matching lines in <see cref="toLines"/>. If the target line isn't
64+
/// unique, continue searching the lines above for a better match and use this as anchor with an offset.
65+
/// The closest match to <see cref="line"/> with the fewest duplicate matches will be used for the matching line.
66+
/// </remarks>
67+
/// <param name="fromLines">The document we're navigating from.</param>
68+
/// <param name="toLines">The document we're navigating to.</param>
69+
/// <param name="line">The 0-based line we're navigating from.</param>
70+
/// <returns>The best matching line in <see cref="toLines"/></returns>
71+
public int FindMatchingLine(IList<string> fromLines, IList<string> toLines, int line, int matchLinesAbove = 0)
72+
{
73+
var matchingLine = -1;
74+
var minMatchedLines = -1;
75+
for (var offset = 0; offset <= matchLinesAbove; offset++)
76+
{
77+
var targetLine = line - offset;
78+
if (targetLine < 0)
79+
{
80+
break;
81+
}
82+
83+
int matchedLines;
84+
var nearestLine = FindNearestMatchingLine(fromLines, toLines, targetLine, out matchedLines);
85+
if (nearestLine != -1)
86+
{
87+
if (matchingLine == -1 || minMatchedLines >= matchedLines)
88+
{
89+
matchingLine = nearestLine + offset;
90+
minMatchedLines = matchedLines;
91+
}
92+
93+
if (minMatchedLines == 1)
94+
{
95+
break; // We've found a unique matching line!
96+
}
97+
}
98+
}
99+
100+
if (matchingLine >= toLines.Count)
101+
{
102+
matchingLine = toLines.Count - 1;
103+
}
104+
105+
return matchingLine;
106+
}
107+
108+
/// <summary>
109+
/// Find the nearest matching line to <see cref="line"/> and the number of similar matched lines in the text.
110+
/// </summary>
111+
/// <param name="fromLines">The document we're navigating from.</param>
112+
/// <param name="toLines">The document we're navigating to.</param>
113+
/// <param name="line">The 0-based line we're navigating from.</param>
114+
/// <param name="matchedLines">The number of similar matched lines in <see cref="toLines"/></param>
115+
/// <returns>Find the nearest matching line in <see cref="toLines"/>.</returns>
116+
public int FindNearestMatchingLine(IList<string> fromLines, IList<string> toLines, int line, out int matchedLines)
117+
{
118+
line = line < fromLines.Count ? line : fromLines.Count - 1; // VS shows one extra line at end
119+
var fromLine = fromLines[line];
120+
121+
matchedLines = 0;
122+
var matchingLine = -1;
123+
for (var offset = 0; true; offset++)
124+
{
125+
var lineAbove = line + offset;
126+
var checkAbove = lineAbove < toLines.Count;
127+
if (checkAbove && toLines[lineAbove] == fromLine)
128+
{
129+
if (matchedLines == 0)
130+
{
131+
matchingLine = lineAbove;
132+
}
133+
134+
matchedLines++;
135+
}
136+
137+
var lineBelow = line - offset;
138+
var checkBelow = lineBelow >= 0;
139+
if (checkBelow && offset > 0 && lineBelow < toLines.Count && toLines[lineBelow] == fromLine)
140+
{
141+
if (matchedLines == 0)
142+
{
143+
matchingLine = lineBelow;
144+
}
145+
146+
matchedLines++;
147+
}
148+
149+
if (!checkAbove && !checkBelow)
150+
{
151+
break;
152+
}
153+
}
154+
155+
return matchingLine;
156+
}
157+
158+
string GetText(IVsTextView textView)
159+
{
160+
IVsTextLines buffer;
161+
ErrorHandler.ThrowOnFailure(textView.GetBuffer(out buffer));
162+
163+
int line;
164+
int index;
165+
ErrorHandler.ThrowOnFailure(buffer.GetLastLineIndex(out line, out index));
166+
167+
string text;
168+
ErrorHandler.ThrowOnFailure(buffer.GetLineText(0, 0, line, index, out text));
169+
return text;
170+
}
171+
172+
IVsTextView OpenDocument(string fullPath)
173+
{
174+
var logicalView = VSConstants.LOGVIEWID.TextView_guid;
175+
IVsUIHierarchy hierarchy;
176+
uint itemID;
177+
IVsWindowFrame windowFrame;
178+
IVsTextView view;
179+
VsShellUtilities.OpenDocument(serviceProvider, fullPath, logicalView, out hierarchy, out itemID, out windowFrame, out view);
180+
return view;
181+
}
182+
183+
static IList<string> ReadLines(string text)
184+
{
185+
var lines = new List<string>();
186+
var reader = new DiffUtilities.LineReader(text);
187+
string line;
188+
while ((line = reader.ReadLine()) != null)
189+
{
190+
lines.Add(line);
191+
}
192+
193+
return lines;
194+
}
195+
}
196+
}

0 commit comments

Comments
 (0)