Merge branch 'release/v8.13'

This commit is contained in:
leo 2024-05-20 09:23:55 +08:00
commit 12d7fa6670
22 changed files with 216 additions and 138 deletions

View file

@ -39,8 +39,8 @@ This software creates a folder `$"{System.Environment.SpecialFolder.ApplicationD
| OS | PATH | | OS | PATH |
| --- | --- | | --- | --- |
| Windows | `C:\Users\USER_NAME\AppData\Roaming\SourceGit` | | Windows | `C:\Users\USER_NAME\AppData\Roaming\SourceGit` |
| Linux | `/home/USER_NAME/.config/SourceGit` | | Linux | `${HOME}/.config/SourceGit` |
| macOS | `/Users/USER_NAME/.config/SourceGit` | | macOS | `${HOME}/Library/Application Support/SourceGit` |
For **Windows** users: For **Windows** users:

View file

@ -1 +1 @@
8.12 8.13

0
build/build.osx.command Normal file → Executable file
View file

View file

@ -24,7 +24,7 @@ chmod 755 -R $RPM_BUILD_ROOT
%files %files
/opt /opt
/usr/bin %attr(555,root,root)/usr/bin
/usr/share /usr/share
%changelog %changelog

Binary file not shown.

Before

Width:  |  Height:  |  Size: 519 KiB

After

Width:  |  Height:  |  Size: 559 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 KiB

After

Width:  |  Height:  |  Size: 569 KiB

View file

@ -26,12 +26,12 @@
<NativeMenu.Menu> <NativeMenu.Menu>
<NativeMenu> <NativeMenu>
<NativeMenuItem Header="{DynamicResource Text.About.Menu}" Command="{x:Static s:App.OpenAboutCommand}"/> <NativeMenuItem Header="{DynamicResource Text.About.Menu}" Command="{x:Static s:App.OpenAboutCommand}"/>
<NativeMenuItem Header="{DynamicResource Text.Hotkeys.Menu}" Command="{x:Static s:App.OpenHotkeysCommand}"/> <NativeMenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}"/>
<NativeMenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}"/> <NativeMenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}"/>
<NativeMenuItemSeparator/> <NativeMenuItemSeparator/>
<NativeMenuItem Header="{DynamicResource Text.Preference}" Command="{x:Static s:App.OpenPreferenceCommand}" Gesture="⌘+,"/> <NativeMenuItem Header="{DynamicResource Text.Preference}" Command="{x:Static s:App.OpenPreferenceCommand}" Gesture="⌘+,"/>
<NativeMenuItemSeparator/> <NativeMenuItemSeparator/>
<NativeMenuItem Header="{DynamicResource Text.Quit}" Command="{x:Static s:App.QuitCommand}"/> <NativeMenuItem Header="{DynamicResource Text.Quit}" Command="{x:Static s:App.QuitCommand}" Gesture="⌘+Q"/>
</NativeMenu> </NativeMenu>
</NativeMenu.Menu> </NativeMenu.Menu>
</Application> </Application>

View file

@ -266,6 +266,7 @@ namespace SourceGit
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{ {
BindingPlugins.DataValidators.RemoveAt(0); BindingPlugins.DataValidators.RemoveAt(0);
Native.OS.SetupEnternalTools();
var launcher = new Views.Launcher(); var launcher = new Views.Launcher();
_notificationReceiver = launcher; _notificationReceiver = launcher;

View file

@ -12,34 +12,23 @@ namespace SourceGit.Models
{ {
public class ExternalTool public class ExternalTool
{ {
public string Name { get; set; } = string.Empty; public string Name { get; private set; } = string.Empty;
public string Icon { get; set; } = string.Empty; public string Executable { get; private set; } = string.Empty;
public string Executable { get; set; } = string.Empty; public string OpenCmdArgs { get; private set; } = string.Empty;
public string OpenCmdArgs { get; set; } = string.Empty; public Bitmap IconImage { get; private set; } = null;
public Bitmap IconImage public ExternalTool(string name, string icon, string executable, string openCmdArgs)
{ {
get Name = name;
{ Executable = executable;
if (_isFirstTimeGetIcon) OpenCmdArgs = openCmdArgs;
{
_isFirstTimeGetIcon = false;
if (!string.IsNullOrWhiteSpace(Icon))
{
try try
{ {
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{Icon}.png", UriKind.RelativeOrAbsolute)); var asset = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{icon}.png", UriKind.RelativeOrAbsolute));
_iconImage = new Bitmap(icon); IconImage = new Bitmap(asset);
}
catch
{
}
}
}
return _iconImage;
} }
catch { }
} }
public void Open(string repo) public void Open(string repo)
@ -52,9 +41,6 @@ namespace SourceGit.Models
UseShellExecute = false, UseShellExecute = false,
}); });
} }
private bool _isFirstTimeGetIcon = true;
private Bitmap _iconImage = null;
} }
public class JetBrainsState public class JetBrainsState
@ -107,13 +93,7 @@ namespace SourceGit.Models
return; return;
} }
Founded.Add(new ExternalTool Founded.Add(new ExternalTool(name, icon, path, args));
{
Name = name,
Icon = icon,
OpenCmdArgs = args,
Executable = path
});
} }
public void VSCode(Func<string> platformFinder) public void VSCode(Func<string> platformFinder)
@ -154,13 +134,11 @@ namespace SourceGit.Models
if (exclude.Contains(tool.ToolId.ToLowerInvariant())) if (exclude.Contains(tool.ToolId.ToLowerInvariant()))
continue; continue;
Founded.Add(new ExternalTool Founded.Add(new ExternalTool(
{ $"{tool.DisplayName} {tool.DisplayVersion}",
Name = $"{tool.DisplayName} {tool.DisplayVersion}", supported_icons.Contains(tool.ProductCode) ? $"JetBrains/{tool.ProductCode}" : "JetBrains/JB",
Icon = supported_icons.Contains(tool.ProductCode) ? $"JetBrains/{tool.ProductCode}" : $"JetBrains/JB", Path.Combine(tool.InstallLocation, tool.LaunchCommand),
OpenCmdArgs = "\"{0}\"", "\"{0}\""));
Executable = Path.Combine(tool.InstallLocation, tool.LaunchCommand),
});
} }
} }
} }

View file

@ -25,20 +25,14 @@ namespace SourceGit.Models
public static User FindOrAdd(string data) public static User FindOrAdd(string data)
{ {
if (_caches.TryGetValue(data, out var value)) return _caches.GetOrAdd(data, key =>
{ {
return value; var nameEndIdx = key.IndexOf('<', System.StringComparison.Ordinal);
} var name = nameEndIdx >= 2 ? key.Substring(0, nameEndIdx - 1) : string.Empty;
else var email = key.Substring(nameEndIdx + 1);
{
var nameEndIdx = data.IndexOf('<', System.StringComparison.Ordinal);
var name = nameEndIdx >= 2 ? data.Substring(0, nameEndIdx - 1) : string.Empty;
var email = data.Substring(nameEndIdx + 1);
User user = new User() { Name = name, Email = email }; return new User() { Name = name, Email = email };
_caches.TryAdd(data, user); });
return user;
}
} }
private static ConcurrentDictionary<string, User> _caches = new ConcurrentDictionary<string, User>(); private static ConcurrentDictionary<string, User> _caches = new ConcurrentDictionary<string, User>();

View file

@ -43,6 +43,11 @@ namespace SourceGit.Native
DefaultFamilyName = "fonts:SourceGit#JetBrains Mono", DefaultFamilyName = "fonts:SourceGit#JetBrains Mono",
}); });
builder.With(new X11PlatformOptions()
{
EnableIme = true,
});
// Free-desktop file picker has an extra black background panel. // Free-desktop file picker has an extra black background panel.
builder.UseManagedSystemDialogs(); builder.UseManagedSystemDialogs();
} }

View file

@ -41,8 +41,6 @@ namespace SourceGit.Native
{ {
throw new Exception("Platform unsupported!!!"); throw new Exception("Platform unsupported!!!");
} }
ExternalTools = _backend.FindExternalTools();
} }
public static Models.Shell GetShell() public static Models.Shell GetShell()
@ -77,6 +75,11 @@ namespace SourceGit.Native
_backend.SetupApp(builder); _backend.SetupApp(builder);
} }
public static void SetupEnternalTools()
{
ExternalTools = _backend.FindExternalTools();
}
public static string FindGitExecutable() public static string FindGitExecutable()
{ {
return _backend.FindGitExecutable(); return _backend.FindGitExecutable();

View file

@ -210,8 +210,7 @@
<x:String x:Key="Text.Histories.Search" xml:space="preserve">SEARCH SHA/SUBJECT/AUTHOR. PRESS ENTER TO SEARCH, ESC TO QUIT</x:String> <x:String x:Key="Text.Histories.Search" xml:space="preserve">SEARCH SHA/SUBJECT/AUTHOR. PRESS ENTER TO SEARCH, ESC TO QUIT</x:String>
<x:String x:Key="Text.Histories.SearchClear" xml:space="preserve">CLEAR</x:String> <x:String x:Key="Text.Histories.SearchClear" xml:space="preserve">CLEAR</x:String>
<x:String x:Key="Text.Histories.Selected" xml:space="preserve">SELECTED {0} COMMITS</x:String> <x:String x:Key="Text.Histories.Selected" xml:space="preserve">SELECTED {0} COMMITS</x:String>
<x:String x:Key="Text.Hotkeys" xml:space="preserve">HotKeys</x:String> <x:String x:Key="Text.Hotkeys" xml:space="preserve">Keyboard Shortcuts Reference</x:String>
<x:String x:Key="Text.Hotkeys.Menu" xml:space="preserve">About HotKeys</x:String>
<x:String x:Key="Text.Hotkeys.Global" xml:space="preserve">GLOBAL</x:String> <x:String x:Key="Text.Hotkeys.Global" xml:space="preserve">GLOBAL</x:String>
<x:String x:Key="Text.Hotkeys.Global.CancelPopup" xml:space="preserve">Cancel current popup</x:String> <x:String x:Key="Text.Hotkeys.Global.CancelPopup" xml:space="preserve">Cancel current popup</x:String>
<x:String x:Key="Text.Hotkeys.Global.CloseTab" xml:space="preserve">Close current page</x:String> <x:String x:Key="Text.Hotkeys.Global.CloseTab" xml:space="preserve">Close current page</x:String>
@ -343,6 +342,7 @@
<x:String x:Key="Text.Repository.Configure" xml:space="preserve">Configure this repository</x:String> <x:String x:Key="Text.Repository.Configure" xml:space="preserve">Configure this repository</x:String>
<x:String x:Key="Text.Repository.Continue" xml:space="preserve">CONTINUE</x:String> <x:String x:Key="Text.Repository.Continue" xml:space="preserve">CONTINUE</x:String>
<x:String x:Key="Text.Repository.Explore" xml:space="preserve">Open In File Browser</x:String> <x:String x:Key="Text.Repository.Explore" xml:space="preserve">Open In File Browser</x:String>
<x:String x:Key="Text.Repository.FilterBranchTip" xml:space="preserve">Filter Branches</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.NewBranch" xml:space="preserve">Create Branch</x:String> <x:String x:Key="Text.Repository.NewBranch" xml:space="preserve">Create Branch</x:String>

View file

@ -210,8 +210,7 @@
<x:String x:Key="Text.Histories.Search" xml:space="preserve">查询提交指纹、信息、作者。回车键开始ESC键取消</x:String> <x:String x:Key="Text.Histories.Search" xml:space="preserve">查询提交指纹、信息、作者。回车键开始ESC键取消</x:String>
<x:String x:Key="Text.Histories.SearchClear" xml:space="preserve">清空</x:String> <x:String x:Key="Text.Histories.SearchClear" xml:space="preserve">清空</x:String>
<x:String x:Key="Text.Histories.Selected" xml:space="preserve">已选中 {0} 项提交</x:String> <x:String x:Key="Text.Histories.Selected" xml:space="preserve">已选中 {0} 项提交</x:String>
<x:String x:Key="Text.Hotkeys" xml:space="preserve">快捷键</x:String> <x:String x:Key="Text.Hotkeys" xml:space="preserve">快捷键参考</x:String>
<x:String x:Key="Text.Hotkeys.Menu" xml:space="preserve">显示快捷键</x:String>
<x:String x:Key="Text.Hotkeys.Global" xml:space="preserve">全局快捷键</x:String> <x:String x:Key="Text.Hotkeys.Global" xml:space="preserve">全局快捷键</x:String>
<x:String x:Key="Text.Hotkeys.Global.CancelPopup" xml:space="preserve">取消弹出面板</x:String> <x:String x:Key="Text.Hotkeys.Global.CancelPopup" xml:space="preserve">取消弹出面板</x:String>
<x:String x:Key="Text.Hotkeys.Global.CloseTab" xml:space="preserve">关闭当前页面</x:String> <x:String x:Key="Text.Hotkeys.Global.CloseTab" xml:space="preserve">关闭当前页面</x:String>
@ -343,6 +342,7 @@
<x:String x:Key="Text.Repository.Configure" xml:space="preserve">配置本仓库</x:String> <x:String x:Key="Text.Repository.Configure" xml:space="preserve">配置本仓库</x:String>
<x:String x:Key="Text.Repository.Continue" xml:space="preserve">下一步</x:String> <x:String x:Key="Text.Repository.Continue" xml:space="preserve">下一步</x:String>
<x:String x:Key="Text.Repository.Explore" xml:space="preserve">在文件浏览器中打开</x:String> <x:String x:Key="Text.Repository.Explore" xml:space="preserve">在文件浏览器中打开</x:String>
<x:String x:Key="Text.Repository.FilterBranchTip" xml:space="preserve">过滤显示分支</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.NewBranch" xml:space="preserve">新建分支</x:String> <x:String x:Key="Text.Repository.NewBranch" xml:space="preserve">新建分支</x:String>

View file

@ -1,9 +1,9 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using Avalonia.Collections; using Avalonia.Collections;
namespace SourceGit.Models namespace SourceGit.ViewModels
{ {
public enum BranchTreeNodeType public enum BranchTreeNodeType
{ {
@ -23,12 +23,12 @@ namespace SourceGit.Models
public bool IsUpstreamTrackStatusVisible public bool IsUpstreamTrackStatusVisible
{ {
get => IsBranch && !string.IsNullOrEmpty((Backend as Branch).UpstreamTrackStatus); get => IsBranch && !string.IsNullOrEmpty((Backend as Models.Branch).UpstreamTrackStatus);
} }
public string UpstreamTrackStatus public string UpstreamTrackStatus
{ {
get => Type == BranchTreeNodeType.Branch ? (Backend as Branch).UpstreamTrackStatus : ""; get => Type == BranchTreeNodeType.Branch ? (Backend as Models.Branch).UpstreamTrackStatus : "";
} }
public bool IsRemote public bool IsRemote
@ -48,7 +48,7 @@ namespace SourceGit.Models
public bool IsCurrent public bool IsCurrent
{ {
get => IsBranch && (Backend as Branch).IsCurrent; get => IsBranch && (Backend as Models.Branch).IsCurrent;
} }
public class Builder public class Builder
@ -56,8 +56,10 @@ namespace SourceGit.Models
public List<BranchTreeNode> Locals => _locals; public List<BranchTreeNode> Locals => _locals;
public List<BranchTreeNode> Remotes => _remotes; public List<BranchTreeNode> Remotes => _remotes;
public void Run(List<Branch> branches, List<Remote> remotes) public void Run(List<Models.Branch> branches, List<Models.Remote> remotes, bool bForceExpanded)
{ {
var folders = new Dictionary<string, BranchTreeNode>();
foreach (var remote in remotes) foreach (var remote in remotes)
{ {
var path = $"remote/{remote.Name}"; var path = $"remote/{remote.Name}";
@ -66,10 +68,10 @@ namespace SourceGit.Models
Name = remote.Name, Name = remote.Name,
Type = BranchTreeNodeType.Remote, Type = BranchTreeNodeType.Remote,
Backend = remote, Backend = remote,
IsExpanded = _expanded.Contains(path), IsExpanded = bForceExpanded || _expanded.Contains(path),
}; };
_maps.Add(path, node); folders.Add(path, node);
_remotes.Add(node); _remotes.Add(node);
} }
@ -78,16 +80,17 @@ namespace SourceGit.Models
var isFiltered = _filters.Contains(branch.FullName); var isFiltered = _filters.Contains(branch.FullName);
if (branch.IsLocal) if (branch.IsLocal)
{ {
MakeBranchNode(branch, _locals, "local", isFiltered); MakeBranchNode(branch, _locals, folders, "local", isFiltered, bForceExpanded);
} }
else else
{ {
var remote = _remotes.Find(x => x.Name == branch.Remote); var remote = _remotes.Find(x => x.Name == branch.Remote);
if (remote != null) if (remote != null)
MakeBranchNode(branch, remote.Children, $"remote/{remote.Name}", isFiltered); MakeBranchNode(branch, remote.Children, folders, $"remote/{remote.Name}", isFiltered, bForceExpanded);
} }
} }
folders.Clear();
SortNodes(_locals); SortNodes(_locals);
SortNodes(_remotes); SortNodes(_remotes);
} }
@ -113,67 +116,69 @@ namespace SourceGit.Models
} }
} }
private void MakeBranchNode(Branch branch, List<BranchTreeNode> roots, string prefix, bool isFiltered) private void MakeBranchNode(Models.Branch branch, List<BranchTreeNode> roots, Dictionary<string, BranchTreeNode> folders, string prefix, bool isFiltered, bool bForceExpanded)
{ {
var subs = branch.Name.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries); var sepIdx = branch.Name.IndexOf('/', StringComparison.Ordinal);
if (sepIdx == -1)
if (subs.Length == 1)
{ {
var node = new BranchTreeNode() roots.Add(new BranchTreeNode()
{ {
Name = subs[0], Name = branch.Name,
Type = BranchTreeNodeType.Branch, Type = BranchTreeNodeType.Branch,
Backend = branch, Backend = branch,
IsExpanded = false, IsExpanded = false,
IsFiltered = isFiltered, IsFiltered = isFiltered,
}; });
roots.Add(node);
return; return;
} }
BranchTreeNode lastFolder = null; var lastFolder = null as BranchTreeNode;
string path = prefix; var start = 0;
for (int i = 0; i < subs.Length - 1; i++)
while (sepIdx != -1)
{ {
path = string.Concat(path, "/", subs[i]); var folder = string.Concat(prefix, "/", branch.Name.Substring(0, sepIdx));
if (_maps.TryGetValue(path, out var value)) var name = branch.Name.Substring(start, sepIdx - start);
if (folders.TryGetValue(folder, out var val))
{ {
lastFolder = value; lastFolder = val;
} }
else if (lastFolder == null) else if (lastFolder == null)
{ {
lastFolder = new BranchTreeNode() lastFolder = new BranchTreeNode()
{ {
Name = subs[i], Name = name,
Type = BranchTreeNodeType.Folder, Type = BranchTreeNodeType.Folder,
IsExpanded = branch.IsCurrent || _expanded.Contains(path), IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
}; };
roots.Add(lastFolder); roots.Add(lastFolder);
_maps.Add(path, lastFolder); folders.Add(folder, lastFolder);
} }
else else
{ {
var folder = new BranchTreeNode() var cur = new BranchTreeNode()
{ {
Name = subs[i], Name = name,
Type = BranchTreeNodeType.Folder, Type = BranchTreeNodeType.Folder,
IsExpanded = branch.IsCurrent || _expanded.Contains(path), IsExpanded = bForceExpanded || branch.IsCurrent || _expanded.Contains(folder),
}; };
_maps.Add(path, folder); lastFolder.Children.Add(cur);
lastFolder.Children.Add(folder); folders.Add(folder, cur);
lastFolder = folder; lastFolder = cur;
}
} }
var last = new BranchTreeNode() start = sepIdx + 1;
sepIdx = branch.Name.IndexOf('/', start);
}
lastFolder.Children.Add(new BranchTreeNode()
{ {
Name = subs[subs.Length - 1], Name = Path.GetFileName(branch.Name),
Type = BranchTreeNodeType.Branch, Type = BranchTreeNodeType.Branch,
Backend = branch, Backend = branch,
IsExpanded = false, IsExpanded = false,
IsFiltered = isFiltered, IsFiltered = isFiltered,
}; });
lastFolder.Children.Add(last);
} }
private void SortNodes(List<BranchTreeNode> nodes) private void SortNodes(List<BranchTreeNode> nodes)
@ -186,7 +191,7 @@ namespace SourceGit.Models
} }
else else
{ {
return (int)(l.Type) - (int)(r.Type); return (int)l.Type - (int)r.Type;
} }
}); });
@ -198,7 +203,6 @@ namespace SourceGit.Models
private readonly List<BranchTreeNode> _remotes = new List<BranchTreeNode>(); private readonly List<BranchTreeNode> _remotes = new List<BranchTreeNode>();
private readonly HashSet<string> _expanded = new HashSet<string>(); private readonly HashSet<string> _expanded = new HashSet<string>();
private readonly List<string> _filters = new List<string>(); private readonly List<string> _filters = new List<string>();
private readonly Dictionary<string, BranchTreeNode> _maps = new Dictionary<string, BranchTreeNode>();
} }
} }
} }

View file

@ -89,6 +89,21 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _selectedView, value); set => SetProperty(ref _selectedView, value);
} }
[JsonIgnore]
public string SearchBranchFilter
{
get => _searchBranchFilter;
set
{
if (SetProperty(ref _searchBranchFilter, value))
{
var builder = BuildBranchTree(_branches, _remotes);
LocalBranchTrees = builder.Locals;
RemoteBranchTrees = builder.Remotes;
}
}
}
[JsonIgnore] [JsonIgnore]
public List<Models.Remote> Remotes public List<Models.Remote> Remotes
{ {
@ -104,14 +119,14 @@ namespace SourceGit.ViewModels
} }
[JsonIgnore] [JsonIgnore]
public List<Models.BranchTreeNode> LocalBranchTrees public List<BranchTreeNode> LocalBranchTrees
{ {
get => _localBranchTrees; get => _localBranchTrees;
private set => SetProperty(ref _localBranchTrees, value); private set => SetProperty(ref _localBranchTrees, value);
} }
[JsonIgnore] [JsonIgnore]
public List<Models.BranchTreeNode> RemoteBranchTrees public List<BranchTreeNode> RemoteBranchTrees
{ {
get => _remoteBranchTrees; get => _remoteBranchTrees;
private set => SetProperty(ref _remoteBranchTrees, value); private set => SetProperty(ref _remoteBranchTrees, value);
@ -221,6 +236,7 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _hasUnsolvedConflicts, value); private set => SetProperty(ref _hasUnsolvedConflicts, value);
} }
[JsonIgnore]
public Models.Commit SearchResultSelectedCommit public Models.Commit SearchResultSelectedCommit
{ {
get => _searchResultSelectedCommit; get => _searchResultSelectedCommit;
@ -422,6 +438,11 @@ namespace SourceGit.ViewModels
SearchedCommits = visible; SearchedCommits = visible;
} }
public void ClearSearchBranchFilter()
{
SearchBranchFilter = string.Empty;
}
public void SetWatcherEnabled(bool enabled) public void SetWatcherEnabled(bool enabled)
{ {
if (_watcher != null) if (_watcher != null)
@ -533,12 +554,7 @@ namespace SourceGit.ViewModels
{ {
var branches = new Commands.QueryBranches(FullPath).Result(); var branches = new Commands.QueryBranches(FullPath).Result();
var remotes = new Commands.QueryRemotes(FullPath).Result(); var remotes = new Commands.QueryRemotes(FullPath).Result();
var builder = BuildBranchTree(branches, remotes);
var builder = new Models.BranchTreeNode.Builder();
builder.SetFilters(Filters);
builder.CollectExpandedNodes(_localBranchTrees, true);
builder.CollectExpandedNodes(_remoteBranchTrees, false);
builder.Run(branches, remotes);
Dispatcher.UIThread.Invoke(() => Dispatcher.UIThread.Invoke(() =>
{ {
@ -1354,6 +1370,32 @@ namespace SourceGit.ViewModels
return menu; return menu;
} }
private BranchTreeNode.Builder BuildBranchTree(List<Models.Branch> branches, List<Models.Remote> remotes)
{
var builder = new BranchTreeNode.Builder();
builder.SetFilters(Filters);
if (string.IsNullOrEmpty(_searchBranchFilter))
{
builder.CollectExpandedNodes(_localBranchTrees, true);
builder.CollectExpandedNodes(_remoteBranchTrees, false);
builder.Run(branches, remotes, false);
}
else
{
var visibles = new List<Models.Branch>();
foreach (var b in branches)
{
if (b.FullName.Contains(_searchBranchFilter, StringComparison.OrdinalIgnoreCase))
visibles.Add(b);
}
builder.Run(visibles, remotes, visibles.Count <= 20);
}
return builder;
}
private string _fullpath = string.Empty; private string _fullpath = string.Empty;
private string _gitDir = string.Empty; private string _gitDir = string.Empty;
private Models.GitFlow _gitflow = new Models.GitFlow(); private Models.GitFlow _gitflow = new Models.GitFlow();
@ -1372,10 +1414,12 @@ namespace SourceGit.ViewModels
private bool _isTagGroupExpanded = false; private bool _isTagGroupExpanded = false;
private bool _isSubmoduleGroupExpanded = false; private bool _isSubmoduleGroupExpanded = false;
private string _searchBranchFilter = string.Empty;
private List<Models.Remote> _remotes = new List<Models.Remote>(); private List<Models.Remote> _remotes = new List<Models.Remote>();
private List<Models.Branch> _branches = new List<Models.Branch>(); private List<Models.Branch> _branches = new List<Models.Branch>();
private List<Models.BranchTreeNode> _localBranchTrees = new List<Models.BranchTreeNode>(); private List<BranchTreeNode> _localBranchTrees = new List<BranchTreeNode>();
private List<Models.BranchTreeNode> _remoteBranchTrees = new List<Models.BranchTreeNode>(); private List<BranchTreeNode> _remoteBranchTrees = new List<BranchTreeNode>();
private List<Models.Tag> _tags = new List<Models.Tag>(); private List<Models.Tag> _tags = new List<Models.Tag>();
private List<string> _submodules = new List<string>(); private List<string> _submodules = new List<string>();
private bool _canCommitWithPush = false; private bool _canCommitWithPush = false;

View file

@ -68,7 +68,7 @@
Margin="0,0,0,8"/> Margin="0,0,0,8"/>
<Grid RowDefinitions="20,20,20,20,20,20" ColumnDefinitions="150,*"> <Grid RowDefinitions="20,20,20,20,20,20" ColumnDefinitions="150,*">
<TextBlock Grid.Row="0" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+Shift+T, macOS=⌘+,}"/> <TextBlock Grid.Row="0" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+Shift+P, macOS=⌘+\,}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.OpenPreference}"/> <TextBlock Grid.Row="0" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Global.OpenPreference}"/>
<TextBlock Grid.Row="1" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+T, macOS=⌘+T}"/> <TextBlock Grid.Row="1" Grid.Column="0" Classes="monospace bold" Text="{OnPlatform Ctrl+T, macOS=⌘+T}"/>

View file

@ -222,9 +222,12 @@ namespace SourceGit.Views
} }
private void BeginMoveWindow(object sender, PointerPressedEventArgs e) private void BeginMoveWindow(object sender, PointerPressedEventArgs e)
{
if (e.ClickCount != 2)
{ {
BeginMoveDrag(e); BeginMoveDrag(e);
} }
}
private void ScrollTabs(object sender, PointerWheelEventArgs e) private void ScrollTabs(object sender, PointerWheelEventArgs e)
{ {

View file

@ -109,7 +109,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- Left Normal Mode --> <!-- Left Normal Mode -->
<Grid Grid.Column="0" RowDefinitions="28,Auto,28,Auto,28,*,28,Auto,28,Auto" Margin="0,0,0,4" IsVisible="{Binding !IsSearching}"> <Grid Grid.Column="0" RowDefinitions="28,Auto,5,28,28,Auto,28,*,28,Auto,28,Auto" Margin="0,0,0,4" IsVisible="{Binding !IsSearching}">
<!-- WorkingCopy --> <!-- WorkingCopy -->
<TextBlock Grid.Row="0" Classes="group_header_label" Text="{DynamicResource Text.Repository.Workspace}"/> <TextBlock Grid.Row="0" Classes="group_header_label" Text="{DynamicResource Text.Repository.Workspace}"/>
<ListBox Grid.Row="1" Classes="page_switcher" Background="Transparent" SelectedIndex="{Binding SelectedViewIndex, Mode=TwoWay}"> <ListBox Grid.Row="1" Classes="page_switcher" Background="Transparent" SelectedIndex="{Binding SelectedViewIndex, Mode=TwoWay}">
@ -159,9 +159,43 @@
</ListBoxItem> </ListBoxItem>
</ListBox> </ListBox>
<!-- Filter Branches -->
<Rectangle Grid.Row="2" Height=".65" HorizontalAlignment="Stretch" VerticalAlignment="Center" Fill="{DynamicResource Brush.Border2}"/>
<TextBox Grid.Row="3"
Margin="4,2,1,0"
Height="24"
BorderThickness="1"
CornerRadius="3"
BorderBrush="{DynamicResource Brush.Border2}"
Background="{DynamicResource Brush.Contents}"
Watermark="{DynamicResource Text.Repository.FilterBranchTip}"
Text="{Binding SearchBranchFilter, Mode=TwoWay}"
VerticalContentAlignment="Center">
<TextBox.InnerLeftContent>
<Path Width="14" Height="14"
Margin="6,0,0,0"
Fill="{DynamicResource Brush.FG2}"
Data="{StaticResource Icons.Search}"/>
</TextBox.InnerLeftContent>
<TextBox.InnerRightContent>
<Button Classes="icon_button"
Width="16"
Margin="0,0,6,0"
Command="{Binding ClearSearchBranchFilter}"
IsVisible="{Binding SearchBranchFilter, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
HorizontalAlignment="Right">
<Path Width="14" Height="14"
Margin="0,1,0,0"
Fill="{DynamicResource Brush.FG1}"
Data="{StaticResource Icons.Clear}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
<!-- Local Branches --> <!-- Local Branches -->
<TextBlock Grid.Row="2" Classes="group_header_label" Text="{DynamicResource Text.Repository.LocalBranches}"/> <TextBlock Grid.Row="4" Classes="group_header_label" Text="{DynamicResource Text.Repository.LocalBranches}"/>
<TreeView Grid.Row="3" <TreeView Grid.Row="5"
x:Name="localBranchTree" x:Name="localBranchTree"
MaxHeight="400" MaxHeight="400"
ItemsSource="{Binding LocalBranchTrees}" ItemsSource="{Binding LocalBranchTrees}"
@ -170,12 +204,12 @@
LostFocus="OnLocalBranchTreeLostFocus" LostFocus="OnLocalBranchTreeLostFocus"
SelectionChanged="OnLocalBranchTreeSelectionChanged"> SelectionChanged="OnLocalBranchTreeSelectionChanged">
<TreeView.Styles> <TreeView.Styles>
<Style Selector="TreeViewItem" x:DataType="m:BranchTreeNode"> <Style Selector="TreeViewItem" x:DataType="vm:BranchTreeNode">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/> <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style> </Style>
</TreeView.Styles> </TreeView.Styles>
<TreeView.ItemTemplate> <TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type m:BranchTreeNode}"> <TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:BranchTreeNode}">
<Grid Height="24" ColumnDefinitions="20,*,Auto,Auto" Background="Transparent" ContextRequested="OnLocalBranchContextMenuRequested" DoubleTapped="OnDoubleTappedLocalBranchNode"> <Grid Height="24" ColumnDefinitions="20,*,Auto,Auto" Background="Transparent" ContextRequested="OnLocalBranchContextMenuRequested" DoubleTapped="OnDoubleTappedLocalBranchNode">
<Path Grid.Column="0" Classes="folder_icon" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,1,0,0" IsVisible="{Binding IsFolder}"/> <Path Grid.Column="0" Classes="folder_icon" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,1,0,0" IsVisible="{Binding IsFolder}"/>
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,2,0,0" Data="{StaticResource Icons.Check}" IsVisible="{Binding IsCurrent}" VerticalAlignment="Center"/> <Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,2,0,0" Data="{StaticResource Icons.Check}" IsVisible="{Binding IsCurrent}" VerticalAlignment="Center"/>
@ -208,13 +242,13 @@
</TreeView> </TreeView>
<!-- Remotes --> <!-- Remotes -->
<Grid Grid.Row="4" ColumnDefinitions="*,Auto"> <Grid Grid.Row="6" ColumnDefinitions="*,Auto">
<TextBlock Grid.Column="0" Classes="group_header_label" Text="{DynamicResource Text.Repository.Remotes}"/> <TextBlock Grid.Column="0" Classes="group_header_label" Text="{DynamicResource Text.Repository.Remotes}"/>
<Button Grid.Column="1" Classes="icon_button" Width="14" Margin="8,0" Command="{Binding AddRemote}" ToolTip.Tip="{DynamicResource Text.Repository.Remotes.Add}"> <Button Grid.Column="1" Classes="icon_button" Width="14" Margin="8,0" Command="{Binding AddRemote}" ToolTip.Tip="{DynamicResource Text.Repository.Remotes.Add}">
<Path Width="12" Height="12" Data="{StaticResource Icons.Remote.Add}"/> <Path Width="12" Height="12" Data="{StaticResource Icons.Remote.Add}"/>
</Button> </Button>
</Grid> </Grid>
<TreeView Grid.Row="5" <TreeView Grid.Row="7"
x:Name="remoteBranchTree" x:Name="remoteBranchTree"
ItemsSource="{Binding RemoteBranchTrees}" ItemsSource="{Binding RemoteBranchTrees}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.HorizontalScrollBarVisibility="Disabled"
@ -222,13 +256,13 @@
LostFocus="OnRemoteBranchTreeLostFocus" LostFocus="OnRemoteBranchTreeLostFocus"
SelectionChanged="OnRemoteBranchTreeSelectionChanged"> SelectionChanged="OnRemoteBranchTreeSelectionChanged">
<TreeView.Styles> <TreeView.Styles>
<Style Selector="TreeViewItem" x:DataType="m:BranchTreeNode"> <Style Selector="TreeViewItem" x:DataType="vm:BranchTreeNode">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/> <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style> </Style>
</TreeView.Styles> </TreeView.Styles>
<TreeView.ItemTemplate> <TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type m:BranchTreeNode}"> <TreeDataTemplate ItemsSource="{Binding Children}" x:DataType="{x:Type vm:BranchTreeNode}">
<Grid Height="24" ColumnDefinitions="20,*,Auto" Background="Transparent" ContextRequested="OnRemoteBranchContextMenuRequested"> <Grid Height="24" ColumnDefinitions="20,*,Auto" Background="Transparent" ContextRequested="OnRemoteBranchContextMenuRequested">
<Path Grid.Column="0" Classes="folder_icon" Width="10" Height="10" HorizontalAlignment="Left" Margin="0,2,0,0" IsVisible="{Binding IsFolder}" VerticalAlignment="Center"/> <Path Grid.Column="0" Classes="folder_icon" Width="10" Height="10" HorizontalAlignment="Left" Margin="0,2,0,0" IsVisible="{Binding IsFolder}" VerticalAlignment="Center"/>
<Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,2,0,0" Data="{StaticResource Icons.Remote}" IsVisible="{Binding IsRemote}" VerticalAlignment="Center"/> <Path Grid.Column="0" Width="12" Height="12" HorizontalAlignment="Left" Margin="0,2,0,0" Data="{StaticResource Icons.Remote}" IsVisible="{Binding IsRemote}" VerticalAlignment="Center"/>
@ -250,7 +284,7 @@
</TreeView> </TreeView>
<!-- Tags --> <!-- Tags -->
<ToggleButton Grid.Row="6" Classes="group_expander" IsChecked="{Binding IsTagGroupExpanded, Mode=TwoWay}"> <ToggleButton Grid.Row="8" Classes="group_expander" IsChecked="{Binding IsTagGroupExpanded, Mode=TwoWay}">
<Grid ColumnDefinitions="Auto,*,Auto"> <Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Classes="group_header_label" Margin="4,0,0,0" Text="{DynamicResource Text.Repository.Tags}"/> <TextBlock Grid.Column="0" Classes="group_header_label" Margin="4,0,0,0" Text="{DynamicResource Text.Repository.Tags}"/>
<TextBlock Grid.Column="1" Text="{Binding Tags, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/> <TextBlock Grid.Column="1" Text="{Binding Tags, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
@ -259,7 +293,7 @@
</Button> </Button>
</Grid> </Grid>
</ToggleButton> </ToggleButton>
<DataGrid Grid.Row="7" <DataGrid Grid.Row="9"
MaxHeight="200" MaxHeight="200"
Background="Transparent" Background="Transparent"
ItemsSource="{Binding Tags}" ItemsSource="{Binding Tags}"
@ -310,7 +344,7 @@
</DataGrid> </DataGrid>
<!-- Submodules --> <!-- Submodules -->
<ToggleButton Grid.Row="8" Classes="group_expander" IsChecked="{Binding IsSubmoduleGroupExpanded, Mode=TwoWay}"> <ToggleButton Grid.Row="10" Classes="group_expander" IsChecked="{Binding IsSubmoduleGroupExpanded, Mode=TwoWay}">
<Grid ColumnDefinitions="Auto,*,Auto,Auto"> <Grid ColumnDefinitions="Auto,*,Auto,Auto">
<TextBlock Grid.Column="0" Classes="group_header_label" Margin="4,0,0,0" Text="{DynamicResource Text.Repository.Submodules}"/> <TextBlock Grid.Column="0" Classes="group_header_label" Margin="4,0,0,0" Text="{DynamicResource Text.Repository.Submodules}"/>
<TextBlock Grid.Column="1" Text="{Binding Submodules, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/> <TextBlock Grid.Column="1" Text="{Binding Submodules, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
@ -328,7 +362,7 @@
</Button> </Button>
</Grid> </Grid>
</ToggleButton> </ToggleButton>
<DataGrid Grid.Row="9" <DataGrid Grid.Row="11"
MaxHeight="200" MaxHeight="200"
Background="Transparent" Background="Transparent"
ItemsSource="{Binding Submodules}" ItemsSource="{Binding Submodules}"

View file

@ -98,7 +98,7 @@ namespace SourceGit.Views
{ {
remoteBranchTree.UnselectAll(); remoteBranchTree.UnselectAll();
var node = tree.SelectedItem as Models.BranchTreeNode; var node = tree.SelectedItem as ViewModels.BranchTreeNode;
if (node.IsBranch && DataContext is ViewModels.Repository repo) if (node.IsBranch && DataContext is ViewModels.Repository repo)
{ {
repo.NavigateToCommit((node.Backend as Models.Branch).Head); repo.NavigateToCommit((node.Backend as Models.Branch).Head);
@ -112,7 +112,7 @@ namespace SourceGit.Views
{ {
localBranchTree.UnselectAll(); localBranchTree.UnselectAll();
var node = tree.SelectedItem as Models.BranchTreeNode; var node = tree.SelectedItem as ViewModels.BranchTreeNode;
if (node.IsBranch && DataContext is ViewModels.Repository repo) if (node.IsBranch && DataContext is ViewModels.Repository repo)
{ {
repo.NavigateToCommit((node.Backend as Models.Branch).Head); repo.NavigateToCommit((node.Backend as Models.Branch).Head);
@ -171,7 +171,7 @@ namespace SourceGit.Views
if (sender is ToggleButton toggle) if (sender is ToggleButton toggle)
{ {
var filter = string.Empty; var filter = string.Empty;
if (toggle.DataContext is Models.BranchTreeNode node) if (toggle.DataContext is ViewModels.BranchTreeNode node)
{ {
if (node.IsBranch) if (node.IsBranch)
{ {
@ -196,7 +196,7 @@ namespace SourceGit.Views
{ {
remoteBranchTree.UnselectAll(); remoteBranchTree.UnselectAll();
if (sender is Grid grid && grid.DataContext is Models.BranchTreeNode node) if (sender is Grid grid && grid.DataContext is ViewModels.BranchTreeNode node)
{ {
if (node.IsBranch && DataContext is ViewModels.Repository repo) if (node.IsBranch && DataContext is ViewModels.Repository repo)
{ {
@ -213,7 +213,7 @@ namespace SourceGit.Views
{ {
localBranchTree.UnselectAll(); localBranchTree.UnselectAll();
if (sender is Grid grid && grid.DataContext is Models.BranchTreeNode node && DataContext is ViewModels.Repository repo) if (sender is Grid grid && grid.DataContext is ViewModels.BranchTreeNode node && DataContext is ViewModels.Repository repo)
{ {
if (node.IsRemote) if (node.IsRemote)
{ {
@ -291,7 +291,7 @@ namespace SourceGit.Views
if (sender is Grid grid && DataContext is ViewModels.Repository repo) if (sender is Grid grid && DataContext is ViewModels.Repository repo)
{ {
var node = grid.DataContext as Models.BranchTreeNode; var node = grid.DataContext as ViewModels.BranchTreeNode;
if (node != null && node.IsBranch) if (node != null && node.IsBranch)
{ {
var branch = node.Backend as Models.Branch; var branch = node.Backend as Models.Branch;

View file

@ -45,13 +45,13 @@ namespace SourceGit.Views
{ {
c._hitBoxes.Clear(); c._hitBoxes.Clear();
c._lastHitIdx = -1; c._lastHitIdx = -1;
c.InvalidateVisual(); c.InvalidateMeasure();
}); });
} }
public override void Render(DrawingContext context) public override void Render(DrawingContext context)
{ {
if (Samples == null) if (Samples == null || Bounds.Width == 0)
return; return;
var samples = Samples; var samples = Samples;
@ -97,7 +97,7 @@ namespace SourceGit.Views
var height = Bounds.Height; var height = Bounds.Height;
// Transparent background to block mouse move events. // Transparent background to block mouse move events.
context.DrawRectangle(Brushes.Transparent, null, new Rect(0, 0, Bounds.Width, Bounds.Height)); context.DrawRectangle(Brushes.Transparent, null, new Rect(0, 0, width, height));
// Draw coordinate // Draw coordinate
var maxLabel = new FormattedText($"{maxV}", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 12.0, LineBrush); var maxLabel = new FormattedText($"{maxV}", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeface, 12.0, LineBrush);

View file

@ -85,6 +85,12 @@ namespace SourceGit.Views
} }
} }
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
InvalidateMeasure();
}
private readonly CombinedTextDiffPresenter _editor; private readonly CombinedTextDiffPresenter _editor;
private readonly bool _isOldLine; private readonly bool _isOldLine;
} }
@ -458,6 +464,12 @@ namespace SourceGit.Views
} }
} }
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
InvalidateMeasure();
}
private readonly SingleSideTextDiffPresenter _editor; private readonly SingleSideTextDiffPresenter _editor;
} }