Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
fff0c48
Navigate diff to editor on key press
jcansdale Jan 2, 2018
e044f26
Move to equivalent line/col on live document
jcansdale Jan 3, 2018
78a411f
Center editable document position on caret
jcansdale Jan 3, 2018
b0f6f40
Enable navigation from Left and Inline diff views
jcansdale Jan 3, 2018
c50db4e
Give hint about how to edit PR diff files on status bar
jcansdale Jan 4, 2018
4310c4e
Refactor to prepare for FindEquivalentLine
jcansdale Jan 10, 2018
deb07d5
Refactor navigation into NavigationService
jcansdale Jan 10, 2018
c07bd16
Fix CA errors
jcansdale Jan 11, 2018
2f94a75
Trigger navigation using command dispacher
jcansdale Jan 11, 2018
c550770
Add simple line matching algorithm
jcansdale Jan 11, 2018
71121df
Fix index out of range bugs
jcansdale Jan 11, 2018
cb51a1c
Enable navigate from read-only PR file
jcansdale Jan 11, 2018
8c538df
Merge branch 'master' into feature/navigate-diff-to-editor
jcansdale Jan 11, 2018
448ec65
Add `Open File in Solution` context menu item
jcansdale Jan 12, 2018
6415049
Move command off `GitHub` sub-menu
jcansdale Jan 12, 2018
4ae78f8
Warn if user tries to open solution file that isn't checked out
jcansdale Jan 12, 2018
9857cdc
Advertise Open File in Solution on Inline Comment view
jcansdale Jan 15, 2018
79733dd
Merge branch 'master' into feature/navigate-diff-to-editor
Jan 19, 2018
5cf8b4f
Revert "Advertise Open File in Solution on Inline Comment view"
jcansdale Jan 24, 2018
ea9b355
Merge branch 'master' into feature/navigate-diff-to-editor
jcansdale Jan 29, 2018
ad08473
Add unit tests for FindNearestMatchingLine
jcansdale Jan 31, 2018
4600c80
Merge branch 'master' into feature/navigate-diff-to-editor
jcansdale Jan 31, 2018
7f37a14
Return number of matching lines when finding nearest
jcansdale Jan 31, 2018
6a3ecae
Add tests for matching when target line has changed
jcansdale Jan 31, 2018
81c53cc
Enable matching when target line has changed
jcansdale Jan 31, 2018
0974e48
Enable usage tracker for NumberOfPRDetailsNavigateToEditor
jcansdale Jan 31, 2018
237e40f
Add xmldoc comments for TextViewCommandDispatcher
jcansdale Jan 31, 2018
6b38d26
Changed to use IGitHubServiceProvider
jcansdale Jan 31, 2018
c37a57e
Add xmldoc comments for NavigationService
jcansdale Jan 31, 2018
2450e58
Move MatchLinesAboveTarget const out of FindMatchingLine
jcansdale Jan 31, 2018
b95c2ff
Rename NavigationService to PullRequestEditorService
jcansdale Feb 2, 2018
fae5e7e
Merge branch 'master' into feature/navigate-diff-to-editor
jcansdale Feb 2, 2018
3a4774c
Merge branch 'master' into feature/navigate-diff-to-editor
grokys Feb 6, 2018
c3da975
Improve xmldoc description on TextViewCommandDispatcher
jcansdale Feb 6, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/GitHub.App/GitHub.App.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
<HintPath>..\..\packages\Microsoft.VisualStudio.ComponentModelHost.14.0.25424\lib\net45\Microsoft.VisualStudio.ComponentModelHost.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath>
<Private>True</Private>
Expand All @@ -69,10 +73,18 @@
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Threading, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\packages\Microsoft.VisualStudio.Threading.14.1.131\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath>
Expand Down
3 changes: 3 additions & 0 deletions src/GitHub.App/packages.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
<package id="LibGit2Sharp" version="0.23.1" targetFramework="net461" />
<package id="LibGit2Sharp.NativeBinaries" version="1.0.164" targetFramework="net461" />
<package id="Microsoft.VisualStudio.ComponentModelHost" version="14.0.25424" targetFramework="net461" />
<package id="Microsoft.VisualStudio.OLE.Interop" version="7.10.6070" targetFramework="net461" />
<package id="Microsoft.VisualStudio.Shell.14.0" version="14.3.25407" targetFramework="net461" />
<package id="Microsoft.VisualStudio.Shell.Immutable.10.0" version="10.0.30319" targetFramework="net461" />
<package id="Microsoft.VisualStudio.Shell.Interop" version="7.10.6071" targetFramework="net461" />
<package id="Microsoft.VisualStudio.TextManager.Interop" version="7.10.6070" targetFramework="net461" />
<package id="Microsoft.VisualStudio.TextManager.Interop.8.0" version="8.0.50727" targetFramework="net461" />
<package id="Newtonsoft.Json" version="6.0.8" targetFramework="net45" />
<package id="Rothko" version="0.0.3-ghfvs" targetFramework="net461" />
<package id="Rx-Core" version="2.2.5-custom" targetFramework="net45" />
Expand Down
62 changes: 62 additions & 0 deletions src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,46 @@
<HintPath>..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Imaging, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.VisualStudio.Imaging.14.3.25407\lib\net45\Microsoft.VisualStudio.Imaging.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.OLE.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.10.0, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.11.0, Version=11.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.12.0, Version=12.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.12.0.12.0.21003\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Immutable.14.0, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Shell.Interop.9.0, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Text.Data, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll</HintPath>
<Private>True</Private>
Expand All @@ -69,6 +109,26 @@
<HintPath>..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TextManager.Interop, Version=7.1.40304.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.TextManager.Interop.8.0, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Threading, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.VisualStudio.Threading.14.1.111\lib\net45\Microsoft.VisualStudio.Threading.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Utilities, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.VisualStudio.Validation, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
Expand Down Expand Up @@ -115,12 +175,14 @@
<Compile Include="Models\PullRequestTextBufferInfo.cs" />
<Compile Include="Services\IModelService.cs" />
<Compile Include="Services\IGistPublishService.cs" />
<Compile Include="Services\IPullRequestEditorService.cs" />
<Compile Include="Services\IPullRequestSession.cs" />
<Compile Include="Services\IPullRequestService.cs" />
<Compile Include="Services\IPullRequestSessionManager.cs" />
<Compile Include="Services\IShowDialogService.cs" />
<Compile Include="Services\LocalRepositoriesExtensions.cs" />
<Compile Include="Services\ModelServiceExtensions.cs" />
<Compile Include="Services\PullRequestEditorService.cs" />
<Compile Include="ViewModels\Dialog\IDialogContentViewModel.cs" />
<Compile Include="ViewModels\Dialog\IGitHubDialogWindowViewModel.cs" />
<Compile Include="ViewModels\Dialog\IGistCreationViewModel.cs" />
Expand Down
21 changes: 21 additions & 0 deletions src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.VisualStudio.TextManager.Interop;

