mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-24 20:57:19 -08:00
Compare commits
17 commits
0d919273b4
...
a80a5f95bd
Author | SHA1 | Date | |
---|---|---|---|
|
a80a5f95bd | ||
|
b0a75932e9 | ||
|
491829968c | ||
|
2e815a867c | ||
|
d9892d278b | ||
|
f7e69d99d6 | ||
|
6026fd8d5c | ||
|
6e69c0567a | ||
|
7d857a49f7 | ||
|
938876e924 | ||
|
e65ac18afc | ||
|
f028513d49 | ||
|
4cb9dbfd14 | ||
|
4aad6a7f86 | ||
|
db8ee3410b | ||
|
dfc03d7a8f | ||
|
400aaacf18 |
21 changed files with 490 additions and 140 deletions
|
@ -206,6 +206,9 @@ dotnet_diagnostic.CA1854.severity = warning
|
|||
#CA2211:Non-constant fields should not be visible
|
||||
dotnet_diagnostic.CA2211.severity = error
|
||||
|
||||
# IDE0005: remove used namespace using
|
||||
dotnet_diagnostic.IDE0005.severity = error
|
||||
|
||||
# Wrapping preferences
|
||||
csharp_wrap_before_ternary_opsigns = false
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
## How to build this project manually
|
||||
|
||||
1. Make sure [.NET SDK 8](https://dotnet.microsoft.com/en-us/download) is installed on your machine.
|
||||
1. Make sure [.NET SDK 9](https://dotnet.microsoft.com/en-us/download) is installed on your machine.
|
||||
2. Clone this project
|
||||
3. Run the follow command under the project root dir
|
||||
```sh
|
||||
|
|
|
@ -164,12 +164,7 @@ namespace SourceGit
|
|||
var resDic = new ResourceDictionary();
|
||||
var overrides = JsonSerializer.Deserialize(File.ReadAllText(themeOverridesFile), JsonCodeGen.Default.ThemeOverrides);
|
||||
foreach (var kv in overrides.BasicColors)
|
||||
{
|
||||
if (kv.Key.Equals("SystemAccentColor", StringComparison.Ordinal))
|
||||
resDic["SystemAccentColor"] = kv.Value;
|
||||
else
|
||||
resDic[$"Color.{kv.Key}"] = kv.Value;
|
||||
}
|
||||
resDic[$"Color.{kv.Key}"] = kv.Value;
|
||||
|
||||
if (overrides.GraphColors.Count > 0)
|
||||
Models.CommitGraph.SetPens(overrides.GraphColors, overrides.GraphPenThickness);
|
||||
|
|
|
@ -51,6 +51,9 @@ namespace SourceGit.Commands
|
|||
_result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine);
|
||||
}
|
||||
|
||||
if (_result.TextDiff != null)
|
||||
_result.TextDiff.ProcessChangeBlocks();
|
||||
|
||||
return _result;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Media.Imaging;
|
||||
|
||||
|
@ -59,16 +61,70 @@ namespace SourceGit.Models
|
|||
}
|
||||
}
|
||||
|
||||
public partial class TextDiff
|
||||
public class TextDiffChangeBlock
|
||||
{
|
||||
public TextDiffChangeBlock(int startLine, int endLine)
|
||||
{
|
||||
StartLine = startLine;
|
||||
EndLine = endLine;
|
||||
}
|
||||
|
||||
public int StartLine { get; set; } = 0;
|
||||
public int EndLine { get; set; } = 0;
|
||||
|
||||
public bool IsInRange(int line)
|
||||
{
|
||||
return line >= StartLine && line <= EndLine;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class TextDiff : ObservableObject
|
||||
{
|
||||
public string File { get; set; } = string.Empty;
|
||||
public List<TextDiffLine> Lines { get; set; } = new List<TextDiffLine>();
|
||||
public Vector ScrollOffset { get; set; } = Vector.Zero;
|
||||
public int MaxLineNumber = 0;
|
||||
|
||||
public int CurrentChangeBlockIdx
|
||||
{
|
||||
get => _currentChangeBlockIdx;
|
||||
set => SetProperty(ref _currentChangeBlockIdx, value);
|
||||
}
|
||||
|
||||
public string Repo { get; set; } = null;
|
||||
public DiffOption Option { get; set; } = null;
|
||||
|
||||
public List<TextDiffChangeBlock> ChangeBlocks { get; set; } = [];
|
||||
|
||||
public void ProcessChangeBlocks()
|
||||
{
|
||||
ChangeBlocks.Clear();
|
||||
int lineIdx = 0, blockStartIdx = 0;
|
||||
bool isNewBlock = true;
|
||||
foreach (var line in Lines)
|
||||
{
|
||||
lineIdx++;
|
||||
if (line.Type == Models.TextDiffLineType.Added ||
|
||||
line.Type == Models.TextDiffLineType.Deleted ||
|
||||
line.Type == Models.TextDiffLineType.None) // Empty
|
||||
{
|
||||
if (isNewBlock)
|
||||
{
|
||||
isNewBlock = false;
|
||||
blockStartIdx = lineIdx;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isNewBlock)
|
||||
{
|
||||
ChangeBlocks.Add(new TextDiffChangeBlock(blockStartIdx, lineIdx - 1));
|
||||
isNewBlock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TextDiffSelection MakeSelection(int startLine, int endLine, bool isCombined, bool isOldSide)
|
||||
{
|
||||
var rs = new TextDiffSelection();
|
||||
|
@ -626,6 +682,8 @@ namespace SourceGit.Models
|
|||
return true;
|
||||
}
|
||||
|
||||
private int _currentChangeBlockIdx = -1; // NOTE: Use -1 as "not set".
|
||||
|
||||
[GeneratedRegex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@")]
|
||||
private static partial Regex REG_INDICATOR();
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
<StreamGeometry x:Key="Icons.Grid">M30 271l241 0 0-241-241 0 0 241zM392 271l241 0 0-241-241 0 0 241zM753 30l0 241 241 0 0-241-241 0zM30 632l241 0 0-241-241 0 0 241zM392 632l241 0 0-241-241 0 0 241zM753 632l241 0 0-241-241 0 0 241zM30 994l241 0 0-241-241 0 0 241zM392 994l241 0 0-241-241 0 0 241zM753 994l241 0 0-241-241 0 0 241z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Head">M0 512M1024 512M512 0M512 1024M955 323q0 23-16 39l-414 414-78 78q-16 16-39 16t-39-16l-78-78-207-207q-16-16-16-39t16-39l78-78q16-16 39-16t39 16l168 169 375-375q16-16 39-16t39 16l78 78q16 16 16 39z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.HiddenSymbol">M416 64H768v64h-64v704h64v64H448v-64h64V512H416a224 224 0 1 1 0-448zM576 832h64V128H576v704zM416 128H512v320H416a160 160 0 0 1 0-320z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Highlight">M20.2585648,2.00438474 C20.6382605,2.00472706 20.9518016,2.28716326 21.0011348,2.65328337 L21.0078899,2.75506004 L21.0038407,7.25276883 C21.0009137,8.40908568 20.1270954,9.36072944 19.0029371,9.48671858 L19.0024932,11.7464847 C19.0024932,12.9373487 18.0773316,13.9121296 16.906542,13.9912939 L16.7524932,13.9964847 L16.501,13.9963847 L16.5017549,16.7881212 C16.5017549,17.6030744 16.0616895,18.349347 15.3600767,18.7462439 L15.2057929,18.8258433 L8.57108142,21.9321389 C8.10484975,22.1504232 7.57411944,21.8450614 7.50959937,21.3535767 L7.50306874,21.2528982 L7.503,13.9963847 L7.25,13.9964847 C6.05913601,13.9964847 5.08435508,13.0713231 5.00519081,11.9005335 L5,11.7464847 L5.00043957,9.4871861 C3.92882124,9.36893736 3.08392302,8.49812196 3.0058865,7.41488149 L3,7.25086975 L3,2.75438506 C3,2.3401715 3.33578644,2.00438474 3.75,2.00438474 C4.12969577,2.00438474 4.44349096,2.28653894 4.49315338,2.6526145 L4.5,2.75438506 L4.5,7.25086975 C4.5,7.63056552 4.78215388,7.94436071 5.14822944,7.99402313 L5.25,8.00086975 L18.7512697,8.00087075 C19.1315998,8.00025031 19.4461483,7.71759877 19.4967392,7.3518545 L19.5038434,7.25019537 L19.5078902,2.75371008 C19.508263,2.33949668 19.8443515,2.00401258 20.2585648,2.00438474 Z M15.001,13.9963847 L9.003,13.9963847 L9.00306874,20.0736262 L14.5697676,17.4673619 C14.8004131,17.3593763 14.9581692,17.1431606 14.9940044,16.89581 L15.0017549,16.7881212 L15.001,13.9963847 Z M17.502,9.50038474 L6.5,9.50038474 L6.5,11.7464847 C6.5,12.1261805 6.78215388,12.4399757 7.14822944,12.4896381 L7.25,12.4964847 L16.7524932,12.4964847 C17.1321889,12.4964847 17.4459841,12.2143308 17.4956465,11.8482552 L17.5024932,11.7464847 L17.502,9.50038474 Z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Histories">M24 512A488 488 0 01512 24A488 488 0 011000 512A488 488 0 01512 1000A488 488 0 0124 512zm447-325v327L243 619l51 111 300-138V187H471z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Home">M832 64h128v278l-128-146V64zm64 448L512 73 128 512H0L448 0h128l448 512h-128zm0 83V1024H640V704c0-35-29-64-64-64h-128a64 64 0 00-64 64v320H128V595l384-424 384 424z</StreamGeometry>
|
||||
<StreamGeometry x:Key="Icons.Hotkeys">M512 0C229 0 0 229 0 512c0 283 229 512 512 512s512-229 512-512c0-283-229-512-512-512zm0 958C266 958 66 758 66 512S266 66 512 66 958 266 958 512 758 958 512 958zM192 416h96a32 32 0 0032-32v-32a32 32 0 00-32-32H192a32 32 0 00-32 32v32a32 32 0 0032 32zM384 416h96a32 32 0 0032-32v-32a32 32 0 00-32-32h-96a32 32 0 00-32 32v32a32 32 0 0032 32zM576 416h96a32 32 0 0032-32v-32a32 32 0 00-32-32h-96a32 32 0 00-32 32v32a32 32 0 0032 32zM832 320h-64a32 32 0 00-32 32v128h-160a32 32 0 00-32 32v32a32 32 0 0032 32h256a32 32 0 0032-32v-192a32 32 0 00-32-32zM320 544v-32a32 32 0 00-32-32H192a32 32 0 00-32 32v32a32 32 0 0032 32h96a32 32 0 0032-32zM384 576h96a32 32 0 0032-32v-32a32 32 0 00-32-32h-96a32 32 0 00-32 32v32a32 32 0 0032 32zM800 640H256a32 32 0 00-32 32v32a32 32 0 0032 32h544a32 32 0 0032-32v-32a32 32 0 00-32-32z</StreamGeometry>
|
||||
|
|
|
@ -232,6 +232,7 @@
|
|||
<x:String x:Key="Text.Diff.Binary.New" xml:space="preserve">NEW</x:String>
|
||||
<x:String x:Key="Text.Diff.Binary.Old" xml:space="preserve">OLD</x:String>
|
||||
<x:String x:Key="Text.Diff.Copy" xml:space="preserve">Copy</x:String>
|
||||
<x:String x:Key="Text.Diff.HighlightedDiffNavigation" xml:space="preserve">Highlighted Diff Navigation</x:String>
|
||||
<x:String x:Key="Text.Diff.FileModeChanged" xml:space="preserve">File Mode Changed</x:String>
|
||||
<x:String x:Key="Text.Diff.IgnoreWhitespace" xml:space="preserve">Ignore Whitespace Change</x:String>
|
||||
<x:String x:Key="Text.Diff.LFS" xml:space="preserve">LFS OBJECT CHANGE</x:String>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SourceGit.ViewModels
|
||||
|
|
|
@ -51,6 +51,12 @@ namespace SourceGit.ViewModels
|
|||
private set => SetProperty(ref _unifiedLines, value);
|
||||
}
|
||||
|
||||
public string ChangeBlockIndicator
|
||||
{
|
||||
get => _changeBlockIndicator;
|
||||
private set => SetProperty(ref _changeBlockIndicator, value);
|
||||
}
|
||||
|
||||
public DiffContext(string repo, Models.DiffOption option, DiffContext previous = null)
|
||||
{
|
||||
_repo = repo;
|
||||
|
@ -73,12 +79,68 @@ namespace SourceGit.ViewModels
|
|||
LoadDiffContent();
|
||||
}
|
||||
|
||||
public void PrevChange()
|
||||
{
|
||||
if (_content is Models.TextDiff textDiff)
|
||||
{
|
||||
if (textDiff.CurrentChangeBlockIdx > 0)
|
||||
{
|
||||
textDiff.CurrentChangeBlockIdx--;
|
||||
}
|
||||
else if (textDiff.ChangeBlocks.Count > 0)
|
||||
{
|
||||
// Force property value change and (re-)jump to first change block
|
||||
textDiff.CurrentChangeBlockIdx = -1;
|
||||
textDiff.CurrentChangeBlockIdx = 0;
|
||||
}
|
||||
}
|
||||
RefreshChangeBlockIndicator();
|
||||
}
|
||||
|
||||
public void NextChange()
|
||||
{
|
||||
if (_content is Models.TextDiff textDiff)
|
||||
{
|
||||
if (textDiff.CurrentChangeBlockIdx < textDiff.ChangeBlocks.Count - 1)
|
||||
{
|
||||
textDiff.CurrentChangeBlockIdx++;
|
||||
}
|
||||
else if (textDiff.ChangeBlocks.Count > 0)
|
||||
{
|
||||
// Force property value change and (re-)jump to last change block
|
||||
textDiff.CurrentChangeBlockIdx = -1;
|
||||
textDiff.CurrentChangeBlockIdx = textDiff.ChangeBlocks.Count - 1;
|
||||
}
|
||||
RefreshChangeBlockIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
public void RefreshChangeBlockIndicator()
|
||||
{
|
||||
string curr = "-", tot = "-";
|
||||
if (_content is Models.TextDiff textDiff)
|
||||
{
|
||||
if (textDiff.CurrentChangeBlockIdx >= 0)
|
||||
curr = (textDiff.CurrentChangeBlockIdx + 1).ToString();
|
||||
tot = (textDiff.ChangeBlocks.Count).ToString();
|
||||
}
|
||||
ChangeBlockIndicator = curr + "/" + tot;
|
||||
}
|
||||
|
||||
public void ToggleFullTextDiff()
|
||||
{
|
||||
Preference.Instance.UseFullTextDiff = !Preference.Instance.UseFullTextDiff;
|
||||
LoadDiffContent();
|
||||
}
|
||||
|
||||
public void ToggleHighlightedDiffNavigation()
|
||||
{
|
||||
Preference.Instance.EnableChangeBlocks = !Preference.Instance.EnableChangeBlocks;
|
||||
if (_content is Models.TextDiff textDiff)
|
||||
textDiff.CurrentChangeBlockIdx = -1;
|
||||
RefreshChangeBlockIndicator();
|
||||
}
|
||||
|
||||
public void IncrUnified()
|
||||
{
|
||||
UnifiedLines = _unifiedLines + 1;
|
||||
|
@ -91,6 +153,12 @@ namespace SourceGit.ViewModels
|
|||
LoadDiffContent();
|
||||
}
|
||||
|
||||
public void ToggleTwoSideDiff()
|
||||
{
|
||||
Preference.Instance.UseSideBySideDiff = !Preference.Instance.UseSideBySideDiff;
|
||||
RefreshChangeBlockIndicator();
|
||||
}
|
||||
|
||||
public void OpenExternalMergeTool()
|
||||
{
|
||||
var toolType = Preference.Instance.ExternalMergeToolType;
|
||||
|
@ -217,7 +285,9 @@ namespace SourceGit.ViewModels
|
|||
FileModeChange = latest.FileModeChange;
|
||||
Content = rs;
|
||||
IsTextDiff = rs is Models.TextDiff;
|
||||
});
|
||||
|
||||
RefreshChangeBlockIndicator();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -281,6 +351,7 @@ namespace SourceGit.ViewModels
|
|||
private string _title;
|
||||
private string _fileModeChange = string.Empty;
|
||||
private int _unifiedLines = 4;
|
||||
private string _changeBlockIndicator = "-/-";
|
||||
private bool _isTextDiff = false;
|
||||
private bool _ignoreWhitespace = false;
|
||||
private object _content = null;
|
||||
|
|
|
@ -813,6 +813,8 @@ namespace SourceGit.ViewModels
|
|||
submenu.Icon = App.CreateMenuIcon("Icons.Branch");
|
||||
submenu.Header = current.Name;
|
||||
|
||||
FillBranchVisibilityMenu(submenu, current);
|
||||
|
||||
if (!string.IsNullOrEmpty(current.Upstream))
|
||||
{
|
||||
var upstream = current.Upstream.Substring(13);
|
||||
|
@ -852,6 +854,17 @@ namespace SourceGit.ViewModels
|
|||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(push);
|
||||
|
||||
var rename = new MenuItem();
|
||||
rename.Header = new Views.NameHighlightedTextBlock("BranchCM.Rename", current.Name);
|
||||
rename.Icon = App.CreateMenuIcon("Icons.Rename");
|
||||
rename.Click += (_, e) =>
|
||||
{
|
||||
if (PopupHost.CanCreatePopup())
|
||||
PopupHost.ShowPopup(new RenameBranch(_repo, current));
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(rename);
|
||||
submenu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
var detect = Commands.GitFlow.DetectType(_repo.FullPath, _repo.Branches, current.Name);
|
||||
|
@ -870,18 +883,15 @@ namespace SourceGit.ViewModels
|
|||
submenu.Items.Add(new MenuItem() { Header = "-" });
|
||||
}
|
||||
|
||||
FillBranchVisibilityMenu(submenu, current);
|
||||
|
||||
var rename = new MenuItem();
|
||||
rename.Header = new Views.NameHighlightedTextBlock("BranchCM.Rename", current.Name);
|
||||
rename.Icon = App.CreateMenuIcon("Icons.Rename");
|
||||
rename.Click += (_, e) =>
|
||||
var copy = new MenuItem();
|
||||
copy.Header = App.Text("BranchCM.CopyName");
|
||||
copy.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copy.Click += (_, e) =>
|
||||
{
|
||||
if (PopupHost.CanCreatePopup())
|
||||
PopupHost.ShowPopup(new RenameBranch(_repo, current));
|
||||
App.CopyText(current.Name);
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(rename);
|
||||
submenu.Items.Add(copy);
|
||||
|
||||
menu.Items.Add(submenu);
|
||||
}
|
||||
|
@ -892,6 +902,8 @@ namespace SourceGit.ViewModels
|
|||
submenu.Icon = App.CreateMenuIcon("Icons.Branch");
|
||||
submenu.Header = branch.Name;
|
||||
|
||||
FillBranchVisibilityMenu(submenu, branch);
|
||||
|
||||
var checkout = new MenuItem();
|
||||
checkout.Header = new Views.NameHighlightedTextBlock("BranchCM.Checkout", branch.Name);
|
||||
checkout.Icon = App.CreateMenuIcon("Icons.Check");
|
||||
|
@ -913,25 +925,6 @@ namespace SourceGit.ViewModels
|
|||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(merge);
|
||||
submenu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
var detect = Commands.GitFlow.DetectType(_repo.FullPath, _repo.Branches, branch.Name);
|
||||
if (detect.IsGitFlowBranch)
|
||||
{
|
||||
var finish = new MenuItem();
|
||||
finish.Header = new Views.NameHighlightedTextBlock("BranchCM.Finish", branch.Name);
|
||||
finish.Icon = App.CreateMenuIcon("Icons.GitFlow");
|
||||
finish.Click += (_, e) =>
|
||||
{
|
||||
if (PopupHost.CanCreatePopup())
|
||||
PopupHost.ShowPopup(new GitFlowFinish(_repo, branch, detect.Type, detect.Prefix));
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(finish);
|
||||
submenu.Items.Add(new MenuItem() { Header = "-" });
|
||||
}
|
||||
|
||||
FillBranchVisibilityMenu(submenu, branch);
|
||||
|
||||
var rename = new MenuItem();
|
||||
rename.Header = new Views.NameHighlightedTextBlock("BranchCM.Rename", branch.Name);
|
||||
|
@ -954,6 +947,33 @@ namespace SourceGit.ViewModels
|
|||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(delete);
|
||||
submenu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
var detect = Commands.GitFlow.DetectType(_repo.FullPath, _repo.Branches, branch.Name);
|
||||
if (detect.IsGitFlowBranch)
|
||||
{
|
||||
var finish = new MenuItem();
|
||||
finish.Header = new Views.NameHighlightedTextBlock("BranchCM.Finish", branch.Name);
|
||||
finish.Icon = App.CreateMenuIcon("Icons.GitFlow");
|
||||
finish.Click += (_, e) =>
|
||||
{
|
||||
if (PopupHost.CanCreatePopup())
|
||||
PopupHost.ShowPopup(new GitFlowFinish(_repo, branch, detect.Type, detect.Prefix));
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(finish);
|
||||
submenu.Items.Add(new MenuItem() { Header = "-" });
|
||||
}
|
||||
|
||||
var copy = new MenuItem();
|
||||
copy.Header = App.Text("BranchCM.CopyName");
|
||||
copy.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copy.Click += (_, e) =>
|
||||
{
|
||||
App.CopyText(branch.Name);
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(copy);
|
||||
|
||||
menu.Items.Add(submenu);
|
||||
}
|
||||
|
@ -966,6 +986,8 @@ namespace SourceGit.ViewModels
|
|||
submenu.Icon = App.CreateMenuIcon("Icons.Branch");
|
||||
submenu.Header = name;
|
||||
|
||||
FillBranchVisibilityMenu(submenu, branch);
|
||||
|
||||
var checkout = new MenuItem();
|
||||
checkout.Header = new Views.NameHighlightedTextBlock("BranchCM.Checkout", name);
|
||||
checkout.Icon = App.CreateMenuIcon("Icons.Check");
|
||||
|
@ -988,9 +1010,6 @@ namespace SourceGit.ViewModels
|
|||
};
|
||||
|
||||
submenu.Items.Add(merge);
|
||||
submenu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
FillBranchVisibilityMenu(submenu, branch);
|
||||
|
||||
var delete = new MenuItem();
|
||||
delete.Header = new Views.NameHighlightedTextBlock("BranchCM.Delete", name);
|
||||
|
@ -1002,6 +1021,17 @@ namespace SourceGit.ViewModels
|
|||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(delete);
|
||||
submenu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
var copy = new MenuItem();
|
||||
copy.Header = App.Text("BranchCM.CopyName");
|
||||
copy.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copy.Click += (_, e) =>
|
||||
{
|
||||
App.CopyText(name);
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(copy);
|
||||
|
||||
menu.Items.Add(submenu);
|
||||
}
|
||||
|
@ -1013,6 +1043,8 @@ namespace SourceGit.ViewModels
|
|||
submenu.Icon = App.CreateMenuIcon("Icons.Tag");
|
||||
submenu.MinWidth = 200;
|
||||
|
||||
FillTagVisibilityMenu(submenu, tag);
|
||||
|
||||
var push = new MenuItem();
|
||||
push.Header = new Views.NameHighlightedTextBlock("TagCM.Push", tag.Name);
|
||||
push.Icon = App.CreateMenuIcon("Icons.Push");
|
||||
|
@ -1036,9 +1068,6 @@ namespace SourceGit.ViewModels
|
|||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(merge);
|
||||
submenu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
FillTagVisibilityMenu(submenu, tag);
|
||||
|
||||
var delete = new MenuItem();
|
||||
delete.Header = new Views.NameHighlightedTextBlock("TagCM.Delete", tag.Name);
|
||||
|
@ -1050,6 +1079,17 @@ namespace SourceGit.ViewModels
|
|||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(delete);
|
||||
submenu.Items.Add(new MenuItem() { Header = "-" });
|
||||
|
||||
var copy = new MenuItem();
|
||||
copy.Header = App.Text("TagCM.Copy");
|
||||
copy.Icon = App.CreateMenuIcon("Icons.Copy");
|
||||
copy.Click += (_, e) =>
|
||||
{
|
||||
App.CopyText(tag.Name);
|
||||
e.Handled = true;
|
||||
};
|
||||
submenu.Items.Add(copy);
|
||||
|
||||
menu.Items.Add(submenu);
|
||||
}
|
||||
|
|
|
@ -206,6 +206,12 @@ namespace SourceGit.ViewModels
|
|||
set => SetProperty(ref _useFullTextDiff, value);
|
||||
}
|
||||
|
||||
public bool EnableChangeBlocks
|
||||
{
|
||||
get => _enableChangeBlocks;
|
||||
set => SetProperty(ref _enableChangeBlocks, value);
|
||||
}
|
||||
|
||||
public Models.ChangeViewMode UnstagedChangeViewMode
|
||||
{
|
||||
get => _unstagedChangeViewMode;
|
||||
|
@ -614,6 +620,7 @@ namespace SourceGit.ViewModels
|
|||
private bool _enableDiffViewWordWrap = false;
|
||||
private bool _showHiddenSymbolsInDiffView = false;
|
||||
private bool _useFullTextDiff = false;
|
||||
private bool _enableChangeBlocks = false;
|
||||
|
||||
private Models.ChangeViewMode _unstagedChangeViewMode = Models.ChangeViewMode.List;
|
||||
private Models.ChangeViewMode _stagedChangeViewMode = Models.ChangeViewMode.List;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Avalonia.Collections;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
|
|
|
@ -45,10 +45,43 @@ namespace SourceGit.ViewModels
|
|||
|
||||
FillEmptyLines();
|
||||
|
||||
ProcessChangeBlocks();
|
||||
|
||||
if (previous != null && previous.File == File)
|
||||
_syncScrollOffset = previous._syncScrollOffset;
|
||||
}
|
||||
|
||||
public List<Models.TextDiffChangeBlock> ChangeBlocks { get; set; } = [];
|
||||
|
||||
public void ProcessChangeBlocks()
|
||||
{
|
||||
ChangeBlocks.Clear();
|
||||
int lineIdx = 0, blockStartIdx = 0;
|
||||
bool isNewBlock = true;
|
||||
foreach (var line in Old) // NOTE: Same block size in both Old and New lines.
|
||||
{
|
||||
lineIdx++;
|
||||
if (line.Type == Models.TextDiffLineType.Added ||
|
||||
line.Type == Models.TextDiffLineType.Deleted ||
|
||||
line.Type == Models.TextDiffLineType.None) // Empty
|
||||
{
|
||||
if (isNewBlock)
|
||||
{
|
||||
isNewBlock = false;
|
||||
blockStartIdx = lineIdx;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!isNewBlock)
|
||||
{
|
||||
ChangeBlocks.Add(new Models.TextDiffChangeBlock(blockStartIdx, lineIdx - 1));
|
||||
isNewBlock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ConvertsToCombinedRange(Models.TextDiff combined, ref int startLine, ref int endLine, bool isOldSide)
|
||||
{
|
||||
endLine = Math.Min(endLine, combined.Lines.Count - 1);
|
||||
|
|
|
@ -296,7 +296,8 @@ namespace SourceGit.Views
|
|||
if (currentParent is { DataContext: ViewModels.CommitDetail currentDetail } &&
|
||||
currentDetail.Commit.SHA == lastDetailCommit)
|
||||
{
|
||||
_inlineCommits.Add(sha, c);
|
||||
if (!_inlineCommits.ContainsKey(sha))
|
||||
_inlineCommits.Add(sha, c);
|
||||
|
||||
// Make sure user still hovers the target SHA.
|
||||
if (_lastHover == link && c != null)
|
||||
|
|
|
@ -34,6 +34,15 @@
|
|||
|
||||
<!-- Toolbar Buttons -->
|
||||
<StackPanel Grid.Column="3" Margin="8,0,0,0" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<ToggleButton Classes="line_path"
|
||||
Width="28"
|
||||
Command="{Binding ToggleHighlightedDiffNavigation}"
|
||||
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableChangeBlocks, Mode=OneWay}"
|
||||
IsVisible="{Binding IsTextDiff}"
|
||||
ToolTip.Tip="{DynamicResource Text.Diff.HighlightedDiffNavigation}">
|
||||
<Path Width="13" Height="13" Data="{StaticResource Icons.Highlight}" Margin="0,3,0,0"/>
|
||||
</ToggleButton>
|
||||
|
||||
<Button Classes="icon_button"
|
||||
Width="28"
|
||||
Click="OnGotoPrevChange"
|
||||
|
@ -42,6 +51,18 @@
|
|||
<Path Width="12" Height="12" Stretch="Uniform" Margin="0,6,0,0" Data="{StaticResource Icons.Up}"/>
|
||||
</Button>
|
||||
|
||||
<TextBlock Classes="primary"
|
||||
Margin="0,0,0,0"
|
||||
Text="{Binding ChangeBlockIndicator}"
|
||||
FontSize="11">
|
||||
<TextBlock.IsVisible>
|
||||
<MultiBinding Converter="{x:Static BoolConverters.And}">
|
||||
<Binding Path="IsTextDiff"/>
|
||||
<Binding Source="{x:Static vm:Preference.Instance}" Path="EnableChangeBlocks" Mode="OneWay"/>
|
||||
</MultiBinding>
|
||||
</TextBlock.IsVisible>
|
||||
</TextBlock>
|
||||
|
||||
<Button Classes="icon_button"
|
||||
Width="28"
|
||||
Click="OnGotoNextChange"
|
||||
|
@ -124,7 +145,8 @@
|
|||
|
||||
<ToggleButton Classes="line_path"
|
||||
Width="28" Height="18"
|
||||
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=TwoWay}"
|
||||
Command="{Binding ToggleTwoSideDiff}"
|
||||
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=OneWay}"
|
||||
IsVisible="{Binding IsTextDiff}"
|
||||
ToolTip.Tip="{DynamicResource Text.Diff.SideBySide}">
|
||||
<Path Width="12" Height="12" Data="{StaticResource Icons.LayoutHorizontal}" Margin="0,2,0,0"/>
|
||||
|
@ -241,7 +263,8 @@
|
|||
<DataTemplate DataType="m:TextDiff">
|
||||
<v:TextDiffView
|
||||
UseSideBySideDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=OneWay}"
|
||||
UseFullTextDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseFullTextDiff, Mode=OneWay}"/>
|
||||
UseFullTextDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseFullTextDiff, Mode=OneWay}"
|
||||
CurrentChangeBlockIdx="{Binding CurrentChangeBlockIdx, Mode=OneWay}"/>
|
||||
</DataTemplate>
|
||||
|
||||
<!-- Empty or only EOL changes -->
|
||||
|
|
|
@ -13,28 +13,44 @@ namespace SourceGit.Views
|
|||
|
||||
private void OnGotoPrevChange(object _, RoutedEventArgs e)
|
||||
{
|
||||
var textDiff = this.FindDescendantOfType<ThemedTextDiffPresenter>();
|
||||
if (textDiff == null)
|
||||
return;
|
||||
if (ViewModels.Preference.Instance.EnableChangeBlocks)
|
||||
{
|
||||
if (DataContext is ViewModels.DiffContext diffCtx)
|
||||
diffCtx.PrevChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
var textDiff = this.FindDescendantOfType<ThemedTextDiffPresenter>();
|
||||
if (textDiff == null)
|
||||
return;
|
||||
|
||||
textDiff.GotoPrevChange();
|
||||
if (textDiff is SingleSideTextDiffPresenter presenter)
|
||||
presenter.ForceSyncScrollOffset();
|
||||
textDiff.GotoPrevChange();
|
||||
if (textDiff is SingleSideTextDiffPresenter presenter)
|
||||
presenter.ForceSyncScrollOffset();
|
||||
|
||||
e.Handled = true;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGotoNextChange(object _, RoutedEventArgs e)
|
||||
{
|
||||
var textDiff = this.FindDescendantOfType<ThemedTextDiffPresenter>();
|
||||
if (textDiff == null)
|
||||
return;
|
||||
if (ViewModels.Preference.Instance.EnableChangeBlocks)
|
||||
{
|
||||
if (DataContext is ViewModels.DiffContext diffCtx)
|
||||
diffCtx.NextChange();
|
||||
}
|
||||
else
|
||||
{
|
||||
var textDiff = this.FindDescendantOfType<ThemedTextDiffPresenter>();
|
||||
if (textDiff == null)
|
||||
return;
|
||||
|
||||
textDiff.GotoNextChange();
|
||||
if (textDiff is SingleSideTextDiffPresenter presenter)
|
||||
presenter.ForceSyncScrollOffset();
|
||||
textDiff.GotoNextChange();
|
||||
if (textDiff is SingleSideTextDiffPresenter presenter)
|
||||
presenter.ForceSyncScrollOffset();
|
||||
|
||||
e.Handled = true;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
<Setter Property="Margin" Value="0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Height" Value="28"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
|
||||
|
@ -76,13 +77,16 @@
|
|||
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate DataType="vm:InteractiveRebaseItem">
|
||||
<Grid ColumnDefinitions="16,110,*,40,100,96,156,32,32">
|
||||
<Grid ColumnDefinitions="16,110,*,456" Margin="8,0" ClipToBounds="True">
|
||||
<!-- Drag & Drop Anchor -->
|
||||
<Border Grid.Column="0" Background="Transparent"
|
||||
Margin="4,0,0,0"
|
||||
Loaded="OnSetupRowHeaderDragDrop"
|
||||
PointerPressed="OnRowHeaderPointerPressed">
|
||||
<Path Width="14" Height="14" Data="{StaticResource Icons.Move}" Fill="{DynamicResource Brush.FG2}"/>
|
||||
<Path Width="14" Height="14"
|
||||
Data="{StaticResource Icons.Move}"
|
||||
Fill="{DynamicResource Brush.FG2}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
|
||||
<!-- Action -->
|
||||
|
@ -170,8 +174,8 @@
|
|||
</Button>
|
||||
|
||||
<!-- Subject -->
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" ClipToBounds="True">
|
||||
<Button Classes="icon_button" IsVisible="{Binding Action, Converter={x:Static c:InteractiveRebaseActionConverters.CanEditMessage}}">
|
||||
<Grid Grid.Column="2" ColumnDefinitions="Auto,*" ClipToBounds="True">
|
||||
<Button Grid.Column="0" Classes="icon_button" Margin="0,0,8,0" IsVisible="{Binding Action, Converter={x:Static c:InteractiveRebaseActionConverters.CanEditMessage}}">
|
||||
<Button.Flyout>
|
||||
<Flyout Placement="BottomEdgeAlignedLeft">
|
||||
<Panel Width="600" Height="120">
|
||||
|
@ -181,39 +185,47 @@
|
|||
</Button.Flyout>
|
||||
<Path Width="14" Height="14" Margin="0,4,0,0" Data="{StaticResource Icons.Edit}"/>
|
||||
</Button>
|
||||
<TextBlock Classes="primary" Text="{Binding Subject}" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" Classes="primary" Margin="0,0,4,0" Text="{Binding Subject}"/>
|
||||
</Grid>
|
||||
|
||||
<!-- Avatar -->
|
||||
<v:Avatar Grid.Column="3"
|
||||
Width="16" Height="16"
|
||||
Margin="16,0,8,0"
|
||||
VerticalAlignment="Center"
|
||||
IsHitTestVisible="False"
|
||||
User="{Binding Commit.Author}"/>
|
||||
<Grid Grid.Column="3" ColumnDefinitions="32,108,96,156,32,32" IsHitTestVisible="False" ClipToBounds="True">
|
||||
<!-- Author Avatar -->
|
||||
<v:Avatar Grid.Column="0"
|
||||
Width="16" Height="16"
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
User="{Binding Commit.Author}"/>
|
||||
|
||||
<!-- Author -->
|
||||
<Border Grid.Column="4" ClipToBounds="True">
|
||||
<TextBlock Classes="primary" Text="{Binding Commit.Author.Name}" HorizontalAlignment="Left"/>
|
||||
</Border>
|
||||
|
||||
<!-- Commit SHA -->
|
||||
<TextBlock Grid.Column="5" Classes="primary"
|
||||
Text="{Binding Commit.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}"
|
||||
Margin="12,0"/>
|
||||
<!-- Author Name -->
|
||||
<TextBlock Grid.Column="1"
|
||||
Classes="primary"
|
||||
MaxWidth="90"
|
||||
Margin="6,0,12,0"
|
||||
Text="{Binding Commit.Author.Name}"
|
||||
HorizontalAlignment="Left"/>
|
||||
|
||||
<!-- Commit Time -->
|
||||
<TextBlock Grid.Column="6" Classes="primary" Text="{Binding Commit.CommitterTimeStr}" Margin="8,0"/>
|
||||
<!-- Commit SHA -->
|
||||
<Border Grid.Column="2" ClipToBounds="True">
|
||||
<TextBlock Classes="primary"
|
||||
Text="{Binding Commit.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}"
|
||||
HorizontalAlignment="Center"/>
|
||||
</Border>
|
||||
|
||||
<!-- MoveUp Button -->
|
||||
<Button Grid.Column="7" Classes="icon_button" Click="OnMoveItemUp" ToolTip.Tip="Alt+Up">
|
||||
<Path Width="14" Height="14" Margin="0,4,0,0" Data="{StaticResource Icons.Up}"/>
|
||||
</Button>
|
||||
|
||||
<!-- MoveDown Button -->
|
||||
<Button Grid.Column="8" Classes="icon_button" Click="OnMoveItemDown" ToolTip.Tip="Alt+Down">
|
||||
<Path Width="14" Height="14" Margin="0,4,0,0" Data="{StaticResource Icons.Down}"/>
|
||||
</Button>
|
||||
<!-- Commit Time -->
|
||||
<Border Grid.Column="3" ClipToBounds="True">
|
||||
<TextBlock Classes="primary" Text="{Binding Commit.CommitterTimeStr}" Margin="8,0"/>
|
||||
</Border>
|
||||
|
||||
<!-- MoveUp Button -->
|
||||
<Button Grid.Column="4" Classes="icon_button" Click="OnMoveItemUp" ToolTip.Tip="Alt+Up">
|
||||
<Path Width="14" Height="14" Margin="0,4,0,0" Data="{StaticResource Icons.Up}"/>
|
||||
</Button>
|
||||
|
||||
<!-- MoveDown Button -->
|
||||
<Button Grid.Column="5" Classes="icon_button" Click="OnMoveItemDown" ToolTip.Tip="Alt+Down">
|
||||
<Path Width="14" Height="14" Margin="0,4,0,0" Data="{StaticResource Icons.Down}"/>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
|
|
|
@ -50,9 +50,6 @@ namespace SourceGit.Views
|
|||
if (launcher is not null && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
var startDirectly = launcher.HasKeyModifier(KeyModifiers.Control);
|
||||
if (!startDirectly && OperatingSystem.IsMacOS())
|
||||
startDirectly = launcher.HasKeyModifier(KeyModifiers.Meta);
|
||||
|
||||
launcher.ClearKeyModifier();
|
||||
repo.Fetch(startDirectly);
|
||||
e.Handled = true;
|
||||
|
@ -65,9 +62,6 @@ namespace SourceGit.Views
|
|||
if (launcher is not null && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
var startDirectly = launcher.HasKeyModifier(KeyModifiers.Control);
|
||||
if (!startDirectly && OperatingSystem.IsMacOS())
|
||||
startDirectly = launcher.HasKeyModifier(KeyModifiers.Meta);
|
||||
|
||||
launcher.ClearKeyModifier();
|
||||
repo.Pull(startDirectly);
|
||||
e.Handled = true;
|
||||
|
@ -80,9 +74,6 @@ namespace SourceGit.Views
|
|||
if (launcher is not null && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
var startDirectly = launcher.HasKeyModifier(KeyModifiers.Control);
|
||||
if (!startDirectly && OperatingSystem.IsMacOS())
|
||||
startDirectly = launcher.HasKeyModifier(KeyModifiers.Meta);
|
||||
|
||||
launcher.ClearKeyModifier();
|
||||
repo.Push(startDirectly);
|
||||
e.Handled = true;
|
||||
|
@ -95,9 +86,6 @@ namespace SourceGit.Views
|
|||
if (launcher is not null && DataContext is ViewModels.Repository repo)
|
||||
{
|
||||
var startDirectly = launcher.HasKeyModifier(KeyModifiers.Control);
|
||||
if (!startDirectly && OperatingSystem.IsMacOS())
|
||||
startDirectly = launcher.HasKeyModifier(KeyModifiers.Meta);
|
||||
|
||||
launcher.ClearKeyModifier();
|
||||
repo.StashAll(startDirectly);
|
||||
e.Handled = true;
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
|
||||
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"
|
||||
ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
||||
CurrentChangeBlockIdx="{Binding #ThisControl.CurrentChangeBlockIdx}"
|
||||
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
||||
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
|
||||
|
||||
|
@ -61,6 +62,7 @@
|
|||
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
|
||||
WordWrap="False"
|
||||
ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
||||
CurrentChangeBlockIdx="{Binding #ThisControl.CurrentChangeBlockIdx}"
|
||||
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
||||
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
|
||||
|
||||
|
@ -82,6 +84,7 @@
|
|||
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
|
||||
WordWrap="False"
|
||||
ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
||||
CurrentChangeBlockIdx="{Binding #ThisControl.CurrentChangeBlockIdx}"
|
||||
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
||||
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
using Avalonia;
|
||||
|
@ -254,6 +255,10 @@ namespace SourceGit.Views
|
|||
if (_presenter.Document == null || !textView.VisualLinesValid)
|
||||
return;
|
||||
|
||||
var changeBlock = _presenter.GetCurrentChangeBlock();
|
||||
Brush changeBlockBG = new SolidColorBrush(Colors.Gray, 0.25);
|
||||
Pen changeBlockFG = new Pen(Brushes.Gray, 1);
|
||||
|
||||
var lines = _presenter.GetLines();
|
||||
var width = textView.Bounds.Width;
|
||||
foreach (var line in textView.VisualLines)
|
||||
|
@ -266,51 +271,63 @@ namespace SourceGit.Views
|
|||
break;
|
||||
|
||||
var info = lines[index - 1];
|
||||
var bg = GetBrushByLineType(info.Type);
|
||||
if (bg == null)
|
||||
continue;
|
||||
|
||||
var startY = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.LineTop) - textView.VerticalOffset;
|
||||
var endY = line.GetTextLineVisualYPosition(line.TextLines[^1], VisualYPosition.LineBottom) - textView.VerticalOffset;
|
||||
drawingContext.DrawRectangle(bg, null, new Rect(0, startY, width, endY - startY));
|
||||
|
||||
if (info.Highlights.Count > 0)
|
||||
var bg = GetBrushByLineType(info.Type);
|
||||
if (bg != null)
|
||||
{
|
||||
var highlightBG = info.Type == Models.TextDiffLineType.Added ? _presenter.AddedHighlightBrush : _presenter.DeletedHighlightBrush;
|
||||
var processingIdxStart = 0;
|
||||
var processingIdxEnd = 0;
|
||||
var nextHightlight = 0;
|
||||
if (bg != null)
|
||||
drawingContext.DrawRectangle(bg, null, new Rect(0, startY, width, endY - startY));
|
||||
|
||||
foreach (var tl in line.TextLines)
|
||||
if (info.Highlights.Count > 0)
|
||||
{
|
||||
processingIdxEnd += tl.Length;
|
||||
var highlightBG = info.Type == Models.TextDiffLineType.Added ? _presenter.AddedHighlightBrush : _presenter.DeletedHighlightBrush;
|
||||
var processingIdxStart = 0;
|
||||
var processingIdxEnd = 0;
|
||||
var nextHighlight = 0;
|
||||
|
||||
var y = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineTop) - textView.VerticalOffset;
|
||||
var h = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineBottom) - textView.VerticalOffset - y;
|
||||
|
||||
while (nextHightlight < info.Highlights.Count)
|
||||
foreach (var tl in line.TextLines)
|
||||
{
|
||||
var highlight = info.Highlights[nextHightlight];
|
||||
if (highlight.Start >= processingIdxEnd)
|
||||
break;
|
||||
processingIdxEnd += tl.Length;
|
||||
|
||||
var start = line.GetVisualColumn(highlight.Start < processingIdxStart ? processingIdxStart : highlight.Start);
|
||||
var end = line.GetVisualColumn(highlight.End >= processingIdxEnd ? processingIdxEnd : highlight.End + 1);
|
||||
var y = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineTop) - textView.VerticalOffset;
|
||||
var h = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineBottom) - textView.VerticalOffset - y;
|
||||
|
||||
var x = line.GetTextLineVisualXPosition(tl, start) - textView.HorizontalOffset;
|
||||
var w = line.GetTextLineVisualXPosition(tl, end) - textView.HorizontalOffset - x;
|
||||
var rect = new Rect(x, y, w, h);
|
||||
drawingContext.DrawRectangle(highlightBG, null, rect);
|
||||
while (nextHighlight < info.Highlights.Count)
|
||||
{
|
||||
var highlight = info.Highlights[nextHighlight];
|
||||
if (highlight.Start >= processingIdxEnd)
|
||||
break;
|
||||
|
||||
if (highlight.End >= processingIdxEnd)
|
||||
break;
|
||||
var start = line.GetVisualColumn(highlight.Start < processingIdxStart ? processingIdxStart : highlight.Start);
|
||||
var end = line.GetVisualColumn(highlight.End >= processingIdxEnd ? processingIdxEnd : highlight.End + 1);
|
||||
|
||||
nextHightlight++;
|
||||
var x = line.GetTextLineVisualXPosition(tl, start) - textView.HorizontalOffset;
|
||||
var w = line.GetTextLineVisualXPosition(tl, end) - textView.HorizontalOffset - x;
|
||||
var rect = new Rect(x, y, w, h);
|
||||
drawingContext.DrawRectangle(highlightBG, null, rect);
|
||||
|
||||
if (highlight.End >= processingIdxEnd)
|
||||
break;
|
||||
|
||||
nextHighlight++;
|
||||
}
|
||||
|
||||
processingIdxStart = processingIdxEnd;
|
||||
}
|
||||
|
||||
processingIdxStart = processingIdxEnd;
|
||||
}
|
||||
}
|
||||
|
||||
if (changeBlock != null && changeBlock.IsInRange(index))
|
||||
{
|
||||
drawingContext.DrawRectangle(changeBlockBG, null, new Rect(0, startY, width, endY - startY));
|
||||
if (index == changeBlock.StartLine)
|
||||
drawingContext.DrawLine(changeBlockFG, new Point(0, startY), new Point(width, startY));
|
||||
if (index == changeBlock.EndLine)
|
||||
drawingContext.DrawLine(changeBlockFG, new Point(0, endY), new Point(width, endY));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -486,6 +503,15 @@ namespace SourceGit.Views
|
|||
set => SetValue(DisplayRangeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<int> CurrentChangeBlockIdxProperty =
|
||||
AvaloniaProperty.Register<ThemedTextDiffPresenter, int>(nameof(CurrentChangeBlockIdx));
|
||||
|
||||
public int CurrentChangeBlockIdx
|
||||
{
|
||||
get => GetValue(CurrentChangeBlockIdxProperty);
|
||||
set => SetValue(CurrentChangeBlockIdxProperty, value);
|
||||
}
|
||||
|
||||
protected override Type StyleKeyOverride => typeof(TextEditor);
|
||||
|
||||
public ThemedTextDiffPresenter(TextArea area, TextDocument doc) : base(area, doc)
|
||||
|
@ -590,6 +616,28 @@ namespace SourceGit.Views
|
|||
}
|
||||
}
|
||||
|
||||
public Models.TextDiffChangeBlock GetCurrentChangeBlock()
|
||||
{
|
||||
return GetChangeBlock(CurrentChangeBlockIdx);
|
||||
}
|
||||
|
||||
public virtual Models.TextDiffChangeBlock GetChangeBlock(int changeBlockIdx)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public void JumpToChangeBlock(int changeBlockIdx)
|
||||
{
|
||||
var changeBlock = GetChangeBlock(changeBlockIdx);
|
||||
if (changeBlock != null)
|
||||
{
|
||||
TextArea.Caret.Line = changeBlock.StartLine;
|
||||
//TextArea.Caret.BringCaretToView(); // NOTE: Brings caret line (barely) into view.
|
||||
ScrollToLine(changeBlock.StartLine); // NOTE: Brings specified line into center of view.
|
||||
}
|
||||
TextArea.TextView.Redraw();
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
base.Render(context);
|
||||
|
@ -665,6 +713,10 @@ namespace SourceGit.Views
|
|||
{
|
||||
InvalidateVisual();
|
||||
}
|
||||
else if (change.Property == CurrentChangeBlockIdxProperty)
|
||||
{
|
||||
InvalidateVisual();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
|
@ -1018,6 +1070,16 @@ namespace SourceGit.Views
|
|||
}
|
||||
}
|
||||
|
||||
public override Models.TextDiffChangeBlock GetChangeBlock(int changeBlockIdx)
|
||||
{
|
||||
if (DataContext is Models.TextDiff diff)
|
||||
{
|
||||
if (changeBlockIdx >= 0 && changeBlockIdx < diff.ChangeBlocks.Count)
|
||||
return diff.ChangeBlocks[changeBlockIdx];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
|
@ -1089,6 +1151,8 @@ namespace SourceGit.Views
|
|||
|
||||
public void ForceSyncScrollOffset()
|
||||
{
|
||||
if (_scrollViewer == null)
|
||||
return;
|
||||
if (DataContext is ViewModels.TwoSideTextDiff diff)
|
||||
diff.SyncScrollOffset = _scrollViewer?.Offset ?? Vector.Zero;
|
||||
}
|
||||
|
@ -1234,6 +1298,16 @@ namespace SourceGit.Views
|
|||
}
|
||||
}
|
||||
|
||||
public override Models.TextDiffChangeBlock GetChangeBlock(int changeBlockIdx)
|
||||
{
|
||||
if (DataContext is ViewModels.TwoSideTextDiff diff)
|
||||
{
|
||||
if (changeBlockIdx >= 0 && changeBlockIdx < diff.ChangeBlocks.Count)
|
||||
return diff.ChangeBlocks[changeBlockIdx];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
|
@ -1480,6 +1554,15 @@ namespace SourceGit.Views
|
|||
set => SetValue(EnableChunkSelectionProperty, value);
|
||||
}
|
||||
|
||||
public static readonly StyledProperty<int> CurrentChangeBlockIdxProperty =
|
||||
AvaloniaProperty.Register<TextDiffView, int>(nameof(CurrentChangeBlockIdx));
|
||||
|
||||
public int CurrentChangeBlockIdx
|
||||
{
|
||||
get => GetValue(CurrentChangeBlockIdxProperty);
|
||||
set => SetValue(CurrentChangeBlockIdxProperty, value);
|
||||
}
|
||||
|
||||
static TextDiffView()
|
||||
{
|
||||
UseSideBySideDiffProperty.Changed.AddClassHandler<TextDiffView>((v, _) =>
|
||||
|
@ -1506,6 +1589,19 @@ namespace SourceGit.Views
|
|||
v.Popup.Margin = new Thickness(0, top, right, 0);
|
||||
v.Popup.IsVisible = true;
|
||||
});
|
||||
|
||||
CurrentChangeBlockIdxProperty.Changed.AddClassHandler<TextDiffView>((v, e) =>
|
||||
{
|
||||
if (v.Editor.Presenter != null)
|
||||
{
|
||||
foreach (var p in v.Editor.Presenter.GetVisualDescendants().OfType<ThemedTextDiffPresenter>())
|
||||
{
|
||||
p.JumpToChangeBlock((int)e.NewValue);
|
||||
if (p is SingleSideTextDiffPresenter ssp)
|
||||
ssp.ForceSyncScrollOffset();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public TextDiffView()
|
||||
|
@ -1553,6 +1649,8 @@ namespace SourceGit.Views
|
|||
|
||||
IsUnstagedChange = diff.Option.IsUnstaged;
|
||||
EnableChunkSelection = diff.Option.WorkingCopyChange != null;
|
||||
|
||||
diff.CurrentChangeBlockIdx = -1; // Unset current change block.
|
||||
}
|
||||
|
||||
private void OnStageChunk(object _1, RoutedEventArgs _2)
|
||||
|
|
Loading…
Reference in a new issue