mirror of
https://github.com/sourcegit-scm/sourcegit.git
synced 2024-12-25 21:07:20 -08:00
Compare commits
26 commits
c0eba5121e
...
34b2451d1d
Author | SHA1 | Date | |
---|---|---|---|
|
34b2451d1d | ||
|
e8c8ac55a1 | ||
|
f3c9690d47 | ||
|
88fb754415 | ||
|
f96f602456 | ||
|
3fb1c763f3 | ||
|
c611b62992 | ||
|
7a9c8d7444 | ||
|
8021cd8566 | ||
|
73687689ce | ||
|
b452e13453 | ||
|
814529a690 | ||
|
00804e453e | ||
|
b25f9bdb6c | ||
|
f45bed6f92 | ||
|
3be90b2ef6 | ||
|
f7ef61f1ce | ||
|
0da46cb90b | ||
|
8b3d129890 | ||
|
309db6e362 | ||
|
5b55e3530d | ||
|
d07a664166 | ||
|
ea1d966d27 | ||
|
f4618afee6 | ||
|
3b09ea45f5 | ||
|
13805f794a |
34 changed files with 964 additions and 201 deletions
|
@ -47,7 +47,7 @@
|
||||||
|
|
||||||
## Translation Status
|
## Translation Status
|
||||||
|
|
||||||
[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-100.00%25-brightgreen)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-99.14%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-98.42%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-99.14%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-100.00%25-brightgreen)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md)
|
[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.57%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-98.71%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-97.99%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-98.71%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-99.57%25-yellow)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md)
|
||||||
|
|
||||||
## How to Use
|
## How to Use
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
### de_DE.axaml: 100.00%
|
### de_DE.axaml: 99.57%
|
||||||
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Missing Keys</summary>
|
<summary>Missing Keys</summary>
|
||||||
|
|
||||||
|
- Text.Repository.HistoriesOrder
|
||||||
|
- Text.Repository.HistoriesOrder.ByDate
|
||||||
|
- Text.Repository.HistoriesOrder.Topo
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### es_ES.axaml: 99.14%
|
### es_ES.axaml: 98.71%
|
||||||
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
@ -20,10 +22,13 @@
|
||||||
- Text.Repository.FilterCommits.Default
|
- Text.Repository.FilterCommits.Default
|
||||||
- Text.Repository.FilterCommits.Exclude
|
- Text.Repository.FilterCommits.Exclude
|
||||||
- Text.Repository.FilterCommits.Include
|
- Text.Repository.FilterCommits.Include
|
||||||
|
- Text.Repository.HistoriesOrder
|
||||||
|
- Text.Repository.HistoriesOrder.ByDate
|
||||||
|
- Text.Repository.HistoriesOrder.Topo
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### fr_FR.axaml: 98.42%
|
### fr_FR.axaml: 97.99%
|
||||||
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
@ -39,11 +44,14 @@
|
||||||
- Text.Repository.FilterCommits.Default
|
- Text.Repository.FilterCommits.Default
|
||||||
- Text.Repository.FilterCommits.Exclude
|
- Text.Repository.FilterCommits.Exclude
|
||||||
- Text.Repository.FilterCommits.Include
|
- Text.Repository.FilterCommits.Include
|
||||||
|
- Text.Repository.HistoriesOrder
|
||||||
|
- Text.Repository.HistoriesOrder.ByDate
|
||||||
|
- Text.Repository.HistoriesOrder.Topo
|
||||||
- Text.ScanRepositories
|
- Text.ScanRepositories
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### pt_BR.axaml: 99.14%
|
### pt_BR.axaml: 98.71%
|
||||||
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
@ -55,16 +63,21 @@
|
||||||
- Text.Repository.FilterCommits.Default
|
- Text.Repository.FilterCommits.Default
|
||||||
- Text.Repository.FilterCommits.Exclude
|
- Text.Repository.FilterCommits.Exclude
|
||||||
- Text.Repository.FilterCommits.Include
|
- Text.Repository.FilterCommits.Include
|
||||||
|
- Text.Repository.HistoriesOrder
|
||||||
|
- Text.Repository.HistoriesOrder.ByDate
|
||||||
|
- Text.Repository.HistoriesOrder.Topo
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### ru_RU.axaml: 100.00%
|
### ru_RU.axaml: 99.57%
|
||||||
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Missing Keys</summary>
|
<summary>Missing Keys</summary>
|
||||||
|
|
||||||
|
- Text.Repository.HistoriesOrder
|
||||||
|
- Text.Repository.HistoriesOrder.ByDate
|
||||||
|
- Text.Repository.HistoriesOrder.Topo
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
8.38
|
8.39
|
34
src/Commands/QueryCommitChildren.cs
Normal file
34
src/Commands/QueryCommitChildren.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using SourceGit.ViewModels;
|
||||||
|
|
||||||
|
namespace SourceGit.Commands
|
||||||
|
{
|
||||||
|
public class QueryCommitChildren : Command
|
||||||
|
{
|
||||||
|
public QueryCommitChildren(string repo, string commit, string filters)
|
||||||
|
{
|
||||||
|
WorkingDirectory = repo;
|
||||||
|
Context = repo;
|
||||||
|
_commit = commit;
|
||||||
|
if (string.IsNullOrEmpty(filters))
|
||||||
|
filters = "--all";
|
||||||
|
Args = $"rev-list -{Preference.Instance.MaxHistoryCommits} --parents {filters} ^{commit}";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnReadline(string line)
|
||||||
|
{
|
||||||
|
if (line.Contains(_commit))
|
||||||
|
_lines.Add(line.Substring(0, 40));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<string> Result()
|
||||||
|
{
|
||||||
|
Exec();
|
||||||
|
return _lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _commit;
|
||||||
|
private List<string> _lines = new List<string>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,11 +6,13 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public class QueryCommits : Command
|
public class QueryCommits : Command
|
||||||
{
|
{
|
||||||
public QueryCommits(string repo, string limits, bool needFindHead = true)
|
public QueryCommits(string repo, bool useTopoOrder, string limits, bool needFindHead = true)
|
||||||
{
|
{
|
||||||
|
var order = useTopoOrder ? "--topo-order" : "--date-order";
|
||||||
|
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
Args = "log --date-order --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s " + limits;
|
Args = $"log {order} --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s {limits}";
|
||||||
_findFirstMerged = needFindHead;
|
_findFirstMerged = needFindHead;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,11 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public class Statistics : Command
|
public class Statistics : Command
|
||||||
{
|
{
|
||||||
public Statistics(string repo)
|
public Statistics(string repo, int max)
|
||||||
{
|
{
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
Args = $"log --date-order --branches --remotes -40000 --pretty=format:\"%ct$%aN\"";
|
Args = $"log --date-order --branches --remotes -{max} --pretty=format:\"%ct$%aN\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
public Models.Statistics Result()
|
public Models.Statistics Result()
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
@ -9,9 +6,6 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
public partial class CommitTemplate : ObservableObject
|
public partial class CommitTemplate : ObservableObject
|
||||||
{
|
{
|
||||||
[GeneratedRegex(@"\$\{files(\:\d+)?\}")]
|
|
||||||
private static partial Regex REG_COMMIT_TEMPLATE_FILES();
|
|
||||||
|
|
||||||
public string Name
|
public string Name
|
||||||
{
|
{
|
||||||
get => _name;
|
get => _name;
|
||||||
|
@ -26,55 +20,8 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
public string Apply(Branch branch, List<Change> changes)
|
public string Apply(Branch branch, List<Change> changes)
|
||||||
{
|
{
|
||||||
var content = _content
|
var te = new TemplateEngine();
|
||||||
.Replace("${files_num}", $"{changes.Count}")
|
return te.Eval(_content, branch, changes);
|
||||||
.Replace("${branch_name}", branch.Name);
|
|
||||||
|
|
||||||
var matches = REG_COMMIT_TEMPLATE_FILES().Matches(content);
|
|
||||||
if (matches.Count == 0)
|
|
||||||
return content;
|
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
|
||||||
var last = 0;
|
|
||||||
for (int i = 0; i < matches.Count; i++)
|
|
||||||
{
|
|
||||||
var match = matches[i];
|
|
||||||
if (!match.Success)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var start = match.Index;
|
|
||||||
if (start != last)
|
|
||||||
builder.Append(content.Substring(last, start - last));
|
|
||||||
|
|
||||||
var countStr = match.Groups[1].Value;
|
|
||||||
var paths = new List<string>();
|
|
||||||
var more = string.Empty;
|
|
||||||
if (countStr is { Length: <= 1 })
|
|
||||||
{
|
|
||||||
foreach (var c in changes)
|
|
||||||
paths.Add(c.Path);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var count = Math.Min(int.Parse(countStr.Substring(1)), changes.Count);
|
|
||||||
for (int j = 0; j < count; j++)
|
|
||||||
paths.Add(changes[j].Path);
|
|
||||||
|
|
||||||
if (count < changes.Count)
|
|
||||||
more = $" and {changes.Count - count} other files";
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.Append(string.Join(", ", paths));
|
|
||||||
if (!string.IsNullOrEmpty(more))
|
|
||||||
builder.Append(more);
|
|
||||||
|
|
||||||
last = start + match.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (last != content.Length - 1)
|
|
||||||
builder.Append(content.Substring(last));
|
|
||||||
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _name = string.Empty;
|
private string _name = string.Empty;
|
||||||
|
|
|
@ -39,7 +39,7 @@ namespace SourceGit.Models
|
||||||
new ExternalMerger(4, "tortoise_merge", "Tortoise Merge", "TortoiseMerge.exe;TortoiseGitMerge.exe", "-base:\"$BASE\" -theirs:\"$REMOTE\" -mine:\"$LOCAL\" -merged:\"$MERGED\"", "-base:\"$LOCAL\" -theirs:\"$REMOTE\""),
|
new ExternalMerger(4, "tortoise_merge", "Tortoise Merge", "TortoiseMerge.exe;TortoiseGitMerge.exe", "-base:\"$BASE\" -theirs:\"$REMOTE\" -mine:\"$LOCAL\" -merged:\"$MERGED\"", "-base:\"$LOCAL\" -theirs:\"$REMOTE\""),
|
||||||
new ExternalMerger(5, "kdiff3", "KDiff3", "kdiff3.exe", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
|
new ExternalMerger(5, "kdiff3", "KDiff3", "kdiff3.exe", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
|
||||||
new ExternalMerger(6, "beyond_compare", "Beyond Compare", "BComp.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
|
new ExternalMerger(6, "beyond_compare", "Beyond Compare", "BComp.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
|
||||||
new ExternalMerger(7, "win_merge", "WinMerge", "WinMergeU.exe", "-u -e \"$REMOTE\" \"$LOCAL\" \"$MERGED\"", "-u -e \"$LOCAL\" \"$REMOTE\""),
|
new ExternalMerger(7, "win_merge", "WinMerge", "WinMergeU.exe", "\"$MERGED\"", "-u -e \"$LOCAL\" \"$REMOTE\""),
|
||||||
new ExternalMerger(8, "codium", "VSCodium", "VSCodium.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
|
new ExternalMerger(8, "codium", "VSCodium", "VSCodium.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
|
||||||
new ExternalMerger(9, "p4merge", "P4Merge", "p4merge.exe", "-tw 4 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-tw 4 \"$LOCAL\" \"$REMOTE\""),
|
new ExternalMerger(9, "p4merge", "P4Merge", "p4merge.exe", "-tw 4 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-tw 4 \"$LOCAL\" \"$REMOTE\""),
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,23 +6,25 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
public static readonly ResetMode[] Supported =
|
public static readonly ResetMode[] Supported =
|
||||||
[
|
[
|
||||||
new ResetMode("Soft", "Keep all changes. Stage differences", "--soft", Brushes.Green),
|
new ResetMode("Soft", "Keep all changes. Stage differences", "--soft", "S", Brushes.Green),
|
||||||
new ResetMode("Mixed", "Keep all changes. Unstage differences", "--mixed", Brushes.Orange),
|
new ResetMode("Mixed", "Keep all changes. Unstage differences", "--mixed", "M", Brushes.Orange),
|
||||||
new ResetMode("Merge", "Reset while keeping unmerged changes", "--merge", Brushes.Purple),
|
new ResetMode("Merge", "Reset while keeping unmerged changes", "--merge", "G", Brushes.Purple),
|
||||||
new ResetMode("Keep", "Reset while keeping local modifications", "--keep", Brushes.Purple),
|
new ResetMode("Keep", "Reset while keeping local modifications", "--keep", "K", Brushes.Purple),
|
||||||
new ResetMode("Hard", "Discard all changes", "--hard", Brushes.Red),
|
new ResetMode("Hard", "Discard all changes", "--hard", "H", Brushes.Red),
|
||||||
];
|
];
|
||||||
|
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Desc { get; set; }
|
public string Desc { get; set; }
|
||||||
public string Arg { get; set; }
|
public string Arg { get; set; }
|
||||||
|
public string Key { get; set; }
|
||||||
public IBrush Color { get; set; }
|
public IBrush Color { get; set; }
|
||||||
|
|
||||||
public ResetMode(string n, string d, string a, IBrush b)
|
public ResetMode(string n, string d, string a, string k, IBrush b)
|
||||||
{
|
{
|
||||||
Name = n;
|
Name = n;
|
||||||
Desc = d;
|
Desc = d;
|
||||||
Arg = a;
|
Arg = a;
|
||||||
|
Key = k;
|
||||||
Color = b;
|
Color = b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
using LiveChartsCore;
|
using LiveChartsCore;
|
||||||
using LiveChartsCore.Defaults;
|
using LiveChartsCore.Defaults;
|
||||||
|
@ -138,7 +139,8 @@ namespace SourceGit.Models
|
||||||
public Statistics()
|
public Statistics()
|
||||||
{
|
{
|
||||||
_today = DateTime.Now.ToLocalTime().Date;
|
_today = DateTime.Now.ToLocalTime().Date;
|
||||||
_thisWeekStart = _today.AddSeconds(-(int)_today.DayOfWeek * 3600 * 24);
|
var weekOffset = (7 + (int)_today.DayOfWeek - (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek) % 7;
|
||||||
|
_thisWeekStart = _today.AddDays(-weekOffset);
|
||||||
_thisMonthStart = _today.AddDays(1 - _today.Day);
|
_thisMonthStart = _today.AddDays(1 - _today.Day);
|
||||||
|
|
||||||
All = new StatisticsReport(StaticsticsMode.All, DateTime.MinValue);
|
All = new StatisticsReport(StaticsticsMode.All, DateTime.MinValue);
|
||||||
|
|
410
src/Models/TemplateEngine.cs
Normal file
410
src/Models/TemplateEngine.cs
Normal file
|
@ -0,0 +1,410 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace SourceGit.Models
|
||||||
|
{
|
||||||
|
public class TemplateEngine
|
||||||
|
{
|
||||||
|
private class Context(Branch branch, IReadOnlyList<Change> changes)
|
||||||
|
{
|
||||||
|
public Branch branch = branch;
|
||||||
|
public IReadOnlyList<Change> changes = changes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Text(string text)
|
||||||
|
{
|
||||||
|
public string text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Variable(string name)
|
||||||
|
{
|
||||||
|
public string name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SlicedVariable(string name, int count)
|
||||||
|
{
|
||||||
|
public string name = name;
|
||||||
|
public int count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RegexVariable(string name, Regex regex, string replacement)
|
||||||
|
{
|
||||||
|
public string name = name;
|
||||||
|
public Regex regex = regex;
|
||||||
|
public string replacement = replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
private const char ESCAPE = '\\';
|
||||||
|
private const char VARIABLE_ANCHOR = '$';
|
||||||
|
private const char VARIABLE_START = '{';
|
||||||
|
private const char VARIABLE_END = '}';
|
||||||
|
private const char VARIABLE_SLICE = ':';
|
||||||
|
private const char VARIABLE_REGEX = '/';
|
||||||
|
private const char NEWLINE = '\n';
|
||||||
|
private const RegexOptions REGEX_OPTIONS = RegexOptions.Singleline | RegexOptions.IgnoreCase;
|
||||||
|
|
||||||
|
public string Eval(string text, Branch branch, IReadOnlyList<Change> changes)
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
|
||||||
|
_chars = text.ToCharArray();
|
||||||
|
Parse();
|
||||||
|
|
||||||
|
var context = new Context(branch, changes);
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.EnsureCapacity(text.Length);
|
||||||
|
foreach (var token in _tokens)
|
||||||
|
{
|
||||||
|
switch (token)
|
||||||
|
{
|
||||||
|
case Text text_token:
|
||||||
|
sb.Append(text_token.text);
|
||||||
|
break;
|
||||||
|
case Variable var_token:
|
||||||
|
sb.Append(EvalVariable(context, var_token));
|
||||||
|
break;
|
||||||
|
case SlicedVariable sliced_var:
|
||||||
|
sb.Append(EvalVariable(context, sliced_var));
|
||||||
|
break;
|
||||||
|
case RegexVariable regex_var:
|
||||||
|
sb.Append(EvalVariable(context, regex_var));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Reset()
|
||||||
|
{
|
||||||
|
_pos = 0;
|
||||||
|
_chars = [];
|
||||||
|
_tokens.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private char? Next()
|
||||||
|
{
|
||||||
|
var c = Peek();
|
||||||
|
if (c is not null)
|
||||||
|
{
|
||||||
|
_pos++;
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private char? Peek()
|
||||||
|
{
|
||||||
|
return (_pos >= _chars.Length) ? null : _chars[_pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
private int? Integer()
|
||||||
|
{
|
||||||
|
var start = _pos;
|
||||||
|
while (Peek() is char c && c >= '0' && c <= '9')
|
||||||
|
{
|
||||||
|
_pos++;
|
||||||
|
}
|
||||||
|
if (start >= _pos)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var chars = new ReadOnlySpan<char>(_chars, start, _pos - start);
|
||||||
|
return int.Parse(chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Parse()
|
||||||
|
{
|
||||||
|
// text token start
|
||||||
|
var tok = _pos;
|
||||||
|
bool esc = false;
|
||||||
|
while (Next() is char c)
|
||||||
|
{
|
||||||
|
if (esc)
|
||||||
|
{
|
||||||
|
esc = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case ESCAPE:
|
||||||
|
// allow to escape only \ and $
|
||||||
|
if (Peek() is char nc && (nc == ESCAPE || nc == VARIABLE_ANCHOR))
|
||||||
|
{
|
||||||
|
esc = true;
|
||||||
|
FlushText(tok, _pos - 1);
|
||||||
|
tok = _pos;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case VARIABLE_ANCHOR:
|
||||||
|
// backup the position
|
||||||
|
var bak = _pos;
|
||||||
|
var variable = TryParseVariable();
|
||||||
|
if (variable is null)
|
||||||
|
{
|
||||||
|
// no variable found, rollback
|
||||||
|
_pos = bak;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// variable found, flush a text token
|
||||||
|
FlushText(tok, bak - 1);
|
||||||
|
_tokens.Add(variable);
|
||||||
|
tok = _pos;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// flush text token
|
||||||
|
FlushText(tok, _pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FlushText(int start, int end)
|
||||||
|
{
|
||||||
|
int len = end - start;
|
||||||
|
if (len <= 0)
|
||||||
|
return;
|
||||||
|
var text = new string(_chars, start, len);
|
||||||
|
_tokens.Add(new Text(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
private object TryParseVariable()
|
||||||
|
{
|
||||||
|
if (Next() != VARIABLE_START)
|
||||||
|
return null;
|
||||||
|
int name_start = _pos;
|
||||||
|
while (Next() is char c)
|
||||||
|
{
|
||||||
|
// name character, continue advancing
|
||||||
|
if (IsNameChar(c))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var name_end = _pos - 1;
|
||||||
|
// not a name character but name is empty, cancel
|
||||||
|
if (name_start >= name_end)
|
||||||
|
return null;
|
||||||
|
var name = new string(_chars, name_start, name_end - name_start);
|
||||||
|
|
||||||
|
return c switch
|
||||||
|
{
|
||||||
|
// variable
|
||||||
|
VARIABLE_END => new Variable(name),
|
||||||
|
// sliced variable
|
||||||
|
VARIABLE_SLICE => TryParseSlicedVariable(name),
|
||||||
|
// regex variable
|
||||||
|
VARIABLE_REGEX => TryParseRegexVariable(name),
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private object TryParseSlicedVariable(string name)
|
||||||
|
{
|
||||||
|
int? n = Integer();
|
||||||
|
if (n is null)
|
||||||
|
return null;
|
||||||
|
if (Next() != VARIABLE_END)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new SlicedVariable(name, (int)n);
|
||||||
|
}
|
||||||
|
|
||||||
|
private object TryParseRegexVariable(string name)
|
||||||
|
{
|
||||||
|
var regex = ParseRegex();
|
||||||
|
if (regex == null)
|
||||||
|
return null;
|
||||||
|
var replacement = ParseReplacement();
|
||||||
|
if (replacement == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new RegexVariable(name, regex, replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Regex ParseRegex()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var tok = _pos;
|
||||||
|
var esc = false;
|
||||||
|
while (Next() is char c)
|
||||||
|
{
|
||||||
|
if (esc)
|
||||||
|
{
|
||||||
|
esc = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case ESCAPE:
|
||||||
|
// allow to escape only / as \ and { used frequently in regexes
|
||||||
|
if (Peek() == VARIABLE_REGEX)
|
||||||
|
{
|
||||||
|
esc = true;
|
||||||
|
sb.Append(_chars, tok, _pos - 1 - tok);
|
||||||
|
tok = _pos;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case VARIABLE_REGEX:
|
||||||
|
// goto is fine
|
||||||
|
goto Loop_exit;
|
||||||
|
case NEWLINE:
|
||||||
|
// no newlines allowed
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loop_exit:
|
||||||
|
sb.Append(_chars, tok, _pos - 1 - tok);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pattern = sb.ToString();
|
||||||
|
if (pattern.Length == 0)
|
||||||
|
return null;
|
||||||
|
var regex = new Regex(pattern, REGEX_OPTIONS);
|
||||||
|
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
catch (RegexParseException)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ParseReplacement()
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var tok = _pos;
|
||||||
|
var esc = false;
|
||||||
|
while (Next() is char c)
|
||||||
|
{
|
||||||
|
if (esc)
|
||||||
|
{
|
||||||
|
esc = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case ESCAPE:
|
||||||
|
// allow to escape only }
|
||||||
|
if (Peek() == VARIABLE_END)
|
||||||
|
{
|
||||||
|
esc = true;
|
||||||
|
sb.Append(_chars, tok, _pos - 1 - tok);
|
||||||
|
tok = _pos;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case VARIABLE_END:
|
||||||
|
// goto is fine
|
||||||
|
goto Loop_exit;
|
||||||
|
case NEWLINE:
|
||||||
|
// no newlines allowed
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loop_exit:
|
||||||
|
sb.Append(_chars, tok, _pos - 1 - tok);
|
||||||
|
|
||||||
|
var replacement = sb.ToString();
|
||||||
|
|
||||||
|
return replacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsNameChar(char c)
|
||||||
|
{
|
||||||
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
|
||||||
|
}
|
||||||
|
|
||||||
|
// (?) notice or log if variable is not found
|
||||||
|
private static string EvalVariable(Context context, string name)
|
||||||
|
{
|
||||||
|
if (!s_variables.TryGetValue(name, out var getter))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
return getter(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string EvalVariable(Context context, Variable variable)
|
||||||
|
{
|
||||||
|
return EvalVariable(context, variable.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string EvalVariable(Context context, SlicedVariable variable)
|
||||||
|
{
|
||||||
|
if (!s_slicedVariables.TryGetValue(variable.name, out var getter))
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
return getter(context, variable.count);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string EvalVariable(Context context, RegexVariable variable)
|
||||||
|
{
|
||||||
|
var str = EvalVariable(context, variable.name);
|
||||||
|
if (string.IsNullOrEmpty(str))
|
||||||
|
return str;
|
||||||
|
return variable.regex.Replace(str, variable.replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int _pos = 0;
|
||||||
|
private char[] _chars = [];
|
||||||
|
private readonly List<object> _tokens = [];
|
||||||
|
|
||||||
|
private delegate string VariableGetter(Context context);
|
||||||
|
|
||||||
|
private static readonly IReadOnlyDictionary<string, VariableGetter> s_variables = new Dictionary<string, VariableGetter>() {
|
||||||
|
// legacy variables
|
||||||
|
{"branch_name", GetBranchName},
|
||||||
|
{"files_num", GetFilesCount},
|
||||||
|
{"files", GetFiles},
|
||||||
|
//
|
||||||
|
{"BRANCH", GetBranchName},
|
||||||
|
{"FILES_COUNT", GetFilesCount},
|
||||||
|
{"FILES", GetFiles},
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string GetBranchName(Context context)
|
||||||
|
{
|
||||||
|
return context.branch.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetFilesCount(Context context)
|
||||||
|
{
|
||||||
|
return context.changes.Count.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetFiles(Context context)
|
||||||
|
{
|
||||||
|
var paths = new List<string>();
|
||||||
|
foreach (var c in context.changes)
|
||||||
|
paths.Add(c.Path);
|
||||||
|
return string.Join(", ", paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
private delegate string VariableSliceGetter(Context context, int count);
|
||||||
|
|
||||||
|
private static readonly IReadOnlyDictionary<string, VariableSliceGetter> s_slicedVariables = new Dictionary<string, VariableSliceGetter>() {
|
||||||
|
// legacy variables
|
||||||
|
{"files", GetFilesSliced},
|
||||||
|
//
|
||||||
|
{"FILES", GetFilesSliced},
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string GetFilesSliced(Context context, int count)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
var paths = new List<string>();
|
||||||
|
var max = Math.Min(count, context.changes.Count);
|
||||||
|
for (int i = 0; i < max; i++)
|
||||||
|
paths.Add(context.changes[i].Path);
|
||||||
|
|
||||||
|
sb.AppendJoin(", ", paths);
|
||||||
|
if (max < context.changes.Count)
|
||||||
|
sb.AppendFormat(" and {0} other files", context.changes.Count - max);
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -79,6 +79,7 @@
|
||||||
<StreamGeometry x:Key="Icons.Move">M299 811 299 725 384 725 384 811 299 811M469 811 469 725 555 725 555 811 469 811M640 811 640 725 725 725 725 811 640 811M299 640 299 555 384 555 384 640 299 640M469 640 469 555 555 555 555 640 469 640M640 640 640 555 725 555 725 640 640 640M299 469 299 384 384 384 384 469 299 469M469 469 469 384 555 384 555 469 469 469M640 469 640 384 725 384 725 469 640 469M299 299 299 213 384 213 384 299 299 299M469 299 469 213 555 213 555 299 469 299M640 299 640 213 725 213 725 299 640 299Z</StreamGeometry>
|
<StreamGeometry x:Key="Icons.Move">M299 811 299 725 384 725 384 811 299 811M469 811 469 725 555 725 555 811 469 811M640 811 640 725 725 725 725 811 640 811M299 640 299 555 384 555 384 640 299 640M469 640 469 555 555 555 555 640 469 640M640 640 640 555 725 555 725 640 640 640M299 469 299 384 384 384 384 469 299 469M469 469 469 384 555 384 555 469 469 469M640 469 640 384 725 384 725 469 640 469M299 299 299 213 384 213 384 299 299 299M469 299 469 213 555 213 555 299 469 299M640 299 640 213 725 213 725 299 640 299Z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icons.MoveToAnotherGroup">M64 363l0 204 265 0L329 460c0-11 6-18 14-20C349 437 355 437 362 441c93 60 226 149 226 149 33 22 34 60 0 82 0 0-133 89-226 149-14 9-32-3-32-18l-1-110L64 693l0 117c0 41 34 75 75 75l746 0c41 0 75-34 75-74L960 364c0-0 0-1 0-1L64 363zM64 214l0 75 650 0-33-80c-16-38-62-69-103-69l-440 0C97 139 64 173 64 214z</StreamGeometry>
|
<StreamGeometry x:Key="Icons.MoveToAnotherGroup">M64 363l0 204 265 0L329 460c0-11 6-18 14-20C349 437 355 437 362 441c93 60 226 149 226 149 33 22 34 60 0 82 0 0-133 89-226 149-14 9-32-3-32-18l-1-110L64 693l0 117c0 41 34 75 75 75l746 0c41 0 75-34 75-74L960 364c0-0 0-1 0-1L64 363zM64 214l0 75 650 0-33-80c-16-38-62-69-103-69l-440 0C97 139 64 173 64 214z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icons.OpenWith">M683 409v204L1024 308 683 0v191c-413 0-427 526-427 526c117-229 203-307 427-307zm85 492H102V327h153s38-63 114-122H51c-28 0-51 27-51 61v697c0 34 23 61 51 61h768c28 0 51-27 51-61V614l-102 100v187z</StreamGeometry>
|
<StreamGeometry x:Key="Icons.OpenWith">M683 409v204L1024 308 683 0v191c-413 0-427 526-427 526c117-229 203-307 427-307zm85 492H102V327h153s38-63 114-122H51c-28 0-51 27-51 61v697c0 34 23 61 51 61h768c28 0 51-27 51-61V614l-102 100v187z</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="Icons.Order">M841 627A43 43 0 00811 555h-299v85h196l-183 183A43 43 0 00555 896h299v-85h-196l183-183zM299 170H213v512H85l171 171 171-171H299zM725 128h-85c-18 0-34 11-40 28l-117 313h91L606 384h154l32 85h91l-117-313A43 43 0 00725 128zm-88 171 32-85h26l32 85h-90z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icons.Password">M640 96c-158 0-288 130-288 288 0 17 3 31 5 46L105 681 96 691V928h224v-96h96v-96h96v-95c38 18 82 31 128 31 158 0 288-130 288-288s-130-288-288-288zm0 64c123 0 224 101 224 224s-101 224-224 224a235 235 0 01-109-28l-8-4H448v96h-96v96H256v96H160v-146l253-254 12-11-3-17C419 417 416 400 416 384c0-123 101-224 224-224zm64 96a64 64 0 100 128 64 64 0 100-128z</StreamGeometry>
|
<StreamGeometry x:Key="Icons.Password">M640 96c-158 0-288 130-288 288 0 17 3 31 5 46L105 681 96 691V928h224v-96h96v-96h96v-95c38 18 82 31 128 31 158 0 288-130 288-288s-130-288-288-288zm0 64c123 0 224 101 224 224s-101 224-224 224a235 235 0 01-109-28l-8-4H448v96h-96v96H256v96H160v-146l253-254 12-11-3-17C419 417 416 400 416 384c0-123 101-224 224-224zm64 96a64 64 0 100 128 64 64 0 100-128z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icons.Paste">M544 85c49 0 90 37 95 85h75a96 96 0 0196 89L811 267a32 32 0 01-28 32L779 299a32 32 0 01-32-28L747 267a32 32 0 00-28-32L715 235h-91a96 96 0 01-80 42H395c-33 0-62-17-80-42L224 235a32 32 0 00-32 28L192 267v576c0 16 12 30 28 32l4 0h128a32 32 0 0132 28l0 4a32 32 0 01-32 32h-128a96 96 0 01-96-89L128 843V267a96 96 0 0189-96L224 171h75a96 96 0 0195-85h150zm256 256a96 96 0 0196 89l0 7v405a96 96 0 01-89 96L800 939h-277a96 96 0 01-96-89L427 843v-405a96 96 0 0189-96L523 341h277zm-256-192H395a32 32 0 000 64h150a32 32 0 100-64z</StreamGeometry>
|
<StreamGeometry x:Key="Icons.Paste">M544 85c49 0 90 37 95 85h75a96 96 0 0196 89L811 267a32 32 0 01-28 32L779 299a32 32 0 01-32-28L747 267a32 32 0 00-28-32L715 235h-91a96 96 0 01-80 42H395c-33 0-62-17-80-42L224 235a32 32 0 00-32 28L192 267v576c0 16 12 30 28 32l4 0h128a32 32 0 0132 28l0 4a32 32 0 01-32 32h-128a96 96 0 01-96-89L128 843V267a96 96 0 0189-96L224 171h75a96 96 0 0195-85h150zm256 256a96 96 0 0196 89l0 7v405a96 96 0 01-89 96L800 939h-277a96 96 0 01-96-89L427 843v-405a96 96 0 0189-96L523 341h277zm-256-192H395a32 32 0 000 64h150a32 32 0 100-64z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="Icons.Plus">m186 532 287 0 0 287c0 11 9 20 20 20s20-9 20-20l0-287 287 0c11 0 20-9 20-20s-9-20-20-20l-287 0 0-287c0-11-9-20-20-20s-20 9-20 20l0 287-287 0c-11 0-20 9-20 20s9 20 20 20z</StreamGeometry>
|
<StreamGeometry x:Key="Icons.Plus">m186 532 287 0 0 287c0 11 9 20 20 20s20-9 20-20l0-287 287 0c11 0 20-9 20-20s-9-20-20-20l-287 0 0-287c0-11-9-20-20-20s-20 9-20 20l0 287-287 0c-11 0-20 9-20 20s9 20 20 20z</StreamGeometry>
|
||||||
|
|
|
@ -125,6 +125,7 @@
|
||||||
<x:String x:Key="Text.CommitDetail.Info" xml:space="preserve">INFORMATION</x:String>
|
<x:String x:Key="Text.CommitDetail.Info" xml:space="preserve">INFORMATION</x:String>
|
||||||
<x:String x:Key="Text.CommitDetail.Info.Author" xml:space="preserve">AUTHOR</x:String>
|
<x:String x:Key="Text.CommitDetail.Info.Author" xml:space="preserve">AUTHOR</x:String>
|
||||||
<x:String x:Key="Text.CommitDetail.Info.Changed" xml:space="preserve">CHANGED</x:String>
|
<x:String x:Key="Text.CommitDetail.Info.Changed" xml:space="preserve">CHANGED</x:String>
|
||||||
|
<x:String x:Key="Text.CommitDetail.Info.Children" xml:space="preserve">CHILDREN</x:String>
|
||||||
<x:String x:Key="Text.CommitDetail.Info.Committer" xml:space="preserve">COMMITTER</x:String>
|
<x:String x:Key="Text.CommitDetail.Info.Committer" xml:space="preserve">COMMITTER</x:String>
|
||||||
<x:String x:Key="Text.CommitDetail.Info.ContainsIn" xml:space="preserve">Check refs that contains this commit</x:String>
|
<x:String x:Key="Text.CommitDetail.Info.ContainsIn" xml:space="preserve">Check refs that contains this commit</x:String>
|
||||||
<x:String x:Key="Text.CommitDetail.Info.ContainsIn.Title" xml:space="preserve">COMMIT IS CONTAINED BY</x:String>
|
<x:String x:Key="Text.CommitDetail.Info.ContainsIn.Title" xml:space="preserve">COMMIT IS CONTAINED BY</x:String>
|
||||||
|
@ -451,6 +452,7 @@
|
||||||
<x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">Language</x:String>
|
<x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">Language</x:String>
|
||||||
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">History Commits</x:String>
|
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">History Commits</x:String>
|
||||||
<x:String x:Key="Text.Preference.General.ShowAuthorTime" xml:space="preserve">Show author time intead of commit time in graph</x:String>
|
<x:String x:Key="Text.Preference.General.ShowAuthorTime" xml:space="preserve">Show author time intead of commit time in graph</x:String>
|
||||||
|
<x:String x:Key="Text.Preference.General.ShowChildren" xml:space="preserve">Show children in the comment details</x:String>
|
||||||
<x:String x:Key="Text.Preference.General.SubjectGuideLength" xml:space="preserve">Subject Guide Length</x:String>
|
<x:String x:Key="Text.Preference.General.SubjectGuideLength" xml:space="preserve">Subject Guide Length</x:String>
|
||||||
<x:String x:Key="Text.Preference.Git" xml:space="preserve">GIT</x:String>
|
<x:String x:Key="Text.Preference.Git" xml:space="preserve">GIT</x:String>
|
||||||
<x:String x:Key="Text.Preference.Git.CRLF" xml:space="preserve">Enable Auto CRLF</x:String>
|
<x:String x:Key="Text.Preference.Git.CRLF" xml:space="preserve">Enable Auto CRLF</x:String>
|
||||||
|
@ -543,6 +545,9 @@
|
||||||
<x:String x:Key="Text.Repository.FilterCommits.Default" xml:space="preserve">Unset</x:String>
|
<x:String x:Key="Text.Repository.FilterCommits.Default" xml:space="preserve">Unset</x:String>
|
||||||
<x:String x:Key="Text.Repository.FilterCommits.Exclude" xml:space="preserve">Hide in commit graph</x:String>
|
<x:String x:Key="Text.Repository.FilterCommits.Exclude" xml:space="preserve">Hide in commit graph</x:String>
|
||||||
<x:String x:Key="Text.Repository.FilterCommits.Include" xml:space="preserve">Filter in commit graph</x:String>
|
<x:String x:Key="Text.Repository.FilterCommits.Include" xml:space="preserve">Filter in commit graph</x:String>
|
||||||
|
<x:String x:Key="Text.Repository.HistoriesOrder" xml:space="preserve">Switch Order Mode</x:String>
|
||||||
|
<x:String x:Key="Text.Repository.HistoriesOrder.ByDate" xml:space="preserve">Commit Date (--date-order)</x:String>
|
||||||
|
<x:String x:Key="Text.Repository.HistoriesOrder.Topo" xml:space="preserve">Topologically (--topo-order)</x:String>
|
||||||
<x:String x:Key="Text.Repository.LocalBranches" xml:space="preserve">LOCAL BRANCHES</x:String>
|
<x:String x:Key="Text.Repository.LocalBranches" xml:space="preserve">LOCAL BRANCHES</x:String>
|
||||||
<x:String x:Key="Text.Repository.NavigateToCurrentHead" xml:space="preserve">Navigate to HEAD</x:String>
|
<x:String x:Key="Text.Repository.NavigateToCurrentHead" xml:space="preserve">Navigate to HEAD</x:String>
|
||||||
<x:String x:Key="Text.Repository.FirstParentFilterToggle" xml:space="preserve">Enable '--first-parent' Option</x:String>
|
<x:String x:Key="Text.Repository.FirstParentFilterToggle" xml:space="preserve">Enable '--first-parent' Option</x:String>
|
||||||
|
|
|
@ -126,6 +126,7 @@
|
||||||
<x:String x:Key="Text.CommitDetail.Info" xml:space="preserve">INFORMATIONS</x:String>
|
<x:String x:Key="Text.CommitDetail.Info" xml:space="preserve">INFORMATIONS</x:String>
|
||||||
<x:String x:Key="Text.CommitDetail.Info.Author" xml:space="preserve">AUTEUR</x:String>
|
<x:String x:Key="Text.CommitDetail.Info.Author" xml:space="preserve">AUTEUR</x:String>
|
||||||
<x:String x:Key="Text.CommitDetail.Info.Changed" xml:space="preserve">CHANGÉ</x:String>
|
<x:String x:Key="Text.CommitDetail.Info.Changed" xml:space="preserve">CHANGÉ</x:String>
|
||||||
|
<x:String x:Key="Text.CommitDetail.Info.Children" xml:space="preserve">ENFANTS</x:String>
|
||||||
<x:String x:Key="Text.CommitDetail.Info.Committer" xml:space="preserve">COMMITTER</x:String>
|
<x:String x:Key="Text.CommitDetail.Info.Committer" xml:space="preserve">COMMITTER</x:String>
|
||||||
<x:String x:Key="Text.CommitDetail.Info.ContainsIn" xml:space="preserve">Vérifier les références contenant ce commit</x:String>
|
<x:String x:Key="Text.CommitDetail.Info.ContainsIn" xml:space="preserve">Vérifier les références contenant ce commit</x:String>
|
||||||
<x:String x:Key="Text.CommitDetail.Info.ContainsIn.Title" xml:space="preserve">LE COMMIT EST CONTENU PAR</x:String>
|
<x:String x:Key="Text.CommitDetail.Info.ContainsIn.Title" xml:space="preserve">LE COMMIT EST CONTENU PAR</x:String>
|
||||||
|
|
|
@ -547,6 +547,9 @@
|
||||||
<x:String x:Key="Text.Repository.FilterCommits.Default" xml:space="preserve">不指定</x:String>
|
<x:String x:Key="Text.Repository.FilterCommits.Default" xml:space="preserve">不指定</x:String>
|
||||||
<x:String x:Key="Text.Repository.FilterCommits.Exclude" xml:space="preserve">在提交列表中隐藏</x:String>
|
<x:String x:Key="Text.Repository.FilterCommits.Exclude" xml:space="preserve">在提交列表中隐藏</x:String>
|
||||||
<x:String x:Key="Text.Repository.FilterCommits.Include" xml:space="preserve">使用其对提交列表过滤</x:String>
|
<x:String x:Key="Text.Repository.FilterCommits.Include" xml:space="preserve">使用其对提交列表过滤</x:String>
|
||||||
|
<x:String x:Key="Text.Repository.HistoriesOrder" xml:space="preserve">切换排序模式</x:String>
|
||||||
|
<x:String x:Key="Text.Repository.HistoriesOrder.ByDate" xml:space="preserve">按提交时间 (--date-order)</x:String>
|
||||||
|
<x:String x:Key="Text.Repository.HistoriesOrder.Topo" xml:space="preserve">按拓扑排序 (--topo-order)</x:String>
|
||||||
<x:String x:Key="Text.Repository.LocalBranches" xml:space="preserve">本地分支</x:String>
|
<x:String x:Key="Text.Repository.LocalBranches" xml:space="preserve">本地分支</x:String>
|
||||||
<x:String x:Key="Text.Repository.NavigateToCurrentHead" xml:space="preserve">定位HEAD</x:String>
|
<x:String x:Key="Text.Repository.NavigateToCurrentHead" xml:space="preserve">定位HEAD</x:String>
|
||||||
<x:String x:Key="Text.Repository.FirstParentFilterToggle" xml:space="preserve">启用 --first-parent 过滤选项</x:String>
|
<x:String x:Key="Text.Repository.FirstParentFilterToggle" xml:space="preserve">启用 --first-parent 过滤选项</x:String>
|
||||||
|
|
|
@ -546,6 +546,9 @@
|
||||||
<x:String x:Key="Text.Repository.FilterCommits.Default" xml:space="preserve">不指定</x:String>
|
<x:String x:Key="Text.Repository.FilterCommits.Default" xml:space="preserve">不指定</x:String>
|
||||||
<x:String x:Key="Text.Repository.FilterCommits.Exclude" xml:space="preserve">在提交清單中隱藏</x:String>
|
<x:String x:Key="Text.Repository.FilterCommits.Exclude" xml:space="preserve">在提交清單中隱藏</x:String>
|
||||||
<x:String x:Key="Text.Repository.FilterCommits.Include" xml:space="preserve">使用其來篩選提交清單</x:String>
|
<x:String x:Key="Text.Repository.FilterCommits.Include" xml:space="preserve">使用其來篩選提交清單</x:String>
|
||||||
|
<x:String x:Key="Text.Repository.HistoriesOrder" xml:space="preserve">切換排序方式</x:String>
|
||||||
|
<x:String x:Key="Text.Repository.HistoriesOrder.ByDate" xml:space="preserve">按提交时间排序 (--date-order)</x:String>
|
||||||
|
<x:String x:Key="Text.Repository.HistoriesOrder.Topo" xml:space="preserve">按拓扑排序 (--topo-order)</x:String>
|
||||||
<x:String x:Key="Text.Repository.LocalBranches" xml:space="preserve">本機分支</x:String>
|
<x:String x:Key="Text.Repository.LocalBranches" xml:space="preserve">本機分支</x:String>
|
||||||
<x:String x:Key="Text.Repository.NavigateToCurrentHead" xml:space="preserve">回到 HEAD</x:String>
|
<x:String x:Key="Text.Repository.NavigateToCurrentHead" xml:space="preserve">回到 HEAD</x:String>
|
||||||
<x:String x:Key="Text.Repository.FirstParentFilterToggle" xml:space="preserve">啟用 [--first-parent] 選項</x:String>
|
<x:String x:Key="Text.Repository.FirstParentFilterToggle" xml:space="preserve">啟用 [--first-parent] 選項</x:String>
|
||||||
|
|
|
@ -78,6 +78,12 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AvaloniaList<string> Children
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
private set;
|
||||||
|
} = new AvaloniaList<string>();
|
||||||
|
|
||||||
public string SearchChangeFilter
|
public string SearchChangeFilter
|
||||||
{
|
{
|
||||||
get => _searchChangeFilter;
|
get => _searchChangeFilter;
|
||||||
|
@ -515,6 +521,7 @@ namespace SourceGit.ViewModels
|
||||||
VisibleChanges = null;
|
VisibleChanges = null;
|
||||||
SelectedChanges = null;
|
SelectedChanges = null;
|
||||||
ViewRevisionFileContent = null;
|
ViewRevisionFileContent = null;
|
||||||
|
Children.Clear();
|
||||||
|
|
||||||
if (_commit == null)
|
if (_commit == null)
|
||||||
return;
|
return;
|
||||||
|
@ -535,6 +542,18 @@ namespace SourceGit.ViewModels
|
||||||
_cancelToken.Requested = true;
|
_cancelToken.Requested = true;
|
||||||
|
|
||||||
_cancelToken = new Commands.Command.CancelToken();
|
_cancelToken = new Commands.Command.CancelToken();
|
||||||
|
|
||||||
|
if (Preference.Instance.ShowChildren)
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
var cmdChildren = new Commands.QueryCommitChildren(_repo.FullPath, _commit.SHA, _repo.Settings.BuildHistoriesFilter()) { Cancel = _cancelToken };
|
||||||
|
var children = cmdChildren.Result();
|
||||||
|
if (!cmdChildren.Cancel.Requested)
|
||||||
|
Dispatcher.UIThread.Post(() => Children.AddRange(children));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
var parent = _commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : _commit.Parents[0];
|
var parent = _commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : _commit.Parents[0];
|
||||||
|
|
|
@ -64,7 +64,8 @@ namespace SourceGit.ViewModels
|
||||||
|
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
var commits = new Commands.QueryCommits(_repo.FullPath, $"-n 10000 {commit} -- \"{file}\"", false).Result();
|
var based = commit ?? string.Empty;
|
||||||
|
var commits = new Commands.QueryCommits(_repo.FullPath, false, $"-n 10000 {based} -- \"{file}\"", false).Result();
|
||||||
Dispatcher.UIThread.Invoke(() =>
|
Dispatcher.UIThread.Invoke(() =>
|
||||||
{
|
{
|
||||||
IsLoading = false;
|
IsLoading = false;
|
||||||
|
|
|
@ -294,6 +294,12 @@ namespace SourceGit.ViewModels
|
||||||
set => SetProperty(ref _statisticsSampleColor, value);
|
set => SetProperty(ref _statisticsSampleColor, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ShowChildren
|
||||||
|
{
|
||||||
|
get => _showChildren;
|
||||||
|
set => SetProperty(ref _showChildren, value);
|
||||||
|
}
|
||||||
|
|
||||||
public List<RepositoryNode> RepositoryNodes
|
public List<RepositoryNode> RepositoryNodes
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
@ -617,5 +623,7 @@ namespace SourceGit.ViewModels
|
||||||
private string _externalMergeToolPath = string.Empty;
|
private string _externalMergeToolPath = string.Empty;
|
||||||
|
|
||||||
private uint _statisticsSampleColor = 0xFF00FF00;
|
private uint _statisticsSampleColor = 0xFF00FF00;
|
||||||
|
|
||||||
|
private bool _showChildren = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,16 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool EnableTopoOrderInHistories
|
||||||
|
{
|
||||||
|
get => _enableTopoOrderInHistories;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _enableTopoOrderInHistories, value))
|
||||||
|
Task.Run(RefreshCommits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string Filter
|
public string Filter
|
||||||
{
|
{
|
||||||
get => _filter;
|
get => _filter;
|
||||||
|
@ -852,7 +862,7 @@ namespace SourceGit.ViewModels
|
||||||
else
|
else
|
||||||
builder.Append(filters);
|
builder.Append(filters);
|
||||||
|
|
||||||
var commits = new Commands.QueryCommits(_fullpath, builder.ToString()).Result();
|
var commits = new Commands.QueryCommits(_fullpath, _enableTopoOrderInHistories, builder.ToString()).Result();
|
||||||
var graph = Models.CommitGraph.Parse(commits, _enableFirstParentInHistories);
|
var graph = Models.CommitGraph.Parse(commits, _enableFirstParentInHistories);
|
||||||
|
|
||||||
Dispatcher.UIThread.Invoke(() =>
|
Dispatcher.UIThread.Invoke(() =>
|
||||||
|
@ -2228,6 +2238,7 @@ namespace SourceGit.ViewModels
|
||||||
private bool _onlySearchCommitsInCurrentBranch = false;
|
private bool _onlySearchCommitsInCurrentBranch = false;
|
||||||
private bool _enableReflog = false;
|
private bool _enableReflog = false;
|
||||||
private bool _enableFirstParentInHistories = false;
|
private bool _enableFirstParentInHistories = false;
|
||||||
|
private bool _enableTopoOrderInHistories = false;
|
||||||
private string _searchCommitFilter = string.Empty;
|
private string _searchCommitFilter = string.Empty;
|
||||||
private List<Models.Commit> _searchedCommits = new List<Models.Commit>();
|
private List<Models.Commit> _searchedCommits = new List<Models.Commit>();
|
||||||
private List<string> _revisionFiles = new List<string>();
|
private List<string> _revisionFiles = new List<string>();
|
||||||
|
|
|
@ -54,7 +54,7 @@ namespace SourceGit.ViewModels
|
||||||
{
|
{
|
||||||
Task.Run(() =>
|
Task.Run(() =>
|
||||||
{
|
{
|
||||||
var result = new Commands.Statistics(repo).Result();
|
var result = new Commands.Statistics(repo, Preference.Instance.MaxHistoryCommits).Result();
|
||||||
Dispatcher.UIThread.Invoke(() =>
|
Dispatcher.UIThread.Invoke(() =>
|
||||||
{
|
{
|
||||||
_data = result;
|
_data = result;
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
<Rectangle Height=".65" Margin="8" Fill="{DynamicResource Brush.Border2}" VerticalAlignment="Center"/>
|
<Rectangle Height=".65" Margin="8" Fill="{DynamicResource Brush.Border2}" VerticalAlignment="Center"/>
|
||||||
|
|
||||||
<!-- Base Information -->
|
<!-- Base Information -->
|
||||||
<Grid RowDefinitions="24,Auto,Auto,Auto" ColumnDefinitions="96,*">
|
<Grid RowDefinitions="24,Auto,Auto,Auto,Auto" ColumnDefinitions="96,*">
|
||||||
<!-- SHA -->
|
<!-- SHA -->
|
||||||
<TextBlock Grid.Row="0" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.SHA}" />
|
<TextBlock Grid.Row="0" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.SHA}" />
|
||||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal">
|
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal">
|
||||||
|
@ -102,50 +102,97 @@
|
||||||
|
|
||||||
<!-- PARENTS -->
|
<!-- PARENTS -->
|
||||||
<TextBlock Grid.Row="1" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Parents}" IsVisible="{Binding Parents.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"/>
|
<TextBlock Grid.Row="1" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Parents}" IsVisible="{Binding Parents.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"/>
|
||||||
<ItemsControl Grid.Row="1" Grid.Column="1" Height="24" Margin="12,0,0,0" ItemsSource="{Binding Parents}" IsVisible="{Binding Parents.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}">
|
<ScrollViewer Grid.Row="1" Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Hidden" AllowAutoHide="True">
|
||||||
<ItemsControl.ItemsPanel>
|
<ItemsControl Height="24" Margin="12,0,0,0" ItemsSource="{Binding Parents}" IsVisible="{Binding Parents.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}">
|
||||||
<ItemsPanelTemplate>
|
<ItemsControl.ItemsPanel>
|
||||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
|
<ItemsPanelTemplate>
|
||||||
</ItemsPanelTemplate>
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
|
||||||
</ItemsControl.ItemsPanel>
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Classes="primary"
|
<TextBlock Classes="primary"
|
||||||
Text="{Binding Converter={x:Static c:StringConverters.ToShortSHA}}"
|
Text="{Binding Converter={x:Static c:StringConverters.ToShortSHA}}"
|
||||||
Foreground="DarkOrange"
|
Foreground="DarkOrange"
|
||||||
TextDecorations="Underline"
|
TextDecorations="Underline"
|
||||||
Cursor="Hand"
|
Cursor="Hand"
|
||||||
Margin="0,0,16,0"
|
Margin="0,0,16,0"
|
||||||
PointerEntered="OnSHAPointerEntered"
|
PointerEntered="OnSHAPointerEntered"
|
||||||
PointerPressed="OnSHAPressed">
|
PointerPressed="OnSHAPressed">
|
||||||
<TextBlock.Styles>
|
<TextBlock.Styles>
|
||||||
<Style Selector="ToolTip">
|
<Style Selector="ToolTip">
|
||||||
<Setter Property="MaxWidth" Value="600"/>
|
<Setter Property="MaxWidth" Value="600"/>
|
||||||
</Style>
|
</Style>
|
||||||
</TextBlock.Styles>
|
</TextBlock.Styles>
|
||||||
|
|
||||||
<TextBlock.DataTemplates>
|
<TextBlock.DataTemplates>
|
||||||
<DataTemplate DataType="m:Commit">
|
<DataTemplate DataType="m:Commit">
|
||||||
<StackPanel MinWidth="400" Orientation="Vertical">
|
<StackPanel MinWidth="400" Orientation="Vertical">
|
||||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||||
<v:Avatar Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" IsHitTestVisible="False" User="{Binding Author}"/>
|
<v:Avatar Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" IsHitTestVisible="False" User="{Binding Author}"/>
|
||||||
<TextBlock Grid.Column="1" Classes="primary" Text="{Binding Author.Name}" Margin="8,0,0,0"/>
|
<TextBlock Grid.Column="1" Classes="primary" Text="{Binding Author.Name}" Margin="8,0,0,0"/>
|
||||||
<TextBlock Grid.Column="2" Classes="primary" Text="{Binding CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
|
<TextBlock Grid.Column="2" Classes="primary" Text="{Binding CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<TextBlock Classes="primary" Margin="0,8,0,0" Text="{Binding Subject}" TextWrapping="Wrap"/>
|
<TextBlock Classes="primary" Margin="0,8,0,0" Text="{Binding Subject}" TextWrapping="Wrap"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</TextBlock.DataTemplates>
|
</TextBlock.DataTemplates>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ItemsControl.ItemTemplate>
|
</ItemsControl.ItemTemplate>
|
||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<!-- CHILDREN -->
|
||||||
|
<TextBlock Grid.Row="2" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Children}" IsVisible="{Binding #ThisControl.Children.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"/>
|
||||||
|
<ScrollViewer Grid.Row="2" Grid.Column="1" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Hidden" AllowAutoHide="True">
|
||||||
|
<ItemsControl Height="24" Margin="12,0,0,0" ItemsSource="{Binding #ThisControl.Children}" IsVisible="{Binding #ThisControl.Children.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}">
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Classes="primary"
|
||||||
|
Text="{Binding Converter={x:Static c:StringConverters.ToShortSHA}}"
|
||||||
|
Foreground="DarkOrange"
|
||||||
|
TextDecorations="Underline"
|
||||||
|
Cursor="Hand"
|
||||||
|
Margin="0,0,16,0"
|
||||||
|
PointerEntered="OnSHAPointerEntered"
|
||||||
|
PointerPressed="OnSHAPressed">
|
||||||
|
<TextBlock.Styles>
|
||||||
|
<Style Selector="ToolTip">
|
||||||
|
<Setter Property="MaxWidth" Value="600"/>
|
||||||
|
</Style>
|
||||||
|
</TextBlock.Styles>
|
||||||
|
|
||||||
|
<TextBlock.DataTemplates>
|
||||||
|
<DataTemplate DataType="m:Commit">
|
||||||
|
<StackPanel MinWidth="400" Orientation="Vertical">
|
||||||
|
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||||
|
<v:Avatar Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" IsHitTestVisible="False" User="{Binding Author}"/>
|
||||||
|
<TextBlock Grid.Column="1" Classes="primary" Text="{Binding Author.Name}" Margin="8,0,0,0"/>
|
||||||
|
<TextBlock Grid.Column="2" Classes="primary" Text="{Binding CommitterTimeStr}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<TextBlock Classes="primary" Margin="0,8,0,0" Text="{Binding Subject}" TextWrapping="Wrap"/>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</TextBlock.DataTemplates>
|
||||||
|
</TextBlock>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
<!-- REFS -->
|
<!-- REFS -->
|
||||||
<TextBlock Grid.Row="2" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Refs}" IsVisible="{Binding HasDecorators}"/>
|
<TextBlock Grid.Row="3" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Refs}" IsVisible="{Binding HasDecorators}"/>
|
||||||
<Border Grid.Row="2" Grid.Column="1" Margin="12,0,0,0" Height="24" IsVisible="{Binding HasDecorators}">
|
<Border Grid.Row="3" Grid.Column="1" Margin="12,0,0,0" Height="24" IsVisible="{Binding HasDecorators}">
|
||||||
<v:CommitRefsPresenter TagBackground="{DynamicResource Brush.DecoratorTag}"
|
<v:CommitRefsPresenter TagBackground="{DynamicResource Brush.DecoratorTag}"
|
||||||
Foreground="{DynamicResource Brush.FG1}"
|
Foreground="{DynamicResource Brush.FG1}"
|
||||||
FontFamily="{DynamicResource Fonts.Primary}"
|
FontFamily="{DynamicResource Fonts.Primary}"
|
||||||
|
@ -155,8 +202,8 @@
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
<!-- Messages -->
|
<!-- Messages -->
|
||||||
<TextBlock Grid.Row="3" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Message}" VerticalAlignment="Top" Margin="0,4,0,0" />
|
<TextBlock Grid.Row="4" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Message}" VerticalAlignment="Top" Margin="0,4,0,0" />
|
||||||
<v:CommitMessagePresenter Grid.Row="4" Grid.Column="1"
|
<v:CommitMessagePresenter Grid.Row="5" Grid.Column="1"
|
||||||
Margin="12,5,8,0"
|
Margin="12,5,8,0"
|
||||||
Classes="primary"
|
Classes="primary"
|
||||||
Message="{Binding #ThisControl.Message}"
|
Message="{Binding #ThisControl.Message}"
|
||||||
|
|
|
@ -55,6 +55,15 @@ namespace SourceGit.Views
|
||||||
set => SetValue(IssueTrackerRulesProperty, value);
|
set => SetValue(IssueTrackerRulesProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<AvaloniaList<string>> ChildrenProperty =
|
||||||
|
AvaloniaProperty.Register<CommitBaseInfo, AvaloniaList<string>>(nameof(Children));
|
||||||
|
|
||||||
|
public AvaloniaList<string> Children
|
||||||
|
{
|
||||||
|
get => GetValue(ChildrenProperty);
|
||||||
|
set => SetValue(ChildrenProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
public CommitBaseInfo()
|
public CommitBaseInfo()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
SignInfo="{Binding SignInfo}"
|
SignInfo="{Binding SignInfo}"
|
||||||
SupportsContainsIn="True"
|
SupportsContainsIn="True"
|
||||||
WebLinks="{Binding WebLinks}"
|
WebLinks="{Binding WebLinks}"
|
||||||
|
Children="{Binding Children}"
|
||||||
IssueTrackerRules="{Binding IssueTrackerRules}"/>
|
IssueTrackerRules="{Binding IssueTrackerRules}"/>
|
||||||
|
|
||||||
<!-- Line -->
|
<!-- Line -->
|
||||||
|
|
|
@ -38,7 +38,7 @@ namespace SourceGit.Views
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<IBrush> BackgroundProperty =
|
public static readonly StyledProperty<IBrush> BackgroundProperty =
|
||||||
AvaloniaProperty.Register<CommitRefsPresenter, IBrush>(nameof(Background), null);
|
AvaloniaProperty.Register<CommitRefsPresenter, IBrush>(nameof(Background), Brushes.Transparent);
|
||||||
|
|
||||||
public IBrush Background
|
public IBrush Background
|
||||||
{
|
{
|
||||||
|
@ -56,7 +56,7 @@ namespace SourceGit.Views
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> UseGraphColorProperty =
|
public static readonly StyledProperty<bool> UseGraphColorProperty =
|
||||||
AvaloniaProperty.Register<CommitRefsPresenter, bool>(nameof(UseGraphColor), false);
|
AvaloniaProperty.Register<CommitRefsPresenter, bool>(nameof(UseGraphColor));
|
||||||
|
|
||||||
public bool UseGraphColor
|
public bool UseGraphColor
|
||||||
{
|
{
|
||||||
|
@ -96,7 +96,6 @@ namespace SourceGit.Views
|
||||||
var x = 1.0;
|
var x = 1.0;
|
||||||
foreach (var item in _items)
|
foreach (var item in _items)
|
||||||
{
|
{
|
||||||
var iconRect = new RoundedRect(new Rect(x, 0, 16, 16), new CornerRadius(2, 0, 0, 2));
|
|
||||||
var entireRect = new RoundedRect(new Rect(x, 0, item.Width, 16), new CornerRadius(2));
|
var entireRect = new RoundedRect(new Rect(x, 0, item.Width, 16), new CornerRadius(2));
|
||||||
|
|
||||||
if (item.IsHead)
|
if (item.IsHead)
|
||||||
|
|
|
@ -80,7 +80,7 @@
|
||||||
<Grid RowDefinitions="Auto,*">
|
<Grid RowDefinitions="Auto,*">
|
||||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto">
|
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto">
|
||||||
<v:Avatar Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" IsHitTestVisible="False" User="{Binding Author}"/>
|
<v:Avatar Grid.Column="0" Width="16" Height="16" VerticalAlignment="Center" IsHitTestVisible="False" User="{Binding Author}"/>
|
||||||
<TextBlock Grid.Column="1" Classes="primary" Text="{Binding Author.Name}" Margin="8,0,0,0" ClipToBounds="True"/>
|
<TextBlock Grid.Column="1" Classes="primary" Text="{Binding Author.Name}" Margin="8,0,0,0" TextTrimming="CharacterEllipsis"/>
|
||||||
<TextBlock Grid.Column="2"
|
<TextBlock Grid.Column="2"
|
||||||
Classes="primary"
|
Classes="primary"
|
||||||
Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}"
|
Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}"
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
<ItemsControl ItemsSource="{Binding Notifications}">
|
<ItemsControl ItemsSource="{Binding Notifications}">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate DataType="m:Notification">
|
<DataTemplate DataType="m:Notification">
|
||||||
<Border Margin="6" HorizontalAlignment="Stretch" VerticalAlignment="Top" Effect="drop-shadow(0 0 12 #A0000000)">
|
<Border Margin="6" HorizontalAlignment="Stretch" VerticalAlignment="Top" Effect="drop-shadow(0 0 8 #8F000000)">
|
||||||
<Border Padding="8" CornerRadius="6" Background="{DynamicResource Brush.Popup}">
|
<Border Padding="8" CornerRadius="6" Background="{DynamicResource Brush.Popup}">
|
||||||
<Grid RowDefinitions="26,Auto">
|
<Grid RowDefinitions="26,Auto">
|
||||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Margin="8,0">
|
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto" Margin="8,0">
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
<TabItem.Header>
|
<TabItem.Header>
|
||||||
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.General}"/>
|
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.General}"/>
|
||||||
</TabItem.Header>
|
</TabItem.Header>
|
||||||
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32" ColumnDefinitions="Auto,*">
|
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32" ColumnDefinitions="Auto,*">
|
||||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||||
Text="{DynamicResource Text.Preference.General.Locale}"
|
Text="{DynamicResource Text.Preference.General.Locale}"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
|
@ -114,6 +114,11 @@
|
||||||
Height="32"
|
Height="32"
|
||||||
Content="{DynamicResource Text.Preference.General.Check4UpdatesOnStartup}"
|
Content="{DynamicResource Text.Preference.General.Check4UpdatesOnStartup}"
|
||||||
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=Check4UpdatesOnStartup, Mode=TwoWay}"/>
|
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=Check4UpdatesOnStartup, Mode=TwoWay}"/>
|
||||||
|
|
||||||
|
<CheckBox Grid.Row="6" Grid.Column="1"
|
||||||
|
Height="32"
|
||||||
|
Content="{DynamicResource Text.Preference.General.ShowChildren}"
|
||||||
|
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowChildren, Mode=TwoWay}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
||||||
|
@ -188,7 +193,7 @@
|
||||||
</Border>
|
</Border>
|
||||||
</NumericUpDown.InnerLeftContent>
|
</NumericUpDown.InnerLeftContent>
|
||||||
</NumericUpDown>
|
</NumericUpDown>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<TextBlock Grid.Row="4" Grid.Column="0"
|
<TextBlock Grid.Row="4" Grid.Column="0"
|
||||||
Text="{DynamicResource Text.Preference.Appearance.ThemeOverrides}"
|
Text="{DynamicResource Text.Preference.Appearance.ThemeOverrides}"
|
||||||
|
|
|
@ -67,7 +67,7 @@
|
||||||
</ListBox.ItemsPanel>
|
</ListBox.ItemsPanel>
|
||||||
|
|
||||||
<ListBoxItem>
|
<ListBoxItem>
|
||||||
<Grid Classes="view_mode" ColumnDefinitions="32,*,Auto,Auto,Auto">
|
<Grid Classes="view_mode" ColumnDefinitions="32,*,Auto,Auto,Auto,Auto">
|
||||||
<Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Histories}"/>
|
<Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.Histories}"/>
|
||||||
<TextBlock Grid.Column="1" Classes="primary" Text="{DynamicResource Text.Histories}"/>
|
<TextBlock Grid.Column="1" Classes="primary" Text="{DynamicResource Text.Histories}"/>
|
||||||
<ToggleButton Grid.Column="2"
|
<ToggleButton Grid.Column="2"
|
||||||
|
@ -91,6 +91,13 @@
|
||||||
ToolTip.Tip="{DynamicResource Text.Repository.FirstParentFilterToggle}">
|
ToolTip.Tip="{DynamicResource Text.Repository.FirstParentFilterToggle}">
|
||||||
<Path Width="12" Height="12" Data="{StaticResource Icons.FirstParentFilter}"/>
|
<Path Width="12" Height="12" Data="{StaticResource Icons.FirstParentFilter}"/>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
|
<Button Grid.Column="5"
|
||||||
|
Classes="icon_button"
|
||||||
|
Width="28" Height="26"
|
||||||
|
Click="OnSwitchHistoriesOrderClicked"
|
||||||
|
ToolTip.Tip="{DynamicResource Text.Repository.HistoriesOrder}">
|
||||||
|
<Path Width="12" Height="12" Margin="0,2,0,0" Data="{StaticResource Icons.Order}"/>
|
||||||
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ListBoxItem>
|
</ListBoxItem>
|
||||||
|
|
||||||
|
|
|
@ -395,5 +395,38 @@ namespace SourceGit.Views
|
||||||
}
|
}
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnSwitchHistoriesOrderClicked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button && DataContext is ViewModels.Repository repo)
|
||||||
|
{
|
||||||
|
var checkIcon = App.CreateMenuIcon("Icons.Check");
|
||||||
|
|
||||||
|
var dateOrder = new MenuItem();
|
||||||
|
dateOrder.Header = App.Text("Repository.HistoriesOrder.ByDate");
|
||||||
|
dateOrder.Icon = repo.EnableTopoOrderInHistories ? null : checkIcon;
|
||||||
|
dateOrder.Click += (_, ev) =>
|
||||||
|
{
|
||||||
|
repo.EnableTopoOrderInHistories = false;
|
||||||
|
ev.Handled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
var topoOrder = new MenuItem();
|
||||||
|
topoOrder.Header = App.Text("Repository.HistoriesOrder.Topo");
|
||||||
|
topoOrder.Icon = repo.EnableTopoOrderInHistories ? checkIcon : null;
|
||||||
|
topoOrder.Click += (_, ev) =>
|
||||||
|
{
|
||||||
|
repo.EnableTopoOrderInHistories = true;
|
||||||
|
ev.Handled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
var menu = new ContextMenu();
|
||||||
|
menu.Items.Add(dateOrder);
|
||||||
|
menu.Items.Add(topoOrder);
|
||||||
|
menu.Open(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,16 +37,19 @@
|
||||||
Margin="0,0,8,0"
|
Margin="0,0,8,0"
|
||||||
Text="{DynamicResource Text.Reset.Mode}"/>
|
Text="{DynamicResource Text.Reset.Mode}"/>
|
||||||
<ComboBox Grid.Row="2" Grid.Column="1"
|
<ComboBox Grid.Row="2" Grid.Column="1"
|
||||||
|
x:Name="ResetMode"
|
||||||
Height="28" Padding="8,0"
|
Height="28" Padding="8,0"
|
||||||
VerticalAlignment="Center" HorizontalAlignment="Stretch"
|
VerticalAlignment="Center" HorizontalAlignment="Stretch"
|
||||||
ItemsSource="{Binding Source={x:Static m:ResetMode.Supported}}"
|
ItemsSource="{Binding Source={x:Static m:ResetMode.Supported}}"
|
||||||
SelectedItem="{Binding SelectedMode, Mode=TwoWay}">
|
SelectedItem="{Binding SelectedMode, Mode=TwoWay}"
|
||||||
|
KeyDown="OnResetModeKeyDown">
|
||||||
<ComboBox.ItemTemplate>
|
<ComboBox.ItemTemplate>
|
||||||
<DataTemplate DataType="m:ResetMode">
|
<DataTemplate DataType="m:ResetMode">
|
||||||
<Grid ColumnDefinitions="16,60,*">
|
<Grid ColumnDefinitions="16,60,*">
|
||||||
<Ellipse Grid.Column="0" Width="12" Height="12" Fill="{Binding Color}"/>
|
<Ellipse Grid.Column="0" Width="12" Height="12" Fill="{Binding Color}"/>
|
||||||
<TextBlock Grid.Column="1" Text="{Binding Name}" Margin="4,0,0,0"/>
|
<TextBlock Grid.Column="1" Text="{Binding Name}" Margin="2,0,0,0"/>
|
||||||
<TextBlock Grid.Column="2" Text="{Binding Desc}" FontSize="11" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Right"/>
|
<TextBlock Grid.Column="2" Text="{Binding Desc}" Margin="2,0,16,0" FontSize="11" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Right"/>
|
||||||
|
<TextBlock Grid.Column="3" Text="{Binding Key}" FontSize="11" FontWeight="Bold" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Right"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ComboBox.ItemTemplate>
|
</ComboBox.ItemTemplate>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
namespace SourceGit.Views
|
namespace SourceGit.Views
|
||||||
{
|
{
|
||||||
|
@ -8,5 +10,29 @@ namespace SourceGit.Views
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnLoaded(RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnLoaded(e);
|
||||||
|
|
||||||
|
ResetMode.Focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnResetModeKeyDown(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is ComboBox comboBox)
|
||||||
|
{
|
||||||
|
var key = e.Key.ToString();
|
||||||
|
for (int i = 0; i < Models.ResetMode.Supported.Length; i++)
|
||||||
|
{
|
||||||
|
if (key.Equals(Models.ResetMode.Supported[i].Key, System.StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
comboBox.SelectedIndex = i;
|
||||||
|
e.Handled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,27 +13,39 @@
|
||||||
<ContentControl x:Name="Editor">
|
<ContentControl x:Name="Editor">
|
||||||
<ContentControl.DataTemplates>
|
<ContentControl.DataTemplates>
|
||||||
<DataTemplate DataType="m:TextDiff">
|
<DataTemplate DataType="m:TextDiff">
|
||||||
<v:CombinedTextDiffPresenter FileName="{Binding File}"
|
<Grid ColumnDefinitions="*,1,8">
|
||||||
Foreground="{DynamicResource Brush.FG1}"
|
<v:CombinedTextDiffPresenter Grid.Column="0"
|
||||||
LineBrush="{DynamicResource Brush.Border2}"
|
x:Name="CombinedPresenter"
|
||||||
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
|
FileName="{Binding File}"
|
||||||
AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}"
|
Foreground="{DynamicResource Brush.FG1}"
|
||||||
DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}"
|
LineBrush="{DynamicResource Brush.Border2}"
|
||||||
AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}"
|
EmptyContentBackground="{DynamicResource Brush.Diff.EmptyBG}"
|
||||||
DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}"
|
AddedContentBackground="{DynamicResource Brush.Diff.AddedBG}"
|
||||||
IndicatorForeground="{DynamicResource Brush.FG2}"
|
DeletedContentBackground="{DynamicResource Brush.Diff.DeletedBG}"
|
||||||
FontFamily="{DynamicResource Fonts.Monospace}"
|
AddedHighlightBrush="{DynamicResource Brush.Diff.AddedHighlight}"
|
||||||
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=EditorFontSize}"
|
DeletedHighlightBrush="{DynamicResource Brush.Diff.DeletedHighlight}"
|
||||||
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
|
IndicatorForeground="{DynamicResource Brush.FG2}"
|
||||||
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"
|
FontFamily="{DynamicResource Fonts.Monospace}"
|
||||||
ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=EditorFontSize}"
|
||||||
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting}"
|
||||||
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
|
WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}"
|
||||||
|
ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
||||||
|
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
||||||
|
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
|
||||||
|
|
||||||
|
<Rectangle Grid.Column="1" Fill="{DynamicResource Brush.Border2}" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch"/>
|
||||||
|
|
||||||
|
<v:TextDiffViewMinimap Grid.Column="2"
|
||||||
|
DisplayRange="{Binding #CombinedPresenter.DisplayRange}"
|
||||||
|
AddedLineBrush="{DynamicResource Brush.Diff.AddedBG}"
|
||||||
|
DeletedLineBrush="{DynamicResource Brush.Diff.DeletedBG}"/>
|
||||||
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|
||||||
<DataTemplate DataType="vm:TwoSideTextDiff">
|
<DataTemplate DataType="vm:TwoSideTextDiff">
|
||||||
<Grid ColumnDefinitions="*,1,*">
|
<Grid ColumnDefinitions="*,1,*,1,12">
|
||||||
<v:SingleSideTextDiffPresenter Grid.Column="0"
|
<v:SingleSideTextDiffPresenter Grid.Column="0"
|
||||||
|
x:Name="LeftSidePresenter"
|
||||||
IsOld="True"
|
IsOld="True"
|
||||||
FileName="{Binding File}"
|
FileName="{Binding File}"
|
||||||
Foreground="{DynamicResource Brush.FG1}"
|
Foreground="{DynamicResource Brush.FG1}"
|
||||||
|
@ -72,15 +84,42 @@
|
||||||
ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}"
|
||||||
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}"
|
||||||
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
|
SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/>
|
||||||
|
|
||||||
|
<Rectangle Grid.Column="3" Fill="{DynamicResource Brush.Border2}" Width="1" HorizontalAlignment="Center" VerticalAlignment="Stretch"/>
|
||||||
|
|
||||||
|
<v:TextDiffViewMinimap Grid.Column="4"
|
||||||
|
DisplayRange="{Binding #LeftSidePresenter.DisplayRange}"
|
||||||
|
AddedLineBrush="{DynamicResource Brush.Diff.AddedBG}"
|
||||||
|
DeletedLineBrush="{DynamicResource Brush.Diff.DeletedBG}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ContentControl.DataTemplates>
|
</ContentControl.DataTemplates>
|
||||||
</ContentControl>
|
</ContentControl>
|
||||||
|
|
||||||
<StackPanel x:Name="Popup" IsVisible="False" Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Right" Effect="drop-shadow(0 0 8 #80000000)">
|
<StackPanel x:Name="Popup" IsVisible="False" Orientation="Horizontal" VerticalAlignment="Top" HorizontalAlignment="Right" Effect="drop-shadow(0 0 8 #80000000)">
|
||||||
<Button Classes="flat" Content="{DynamicResource Text.Hunk.Stage}" Click="OnStageChunk" IsVisible="{Binding #ThisControl.IsUnstagedChange}"/>
|
<Button Classes="flat" Click="OnStageChunk" HotKey="{OnPlatform Ctrl+S, macOS=⌘+S}" IsVisible="{Binding #ThisControl.IsUnstagedChange}">
|
||||||
<Button Classes="flat" Content="{DynamicResource Text.Hunk.Unstage}" Click="OnUnstageChunk" IsVisible="{Binding #ThisControl.IsUnstagedChange, Converter={x:Static BoolConverters.Not}}"/>
|
<TextBlock>
|
||||||
<Button Classes="flat" Content="{DynamicResource Text.Hunk.Discard}" Margin="8,0,0,0" Click="OnDiscardChunk" IsVisible="{Binding #ThisControl.IsUnstagedChange}"/>
|
<Run Text="{DynamicResource Text.Hunk.Stage}"/>
|
||||||
|
<Run Text=" "/>
|
||||||
|
<Run Foreground="{DynamicResource Brush.FG2}" FontWeight="Normal" Text="{OnPlatform Ctrl+S, macOS=⌘+S}"/>
|
||||||
|
</TextBlock>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button Classes="flat" Click="OnUnstageChunk" HotKey="{OnPlatform Ctrl+U, macOS=⌘+U}" IsVisible="{Binding #ThisControl.IsUnstagedChange, Converter={x:Static BoolConverters.Not}}">
|
||||||
|
<TextBlock>
|
||||||
|
<Run Text="{DynamicResource Text.Hunk.Unstage}"/>
|
||||||
|
<Run Text=" "/>
|
||||||
|
<Run Foreground="{DynamicResource Brush.FG2}" FontWeight="Normal" Text="{OnPlatform Ctrl+U, macOS=⌘+U}"/>
|
||||||
|
</TextBlock>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button Classes="flat" Margin="8,0,0,0" HotKey="{OnPlatform Ctrl+D, macOS=⌘+D}" Click="OnDiscardChunk" IsVisible="{Binding #ThisControl.IsUnstagedChange}">
|
||||||
|
<TextBlock>
|
||||||
|
<Run Text="{DynamicResource Text.Hunk.Discard}"/>
|
||||||
|
<Run Text=" "/>
|
||||||
|
<Run Foreground="{DynamicResource Brush.FG2}" FontWeight="Normal" Text="{OnPlatform Ctrl+D, macOS=⌘+D}"/>
|
||||||
|
</TextBlock>
|
||||||
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|
|
@ -45,6 +45,18 @@ namespace SourceGit.Views
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public record TextDiffViewRange
|
||||||
|
{
|
||||||
|
public int StartIdx { get; set; } = 0;
|
||||||
|
public int EndIdx { get; set; } = 0;
|
||||||
|
|
||||||
|
public TextDiffViewRange(int startIdx, int endIdx)
|
||||||
|
{
|
||||||
|
StartIdx = startIdx;
|
||||||
|
EndIdx = endIdx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class ThemedTextDiffPresenter : TextEditor
|
public class ThemedTextDiffPresenter : TextEditor
|
||||||
{
|
{
|
||||||
public class VerticalSeperatorMargin : AbstractMargin
|
public class VerticalSeperatorMargin : AbstractMargin
|
||||||
|
@ -210,7 +222,6 @@ namespace SourceGit.Views
|
||||||
if (presenter == null)
|
if (presenter == null)
|
||||||
return new Size(0, 0);
|
return new Size(0, 0);
|
||||||
|
|
||||||
var maxLineNumber = presenter.GetMaxLineNumber();
|
|
||||||
var typeface = TextView.CreateTypeface();
|
var typeface = TextView.CreateTypeface();
|
||||||
var test = new FormattedText(
|
var test = new FormattedText(
|
||||||
$"-",
|
$"-",
|
||||||
|
@ -465,6 +476,15 @@ namespace SourceGit.Views
|
||||||
get => GetValue(SelectedChunkProperty);
|
get => GetValue(SelectedChunkProperty);
|
||||||
set => SetValue(SelectedChunkProperty, value);
|
set => SetValue(SelectedChunkProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<TextDiffViewRange> DisplayRangeProperty =
|
||||||
|
AvaloniaProperty.Register<ThemedTextDiffPresenter, TextDiffViewRange>(nameof(DisplayRange), new TextDiffViewRange(0, 0));
|
||||||
|
|
||||||
|
public TextDiffViewRange DisplayRange
|
||||||
|
{
|
||||||
|
get => GetValue(DisplayRangeProperty);
|
||||||
|
set => SetValue(DisplayRangeProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
protected override Type StyleKeyOverride => typeof(TextEditor);
|
protected override Type StyleKeyOverride => typeof(TextEditor);
|
||||||
|
|
||||||
|
@ -500,25 +520,11 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
public void GotoPrevChange()
|
public void GotoPrevChange()
|
||||||
{
|
{
|
||||||
var view = TextArea.TextView;
|
var firstLineIdx = DisplayRange.StartIdx;
|
||||||
var lines = GetLines();
|
|
||||||
var firstLineIdx = lines.Count;
|
|
||||||
foreach (var line in view.VisualLines)
|
|
||||||
{
|
|
||||||
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var index = line.FirstDocumentLine.LineNumber - 1;
|
|
||||||
if (index >= lines.Count)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (firstLineIdx > index)
|
|
||||||
firstLineIdx = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (firstLineIdx <= 1)
|
if (firstLineIdx <= 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var lines = GetLines();
|
||||||
var firstLineType = lines[firstLineIdx].Type;
|
var firstLineType = lines[firstLineIdx].Type;
|
||||||
var prevLineType = lines[firstLineIdx - 1].Type;
|
var prevLineType = lines[firstLineIdx - 1].Type;
|
||||||
var isChangeFirstLine = firstLineType != Models.TextDiffLineType.Normal && firstLineType != Models.TextDiffLineType.Indicator;
|
var isChangeFirstLine = firstLineType != Models.TextDiffLineType.Normal && firstLineType != Models.TextDiffLineType.Indicator;
|
||||||
|
@ -557,22 +563,8 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
public void GotoNextChange()
|
public void GotoNextChange()
|
||||||
{
|
{
|
||||||
var view = TextArea.TextView;
|
|
||||||
var lines = GetLines();
|
var lines = GetLines();
|
||||||
var lastLineIdx = -1;
|
var lastLineIdx = DisplayRange.EndIdx;
|
||||||
foreach (var line in view.VisualLines)
|
|
||||||
{
|
|
||||||
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var index = line.FirstDocumentLine.LineNumber - 1;
|
|
||||||
if (index >= lines.Count)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (lastLineIdx < index)
|
|
||||||
lastLineIdx = index;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastLineIdx >= lines.Count - 1)
|
if (lastLineIdx >= lines.Count - 1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -624,6 +616,7 @@ namespace SourceGit.Views
|
||||||
TextArea.TextView.PointerEntered += OnTextViewPointerChanged;
|
TextArea.TextView.PointerEntered += OnTextViewPointerChanged;
|
||||||
TextArea.TextView.PointerMoved += OnTextViewPointerChanged;
|
TextArea.TextView.PointerMoved += OnTextViewPointerChanged;
|
||||||
TextArea.TextView.PointerWheelChanged += OnTextViewPointerWheelChanged;
|
TextArea.TextView.PointerWheelChanged += OnTextViewPointerWheelChanged;
|
||||||
|
TextArea.TextView.VisualLinesChanged += OnTextViewVisualLinesChanged;
|
||||||
|
|
||||||
UpdateTextMate();
|
UpdateTextMate();
|
||||||
}
|
}
|
||||||
|
@ -636,6 +629,7 @@ namespace SourceGit.Views
|
||||||
TextArea.TextView.PointerEntered -= OnTextViewPointerChanged;
|
TextArea.TextView.PointerEntered -= OnTextViewPointerChanged;
|
||||||
TextArea.TextView.PointerMoved -= OnTextViewPointerChanged;
|
TextArea.TextView.PointerMoved -= OnTextViewPointerChanged;
|
||||||
TextArea.TextView.PointerWheelChanged -= OnTextViewPointerWheelChanged;
|
TextArea.TextView.PointerWheelChanged -= OnTextViewPointerWheelChanged;
|
||||||
|
TextArea.TextView.VisualLinesChanged -= OnTextViewVisualLinesChanged;
|
||||||
|
|
||||||
if (_textMate != null)
|
if (_textMate != null)
|
||||||
{
|
{
|
||||||
|
@ -743,6 +737,34 @@ namespace SourceGit.Views
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnTextViewVisualLinesChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (!TextArea.TextView.VisualLinesValid)
|
||||||
|
{
|
||||||
|
SetCurrentValue(DisplayRangeProperty, new TextDiffViewRange(0, 0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lines = GetLines();
|
||||||
|
var start = int.MaxValue;
|
||||||
|
var count = 0;
|
||||||
|
foreach (var line in TextArea.TextView.VisualLines)
|
||||||
|
{
|
||||||
|
if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var index = line.FirstDocumentLine.LineNumber - 1;
|
||||||
|
if (index >= lines.Count)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
count++;
|
||||||
|
if (start > index)
|
||||||
|
start = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetCurrentValue(DisplayRangeProperty, new TextDiffViewRange(start, start + count));
|
||||||
|
}
|
||||||
|
|
||||||
protected void TrySetChunk(TextDiffViewChunk chunk)
|
protected void TrySetChunk(TextDiffViewChunk chunk)
|
||||||
{
|
{
|
||||||
var old = SelectedChunk;
|
var old = SelectedChunk;
|
||||||
|
@ -1050,12 +1072,8 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
private void OnTextViewScrollGotFocus(object sender, GotFocusEventArgs e)
|
private void OnTextViewScrollGotFocus(object sender, GotFocusEventArgs e)
|
||||||
{
|
{
|
||||||
if (EnableChunkSelection && sender is ScrollViewer viewer)
|
if (EnableChunkSelection && !TextArea.IsPointerOver)
|
||||||
{
|
TrySetChunk(null);
|
||||||
var area = viewer.FindDescendantOfType<TextArea>();
|
|
||||||
if (!area.IsPointerOver)
|
|
||||||
TrySetChunk(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1071,7 +1089,7 @@ namespace SourceGit.Views
|
||||||
public void ForceSyncScrollOffset()
|
public void ForceSyncScrollOffset()
|
||||||
{
|
{
|
||||||
if (DataContext is ViewModels.TwoSideTextDiff diff)
|
if (DataContext is ViewModels.TwoSideTextDiff diff)
|
||||||
diff.SyncScrollOffset = _scrollViewer.Offset;
|
diff.SyncScrollOffset = _scrollViewer?.Offset ?? Vector.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override List<Models.TextDiffLine> GetLines()
|
public override List<Models.TextDiffLine> GetLines()
|
||||||
|
@ -1277,18 +1295,14 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
private void OnTextViewScrollGotFocus(object sender, GotFocusEventArgs e)
|
private void OnTextViewScrollGotFocus(object sender, GotFocusEventArgs e)
|
||||||
{
|
{
|
||||||
if (EnableChunkSelection && sender is ScrollViewer viewer)
|
if (EnableChunkSelection && !TextArea.IsPointerOver)
|
||||||
{
|
TrySetChunk(null);
|
||||||
var area = viewer.FindDescendantOfType<TextArea>();
|
|
||||||
if (!area.IsPointerOver)
|
|
||||||
TrySetChunk(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e)
|
private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (TextArea.IsFocused && DataContext is ViewModels.TwoSideTextDiff diff)
|
if (TextArea.IsFocused && DataContext is ViewModels.TwoSideTextDiff diff)
|
||||||
diff.SyncScrollOffset = _scrollViewer.Offset;
|
diff.SyncScrollOffset = _scrollViewer?.Offset ?? Vector.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTextAreaPointerWheelChanged(object sender, PointerWheelEventArgs e)
|
private void OnTextAreaPointerWheelChanged(object sender, PointerWheelEventArgs e)
|
||||||
|
@ -1299,7 +1313,125 @@ namespace SourceGit.Views
|
||||||
|
|
||||||
private ScrollViewer _scrollViewer = null;
|
private ScrollViewer _scrollViewer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class TextDiffViewMinimap : Control
|
||||||
|
{
|
||||||
|
public static readonly StyledProperty<IBrush> AddedLineBrushProperty =
|
||||||
|
AvaloniaProperty.Register<TextDiffViewMinimap, IBrush>(nameof(AddedLineBrush), new SolidColorBrush(Color.FromArgb(60, 0, 255, 0)));
|
||||||
|
|
||||||
|
public IBrush AddedLineBrush
|
||||||
|
{
|
||||||
|
get => GetValue(AddedLineBrushProperty);
|
||||||
|
set => SetValue(AddedLineBrushProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<IBrush> DeletedLineBrushProperty =
|
||||||
|
AvaloniaProperty.Register<TextDiffViewMinimap, IBrush>(nameof(DeletedLineBrush), new SolidColorBrush(Color.FromArgb(60, 255, 0, 0)));
|
||||||
|
|
||||||
|
public IBrush DeletedLineBrush
|
||||||
|
{
|
||||||
|
get => GetValue(DeletedLineBrushProperty);
|
||||||
|
set => SetValue(DeletedLineBrushProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<TextDiffViewRange> DisplayRangeProperty =
|
||||||
|
AvaloniaProperty.Register<TextDiffViewMinimap, TextDiffViewRange>(nameof(DisplayRange), new TextDiffViewRange(0, 0));
|
||||||
|
|
||||||
|
public TextDiffViewRange DisplayRange
|
||||||
|
{
|
||||||
|
get => GetValue(DisplayRangeProperty);
|
||||||
|
set => SetValue(DisplayRangeProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<Color> DisplayRangeColorProperty =
|
||||||
|
AvaloniaProperty.Register<TextDiffViewMinimap, Color>(nameof(DisplayRangeColor), Colors.RoyalBlue);
|
||||||
|
|
||||||
|
public Color DisplayRangeColor
|
||||||
|
{
|
||||||
|
get => GetValue(DisplayRangeColorProperty);
|
||||||
|
set => SetValue(DisplayRangeColorProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static TextDiffViewMinimap()
|
||||||
|
{
|
||||||
|
AffectsRender<TextDiffViewMinimap>(
|
||||||
|
AddedLineBrushProperty,
|
||||||
|
DeletedLineBrushProperty,
|
||||||
|
DisplayRangeProperty,
|
||||||
|
DisplayRangeColorProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Render(DrawingContext context)
|
||||||
|
{
|
||||||
|
var total = 0;
|
||||||
|
if (DataContext is ViewModels.TwoSideTextDiff twoSideDiff)
|
||||||
|
{
|
||||||
|
var halfWidth = Bounds.Width * 0.5;
|
||||||
|
total = Math.Max(twoSideDiff.Old.Count, twoSideDiff.New.Count);
|
||||||
|
RenderSingleSide(context, twoSideDiff.Old, 0, halfWidth);
|
||||||
|
RenderSingleSide(context, twoSideDiff.New, halfWidth, halfWidth);
|
||||||
|
}
|
||||||
|
else if (DataContext is Models.TextDiff diff)
|
||||||
|
{
|
||||||
|
total = diff.Lines.Count;
|
||||||
|
RenderSingleSide(context, diff.Lines, 0, Bounds.Width);
|
||||||
|
}
|
||||||
|
|
||||||
|
var range = DisplayRange;
|
||||||
|
if (range.EndIdx == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var startY = range.StartIdx / (total * 1.0) * Bounds.Height;
|
||||||
|
var endY = range.EndIdx / (total * 1.0) * Bounds.Height;
|
||||||
|
var color = DisplayRangeColor;
|
||||||
|
var brush = new SolidColorBrush(color, 0.2);
|
||||||
|
var pen = new Pen(color.ToUInt32());
|
||||||
|
var rect = new Rect(0, startY, Bounds.Width, endY - startY);
|
||||||
|
|
||||||
|
context.DrawRectangle(brush, null, rect);
|
||||||
|
context.DrawLine(pen, rect.TopLeft, rect.TopRight);
|
||||||
|
context.DrawLine(pen, rect.BottomLeft, rect.BottomRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDataContextChanged(EventArgs e)
|
||||||
|
{
|
||||||
|
base.OnDataContextChanged(e);
|
||||||
|
InvalidateVisual();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderSingleSide(DrawingContext context, List<Models.TextDiffLine> lines, double x, double width)
|
||||||
|
{
|
||||||
|
var total = lines.Count;
|
||||||
|
var lastLineType = Models.TextDiffLineType.Indicator;
|
||||||
|
var lastLineTypeStart = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < total; i++)
|
||||||
|
{
|
||||||
|
var line = lines[i];
|
||||||
|
if (line.Type != lastLineType)
|
||||||
|
{
|
||||||
|
RenderBlock(context, lastLineType, lastLineTypeStart, i - lastLineTypeStart, total, x, width);
|
||||||
|
|
||||||
|
lastLineType = line.Type;
|
||||||
|
lastLineTypeStart = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderBlock(context, lastLineType, lastLineTypeStart, total - lastLineTypeStart, total, x, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RenderBlock(DrawingContext context, Models.TextDiffLineType type, int start, int count, int total, double x, double width)
|
||||||
|
{
|
||||||
|
if (type == Models.TextDiffLineType.Added || type == Models.TextDiffLineType.Deleted)
|
||||||
|
{
|
||||||
|
var brush = type == Models.TextDiffLineType.Added ? AddedLineBrush : DeletedLineBrush;
|
||||||
|
var y = start / (total * 1.0) * Bounds.Height;
|
||||||
|
var h = Math.Max(0.5, count / (total * 1.0) * Bounds.Height);
|
||||||
|
context.DrawRectangle(brush, null, new Rect(x, y, width, h));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public partial class TextDiffView : UserControl
|
public partial class TextDiffView : UserControl
|
||||||
{
|
{
|
||||||
public static readonly StyledProperty<bool> UseSideBySideDiffProperty =
|
public static readonly StyledProperty<bool> UseSideBySideDiffProperty =
|
||||||
|
@ -1383,7 +1515,7 @@ namespace SourceGit.Views
|
||||||
protected override void OnDataContextChanged(EventArgs e)
|
protected override void OnDataContextChanged(EventArgs e)
|
||||||
{
|
{
|
||||||
base.OnDataContextChanged(e);
|
base.OnDataContextChanged(e);
|
||||||
RefreshContent(DataContext as Models.TextDiff, true);
|
RefreshContent(DataContext as Models.TextDiff);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnPointerExited(PointerEventArgs e)
|
protected override void OnPointerExited(PointerEventArgs e)
|
||||||
|
|
Loading…
Reference in a new issue