namespace GitHub.Services
{
public interface IPullRequestEditorService
{
/// <summary>
/// Find the active text view.
/// </summary>
/// <returns>The active view or null if view can't be found.</returns>
IVsTextView FindActiveView();

/// <summary>
/// Navigate to and place the caret at the best guess equivalent position in <see cref="targetFile"/>.
/// </summary>
/// <param name="sourceView">The text view to navigate from.</param>
/// <param name="targetFile">The text view to open and navigate to.</param>
/// <returns>The opened text view.</returns>
IVsTextView NavigateToEquivalentPosition(IVsTextView sourceView, string targetFile);
}
}
196 changes: 196 additions & 0 deletions src/GitHub.Exports.Reactive/Services/PullRequestEditorService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
using System.Collections.Generic;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;
using GitHub.Models;

namespace GitHub.Services
{
[Export(typeof(IPullRequestEditorService))]
public class PullRequestEditorService : IPullRequestEditorService
{
readonly IGitHubServiceProvider serviceProvider;

// If the target line doesn't have a unique match, search this number of lines above looking for a match.
public const int MatchLinesAboveTarget = 4;

[ImportingConstructor]
public PullRequestEditorService(IGitHubServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}

public IVsTextView NavigateToEquivalentPosition(IVsTextView sourceView, string targetFile)
{
int line;
int column;
ErrorHandler.ThrowOnFailure(sourceView.GetCaretPos(out line, out column));
var text1 = GetText(sourceView);

var view = OpenDocument(targetFile);
var text2 = VsShellUtilities.GetRunningDocumentContents(serviceProvider, targetFile);

var fromLines = ReadLines(text1);
var toLines = ReadLines(text2);
var matchingLine = FindMatchingLine(fromLines, toLines, line, matchLinesAbove: MatchLinesAboveTarget);
if (matchingLine == -1)
{
// If we can't match line use orignal as best guess.
matchingLine = line < toLines.Count ? line : toLines.Count - 1;
column = 0;
}

ErrorHandler.ThrowOnFailure(view.SetCaretPos(matchingLine, column));
ErrorHandler.ThrowOnFailure(view.CenterLines(matchingLine, 1));

return view;
}

public IVsTextView FindActiveView()
{
var textManager = serviceProvider.GetService<SVsTextManager, IVsTextManager2>();
IVsTextView view;
var hresult = textManager.GetActiveView2(1, null, (uint)_VIEWFRAMETYPE.vftCodeWindow, out view);
return hresult == VSConstants.S_OK ? view : null;
}

/// <summary>
/// Find the closest matching line in <see cref="toLines"/>.
/// </summary>
/// <remarks>
/// When matching we prioritize unique matching lines in <see cref="toLines"/>. If the target line isn't
/// unique, continue searching the lines above for a better match and use this as anchor with an offset.
/// The closest match to <see cref="line"/> with the fewest duplicate matches will be used for the matching line.
/// </remarks>
/// <param name="fromLines">The document we're navigating from.</param>
/// <param name="toLines">The document we're navigating to.</param>
/// <param name="line">The 0-based line we're navigating from.</param>
/// <returns>The best matching line in <see cref="toLines"/></returns>
public int FindMatchingLine(IList<string> fromLines, IList<string> toLines, int line, int matchLinesAbove = 0)
{
var matchingLine = -1;
var minMatchedLines = -1;
for (var offset = 0; offset <= matchLinesAbove; offset++)
{
var targetLine = line - offset;
if (targetLine < 0)
{
break;
}

int matchedLines;
var nearestLine = FindNearestMatchingLine(fromLines, toLines, targetLine, out matchedLines);
if (nearestLine != -1)
{
if (matchingLine == -1 || minMatchedLines >= matchedLines)
{
matchingLine = nearestLine + offset;
minMatchedLines = matchedLines;
}

if (minMatchedLines == 1)
{
break; // We've found a unique matching line!
}
}
}

if (matchingLine >= toLines.Count)
{
matchingLine = toLines.Count - 1;
}

return matchingLine;
}

/// <summary>
/// Find the nearest matching line to <see cref="line"/> and the number of similar matched lines in the text.
/// </summary>
/// <param name="fromLines">The document we're navigating from.</param>
/// <param name="toLines">The document we're navigating to.</param>
/// <param name="line">The 0-based line we're navigating from.</param>
/// <param name="matchedLines">The number of similar matched lines in <see cref="toLines"/></param>
/// <returns>Find the nearest matching line in <see cref="toLines"/>.</returns>
public int FindNearestMatchingLine(IList<string> fromLines, IList<string> toLines, int line, out int matchedLines)
{
line = line < fromLines.Count ? line : fromLines.Count - 1; // VS shows one extra line at end
var fromLine = fromLines[line];

matchedLines = 0;
var matchingLine = -1;
for (var offset = 0; true; offset++)
{
var lineAbove = line + offset;
var checkAbove = lineAbove < toLines.Count;
if (checkAbove && toLines[lineAbove] == fromLine)
{
if (matchedLines == 0)
{
matchingLine = lineAbove;
}

matchedLines++;
}

var lineBelow = line - offset;
var checkBelow = lineBelow >= 0;
if (checkBelow && offset > 0 && lineBelow < toLines.Count && toLines[lineBelow] == fromLine)
{
if (matchedLines == 0)
{
matchingLine = lineBelow;
}

matchedLines++;
}

if (!checkAbove && !checkBelow)
{
break;
}
}

return matchingLine;
}

string GetText(IVsTextView textView)
{
IVsTextLines buffer;
ErrorHandler.ThrowOnFailure(textView.GetBuffer(out buffer));

int line;
int index;
ErrorHandler.ThrowOnFailure(buffer.GetLastLineIndex(out line, out index));

string text;
ErrorHandler.ThrowOnFailure(buffer.GetLineText(0, 0, line, index, out text));
return text;
}

IVsTextView OpenDocument(string fullPath)
{
var logicalView = VSConstants.LOGVIEWID.TextView_guid;
IVsUIHierarchy hierarchy;
uint itemID;
IVsWindowFrame windowFrame;
IVsTextView view;
VsShellUtilities.OpenDocument(serviceProvider, fullPath, logicalView, out hierarchy, out itemID, out windowFrame, out view);
return view;
}

static IList<string> ReadLines(string text)
{
var lines = new List<string>();
var reader = new DiffUtilities.LineReader(text);
string line;
while ((line = reader.ReadLine()) != null)
{
lines.Add(line);
}

return lines;
}
}
}
Loading