Merge branch 'release/v8.8'

This commit is contained in:
leo 2024-04-15 09:20:39 +08:00
commit e9c65d6e81
57 changed files with 839 additions and 487 deletions

View file

@ -2,9 +2,9 @@ name: Continuous Integration
on: on:
push: push:
branches: branches:
- master - develop
pull_request: pull_request:
branches: [master] branches: [develop]
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build: build:

View file

@ -37,6 +37,7 @@ You can download the latest stable from [Releases](https://github.com/sourcegit-
For **Windows** users: For **Windows** users:
* **MSYS Git is NOT supported**. Please use official [Git for Windows](https://git-scm.com/download/win) instead. * **MSYS Git is NOT supported**. Please use official [Git for Windows](https://git-scm.com/download/win) instead.
* `Source.win-x64.zip` may be reported as virus by Windows Defender. I don't know why. I have manually tested the zip to be uploaded using Windows Defender before uploading and no virus was found. If you have installed .NET 8 SDK locally, I suggest you to compile it yourself. And if you have any idea about how to fix this, please open an issue.
For **macOS** users: For **macOS** users:
@ -53,20 +54,20 @@ For **Linux** users:
* Maybe you need to set environment variable `AVALONIA_SCREEN_SCALE_FACTORS`. See https://github.com/AvaloniaUI/Avalonia/wiki/Configuring-X11-per-monitor-DPI. * Maybe you need to set environment variable `AVALONIA_SCREEN_SCALE_FACTORS`. See https://github.com/AvaloniaUI/Avalonia/wiki/Configuring-X11-per-monitor-DPI.
* Modify `SourceGit.desktop.template` (replace SOURCEGIT_LOCAL_FOLDER with real path) and move it into `~/.local/share/applications`. * Modify `SourceGit.desktop.template` (replace SOURCEGIT_LOCAL_FOLDER with real path) and move it into `~/.local/share/applications`.
## External Editors ## External Tools
This app supports open repository in external editors listed in the table below. This app supports open repository in external tools listed in the table below.
| Editor | Windows | macOS | Linux | Environment Variable | | Tool | Windows | macOS | Linux | Environment Variable |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| Visual Studio Code | YES | YES | YES | VSCODE_PATH | | Visual Studio Code | YES | YES | YES | VSCODE_PATH |
| Visual Studio Code - Insiders | YES | YES | YES | VSCODE_INSIDERS_PATH | | Visual Studio Code - Insiders | YES | YES | YES | VSCODE_INSIDERS_PATH |
| JetBrains Fleet | YES | YES | YES | FLEET_PATH | | JetBrains Fleet | YES | YES | YES | FLEET_PATH |
| Sublime Text | YES | YES | YES | SUBLIME_TEXT_PATH | | Sublime Text | YES | YES | YES | SUBLIME_TEXT_PATH |
You can set the given environment variable for special editor if it can NOT be found by this app automatically. You can set the given environment variable for special tool if it can NOT be found by this app automatically.
## Screen Shots ## Screenshots
* Dark Theme * Dark Theme

View file

@ -9,7 +9,7 @@
<key>CFBundleName</key> <key>CFBundleName</key>
<string>SourceGit</string> <string>SourceGit</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>8.7.0</string> <string>8.8.0</string>
<key>LSMinimumSystemVersion</key> <key>LSMinimumSystemVersion</key>
<string>10.12</string> <string>10.12</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
@ -19,8 +19,8 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>8.7</string> <string>8.8</string>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

View file

@ -28,17 +28,31 @@ namespace SourceGit.Commands
return Exec(); return Exec();
} }
public bool File(string file, bool useTheirs) public bool UseTheirs(List<string> files)
{ {
if (useTheirs) StringBuilder builder = new StringBuilder();
builder.Append("checkout --theirs --");
foreach (var f in files)
{ {
Args = $"checkout --theirs -- \"{file}\""; builder.Append(" \"");
} builder.Append(f);
else builder.Append("\"");
{
Args = $"checkout --ours -- \"{file}\"";
} }
Args = builder.ToString();
return Exec();
}
public bool UseMine(List<string> files)
{
StringBuilder builder = new StringBuilder();
builder.Append("checkout --ours --");
foreach (var f in files)
{
builder.Append(" \"");
builder.Append(f);
builder.Append("\"");
}
Args = builder.ToString();
return Exec(); return Exec();
} }

View file

@ -46,6 +46,18 @@ namespace SourceGit.Commands
protected override void OnReadline(string line) protected override void OnReadline(string line)
{ {
if (line.StartsWith("old mode ", StringComparison.Ordinal))
{
_result.OldMode = line.Substring(9);
return;
}
if (line.StartsWith("new mode ", StringComparison.Ordinal))
{
_result.NewMode = line.Substring(9);
return;
}
if (_result.IsBinary) if (_result.IsBinary)
return; return;

View file

@ -24,7 +24,7 @@ namespace SourceGit.Commands
Args = "-c credential.helper=manager "; Args = "-c credential.helper=manager ";
} }
Args += "fetch --progress --verbose "; Args += "fetch --force --progress --verbose ";
if (prune) if (prune)
Args += "--prune "; Args += "--prune ";
Args += remote; Args += remote;

View file

@ -1,61 +0,0 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
namespace SourceGit.Commands
{
public partial class QueryCommitChanges : Command
{
[GeneratedRegex(@"^(\s?[\w\?]{1,4})\s+(.+)$")]
private static partial Regex REG_FORMAT();
public QueryCommitChanges(string repo, string commitSHA)
{
WorkingDirectory = repo;
Context = repo;
Args = $"show --name-status {commitSHA}";
}
public List<Models.Change> Result()
{
Exec();
_changes.Sort((l, r) => l.Path.CompareTo(r.Path));
return _changes;
}
protected override void OnReadline(string line)
{
var match = REG_FORMAT().Match(line);
if (!match.Success)
return;
var change = new Models.Change() { Path = match.Groups[2].Value };
var status = match.Groups[1].Value;
switch (status[0])
{
case 'M':
change.Set(Models.ChangeState.Modified);
_changes.Add(change);
break;
case 'A':
change.Set(Models.ChangeState.Added);
_changes.Add(change);
break;
case 'D':
change.Set(Models.ChangeState.Deleted);
_changes.Add(change);
break;
case 'R':
change.Set(Models.ChangeState.Renamed);
_changes.Add(change);
break;
case 'C':
change.Set(Models.ChangeState.Copied);
_changes.Add(change);
break;
}
}
private readonly List<Models.Change> _changes = new List<Models.Change>();
}
}

View file

@ -7,7 +7,7 @@ namespace SourceGit.Converters
public static class ListConverters public static class ListConverters
{ {
public static readonly FuncValueConverter<IList, string> ToCount = public static readonly FuncValueConverter<IList, string> ToCount =
new FuncValueConverter<IList, string>(v => $" ({v.Count})"); new FuncValueConverter<IList, string>(v => v == null ? " (0)" : $" ({v.Count})");
public static readonly FuncValueConverter<IList, bool> IsNotNullOrEmpty = public static readonly FuncValueConverter<IList, bool> IsNotNullOrEmpty =
new FuncValueConverter<IList, bool>(v => v != null && v.Count > 0); new FuncValueConverter<IList, bool>(v => v != null && v.Count > 0);

View file

@ -11,13 +11,5 @@ namespace SourceGit.Converters
public static readonly FuncValueConverter<string, string> PureDirectoryName = public static readonly FuncValueConverter<string, string> PureDirectoryName =
new FuncValueConverter<string, string>(fullpath => Path.GetDirectoryName(fullpath) ?? ""); new FuncValueConverter<string, string>(fullpath => Path.GetDirectoryName(fullpath) ?? "");
public static readonly FuncValueConverter<string, string> TruncateIfTooLong =
new FuncValueConverter<string, string>(fullpath =>
{
if (fullpath.Length <= 50)
return fullpath;
return fullpath.Substring(0, 20) + ".../" + Path.GetFileName(fullpath);
});
} }
} }

View file

@ -576,11 +576,21 @@ namespace SourceGit.Models
{ {
} }
public class FileModeDiff
{
public string Old { get; set; } = string.Empty;
public string New { get; set; } = string.Empty;
}
public class DiffResult public class DiffResult
{ {
public bool IsBinary { get; set; } = false; public bool IsBinary { get; set; } = false;
public bool IsLFS { get; set; } = false; public bool IsLFS { get; set; } = false;
public string OldMode { get; set; } = string.Empty;
public string NewMode { get; set; } = string.Empty;
public TextDiff TextDiff { get; set; } = null; public TextDiff TextDiff { get; set; } = null;
public LFSDiff LFSDiff { get; set; } = null; public LFSDiff LFSDiff { get; set; } = null;
public string FileModeChange => string.IsNullOrEmpty(OldMode) ? string.Empty : $"{OldMode} → {NewMode}";
} }
} }

View file

@ -1,88 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace SourceGit.Models
{
public class ExternalMergeTools
{
public int Type { get; set; }
public string Name { get; set; }
public string Exec { get; set; }
public string Cmd { get; set; }
public string DiffCmd { get; set; }
public static readonly List<ExternalMergeTools> Supported;
static ExternalMergeTools()
{
if (OperatingSystem.IsWindows())
{
Supported = new List<ExternalMergeTools>() {
new ExternalMergeTools(0, "Custom", "", "", ""),
new ExternalMergeTools(1, "Visual Studio Code", "Code.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMergeTools(2, "Visual Studio Code - Insiders", "Code - Insiders.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMergeTools(3, "Visual Studio 2017/2019/2022", "vsDiffMerge.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\" /m", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMergeTools(4, "Tortoise Merge", "TortoiseMerge.exe;TortoiseGitMerge.exe", "-base:\"$BASE\" -theirs:\"$REMOTE\" -mine:\"$LOCAL\" -merged:\"$MERGED\"", "-base:\"$LOCAL\" -theirs:\"$REMOTE\""),
new ExternalMergeTools(5, "KDiff3", "kdiff3.exe", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMergeTools(6, "Beyond Compare", "BComp.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMergeTools(7, "WinMerge", "WinMergeU.exe", "-u -e \"$REMOTE\" \"$LOCAL\" \"$MERGED\"", "-u -e \"$LOCAL\" \"$REMOTE\""),
};
}
else if (OperatingSystem.IsMacOS())
{
Supported = new List<ExternalMergeTools>() {
new ExternalMergeTools(0, "Custom", "", "", ""),
new ExternalMergeTools(1, "FileMerge", "/usr/bin/opendiff", "\"$BASE\" \"$LOCAL\" \"$REMOTE\" -ancestor \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMergeTools(2, "Visual Studio Code", "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMergeTools(3, "Visual Studio Code - Insiders", "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMergeTools(4, "KDiff3", "/Applications/kdiff3.app/Contents/MacOS/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMergeTools(5, "Beyond Compare", "/Applications/Beyond Compare.app/Contents/MacOS/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
};
}
else if (OperatingSystem.IsLinux())
{
Supported = new List<ExternalMergeTools>() {
new ExternalMergeTools(0, "Custom", "", "", ""),
new ExternalMergeTools(1, "Visual Studio Code", "/usr/share/code/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMergeTools(2, "Visual Studio Code - Insiders", "/usr/share/code-insiders/code-insiders", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMergeTools(3, "KDiff3", "/usr/bin/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMergeTools(4, "Beyond Compare", "/usr/bin/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
};
}
else
{
Supported = new List<ExternalMergeTools>() {
new ExternalMergeTools(0, "Custom", "", "", ""),
};
}
}
public ExternalMergeTools(int type, string name, string exec, string cmd, string diffCmd)
{
Type = type;
Name = name;
Exec = exec;
Cmd = cmd;
DiffCmd = diffCmd;
}
public string[] GetPatterns()
{
if (OperatingSystem.IsWindows())
{
return Exec.Split(';');
}
else
{
var patterns = new List<string>();
var choices = Exec.Split(';', StringSplitOptions.RemoveEmptyEntries);
foreach (var c in choices)
{
patterns.Add(Path.GetFileName(c));
}
return patterns.ToArray();
}
}
}
}

View file

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.IO;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
namespace SourceGit.Models
{
public class ExternalMerger
{
public int Type { get; set; }
public string Icon { get; set; }
public string Name { get; set; }
public string Exec { get; set; }
public string Cmd { get; set; }
public string DiffCmd { get; set; }
public Bitmap IconImage
{
get
{
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{Icon}.png", UriKind.RelativeOrAbsolute));
return new Bitmap(icon);
}
}
public static readonly List<ExternalMerger> Supported;
static ExternalMerger()
{
if (OperatingSystem.IsWindows())
{
Supported = new List<ExternalMerger>() {
new ExternalMerger(0, "custom_diff", "Custom", "", "", ""),
new ExternalMerger(1, "vscode", "Visual Studio Code", "Code.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(2, "vscode_insiders", "Visual Studio Code - Insiders", "Code - Insiders.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(3, "vs", "Visual Studio", "vsDiffMerge.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\" /m", "\"$LOCAL\" \"$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(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\""),
};
}
else if (OperatingSystem.IsMacOS())
{
Supported = new List<ExternalMerger>() {
new ExternalMerger(0, "custom_diff", "Custom", "", "", ""),
new ExternalMerger(1, "xcode", "FileMerge", "/usr/bin/opendiff", "\"$BASE\" \"$LOCAL\" \"$REMOTE\" -ancestor \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(2, "vscode", "Visual Studio Code", "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(3, "vscode_insiders", "Visual Studio Code - Insiders", "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(4, "kdiff3", "KDiff3", "/Applications/kdiff3.app/Contents/MacOS/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(5, "beyond_compare", "Beyond Compare", "/Applications/Beyond Compare.app/Contents/MacOS/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
};
}
else if (OperatingSystem.IsLinux())
{
Supported = new List<ExternalMerger>() {
new ExternalMerger(0, "custom_diff", "Custom", "", "", ""),
new ExternalMerger(1, "vscode", "Visual Studio Code", "/usr/share/code/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(2, "vscode_insiders", "Visual Studio Code - Insiders", "/usr/share/code-insiders/code-insiders", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(3, "kdiff3", "KDiff3", "/usr/bin/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(4, "beyond_compare", "Beyond Compare", "/usr/bin/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(5, "meld", "Meld", "/usr/bin/meld", "\"$LOCAL\" \"$BASE\" \"$REMOTE\" -output \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
};
}
else
{
Supported = new List<ExternalMerger>() {
new ExternalMerger(0, "custom_diff", "Custom", "", "", ""),
};
}
}
public ExternalMerger(int type, string icon, string name, string exec, string cmd, string diffCmd)
{
Type = type;
Icon = icon;
Name = name;
Exec = exec;
Cmd = cmd;
DiffCmd = diffCmd;
}
public string[] GetPatterns()
{
if (OperatingSystem.IsWindows())
{
return Exec.Split(';');
}
else
{
var patterns = new List<string>();
var choices = Exec.Split(';', StringSplitOptions.RemoveEmptyEntries);
foreach (var c in choices)
{
patterns.Add(Path.GetFileName(c));
}
return patterns.ToArray();
}
}
}
}

View file

@ -3,15 +3,27 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
namespace SourceGit.Models namespace SourceGit.Models
{ {
public class ExternalEditor public class ExternalTool
{ {
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string Icon { get; set; } = string.Empty; public string Icon { get; set; } = string.Empty;
public string Executable { get; set; } = string.Empty; public string Executable { get; set; } = string.Empty;
public string OpenCmdArgs { get; set; } = string.Empty; public string OpenCmdArgs { get; set; } = string.Empty;
public Bitmap IconImage
{
get
{
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{Icon}.png", UriKind.RelativeOrAbsolute));
return new Bitmap(icon);
}
}
public void Open(string repo) public void Open(string repo)
{ {
Process.Start(new ProcessStartInfo() Process.Start(new ProcessStartInfo()
@ -24,35 +36,35 @@ namespace SourceGit.Models
} }
} }
public class ExternalEditorFinder public class ExternalToolsFinder
{ {
public List<ExternalEditor> Editors public List<ExternalTool> Founded
{ {
get; get;
private set; private set;
} = new List<ExternalEditor>(); } = new List<ExternalTool>();
public void VSCode(Func<string> platform_finder) public void VSCode(Func<string> platform_finder)
{ {
TryAdd("Visual Studio Code", "vscode.png", "\"{0}\"", "VSCODE_PATH", platform_finder); TryAdd("Visual Studio Code", "vscode", "\"{0}\"", "VSCODE_PATH", platform_finder);
} }
public void VSCodeInsiders(Func<string> platform_finder) public void VSCodeInsiders(Func<string> platform_finder)
{ {
TryAdd("Visual Studio Code - Insiders", "vscode_insiders.png", "\"{0}\"", "VSCODE_INSIDERS_PATH", platform_finder); TryAdd("Visual Studio Code - Insiders", "vscode_insiders", "\"{0}\"", "VSCODE_INSIDERS_PATH", platform_finder);
} }
public void Fleet(Func<string> platform_finder) public void Fleet(Func<string> platform_finder)
{ {
TryAdd("JetBrains Fleet", "fleet.png", "\"{0}\"", "FLEET_PATH", platform_finder); TryAdd("JetBrains Fleet", "fleet", "\"{0}\"", "FLEET_PATH", platform_finder);
} }
public void SublimeText(Func<string> platform_finder) public void SublimeText(Func<string> platform_finder)
{ {
TryAdd("Sublime Text", "sublime_text.png", "\"{0}\"", "SUBLIME_TEXT_PATH", platform_finder); TryAdd("Sublime Text", "sublime_text", "\"{0}\"", "SUBLIME_TEXT_PATH", platform_finder);
} }
private void TryAdd(string name, string icon, string args, string env, Func<string> finder) public void TryAdd(string name, string icon, string args, string env, Func<string> finder)
{ {
var path = Environment.GetEnvironmentVariable(env); var path = Environment.GetEnvironmentVariable(env);
if (string.IsNullOrEmpty(path) || !File.Exists(path)) if (string.IsNullOrEmpty(path) || !File.Exists(path))
@ -62,7 +74,7 @@ namespace SourceGit.Models
return; return;
} }
Editors.Add(new ExternalEditor Founded.Add(new ExternalTool
{ {
Name = name, Name = name,
Icon = icon, Icon = icon,

10
src/Models/Shell.cs Normal file
View file

@ -0,0 +1,10 @@
namespace SourceGit.Models
{
public enum Shell
{
Default = 0,
PowerShell,
CommandPrompt,
DefaultShellOfWindowsTerminal,
}
}

View file

@ -13,6 +13,29 @@ namespace SourceGit.Native
[SupportedOSPlatform("linux")] [SupportedOSPlatform("linux")]
internal class Linux : OS.IBackend internal class Linux : OS.IBackend
{ {
class Terminal
{
public string FilePath { get; set; } = string.Empty;
public string OpenArgFormat { get; set; } = string.Empty;
public Terminal(string exec, string fmt)
{
FilePath = exec;
OpenArgFormat = fmt;
}
public void Open(string dir)
{
Process.Start(FilePath, string.Format(OpenArgFormat, dir));
}
}
public Linux()
{
_xdgOpenPath = FindExecutable("xdg-open");
_terminal = FindTerminal();
}
public void SetupApp(AppBuilder builder) public void SetupApp(AppBuilder builder)
{ {
builder.With(new FontManagerOptions() builder.With(new FontManagerOptions()
@ -26,50 +49,45 @@ namespace SourceGit.Native
public string FindGitExecutable() public string FindGitExecutable()
{ {
if (File.Exists("/usr/bin/git")) return FindExecutable("git");
return "/usr/bin/git";
return string.Empty;
} }
public List<Models.ExternalEditor> FindExternalEditors() public List<Models.ExternalTool> FindExternalTools()
{ {
var finder = new Models.ExternalEditorFinder(); var finder = new Models.ExternalToolsFinder();
finder.VSCode(() => "/usr/share/code/code"); finder.VSCode(() => FindExecutable("code"));
finder.VSCodeInsiders(() => "/usr/share/code-insiders/code-insiders"); finder.VSCodeInsiders(() => FindExecutable("code-insiders"));
finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}/JetBrains/Toolbox/apps/fleet/bin/Fleet"); finder.Fleet(FindJetBrainFleet);
finder.SublimeText(() => File.Exists("/usr/bin/subl") ? "/usr/bin/subl" : "/usr/local/bin/subl"); finder.SublimeText(() => FindExecutable("subl"));
return finder.Editors; return finder.Founded;
} }
public void OpenBrowser(string url) public void OpenBrowser(string url)
{ {
if (!File.Exists("/usr/bin/xdg-open")) if (string.IsNullOrEmpty(_xdgOpenPath))
{ App.RaiseException("", $"Can NOT find `xdg-open` command!!!");
App.RaiseException("", $"You should install xdg-open first!"); else
return; Process.Start(_xdgOpenPath, $"\"{url}\"");
}
Process.Start("xdg-open", $"\"{url}\"");
} }
public void OpenInFileManager(string path, bool select) public void OpenInFileManager(string path, bool select)
{ {
if (!File.Exists("/usr/bin/xdg-open")) if (string.IsNullOrEmpty(_xdgOpenPath))
{ {
App.RaiseException("", $"You should install xdg-open first!"); App.RaiseException("", $"Can NOT find `xdg-open` command!!!");
return; return;
} }
if (Directory.Exists(path)) if (Directory.Exists(path))
{ {
Process.Start("xdg-open", $"\"{path}\""); Process.Start(_xdgOpenPath, $"\"{path}\"");
} }
else else
{ {
var dir = Path.GetDirectoryName(path); var dir = Path.GetDirectoryName(path);
if (Directory.Exists(dir)) if (Directory.Exists(dir))
{ {
Process.Start("xdg-open", $"\"{dir}\""); Process.Start(_xdgOpenPath, $"\"{dir}\"");
} }
} }
} }
@ -77,42 +95,86 @@ namespace SourceGit.Native
public void OpenTerminal(string workdir) public void OpenTerminal(string workdir)
{ {
var dir = string.IsNullOrEmpty(workdir) ? "~" : workdir; var dir = string.IsNullOrEmpty(workdir) ? "~" : workdir;
if (File.Exists("/usr/bin/gnome-terminal")) if (_terminal == null)
{ App.RaiseException(dir, $"Only supports gnome-terminal/konsole/xfce4-terminal/deepin-terminal!");
Process.Start("/usr/bin/gnome-terminal", $"--working-directory=\"{dir}\"");
}
else if (File.Exists("/usr/bin/konsole"))
{
Process.Start("/usr/bin/konsole", $"--workdir \"{dir}\"");
}
else if (File.Exists("/usr/bin/xfce4-terminal"))
{
Process.Start("/usr/bin/xfce4-terminal", $"--working-directory=\"{dir}\"");
}
else else
{ _terminal.Open(dir);
App.RaiseException("", $"Only supports gnome-terminal/konsole/xfce4-terminal!");
return;
}
} }
public void OpenWithDefaultEditor(string file) public void OpenWithDefaultEditor(string file)
{ {
if (!File.Exists("/usr/bin/xdg-open")) if (string.IsNullOrEmpty(_xdgOpenPath))
{ {
App.RaiseException("", $"You should install xdg-open first!"); App.RaiseException("", $"Can NOT find `xdg-open` command!!!");
return; return;
} }
var proc = Process.Start("xdg-open", $"\"{file}\""); var proc = Process.Start(_xdgOpenPath, $"\"{file}\"");
proc.WaitForExit(); proc.WaitForExit();
if (proc.ExitCode != 0) if (proc.ExitCode != 0)
{
App.RaiseException("", $"Failed to open \"{file}\""); App.RaiseException("", $"Failed to open \"{file}\"");
}
proc.Close(); proc.Close();
} }
private string FindExecutable(string filename)
{
var pathVariable = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
var pathes = pathVariable.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
foreach (var path in pathes)
{
var test = Path.Combine(path, filename);
if (File.Exists(test))
{
return test;
}
}
return string.Empty;
}
private Terminal FindTerminal()
{
var pathVariable = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
var pathes = pathVariable.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
foreach (var path in pathes)
{
var test = Path.Combine(path, "gnome-terminal");
if (File.Exists(test))
{
return new Terminal(test, "--working-directory=\"{0}\"");
}
test = Path.Combine(path, "konsole");
if (File.Exists(test))
{
return new Terminal(test, "--workdir \"{0}\"");
}
test = Path.Combine(path, "xfce4-terminal");
if (File.Exists(test))
{
return new Terminal(test, "--working-directory=\"{0}\"");
}
test = Path.Combine(path, "deepin-terminal");
if (File.Exists(test))
{
return new Terminal(test, "--work-directory \"{0}\"");
}
}
return null;
}
private string FindJetBrainFleet()
{
var path = $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}/JetBrains/Toolbox/apps/fleet/bin/Fleet";
return File.Exists(path) ? path : FindExecutable("fleet");
}
private string _xdgOpenPath = string.Empty;
private Terminal _terminal = null;
} }
} }

View file

@ -28,14 +28,14 @@ namespace SourceGit.Native
return string.Empty; return string.Empty;
} }
public List<Models.ExternalEditor> FindExternalEditors() public List<Models.ExternalTool> FindExternalTools()
{ {
var finder = new Models.ExternalEditorFinder(); var finder = new Models.ExternalToolsFinder();
finder.VSCode(() => "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"); finder.VSCode(() => "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code");
finder.VSCodeInsiders(() => "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"); finder.VSCodeInsiders(() => "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code");
finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/Applications/Fleet.app/Contents/MacOS/Fleet"); finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/Applications/Fleet.app/Contents/MacOS/Fleet");
finder.SublimeText(() => "/Applications/Sublime Text.app/Contents/SharedSupport/bin"); finder.SublimeText(() => "/Applications/Sublime Text.app/Contents/SharedSupport/bin");
return finder.Editors; return finder.Founded;
} }
public void OpenBrowser(string url) public void OpenBrowser(string url)

View file

@ -12,7 +12,7 @@ namespace SourceGit.Native
void SetupApp(AppBuilder builder); void SetupApp(AppBuilder builder);
string FindGitExecutable(); string FindGitExecutable();
List<Models.ExternalEditor> FindExternalEditors(); List<Models.ExternalTool> FindExternalTools();
void OpenTerminal(string workdir); void OpenTerminal(string workdir);
void OpenInFileManager(string path, bool select); void OpenInFileManager(string path, bool select);
@ -21,7 +21,7 @@ namespace SourceGit.Native
} }
public static string GitExecutable { get; set; } = string.Empty; public static string GitExecutable { get; set; } = string.Empty;
public static List<Models.ExternalEditor> ExternalEditors { get; set; } = new List<Models.ExternalEditor>(); public static List<Models.ExternalTool> ExternalTools { get; set; } = new List<Models.ExternalTool>();
static OS() static OS()
{ {
@ -42,7 +42,34 @@ namespace SourceGit.Native
throw new Exception("Platform unsupported!!!"); throw new Exception("Platform unsupported!!!");
} }
ExternalEditors = _backend.FindExternalEditors(); ExternalTools = _backend.FindExternalTools();
}
public static Models.Shell GetShell()
{
if (OperatingSystem.IsWindows())
{
return (_backend as Windows).Shell;
}
else
{
return Models.Shell.Default;
}
}
public static bool SetShell(Models.Shell shell)
{
if (OperatingSystem.IsWindows())
{
var windows = (_backend as Windows);
if (windows.Shell != shell)
{
windows.Shell = shell;
return true;
}
}
return false;
} }
public static void SetupApp(AppBuilder builder) public static void SetupApp(AppBuilder builder)

View file

@ -54,6 +54,12 @@ namespace SourceGit.Native
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = false)] [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = false)]
private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, int cild, IntPtr apidl, int dwFlags); private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, int cild, IntPtr apidl, int dwFlags);
public Models.Shell Shell
{
get;
set;
} = Models.Shell.Default;
public void SetupApp(AppBuilder builder) public void SetupApp(AppBuilder builder)
{ {
builder.With(new FontManagerOptions() builder.With(new FontManagerOptions()
@ -67,23 +73,8 @@ namespace SourceGit.Native
v.dwOSVersionInfoSize = (uint)Marshal.SizeOf<RTL_OSVERSIONINFOEX>(); v.dwOSVersionInfoSize = (uint)Marshal.SizeOf<RTL_OSVERSIONINFOEX>();
if (RtlGetVersion(ref v) == 0 && (v.dwMajorVersion < 10 || v.dwBuildNumber < 22000)) if (RtlGetVersion(ref v) == 0 && (v.dwMajorVersion < 10 || v.dwBuildNumber < 22000))
{ {
Window.WindowStateProperty.Changed.AddClassHandler<Window>((w, e) => Window.WindowStateProperty.Changed.AddClassHandler<Window>((w, e) => ExtendWindowFrame(w));
{ Window.LoadedEvent.AddClassHandler<Window>((w, e) => ExtendWindowFrame(w));
if (w.WindowState != WindowState.Maximized)
{
var margins = new MARGINS { cxLeftWidth = 1, cxRightWidth = 1, cyTopHeight = 1, cyBottomHeight = 1 };
DwmExtendFrameIntoClientArea(w.TryGetPlatformHandle().Handle, ref margins);
}
});
Window.LoadedEvent.AddClassHandler<Window>((w, e) =>
{
if (w.WindowState != WindowState.Maximized)
{
var margins = new MARGINS { cxLeftWidth = 1, cxRightWidth = 1, cyTopHeight = 1, cyBottomHeight = 1 };
DwmExtendFrameIntoClientArea(w.TryGetPlatformHandle().Handle, ref margins);
}
});
} }
} }
@ -114,14 +105,14 @@ namespace SourceGit.Native
return null; return null;
} }
public List<Models.ExternalEditor> FindExternalEditors() public List<Models.ExternalTool> FindExternalTools()
{ {
var finder = new Models.ExternalEditorFinder(); var finder = new Models.ExternalToolsFinder();
finder.VSCode(FindVSCode); finder.VSCode(FindVSCode);
finder.VSCodeInsiders(FindVSCodeInsiders); finder.VSCodeInsiders(FindVSCodeInsiders);
finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\Programs\\Fleet\\Fleet.exe"); finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\Programs\\Fleet\\Fleet.exe");
finder.SublimeText(FindSublimeText); finder.SublimeText(FindSublimeText);
return finder.Editors; return finder.Founded;
} }
public void OpenBrowser(string url) public void OpenBrowser(string url)
@ -133,19 +124,50 @@ namespace SourceGit.Native
public void OpenTerminal(string workdir) public void OpenTerminal(string workdir)
{ {
var binDir = Path.GetDirectoryName(OS.GitExecutable); if (string.IsNullOrEmpty(workdir) || !Path.Exists(workdir))
var bash = Path.Combine(binDir, "bash.exe");
if (!File.Exists(bash))
{ {
App.RaiseException(string.IsNullOrEmpty(workdir) ? "" : workdir, $"Can NOT found bash.exe under '{binDir}'"); workdir = ".";
return;
} }
var startInfo = new ProcessStartInfo(); var startInfo = new ProcessStartInfo();
startInfo.UseShellExecute = true; startInfo.WorkingDirectory = workdir;
startInfo.FileName = bash;
if (!string.IsNullOrEmpty(workdir) && Path.Exists(workdir)) switch (Shell)
startInfo.WorkingDirectory = workdir; {
case Models.Shell.Default:
var binDir = Path.GetDirectoryName(OS.GitExecutable);
var bash = Path.Combine(binDir, "bash.exe");
if (!File.Exists(bash))
{
App.RaiseException(workdir, $"Can NOT found bash.exe under '{binDir}'");
return;
}
startInfo.FileName = bash;
break;
case Models.Shell.PowerShell:
startInfo.FileName = ChoosePowerShell();
startInfo.Arguments = startInfo.FileName.EndsWith("pswd.exe") ? $"-WorkingDirectory \"{workdir}\" -Nologo" : "-Nologo";
break;
case Models.Shell.CommandPrompt:
startInfo.FileName = "cmd";
break;
case Models.Shell.DefaultShellOfWindowsTerminal:
var wt = FindWindowsTerminalApp();
if (!File.Exists(wt))
{
App.RaiseException(workdir, $"Can NOT found wt.exe on your system!");
return;
}
startInfo.FileName = FindWindowsTerminalApp();
startInfo.Arguments = $"-d \"{workdir}\"";
break;
default:
App.RaiseException(workdir, $"Bad shell configuration!");
return;
}
Process.Start(startInfo); Process.Start(startInfo);
} }
@ -189,6 +211,62 @@ namespace SourceGit.Native
Process.Start(start); Process.Start(start);
} }
private void ExtendWindowFrame(Window w)
{
var platformHandle = w.TryGetPlatformHandle();
if (platformHandle == null)
return;
var margins = new MARGINS { cxLeftWidth = 1, cxRightWidth = 1, cyTopHeight = 1, cyBottomHeight = 1 };
DwmExtendFrameIntoClientArea(platformHandle.Handle, ref margins);
}
// There are two versions of PowerShell : pwsh.exe (preferred) and powershell.exe (system default)
private string ChoosePowerShell()
{
if (!string.IsNullOrEmpty(_powershellPath))
return _powershellPath;
var localMachine = Microsoft.Win32.RegistryKey.OpenBaseKey(
Microsoft.Win32.RegistryHive.LocalMachine,
Microsoft.Win32.RegistryView.Registry64);
var pwsh = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\pwsh.exe");
if (pwsh != null)
{
var path = pwsh.GetValue(null) as string;
if (File.Exists(path))
{
_powershellPath = path;
return _powershellPath;
}
}
var finder = new StringBuilder("powershell.exe", 512);
if (PathFindOnPath(finder, null))
{
_powershellPath = finder.ToString();
return _powershellPath;
}
return string.Empty;
}
private string FindWindowsTerminalApp()
{
if (!string.IsNullOrEmpty(_wtPath))
return _wtPath;
var finder = new StringBuilder("wt.exe", 512);
if (PathFindOnPath(finder, null))
{
_wtPath = finder.ToString();
return _wtPath;
}
return string.Empty;
}
#region EXTERNAL_EDITOR_FINDER #region EXTERNAL_EDITOR_FINDER
private string FindVSCode() private string FindVSCode()
{ {
@ -250,6 +328,7 @@ namespace SourceGit.Native
Microsoft.Win32.RegistryHive.LocalMachine, Microsoft.Win32.RegistryHive.LocalMachine,
Microsoft.Win32.RegistryView.Registry64); Microsoft.Win32.RegistryView.Registry64);
// Sublime Text 4
var sublime = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Sublime Text_is1"); var sublime = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Sublime Text_is1");
if (sublime != null) if (sublime != null)
{ {
@ -257,6 +336,7 @@ namespace SourceGit.Native
return Path.Combine(Path.GetDirectoryName(icon), "subl.exe"); return Path.Combine(Path.GetDirectoryName(icon), "subl.exe");
} }
// Sublime Text 3
var sublime3 = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Sublime Text 3_is1"); var sublime3 = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Sublime Text 3_is1");
if (sublime3 != null) if (sublime3 != null)
{ {
@ -281,5 +361,8 @@ namespace SourceGit.Native
ILFree(pidl); ILFree(pidl);
} }
} }
private string _powershellPath = string.Empty;
private string _wtPath = string.Empty;
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -11,7 +11,7 @@
<StreamGeometry x:Key="Icons.Menu">M192 192m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 512m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 832m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM864 160H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 480H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 800H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32z</StreamGeometry> <StreamGeometry x:Key="Icons.Menu">M192 192m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 512m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 832m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM864 160H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 480H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 800H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32z</StreamGeometry>
<StreamGeometry x:Key="Icons.Goback">M512 945c-238 0-433-195-433-433S274 79 512 79c238 0 433 195 433 433S750 945 512 945M512 0C228 0 0 228 0 512s228 512 512 512 512-228 512-512-228-512-512-512zM752 477H364l128-128a38 38 0 000-55 38 38 0 00-55 0l-185 183a55 55 0 00-16 39c0 16 6 30 16 39l185 185a39 39 0 0028 12 34 34 0 0028-14 38 38 0 000-55l-128-128h386c22 0 41-18 41-39a39 39 0 00-39-39</StreamGeometry> <StreamGeometry x:Key="Icons.Goback">M512 945c-238 0-433-195-433-433S274 79 512 79c238 0 433 195 433 433S750 945 512 945M512 0C228 0 0 228 0 512s228 512 512 512 512-228 512-512-228-512-512-512zM752 477H364l128-128a38 38 0 000-55 38 38 0 00-55 0l-185 183a55 55 0 00-16 39c0 16 6 30 16 39l185 185a39 39 0 0028 12 34 34 0 0028-14 38 38 0 000-55l-128-128h386c22 0 41-18 41-39a39 39 0 00-39-39</StreamGeometry>
<StreamGeometry x:Key="Icons.Error">M576 832C576 867 547 896 512 896 477 896 448 867 448 832 448 797 477 768 512 768 547 768 576 797 576 832ZM512 256C477 256 448 285 448 320L448 640C448 675 477 704 512 704 547 704 576 675 576 640L576 320C576 285 547 256 512 256ZM1024 896C1024 967 967 1024 896 1024L128 1024C57 1024 0 967 0 896 0 875 5 855 14 837L14 837 398 69 398 69C420 28 462 0 512 0 562 0 604 28 626 69L1008 835C1018 853 1024 874 1024 896ZM960 896C960 885 957 875 952 865L952 864 951 863 569 98C557 77 536 64 512 64 488 64 466 77 455 99L452 105 92 825 93 825 71 867C66 876 64 886 64 896 64 931 93 960 128 960L896 960C931 960 960 931 960 896Z</StreamGeometry> <StreamGeometry x:Key="Icons.Error">M576 832C576 867 547 896 512 896 477 896 448 867 448 832 448 797 477 768 512 768 547 768 576 797 576 832ZM512 256C477 256 448 285 448 320L448 640C448 675 477 704 512 704 547 704 576 675 576 640L576 320C576 285 547 256 512 256ZM1024 896C1024 967 967 1024 896 1024L128 1024C57 1024 0 967 0 896 0 875 5 855 14 837L14 837 398 69 398 69C420 28 462 0 512 0 562 0 604 28 626 69L1008 835C1018 853 1024 874 1024 896ZM960 896C960 885 957 875 952 865L952 864 951 863 569 98C557 77 536 64 512 64 488 64 466 77 455 99L452 105 92 825 93 825 71 867C66 876 64 886 64 896 64 931 93 960 128 960L896 960C931 960 960 931 960 896Z</StreamGeometry>
<StreamGeometry x:Key="Icons.Conflict">M352 64h320L960 352v320L672 960h-320L64 672v-320L352 64zm161 363L344 256 260 341 429 512l-169 171L344 768 513 597 682 768l85-85L598 512l169-171L682 256 513 427z</StreamGeometry> <StreamGeometry x:Key="Icons.Conflict">M608 0q48 0 88 23t63 63 23 87v70h55q35 0 67 14t57 38 38 57 14 67V831q0 34-14 66t-38 57-57 38-67 13H426q-34 0-66-13t-57-38-38-57-14-66v-70h-56q-34 0-66-14t-57-38-38-57-13-67V174q0-47 23-87T109 23 196 0h412m175 244H426q-46 0-86 22T278 328t-26 85v348H608q47 0 86-22t63-62 25-85l1-348m-269 318q18 0 31 13t13 31-13 31-31 13-31-13-13-31 13-31 31-13m0-212q13 0 22 9t11 22v125q0 14-9 23t-22 10-23-7-11-22l-1-126q0-13 10-23t23-10z</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>
<StreamGeometry x:Key="Icons.Settings1">M716.3 383.1c0 38.4-6.5 76-19.4 111.8l-10.7 29.7 229.6 229.5c44.5 44.6 44.5 117.1 0 161.6a113.6 113.6 0 01-80.8 33.5a113.6 113.6 0 01-80.8-33.5L529 694l-32 13a331.6 331.6 0 01-111.9 19.4A333.5 333.5 0 0150 383.1c0-39 6.8-77.2 20-113.6L285 482l194-195-214-210A331 331 0 01383.1 50A333.5 333.5 0 01716.3 383.1zM231.6 31.6l-22.9 9.9a22.2 22.2 0 00-5.9 4.2a19.5 19.5 0 000 27.5l215 215.2L288.4 417.8 77.8 207.1a26 26 0 00-17.2-7.1a22.8 22.8 0 00-21.5 15a400.5 400.5 0 00-7.5 16.6A381.6 381.6 0 000 384c0 211.7 172.2 384 384 384c44.3 0 87.6-7.5 129-22.3L743.1 975.8A163.5 163.5 0 00859.5 1024c43.9 0 85.3-17.1 116.4-48.2a164.8 164.8 0 000-233L745.5 513C760.5 471.5 768 428 768 384C768 172 596 0 384 0c-53 0-104 10.5-152.5 31.5z</StreamGeometry> <StreamGeometry x:Key="Icons.Settings1">M716.3 383.1c0 38.4-6.5 76-19.4 111.8l-10.7 29.7 229.6 229.5c44.5 44.6 44.5 117.1 0 161.6a113.6 113.6 0 01-80.8 33.5a113.6 113.6 0 01-80.8-33.5L529 694l-32 13a331.6 331.6 0 01-111.9 19.4A333.5 333.5 0 0150 383.1c0-39 6.8-77.2 20-113.6L285 482l194-195-214-210A331 331 0 01383.1 50A333.5 333.5 0 01716.3 383.1zM231.6 31.6l-22.9 9.9a22.2 22.2 0 00-5.9 4.2a19.5 19.5 0 000 27.5l215 215.2L288.4 417.8 77.8 207.1a26 26 0 00-17.2-7.1a22.8 22.8 0 00-21.5 15a400.5 400.5 0 00-7.5 16.6A381.6 381.6 0 000 384c0 211.7 172.2 384 384 384c44.3 0 87.6-7.5 129-22.3L743.1 975.8A163.5 163.5 0 00859.5 1024c43.9 0 85.3-17.1 116.4-48.2a164.8 164.8 0 000-233L745.5 513C760.5 471.5 768 428 768 384C768 172 596 0 384 0c-53 0-104 10.5-152.5 31.5z</StreamGeometry>
<StreamGeometry x:Key="Icons.Settings2">M928 500a21 21 0 00-19-20L858 472a11 11 0 01-9-9c-1-6-2-13-3-19a11 11 0 015-12l46-25a21 21 0 0010-26l-8-22a21 21 0 00-24-13l-51 10a11 11 0 01-12-6c-3-6-6-11-10-17a11 11 0 011-13l34-39a21 21 0 001-28l-15-18a20 20 0 00-27-4l-45 27a11 11 0 01-13-1c-5-4-10-9-15-12a11 11 0 01-3-12l19-49a21 21 0 00-9-26l-20-12a21 21 0 00-27 6L650 193a9 9 0 01-11 3c-1-1-12-5-20-7a11 11 0 01-7-10l1-52a21 21 0 00-17-22l-23-4a21 21 0 00-24 14L532 164a11 11 0 01-11 7h-20a11 11 0 01-11-7l-17-49a21 21 0 00-24-15l-23 4a21 21 0 00-17 22l1 52a11 11 0 01-8 11c-5 2-15 6-19 7c-4 1-8 0-12-4l-33-40A21 21 0 00313 146l-20 12A21 21 0 00285 184l19 49a11 11 0 01-3 12c-5 4-10 8-15 12a11 11 0 01-13 1L228 231a21 21 0 00-27 4L186 253a21 21 0 001 28L221 320a11 11 0 011 13c-3 5-7 11-10 17a11 11 0 01-12 6l-51-10a21 21 0 00-24 13l-8 22a21 21 0 0010 26l46 25a11 11 0 015 12l0 3c-1 6-2 11-3 16a11 11 0 01-9 9l-51 8A21 21 0 0096 500v23A21 21 0 00114 544l51 8a11 11 0 019 9c1 6 2 13 3 19a11 11 0 01-5 12l-46 25a21 21 0 00-10 26l8 22a21 21 0 0024 13l51-10a11 11 0 0112 6c3 6 6 11 10 17a11 11 0 01-1 13l-34 39a21 21 0 00-1 28l15 18a20 20 0 0027 4l45-27a11 11 0 0113 1c5 4 10 9 15 12a11 11 0 013 12l-19 49a21 21 0 009 26l20 12a21 21 0 0027-6L374 832c3-3 7-5 10-4c7 3 12 5 20 7a11 11 0 018 10l-1 52a21 21 0 0017 22l23 4a21 21 0 0024-14l17-50a11 11 0 0111-7h20a11 11 0 0111 7l17 49a21 21 0 0020 15a19 19 0 004 0l23-4a21 21 0 0017-22l-1-52a11 11 0 018-10c8-3 13-5 18-7l1 0c6-2 9 0 11 3l34 41A21 21 0 00710 878l20-12a21 21 0 009-26l-18-49a11 11 0 013-12c5-4 10-8 15-12a11 11 0 0113-1l45 27a21 21 0 0027-4l15-18a21 21 0 00-1-28l-34-39a11 11 0 01-1-13c3-5 7-11 10-17a11 11 0 0112-6l51 10a21 21 0 0024-13l8-22a21 21 0 00-10-26l-46-25a11 11 0 01-5-12l0-3c1-6 2-11 3-16a11 11 0 019-9l51-8a21 21 0 0018-21v-23zm-565 188a32 32 0 01-51 5a270 270 0 011-363a32 32 0 0151 6l91 161a32 32 0 010 31zM512 782a270 270 0 01-57-6a32 32 0 01-20-47l92-160a32 32 0 0127-16h184a32 32 0 0130 41c-35 109-137 188-257 188zm15-328L436 294a32 32 0 0121-47a268 268 0 0155-6c120 0 222 79 257 188a32 32 0 01-30 41h-184a32 32 0 01-28-16z</StreamGeometry> <StreamGeometry x:Key="Icons.Settings2">M928 500a21 21 0 00-19-20L858 472a11 11 0 01-9-9c-1-6-2-13-3-19a11 11 0 015-12l46-25a21 21 0 0010-26l-8-22a21 21 0 00-24-13l-51 10a11 11 0 01-12-6c-3-6-6-11-10-17a11 11 0 011-13l34-39a21 21 0 001-28l-15-18a20 20 0 00-27-4l-45 27a11 11 0 01-13-1c-5-4-10-9-15-12a11 11 0 01-3-12l19-49a21 21 0 00-9-26l-20-12a21 21 0 00-27 6L650 193a9 9 0 01-11 3c-1-1-12-5-20-7a11 11 0 01-7-10l1-52a21 21 0 00-17-22l-23-4a21 21 0 00-24 14L532 164a11 11 0 01-11 7h-20a11 11 0 01-11-7l-17-49a21 21 0 00-24-15l-23 4a21 21 0 00-17 22l1 52a11 11 0 01-8 11c-5 2-15 6-19 7c-4 1-8 0-12-4l-33-40A21 21 0 00313 146l-20 12A21 21 0 00285 184l19 49a11 11 0 01-3 12c-5 4-10 8-15 12a11 11 0 01-13 1L228 231a21 21 0 00-27 4L186 253a21 21 0 001 28L221 320a11 11 0 011 13c-3 5-7 11-10 17a11 11 0 01-12 6l-51-10a21 21 0 00-24 13l-8 22a21 21 0 0010 26l46 25a11 11 0 015 12l0 3c-1 6-2 11-3 16a11 11 0 01-9 9l-51 8A21 21 0 0096 500v23A21 21 0 00114 544l51 8a11 11 0 019 9c1 6 2 13 3 19a11 11 0 01-5 12l-46 25a21 21 0 00-10 26l8 22a21 21 0 0024 13l51-10a11 11 0 0112 6c3 6 6 11 10 17a11 11 0 01-1 13l-34 39a21 21 0 00-1 28l15 18a20 20 0 0027 4l45-27a11 11 0 0113 1c5 4 10 9 15 12a11 11 0 013 12l-19 49a21 21 0 009 26l20 12a21 21 0 0027-6L374 832c3-3 7-5 10-4c7 3 12 5 20 7a11 11 0 018 10l-1 52a21 21 0 0017 22l23 4a21 21 0 0024-14l17-50a11 11 0 0111-7h20a11 11 0 0111 7l17 49a21 21 0 0020 15a19 19 0 004 0l23-4a21 21 0 0017-22l-1-52a11 11 0 018-10c8-3 13-5 18-7l1 0c6-2 9 0 11 3l34 41A21 21 0 00710 878l20-12a21 21 0 009-26l-18-49a11 11 0 013-12c5-4 10-8 15-12a11 11 0 0113-1l45 27a21 21 0 0027-4l15-18a21 21 0 00-1-28l-34-39a11 11 0 01-1-13c3-5 7-11 10-17a11 11 0 0112-6l51 10a21 21 0 0024-13l8-22a21 21 0 00-10-26l-46-25a11 11 0 01-5-12l0-3c1-6 2-11 3-16a11 11 0 019-9l51-8a21 21 0 0018-21v-23zm-565 188a32 32 0 01-51 5a270 270 0 011-363a32 32 0 0151 6l91 161a32 32 0 010 31zM512 782a270 270 0 01-57-6a32 32 0 01-20-47l92-160a32 32 0 0127-16h184a32 32 0 0130 41c-35 109-137 188-257 188zm15-328L436 294a32 32 0 0121-47a268 268 0 0155-6c120 0 222 79 257 188a32 32 0 01-30 41h-184a32 32 0 01-28-16z</StreamGeometry>
@ -88,4 +88,6 @@
<StreamGeometry x:Key="Icons.SyntaxHighlight">M875 128h-725A107 107 0 0043 235v555A107 107 0 00149 896h725a107 107 0 00107-107v-555A107 107 0 00875 128zm-115 640h-183v-58l25-3c15 0 19-8 14-24l-22-61H419l-28 82 39 2V768h-166v-58l18-3c18-2 22-11 26-24l125-363-40-4V256h168l160 448 39 3zM506 340l-72 218h145l-71-218h-2z</StreamGeometry> <StreamGeometry x:Key="Icons.SyntaxHighlight">M875 128h-725A107 107 0 0043 235v555A107 107 0 00149 896h725a107 107 0 00107-107v-555A107 107 0 00875 128zm-115 640h-183v-58l25-3c15 0 19-8 14-24l-22-61H419l-28 82 39 2V768h-166v-58l18-3c18-2 22-11 26-24l125-363-40-4V256h168l160 448 39 3zM506 340l-72 218h145l-71-218h-2z</StreamGeometry>
<StreamGeometry x:Key="Icons.SoftwareUpdate">M900 287c40 69 60 144 60 225s-20 156-60 225c-40 69-94 123-163 163-69 40-144 60-225 60s-156-20-225-60c-69-40-123-94-163-163C84 668 64 593 64 512s20-156 60-225 94-123 163-163c69-40 144-60 225-60s156 20 225 60 123 94 163 163zM762 512c0-9-3-16-9-22L578 315l-44-44c-6-6-13-9-22-9s-16 3-22 9l-44 44-176 176c-6 6-9 13-9 22s3 16 9 22l44 44c6 6 13 9 22 9s16-3 22-9l92-92v269c0 9 3 16 9 22 6 6 13 9 22 9h62c8 0 16-3 22-9 6-6 9-13 9-22V486l92 92c6 6 13 9 22 9 8 0 16-3 22-9l44-44c6-6 9-13 9-22z</StreamGeometry> <StreamGeometry x:Key="Icons.SoftwareUpdate">M900 287c40 69 60 144 60 225s-20 156-60 225c-40 69-94 123-163 163-69 40-144 60-225 60s-156-20-225-60c-69-40-123-94-163-163C84 668 64 593 64 512s20-156 60-225 94-123 163-163c69-40 144-60 225-60s156 20 225 60 123 94 163 163zM762 512c0-9-3-16-9-22L578 315l-44-44c-6-6-13-9-22-9s-16 3-22 9l-44 44-176 176c-6 6-9 13-9 22s3 16 9 22l44 44c6 6 13 9 22 9s16-3 22-9l92-92v269c0 9 3 16 9 22 6 6 13 9 22 9h62c8 0 16-3 22-9 6-6 9-13 9-22V486l92 92c6 6 13 9 22 9 8 0 16-3 22-9l44-44c6-6 9-13 9-22z</StreamGeometry>
<StreamGeometry x:Key="Icons.Target">M765 555C747 661 661 747 555 765L555 683 469 683 469 765C363 747 277 661 259 555L341 555 341 469 259 469C277 363 363 277 469 259L469 341 555 341 555 259C661 277 747 363 765 469L683 469 683 555 765 555M851 469C832 315 709 192 555 173L555 85 469 85 469 173C315 192 192 315 173 469L85 469 85 555 173 555C192 709 315 832 469 851L469 939 555 939 555 851C709 832 832 709 851 555L939 555 939 469 851 469M512 427C559 427 597 465 597 512 597 559 559 597 512 597 465 597 427 559 427 512 427 465 465 427 512 427L512 427Z</StreamGeometry> <StreamGeometry x:Key="Icons.Target">M765 555C747 661 661 747 555 765L555 683 469 683 469 765C363 747 277 661 259 555L341 555 341 469 259 469C277 363 363 277 469 259L469 341 555 341 555 259C661 277 747 363 765 469L683 469 683 555 765 555M851 469C832 315 709 192 555 173L555 85 469 85 469 173C315 192 192 315 173 469L85 469 85 555 173 555C192 709 315 832 469 851L469 939 555 939 555 851C709 832 832 709 851 555L939 555 939 469 851 469M512 427C559 427 597 465 597 512 597 559 559 597 512 597 465 597 427 559 427 512 427 465 465 427 512 427L512 427Z</StreamGeometry>
<StreamGeometry x:Key="Icons.Incoming">M973 358a51 51 0 0151 51v563a51 51 0 01-51 51H51a51 51 0 01-51-51V410a51 51 0 0151-51h256a51 51 0 110 102H102v461h819V461h-205a51 51 0 110-102h256zM51 102a51 51 0 110-102h256c141 0 256 115 256 256v388l66-66a51 51 0 1172 72l-154 154a51 51 0 01-72 0l-154-154a51 51 0 1172-72L461 644V256c0-85-69-154-154-154H51z</StreamGeometry>
<StreamGeometry x:Key="Icons.Local">M976 0h-928A48 48 0 000 48v652a48 48 0 0048 48h416V928H200a48 48 0 000 96h624a48 48 0 000-96H560v-180h416a48 48 0 0048-48V48A48 48 0 00976 0zM928 652H96V96h832v556z</StreamGeometry>
</ResourceDictionary> </ResourceDictionary>

View file

@ -131,6 +131,7 @@
<x:String x:Key="Text.Diff.Binary.New" xml:space="preserve">NEW</x:String> <x:String x:Key="Text.Diff.Binary.New" xml:space="preserve">NEW</x:String>
<x:String x:Key="Text.Diff.Binary.Old" xml:space="preserve">OLD</x:String> <x:String x:Key="Text.Diff.Binary.Old" xml:space="preserve">OLD</x:String>
<x:String x:Key="Text.Diff.Copy" xml:space="preserve">Copy</x:String> <x:String x:Key="Text.Diff.Copy" xml:space="preserve">Copy</x:String>
<x:String x:Key="Text.Diff.FileModeChanged" xml:space="preserve">File Mode Changed</x:String>
<x:String x:Key="Text.Diff.LFS" xml:space="preserve">LFS OBJECT CHANGE</x:String> <x:String x:Key="Text.Diff.LFS" xml:space="preserve">LFS OBJECT CHANGE</x:String>
<x:String x:Key="Text.Diff.Next" xml:space="preserve">Next Difference</x:String> <x:String x:Key="Text.Diff.Next" xml:space="preserve">Next Difference</x:String>
<x:String x:Key="Text.Diff.NoChange" xml:space="preserve">NO CHANGES OR ONLY EOL CHANGES</x:String> <x:String x:Key="Text.Diff.NoChange" xml:space="preserve">NO CHANGES OR ONLY EOL CHANGES</x:String>
@ -160,6 +161,7 @@
<x:String x:Key="Text.FileCM.Discard" xml:space="preserve">Discard...</x:String> <x:String x:Key="Text.FileCM.Discard" xml:space="preserve">Discard...</x:String>
<x:String x:Key="Text.FileCM.DiscardMulti" xml:space="preserve">Discard {0} files...</x:String> <x:String x:Key="Text.FileCM.DiscardMulti" xml:space="preserve">Discard {0} files...</x:String>
<x:String x:Key="Text.FileCM.DiscardSelectedLines" xml:space="preserve">Discard Changes in Selected Line(s)</x:String> <x:String x:Key="Text.FileCM.DiscardSelectedLines" xml:space="preserve">Discard Changes in Selected Line(s)</x:String>
<x:String x:Key="Text.FileCM.OpenWithExternalMerger" xml:space="preserve">Open External Merge Tool</x:String>
<x:String x:Key="Text.FileCM.SaveAsPatch" xml:space="preserve">Save As Patch...</x:String> <x:String x:Key="Text.FileCM.SaveAsPatch" xml:space="preserve">Save As Patch...</x:String>
<x:String x:Key="Text.FileCM.Stage" xml:space="preserve">Stage...</x:String> <x:String x:Key="Text.FileCM.Stage" xml:space="preserve">Stage...</x:String>
<x:String x:Key="Text.FileCM.StageMulti" xml:space="preserve">Stage {0} files...</x:String> <x:String x:Key="Text.FileCM.StageMulti" xml:space="preserve">Stage {0} files...</x:String>
@ -169,6 +171,8 @@
<x:String x:Key="Text.FileCM.Unstage" xml:space="preserve">Unstage</x:String> <x:String x:Key="Text.FileCM.Unstage" xml:space="preserve">Unstage</x:String>
<x:String x:Key="Text.FileCM.UnstageMulti" xml:space="preserve">Unstage {0} files</x:String> <x:String x:Key="Text.FileCM.UnstageMulti" xml:space="preserve">Unstage {0} files</x:String>
<x:String x:Key="Text.FileCM.UnstageSelectedLines" xml:space="preserve">Unstage Changes in Selected Line(s)</x:String> <x:String x:Key="Text.FileCM.UnstageSelectedLines" xml:space="preserve">Unstage Changes in Selected Line(s)</x:String>
<x:String x:Key="Text.FileCM.UseTheirs" xml:space="preserve">Use Theirs (checkout --theirs)</x:String>
<x:String x:Key="Text.FileCM.UseMine" xml:space="preserve">Use Mine (checkout --ours)</x:String>
<x:String x:Key="Text.FileHistory" xml:space="preserve">File History</x:String> <x:String x:Key="Text.FileHistory" xml:space="preserve">File History</x:String>
<x:String x:Key="Text.Filter" xml:space="preserve">FILTER</x:String> <x:String x:Key="Text.Filter" xml:space="preserve">FILTER</x:String>
<x:String x:Key="Text.GitFlow" xml:space="preserve">Git-Flow</x:String> <x:String x:Key="Text.GitFlow" xml:space="preserve">Git-Flow</x:String>
@ -266,6 +270,7 @@
<x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">User Email</x:String> <x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">User Email</x:String>
<x:String x:Key="Text.Preference.Git.Email.Placeholder" xml:space="preserve">Global git user email</x:String> <x:String x:Key="Text.Preference.Git.Email.Placeholder" xml:space="preserve">Global git user email</x:String>
<x:String x:Key="Text.Preference.Git.Path" xml:space="preserve">Install Path</x:String> <x:String x:Key="Text.Preference.Git.Path" xml:space="preserve">Install Path</x:String>
<x:String x:Key="Text.Preference.Git.Shell" xml:space="preserve">Shell</x:String>
<x:String x:Key="Text.Preference.Git.User" xml:space="preserve">User Name</x:String> <x:String x:Key="Text.Preference.Git.User" xml:space="preserve">User Name</x:String>
<x:String x:Key="Text.Preference.Git.User.Placeholder" xml:space="preserve">Global git user name</x:String> <x:String x:Key="Text.Preference.Git.User.Placeholder" xml:space="preserve">Global git user name</x:String>
<x:String x:Key="Text.Preference.Git.Version" xml:space="preserve">Git version</x:String> <x:String x:Key="Text.Preference.Git.Version" xml:space="preserve">Git version</x:String>
@ -275,7 +280,7 @@
<x:String x:Key="Text.Preference.GPG.Path.Placeholder" xml:space="preserve">Input path for installed gpg program</x:String> <x:String x:Key="Text.Preference.GPG.Path.Placeholder" xml:space="preserve">Input path for installed gpg program</x:String>
<x:String x:Key="Text.Preference.GPG.UserKey" xml:space="preserve">User Signing Key</x:String> <x:String x:Key="Text.Preference.GPG.UserKey" xml:space="preserve">User Signing Key</x:String>
<x:String x:Key="Text.Preference.GPG.UserKey.Placeholder" xml:space="preserve">User's gpg signing key</x:String> <x:String x:Key="Text.Preference.GPG.UserKey.Placeholder" xml:space="preserve">User's gpg signing key</x:String>
<x:String x:Key="Text.Preference.Merger" xml:space="preserve">MERGE</x:String> <x:String x:Key="Text.Preference.Merger" xml:space="preserve">EXTERNAL MERGE TOOL</x:String>
<x:String x:Key="Text.Preference.Merger.CustomDiffCmd" xml:space="preserve">Diff Command</x:String> <x:String x:Key="Text.Preference.Merger.CustomDiffCmd" xml:space="preserve">Diff Command</x:String>
<x:String x:Key="Text.Preference.Merger.CustomMergeCmd" xml:space="preserve">Merge Command</x:String> <x:String x:Key="Text.Preference.Merger.CustomMergeCmd" xml:space="preserve">Merge Command</x:String>
<x:String x:Key="Text.Preference.Merger.Path" xml:space="preserve">Install Path</x:String> <x:String x:Key="Text.Preference.Merger.Path" xml:space="preserve">Install Path</x:String>
@ -430,7 +435,6 @@
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">INCLUDE UNTRACKED FILES</x:String> <x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">INCLUDE UNTRACKED FILES</x:String>
<x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">MESSAGE HISTORIES</x:String> <x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">MESSAGE HISTORIES</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">NO RECENT INPUT MESSAGES</x:String> <x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">NO RECENT INPUT MESSAGES</x:String>
<x:String x:Key="Text.WorkingCopy.OpenMerger" xml:space="preserve">OPEN MERGE</x:String>
<x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">STAGED</x:String> <x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">STAGED</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">UNSTAGE</x:String> <x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">UNSTAGE</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.UnstageAll" xml:space="preserve">UNSTAGE ALL</x:String> <x:String x:Key="Text.WorkingCopy.Staged.UnstageAll" xml:space="preserve">UNSTAGE ALL</x:String>
@ -438,6 +442,5 @@
<x:String x:Key="Text.WorkingCopy.Unstaged.Stage" xml:space="preserve">STAGE</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.Stage" xml:space="preserve">STAGE</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.StageAll" xml:space="preserve">STAGE ALL</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.StageAll" xml:space="preserve">STAGE ALL</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.ViewAssumeUnchaged" xml:space="preserve">VIEW ASSUME UNCHANGED</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.ViewAssumeUnchaged" xml:space="preserve">VIEW ASSUME UNCHANGED</x:String>
<x:String x:Key="Text.WorkingCopy.UseMine" xml:space="preserve">USE MINE</x:String> <x:String x:Key="Text.WorkingCopy.ResolveTip" xml:space="preserve">Right-click the selected file(s), and make your choice to resolve conflicts.</x:String>
<x:String x:Key="Text.WorkingCopy.UseTheirs" xml:space="preserve">USE THEIRS</x:String>
</ResourceDictionary> </ResourceDictionary>

View file

@ -131,6 +131,7 @@
<x:String x:Key="Text.Diff.Binary.New" xml:space="preserve">当前大小</x:String> <x:String x:Key="Text.Diff.Binary.New" xml:space="preserve">当前大小</x:String>
<x:String x:Key="Text.Diff.Binary.Old" xml:space="preserve">原始大小</x:String> <x:String x:Key="Text.Diff.Binary.Old" xml:space="preserve">原始大小</x:String>
<x:String x:Key="Text.Diff.Copy" xml:space="preserve">复制</x:String> <x:String x:Key="Text.Diff.Copy" xml:space="preserve">复制</x:String>
<x:String x:Key="Text.Diff.FileModeChanged" xml:space="preserve">文件权限已变化</x:String>
<x:String x:Key="Text.Diff.LFS" xml:space="preserve">LFS对象变更</x:String> <x:String x:Key="Text.Diff.LFS" xml:space="preserve">LFS对象变更</x:String>
<x:String x:Key="Text.Diff.Next" xml:space="preserve">下一个差异</x:String> <x:String x:Key="Text.Diff.Next" xml:space="preserve">下一个差异</x:String>
<x:String x:Key="Text.Diff.NoChange" xml:space="preserve">没有变更或仅有换行符差异</x:String> <x:String x:Key="Text.Diff.NoChange" xml:space="preserve">没有变更或仅有换行符差异</x:String>
@ -160,6 +161,7 @@
<x:String x:Key="Text.FileCM.Discard" xml:space="preserve">放弃更改...</x:String> <x:String x:Key="Text.FileCM.Discard" xml:space="preserve">放弃更改...</x:String>
<x:String x:Key="Text.FileCM.DiscardMulti" xml:space="preserve">放弃 {0} 个文件的更改...</x:String> <x:String x:Key="Text.FileCM.DiscardMulti" xml:space="preserve">放弃 {0} 个文件的更改...</x:String>
<x:String x:Key="Text.FileCM.DiscardSelectedLines" xml:space="preserve">放弃选中的更改</x:String> <x:String x:Key="Text.FileCM.DiscardSelectedLines" xml:space="preserve">放弃选中的更改</x:String>
<x:String x:Key="Text.FileCM.OpenWithExternalMerger" xml:space="preserve">使用外部合并工具打开</x:String>
<x:String x:Key="Text.FileCM.SaveAsPatch" xml:space="preserve">另存为补丁...</x:String> <x:String x:Key="Text.FileCM.SaveAsPatch" xml:space="preserve">另存为补丁...</x:String>
<x:String x:Key="Text.FileCM.Stage" xml:space="preserve">暂存(add)...</x:String> <x:String x:Key="Text.FileCM.Stage" xml:space="preserve">暂存(add)...</x:String>
<x:String x:Key="Text.FileCM.StageMulti" xml:space="preserve">暂存(add){0} 个文件...</x:String> <x:String x:Key="Text.FileCM.StageMulti" xml:space="preserve">暂存(add){0} 个文件...</x:String>
@ -169,6 +171,8 @@
<x:String x:Key="Text.FileCM.Unstage" xml:space="preserve">从暂存中移除</x:String> <x:String x:Key="Text.FileCM.Unstage" xml:space="preserve">从暂存中移除</x:String>
<x:String x:Key="Text.FileCM.UnstageMulti" xml:space="preserve">从暂存中移除 {0} 个文件</x:String> <x:String x:Key="Text.FileCM.UnstageMulti" xml:space="preserve">从暂存中移除 {0} 个文件</x:String>
<x:String x:Key="Text.FileCM.UnstageSelectedLines" xml:space="preserve">从暂存中移除选中的更改</x:String> <x:String x:Key="Text.FileCM.UnstageSelectedLines" xml:space="preserve">从暂存中移除选中的更改</x:String>
<x:String x:Key="Text.FileCM.UseTheirs" xml:space="preserve">使用 THEIRS (checkout --theirs)</x:String>
<x:String x:Key="Text.FileCM.UseMine" xml:space="preserve">使用 MINE (checkout --ours)</x:String>
<x:String x:Key="Text.FileHistory" xml:space="preserve">文件历史</x:String> <x:String x:Key="Text.FileHistory" xml:space="preserve">文件历史</x:String>
<x:String x:Key="Text.Filter" xml:space="preserve">过滤</x:String> <x:String x:Key="Text.Filter" xml:space="preserve">过滤</x:String>
<x:String x:Key="Text.GitFlow" xml:space="preserve">GIT工作流</x:String> <x:String x:Key="Text.GitFlow" xml:space="preserve">GIT工作流</x:String>
@ -266,6 +270,7 @@
<x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">邮箱</x:String> <x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">邮箱</x:String>
<x:String x:Key="Text.Preference.Git.Email.Placeholder" xml:space="preserve">默认GIT用户邮箱</x:String> <x:String x:Key="Text.Preference.Git.Email.Placeholder" xml:space="preserve">默认GIT用户邮箱</x:String>
<x:String x:Key="Text.Preference.Git.Path" xml:space="preserve">安装路径</x:String> <x:String x:Key="Text.Preference.Git.Path" xml:space="preserve">安装路径</x:String>
<x:String x:Key="Text.Preference.Git.Shell" xml:space="preserve">终端Shell</x:String>
<x:String x:Key="Text.Preference.Git.User" xml:space="preserve">用户名</x:String> <x:String x:Key="Text.Preference.Git.User" xml:space="preserve">用户名</x:String>
<x:String x:Key="Text.Preference.Git.User.Placeholder" xml:space="preserve">默认GIT用户名</x:String> <x:String x:Key="Text.Preference.Git.User.Placeholder" xml:space="preserve">默认GIT用户名</x:String>
<x:String x:Key="Text.Preference.Git.Version" xml:space="preserve">Git 版本</x:String> <x:String x:Key="Text.Preference.Git.Version" xml:space="preserve">Git 版本</x:String>
@ -430,7 +435,6 @@
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">显示未跟踪文件</x:String> <x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">显示未跟踪文件</x:String>
<x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">历史提交信息</x:String> <x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">历史提交信息</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">没有提交信息记录</x:String> <x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">没有提交信息记录</x:String>
<x:String x:Key="Text.WorkingCopy.OpenMerger" xml:space="preserve">打开合并工具</x:String>
<x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">已暂存</x:String> <x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">已暂存</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">从暂存区移除选中</x:String> <x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">从暂存区移除选中</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.UnstageAll" xml:space="preserve">从暂存区移除所有</x:String> <x:String x:Key="Text.WorkingCopy.Staged.UnstageAll" xml:space="preserve">从暂存区移除所有</x:String>
@ -438,6 +442,5 @@
<x:String x:Key="Text.WorkingCopy.Unstaged.Stage" xml:space="preserve">暂存选中</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.Stage" xml:space="preserve">暂存选中</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.StageAll" xml:space="preserve">暂存所有</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.StageAll" xml:space="preserve">暂存所有</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.ViewAssumeUnchaged" xml:space="preserve">查看忽略变更文件</x:String> <x:String x:Key="Text.WorkingCopy.Unstaged.ViewAssumeUnchaged" xml:space="preserve">查看忽略变更文件</x:String>
<x:String x:Key="Text.WorkingCopy.UseMine" xml:space="preserve">使用MINE</x:String> <x:String x:Key="Text.WorkingCopy.ResolveTip" xml:space="preserve">请选中冲突文件,打开右键菜单,选择合适的解决方式</x:String>
<x:String x:Key="Text.WorkingCopy.UseTheirs" xml:space="preserve">使用THEIRS</x:String>
</ResourceDictionary> </ResourceDictionary>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -5,7 +5,7 @@
<BuiltInComInteropSupport>true</BuiltInComInteropSupport> <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
<ApplicationManifest>App.manifest</ApplicationManifest> <ApplicationManifest>App.manifest</ApplicationManifest>
<ApplicationIcon>App.ico</ApplicationIcon> <ApplicationIcon>App.ico</ApplicationIcon>
<Version>8.7</Version> <Version>8.8</Version>
<BuiltInComInteropSupport>false</BuiltInComInteropSupport> <BuiltInComInteropSupport>false</BuiltInComInteropSupport>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault> <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings> <SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings>
@ -22,8 +22,9 @@
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="App.ico" /> <AvaloniaResource Include="App.ico" />
<AvaloniaResource Include="Resources/Fonts/*" />
<AvaloniaResource Include="Resources/ExternalToolIcons/*" /> <AvaloniaResource Include="Resources/ExternalToolIcons/*" />
<AvaloniaResource Include="Resources/Fonts/*" />
<AvaloniaResource Include="Resources/ShellIcons/*" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View file

@ -205,7 +205,7 @@ namespace SourceGit.ViewModels
var type = Preference.Instance.ExternalMergeToolType; var type = Preference.Instance.ExternalMergeToolType;
var exec = Preference.Instance.ExternalMergeToolPath; var exec = Preference.Instance.ExternalMergeToolPath;
var tool = Models.ExternalMergeTools.Supported.Find(x => x.Type == type); var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type);
if (tool == null || !File.Exists(exec)) if (tool == null || !File.Exists(exec))
{ {
App.RaiseException(_repo, "Invalid merge tool in preference setting!"); App.RaiseException(_repo, "Invalid merge tool in preference setting!");
@ -355,7 +355,9 @@ namespace SourceGit.ViewModels
_cancelToken.Requested = true; _cancelToken.Requested = true;
_cancelToken = new Commands.Command.CancelToken(); _cancelToken = new Commands.Command.CancelToken();
var cmdChanges = new Commands.QueryCommitChanges(_repo, _commit.SHA) { Cancel = _cancelToken };
var parent = _commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : _commit.Parents[0];
var cmdChanges = new Commands.CompareRevisions(_repo, parent, _commit.SHA) { Cancel = _cancelToken };
var cmdRevisionFiles = new Commands.QueryRevisionObjects(_repo, _commit.SHA) { Cancel = _cancelToken }; var cmdRevisionFiles = new Commands.QueryRevisionObjects(_repo, _commit.SHA) { Cancel = _cancelToken };
Task.Run(() => Task.Run(() =>

View file

@ -27,19 +27,16 @@ namespace SourceGit.ViewModels
get => _option.IsUnstaged; get => _option.IsUnstaged;
} }
public string FilePath public string Title
{ {
get => _option.Path; get => _title;
private set => SetProperty(ref _title, value);
} }
public bool IsOrgFilePathVisible public string FileModeChange
{ {
get => !string.IsNullOrWhiteSpace(_option.OrgPath) && _option.OrgPath != "/dev/null"; get => _fileModeChange;
} private set => SetProperty(ref _fileModeChange, value);
public string OrgFilePath
{
get => _option.OrgPath;
} }
public bool IsLoading public bool IsLoading
@ -77,10 +74,6 @@ namespace SourceGit.ViewModels
_content = previous._content; _content = previous._content;
} }
OnPropertyChanged(nameof(FilePath));
OnPropertyChanged(nameof(IsOrgFilePathVisible));
OnPropertyChanged(nameof(OrgFilePath));
Task.Run(() => Task.Run(() =>
{ {
var latest = new Commands.Diff(repo, option).Result(); var latest = new Commands.Diff(repo, option).Result();
@ -140,6 +133,12 @@ namespace SourceGit.ViewModels
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
if (string.IsNullOrEmpty(_option.OrgPath))
Title = _option.Path;
else
Title = $"{_option.OrgPath} → {_option.Path}";
FileModeChange = latest.FileModeChange;
Content = rs; Content = rs;
IsTextDiff = latest.TextDiff != null; IsTextDiff = latest.TextDiff != null;
IsLoading = false; IsLoading = false;
@ -152,7 +151,7 @@ namespace SourceGit.ViewModels
var type = Preference.Instance.ExternalMergeToolType; var type = Preference.Instance.ExternalMergeToolType;
var exec = Preference.Instance.ExternalMergeToolPath; var exec = Preference.Instance.ExternalMergeToolPath;
var tool = Models.ExternalMergeTools.Supported.Find(x => x.Type == type); var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type);
if (tool == null || !File.Exists(exec)) if (tool == null || !File.Exists(exec))
{ {
App.RaiseException(_repo, "Invalid merge tool in preference setting!"); App.RaiseException(_repo, "Invalid merge tool in preference setting!");
@ -176,6 +175,8 @@ namespace SourceGit.ViewModels
private readonly string _repo = string.Empty; private readonly string _repo = string.Empty;
private readonly Models.DiffOption _option = null; private readonly Models.DiffOption _option = null;
private string _title = string.Empty;
private string _fileModeChange = string.Empty;
private bool _isLoading = true; private bool _isLoading = true;
private bool _isTextDiff = false; private bool _isTextDiff = false;
private object _content = null; private object _content = null;

View file

@ -129,6 +129,7 @@ namespace SourceGit.ViewModels
{ {
var commit = commits[0] as Models.Commit; var commit = commits[0] as Models.Commit;
AutoSelectedCommit = commit; AutoSelectedCommit = commit;
NavigationId = _navigationId + 1;
if (_detailContext is CommitDetail detail) if (_detailContext is CommitDetail detail)
{ {

View file

@ -200,6 +200,18 @@ namespace SourceGit.ViewModels
} }
} }
public Models.Shell GitShell
{
get => Native.OS.GetShell();
set
{
if (Native.OS.SetShell(value))
{
OnPropertyChanged(nameof(GitShell));
}
}
}
public string GitDefaultCloneDir public string GitDefaultCloneDir
{ {
get => _gitDefaultCloneDir; get => _gitDefaultCloneDir;
@ -225,9 +237,9 @@ namespace SourceGit.ViewModels
set set
{ {
var changed = SetProperty(ref _externalMergeToolType, value); var changed = SetProperty(ref _externalMergeToolType, value);
if (changed && !OperatingSystem.IsWindows() && value > 0 && value < Models.ExternalMergeTools.Supported.Count) if (changed && !OperatingSystem.IsWindows() && value > 0 && value < Models.ExternalMerger.Supported.Count)
{ {
var tool = Models.ExternalMergeTools.Supported[value]; var tool = Models.ExternalMerger.Supported[value];
if (File.Exists(tool.Exec)) if (File.Exists(tool.Exec))
ExternalMergeToolPath = tool.Exec; ExternalMergeToolPath = tool.Exec;
else else

View file

@ -287,10 +287,10 @@ namespace SourceGit.ViewModels
Native.OS.OpenTerminal(_fullpath); Native.OS.OpenTerminal(_fullpath);
} }
public ContextMenu CreateContextMenuForExternalEditors() public ContextMenu CreateContextMenuForExternalTools()
{ {
var editors = Native.OS.ExternalEditors; var tools = Native.OS.ExternalTools;
if (editors.Count == 0) if (tools.Count == 0)
{ {
App.RaiseException(_fullpath, "No available external editors found!"); App.RaiseException(_fullpath, "No available external editors found!");
return null; return null;
@ -300,16 +300,16 @@ namespace SourceGit.ViewModels
menu.Placement = PlacementMode.BottomEdgeAlignedLeft; menu.Placement = PlacementMode.BottomEdgeAlignedLeft;
RenderOptions.SetBitmapInterpolationMode(menu, BitmapInterpolationMode.HighQuality); RenderOptions.SetBitmapInterpolationMode(menu, BitmapInterpolationMode.HighQuality);
foreach (var editor in editors) foreach (var tool in tools)
{ {
var dupEditor = editor; var dupTool = tool;
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{dupEditor.Icon}", UriKind.RelativeOrAbsolute));
var item = new MenuItem(); var item = new MenuItem();
item.Header = App.Text("Repository.OpenIn", dupEditor.Name); item.Header = App.Text("Repository.OpenIn", dupTool.Name);
item.Icon = new Image { Width = 16, Height = 16, Source = new Bitmap(icon) }; item.Icon = new Image { Width = 16, Height = 16, Source = dupTool.IconImage };
item.Click += (o, e) => item.Click += (o, e) =>
{ {
dupEditor.Open(_fullpath); dupTool.Open(_fullpath);
e.Handled = true; e.Handled = true;
}; };

View file

@ -166,7 +166,7 @@ namespace SourceGit.ViewModels
var type = Preference.Instance.ExternalMergeToolType; var type = Preference.Instance.ExternalMergeToolType;
var exec = Preference.Instance.ExternalMergeToolPath; var exec = Preference.Instance.ExternalMergeToolPath;
var tool = Models.ExternalMergeTools.Supported.Find(x => x.Type == type); var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type);
if (tool == null || !File.Exists(exec)) if (tool == null || !File.Exists(exec))
{ {
App.RaiseException(_repo, "Invalid merge tool in preference setting!"); App.RaiseException(_repo, "Invalid merge tool in preference setting!");

View file

@ -386,34 +386,42 @@ namespace SourceGit.ViewModels
} }
} }
public async void UseTheirs() public async void UseTheirs(List<Models.Change> changes)
{ {
if (_detailContext is ConflictContext ctx) var files = new List<string>();
foreach (var change in changes)
{ {
_repo.SetWatcherEnabled(false); if (change.IsConflit)
var succ = await Task.Run(() => new Commands.Checkout(_repo.FullPath).File(ctx.Change.Path, true)); files.Add(change.Path);
if (succ)
{
await Task.Run(() => new Commands.Add(_repo.FullPath, [ctx.Change]).Exec());
}
_repo.MarkWorkingCopyDirtyManually();
_repo.SetWatcherEnabled(true);
} }
_repo.SetWatcherEnabled(false);
var succ = await Task.Run(() => new Commands.Checkout(_repo.FullPath).UseTheirs(files));
if (succ)
{
await Task.Run(() => new Commands.Add(_repo.FullPath, changes).Exec());
}
_repo.MarkWorkingCopyDirtyManually();
_repo.SetWatcherEnabled(true);
} }
public async void UseMine() public async void UseMine(List<Models.Change> changes)
{ {
if (_detailContext is ConflictContext ctx) var files = new List<string>();
foreach (var change in changes)
{ {
_repo.SetWatcherEnabled(false); if (change.IsConflit)
var succ = await Task.Run(() => new Commands.Checkout(_repo.FullPath).File(ctx.Change.Path, false)); files.Add(change.Path);
if (succ)
{
await Task.Run(() => new Commands.Add(_repo.FullPath, [ctx.Change]).Exec());
}
_repo.MarkWorkingCopyDirtyManually();
_repo.SetWatcherEnabled(true);
} }
_repo.SetWatcherEnabled(false);
var succ = await Task.Run(() => new Commands.Checkout(_repo.FullPath).UseMine(files));
if (succ)
{
await Task.Run(() => new Commands.Add(_repo.FullPath, changes).Exec());
}
_repo.MarkWorkingCopyDirtyManually();
_repo.SetWatcherEnabled(true);
} }
public async void UseExternalMergeTool() public async void UseExternalMergeTool()
@ -423,7 +431,7 @@ namespace SourceGit.ViewModels
var type = Preference.Instance.ExternalMergeToolType; var type = Preference.Instance.ExternalMergeToolType;
var exec = Preference.Instance.ExternalMergeToolPath; var exec = Preference.Instance.ExternalMergeToolPath;
var tool = Models.ExternalMergeTools.Supported.Find(x => x.Type == type); var tool = Models.ExternalMerger.Supported.Find(x => x.Type == type);
if (tool == null) if (tool == null)
{ {
App.RaiseException(_repo.FullPath, "Invalid merge tool in preference setting!"); App.RaiseException(_repo.FullPath, "Invalid merge tool in preference setting!");
@ -499,6 +507,7 @@ namespace SourceGit.ViewModels
Native.OS.OpenInFileManager(path, true); Native.OS.OpenInFileManager(path, true);
e.Handled = true; e.Handled = true;
}; };
menu.Items.Add(explore);
var openWith = new MenuItem(); var openWith = new MenuItem();
openWith.Header = App.Text("OpenWith"); openWith.Header = App.Text("OpenWith");
@ -509,81 +518,129 @@ namespace SourceGit.ViewModels
Native.OS.OpenWithDefaultEditor(path); Native.OS.OpenWithDefaultEditor(path);
e.Handled = true; e.Handled = true;
}; };
menu.Items.Add(openWith);
menu.Items.Add(new MenuItem() { Header = "-" });
var stage = new MenuItem(); if (change.IsConflit)
stage.Header = App.Text("FileCM.Stage");
stage.Icon = App.CreateMenuIcon("Icons.File.Add");
stage.Click += (_, e) =>
{ {
StageChanges(changes); var useTheirs = new MenuItem();
e.Handled = true; useTheirs.Icon = App.CreateMenuIcon("Icons.Incoming");
}; useTheirs.Header = App.Text("FileCM.UseTheirs");
useTheirs.Click += (_, e) =>
var discard = new MenuItem();
discard.Header = App.Text("FileCM.Discard");
discard.Icon = App.CreateMenuIcon("Icons.Undo");
discard.Click += (_, e) =>
{
Discard(changes, true);
e.Handled = true;
};
var stash = new MenuItem();
stash.Header = App.Text("FileCM.Stash");
stash.Icon = App.CreateMenuIcon("Icons.Stashes");
stash.Click += (_, e) =>
{
if (PopupHost.CanCreatePopup())
{ {
PopupHost.ShowPopup(new StashChanges(_repo, changes, false)); UseTheirs(changes);
} e.Handled = true;
e.Handled = true; };
};
var patch = new MenuItem(); var useMine = new MenuItem();
patch.Header = App.Text("FileCM.SaveAsPatch"); useMine.Icon = App.CreateMenuIcon("Icons.Local");
patch.Icon = App.CreateMenuIcon("Icons.Diff"); useMine.Header = App.Text("FileCM.UseMine");
patch.Click += async (_, e) => useMine.Click += (_, e) =>
{
var topLevel = App.GetTopLevel();
if (topLevel == null)
return;
var options = new FilePickerSaveOptions();
options.Title = App.Text("FileCM.SaveAsPatch");
options.DefaultExtension = ".patch";
options.FileTypeChoices = [new FilePickerFileType("Patch File") { Patterns = ["*.patch"] }];
var storageFile = await topLevel.StorageProvider.SaveFilePickerAsync(options);
if (storageFile != null)
{ {
var succ = await Task.Run(() => Commands.SaveChangesAsPatch.Exec(_repo.FullPath, changes, true, storageFile.Path.LocalPath)); UseMine(changes);
if (succ) e.Handled = true;
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess")); };
}
e.Handled = true; var openMerger = new MenuItem();
}; openMerger.Icon = App.CreateMenuIcon("Icons.OpenWith");
openMerger.Header = App.Text("FileCM.OpenWithExternalMerger");
openMerger.Click += (_, e) =>
{
UseExternalMergeTool();
e.Handled = true;
};
var history = new MenuItem(); menu.Items.Add(useTheirs);
history.Header = App.Text("FileHistory"); menu.Items.Add(useMine);
history.Icon = App.CreateMenuIcon("Icons.Histories"); menu.Items.Add(openMerger);
history.Click += (_, e) => menu.Items.Add(new MenuItem() { Header = "-" });
}
else
{ {
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo.FullPath, change.Path) }; var stage = new MenuItem();
window.Show(); stage.Header = App.Text("FileCM.Stage");
e.Handled = true; stage.Icon = App.CreateMenuIcon("Icons.File.Add");
}; stage.Click += (_, e) =>
{
StageChanges(changes);
e.Handled = true;
};
var assumeUnchanged = new MenuItem(); var discard = new MenuItem();
assumeUnchanged.Header = App.Text("FileCM.AssumeUnchanged"); discard.Header = App.Text("FileCM.Discard");
assumeUnchanged.Icon = App.CreateMenuIcon("Icons.File.Ignore"); discard.Icon = App.CreateMenuIcon("Icons.Undo");
assumeUnchanged.IsEnabled = change.WorkTree != Models.ChangeState.Untracked; discard.Click += (_, e) =>
assumeUnchanged.Click += (_, e) => {
{ Discard(changes, true);
new Commands.AssumeUnchanged(_repo.FullPath).Add(change.Path); e.Handled = true;
e.Handled = true; };
};
var stash = new MenuItem();
stash.Header = App.Text("FileCM.Stash");
stash.Icon = App.CreateMenuIcon("Icons.Stashes");
stash.Click += (_, e) =>
{
if (PopupHost.CanCreatePopup())
{
PopupHost.ShowPopup(new StashChanges(_repo, changes, false));
}
e.Handled = true;
};
var patch = new MenuItem();
patch.Header = App.Text("FileCM.SaveAsPatch");
patch.Icon = App.CreateMenuIcon("Icons.Diff");
patch.Click += async (_, e) =>
{
var topLevel = App.GetTopLevel();
if (topLevel == null)
return;
var options = new FilePickerSaveOptions();
options.Title = App.Text("FileCM.SaveAsPatch");
options.DefaultExtension = ".patch";
options.FileTypeChoices = [new FilePickerFileType("Patch File") { Patterns = ["*.patch"] }];
var storageFile = await topLevel.StorageProvider.SaveFilePickerAsync(options);
if (storageFile != null)
{
var succ = await Task.Run(() => Commands.SaveChangesAsPatch.Exec(_repo.FullPath, changes, true, storageFile.Path.LocalPath));
if (succ)
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
}
e.Handled = true;
};
var history = new MenuItem();
history.Header = App.Text("FileHistory");
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, e) =>
{
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo.FullPath, change.Path) };
window.Show();
e.Handled = true;
};
var assumeUnchanged = new MenuItem();
assumeUnchanged.Header = App.Text("FileCM.AssumeUnchanged");
assumeUnchanged.Icon = App.CreateMenuIcon("Icons.File.Ignore");
assumeUnchanged.IsEnabled = change.WorkTree != Models.ChangeState.Untracked;
assumeUnchanged.Click += (_, e) =>
{
new Commands.AssumeUnchanged(_repo.FullPath).Add(change.Path);
e.Handled = true;
};
menu.Items.Add(stage);
menu.Items.Add(discard);
menu.Items.Add(stash);
menu.Items.Add(patch);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(history);
menu.Items.Add(assumeUnchanged);
menu.Items.Add(new MenuItem() { Header = "-" });
}
var copy = new MenuItem(); var copy = new MenuItem();
copy.Header = App.Text("CopyPath"); copy.Header = App.Text("CopyPath");
@ -593,22 +650,55 @@ namespace SourceGit.ViewModels
App.CopyText(change.Path); App.CopyText(change.Path);
e.Handled = true; e.Handled = true;
}; };
menu.Items.Add(explore);
menu.Items.Add(openWith);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(stage);
menu.Items.Add(discard);
menu.Items.Add(stash);
menu.Items.Add(patch);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(history);
menu.Items.Add(assumeUnchanged);
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(copy); menu.Items.Add(copy);
} }
else else
{ {
var hasConflicts = false;
var hasNoneConflicts = false;
foreach (var change in changes)
{
if (change.IsConflit)
{
hasConflicts = true;
}
else
{
hasNoneConflicts = true;
}
}
if (hasConflicts)
{
if (hasNoneConflicts)
{
App.RaiseException(_repo.FullPath, "You have selected both non-conflict changes with conflicts!");
return null;
}
var useTheirs = new MenuItem();
useTheirs.Icon = App.CreateMenuIcon("Icons.Incoming");
useTheirs.Header = App.Text("FileCM.UseTheirs");
useTheirs.Click += (_, e) =>
{
UseTheirs(changes);
e.Handled = true;
};
var useMine = new MenuItem();
useMine.Icon = App.CreateMenuIcon("Icons.Local");
useMine.Header = App.Text("FileCM.UseMine");
useMine.Click += (_, e) =>
{
UseMine(changes);
e.Handled = true;
};
menu.Items.Add(useTheirs);
menu.Items.Add(useMine);
return menu;
}
var stage = new MenuItem(); var stage = new MenuItem();
stage.Header = App.Text("FileCM.StageMulti", changes.Count); stage.Header = App.Text("FileCM.StageMulti", changes.Count);
stage.Icon = App.CreateMenuIcon("Icons.File.Add"); stage.Icon = App.CreateMenuIcon("Icons.File.Add");

View file

@ -227,6 +227,8 @@ namespace SourceGit.Views
TextArea.TextView.ContextRequested += OnTextViewContextRequested; TextArea.TextView.ContextRequested += OnTextViewContextRequested;
TextArea.TextView.VisualLinesChanged += OnTextViewVisualLinesChanged; TextArea.TextView.VisualLinesChanged += OnTextViewVisualLinesChanged;
TextArea.TextView.Margin = new Thickness(4, 0); TextArea.TextView.Margin = new Thickness(4, 0);
TextArea.TextView.Options.EnableHyperlinks = false;
TextArea.TextView.Options.EnableEmailHyperlinks = false;
} }
public void OnCommitSHAClicked(string sha) public void OnCommitSHAClicked(string sha)

View file

@ -99,7 +99,7 @@
<!-- 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="3" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Message}" VerticalAlignment="Top" Margin="0,4,0,0" />
<ScrollViewer Grid.Row="3" Grid.Column="1" Margin="12,5,0,0" MaxHeight="100" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"> <ScrollViewer Grid.Row="3" Grid.Column="1" Margin="12,5,0,0" MaxHeight="64" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<SelectableTextBlock Text="{Binding FullMessage}" FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}" TextWrapping="Wrap"/> <SelectableTextBlock Text="{Binding FullMessage}" FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}" TextWrapping="Wrap"/>
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>

View file

@ -13,26 +13,33 @@
<Grid RowDefinitions="26,*"> <Grid RowDefinitions="26,*">
<!-- Toolbar --> <!-- Toolbar -->
<Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border2}"> <Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border2}">
<Grid ColumnDefinitions="Auto,*,Auto"> <Grid ColumnDefinitions="Auto,Auto,*,Auto">
<StackPanel Grid.Column="0" Orientation="Horizontal" IsVisible="{Binding IsOrgFilePathVisible}" VerticalAlignment="Center"> <!-- File Icon -->
<Path Width="12" Height="12" Data="{StaticResource Icons.File}" Margin="8,0,0,0"/> <Path Grid.Column="0" Width="12" Height="12" Data="{StaticResource Icons.File}" Margin="8,0,0,0"/>
<TextBlock Classes="monospace" Margin="4,0,0,0" Text="{Binding OrgFilePath, Converter={x:Static c:PathConverters.TruncateIfTooLong}}" FontSize="11"/>
<TextBlock Margin="8,0,0,0" Text="→"/>
</StackPanel>
<StackPanel Grid.Column="1" Orientation="Horizontal" VerticalAlignment="Center"> <!-- File Mode Change -->
<Path Width="12" Height="12" Data="{StaticResource Icons.File}" Margin="8,0,0,0"/> <Border Grid.Column="1"
<TextBlock Classes="monospace" Margin="4,0,0,0" Text="{Binding FilePath, Converter={x:Static c:PathConverters.TruncateIfTooLong}}" FontSize="11"/> Margin="4,0,0,0"
<Path Classes="rotating" Width="10" Height="10" Margin="8,0" Data="{DynamicResource Icons.Loading}" IsVisible="{Binding IsLoading}"/> Height="18"
</StackPanel> CornerRadius="4"
VerticalAlignment="Center"
Background="{DynamicResource Brush.Badge}"
IsVisible="{Binding FileModeChange, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
ToolTip.Tip="{DynamicResource Text.Diff.FileModeChanged}">
<TextBlock Classes="monospace" FontSize="10" HorizontalAlignment="Center" Margin="4,0" Text="{Binding FileModeChange}"/>
</Border>
<StackPanel Grid.Column="2" Margin="32,0,0,0" Orientation="Horizontal" VerticalAlignment="Center"> <!-- Title -->
<TextBlock Grid.Column="2" Classes="monospace" Margin="4,0,0,0" Text="{Binding Title}" FontSize="11"/>
<!-- Toolbar Buttons -->
<StackPanel Grid.Column="3" Margin="8,0,0,0" Orientation="Horizontal" VerticalAlignment="Center">
<ToggleButton Classes="line_path" <ToggleButton Classes="line_path"
Width="32" Height="18" Width="32" Height="18"
Background="Transparent" Background="Transparent"
Padding="9,6" Padding="9,6"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting, Mode=TwoWay}" IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSyntaxHighlighting, Mode=TwoWay}"
IsVisible="{Binding IsTextDiff}" IsVisible="{Binding IsTextDiff}"
ToolTip.Tip="{DynamicResource Text.Diff.SyntaxHighlight}"> ToolTip.Tip="{DynamicResource Text.Diff.SyntaxHighlight}">
<Path Width="13" Height="13" Data="{StaticResource Icons.SyntaxHighlight}" Margin="0,3,0,0"/> <Path Width="13" Height="13" Data="{StaticResource Icons.SyntaxHighlight}" Margin="0,3,0,0"/>
</ToggleButton> </ToggleButton>
@ -42,11 +49,11 @@
Background="Transparent" Background="Transparent"
Padding="9,6" Padding="9,6"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=TwoWay}" IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=TwoWay}"
IsVisible="{Binding IsTextDiff}" IsVisible="{Binding IsTextDiff}"
ToolTip.Tip="{DynamicResource Text.Diff.SideBySide}"> ToolTip.Tip="{DynamicResource Text.Diff.SideBySide}">
<Path Width="12" Height="12" Data="{StaticResource Icons.LayoutHorizontal}" Margin="0,2,0,0"/> <Path Width="12" Height="12" Data="{StaticResource Icons.LayoutHorizontal}" Margin="0,2,0,0"/>
</ToggleButton> </ToggleButton>
<Button Classes="icon_button" Width="32" Command="{Binding OpenExternalMergeTool}" ToolTip.Tip="{DynamicResource Text.Diff.UseMerger}"> <Button Classes="icon_button" Width="32" Command="{Binding OpenExternalMergeTool}" ToolTip.Tip="{DynamicResource Text.Diff.UseMerger}">
<Path Width="12" Height="12" Stretch="Uniform" Data="{StaticResource Icons.OpenWith}"/> <Path Width="12" Height="12" Stretch="Uniform" Data="{StaticResource Icons.OpenWith}"/>
</Button> </Button>
@ -127,18 +134,18 @@
<TextBlock Grid.Column="4" Classes="monospace" Text="{Binding NewSize}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/> <TextBlock Grid.Column="4" Classes="monospace" Text="{Binding NewSize}" Foreground="{DynamicResource Brush.FG2}" Margin="8,0,0,0"/>
</Grid> </Grid>
<Border Grid.Row="1" Background="{DynamicResource Brush.Window}" Effect="drop-shadow(0 0 8 #A0000000)" Margin="0,8,0,0" HorizontalAlignment="Center"> <Border Grid.Row="1" Background="{DynamicResource Brush.Window}" Effect="drop-shadow(0 0 8 #A0000000)" Margin="0,8,0,0" HorizontalAlignment="Center">
<Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" Margin="8"> <Border BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" Margin="8">
<v:ImageDiffView Alpha="{Binding #ImageDiffSlider.Value}" <v:ImageDiffView Alpha="{Binding #ImageDiffSlider.Value}"
OldImage="{Binding Old}" OldImage="{Binding Old}"
NewImage="{Binding New}" NewImage="{Binding New}"
RenderOptions.BitmapInterpolationMode="HighQuality"/> RenderOptions.BitmapInterpolationMode="HighQuality"/>
</Border> </Border>
</Border> </Border>
<Slider Grid.Row="2" <Slider Grid.Row="2"
x:Name="ImageDiffSlider" x:Name="ImageDiffSlider"
Minimum="0" Maximum="1" Minimum="0" Maximum="1"
VerticalAlignment="Top" VerticalAlignment="Top"
TickPlacement="None" TickPlacement="None"

View file

@ -296,11 +296,6 @@ namespace SourceGit.Views
if (DataContext is ViewModels.Histories histories) if (DataContext is ViewModels.Histories histories)
{ {
histories.Select(commitDataGrid.SelectedItems); histories.Select(commitDataGrid.SelectedItems);
if (histories.DetailContext is ViewModels.CommitDetail detail)
{
commitDataGrid.ScrollIntoView(detail.Commit, null);
}
} }
e.Handled = true; e.Handled = true;
} }

View file

@ -49,10 +49,11 @@
</Border> </Border>
<!-- Menu --> <!-- Menu -->
<Button Grid.Column="{OnPlatform 0, macOS=2}" <Button Grid.Column="{OnPlatform 0, macOS=2}" Classes="icon_button" VerticalAlignment="Bottom">
Classes="icon_button" <Button.Margin>
Margin="4,0,2,3" <OnPlatform Default="4,0,2,3" macOS="4,0,6,3"/>
VerticalAlignment="Bottom"> </Button.Margin>
<Button.Flyout> <Button.Flyout>
<MenuFlyout Placement="BottomEdgeAlignedLeft" VerticalOffset="-8"> <MenuFlyout Placement="BottomEdgeAlignedLeft" VerticalOffset="-8">
<MenuItem Header="{DynamicResource Text.Preference}" Click="OpenPreference"> <MenuItem Header="{DynamicResource Text.Preference}" Click="OpenPreference">

View file

@ -231,7 +231,7 @@
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.Git}"/> <TextBlock Classes="tab_header" Text="{DynamicResource Text.Preference.Git}"/>
</TabItem.Header> </TabItem.Header>
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32" ColumnDefinitions="Auto,*"> <Grid Margin="8" RowDefinitions="32,32,Auto,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.Git.Path}" Text="{DynamicResource Text.Preference.Git.Path}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
@ -254,11 +254,50 @@
<TextBlock Grid.Row="1" Grid.Column="1" <TextBlock Grid.Row="1" Grid.Column="1"
x:Name="txtVersion"/> x:Name="txtVersion"/>
<TextBlock Grid.Row="2" Grid.Column="0" <Border Grid.Row="2" Grid.Column="0"
Height="32"
IsVisible="{OnPlatform False, Windows=True}">
<TextBlock Text="{DynamicResource Text.Preference.Git.Shell}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
</Border>
<ComboBox Grid.Row="2" Grid.Column="1"
MinHeight="28"
Padding="8,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Left"
RenderOptions.BitmapInterpolationMode="HighQuality"
FontSize="{Binding DefaultFontSize, Mode=OneWay}"
SelectedIndex="{Binding GitShell, Mode=TwoWay}"
IsVisible="{OnPlatform False, Windows=True}">
<ComboBox.Items>
<Grid ColumnDefinitions="Auto,*">
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/ShellIcons/git-bash.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
<TextBlock Grid.Column="1" Text="Git Bash" Margin="6,0,0,0"/>
</Grid>
<Grid ColumnDefinitions="Auto,*">
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/ShellIcons/pwsh.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
<TextBlock Grid.Column="1" Text="PowerShell" Margin="6,0,0,0"/>
</Grid>
<Grid ColumnDefinitions="Auto,*">
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/ShellIcons/cmd.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
<TextBlock Grid.Column="1" Text="Command Prompt" Margin="6,0,0,0"/>
</Grid>
<Grid ColumnDefinitions="Auto,*">
<Image Grid.Column="0" Width="16" Height="16" Source="/Resources/ShellIcons/wt.png" RenderOptions.BitmapInterpolationMode="HighQuality"/>
<TextBlock Grid.Column="1" Text="Default Shell in Windows Terminal" Margin="6,0,0,0"/>
</Grid>
</ComboBox.Items>
</ComboBox>
<TextBlock Grid.Row="3" Grid.Column="0"
Text="{DynamicResource Text.Preference.Git.DefaultCloneDir}" Text="{DynamicResource Text.Preference.Git.DefaultCloneDir}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0,0,16,0"/> Margin="0,0,16,0"/>
<TextBox Grid.Row="2" Grid.Column="1" <TextBox Grid.Row="3" Grid.Column="1"
Height="28" Height="28"
CornerRadius="3" CornerRadius="3"
Text="{Binding GitDefaultCloneDir, Mode=TwoWay}"> Text="{Binding GitDefaultCloneDir, Mode=TwoWay}">
@ -269,31 +308,31 @@
</TextBox.InnerRightContent> </TextBox.InnerRightContent>
</TextBox> </TextBox>
<TextBlock Grid.Row="3" Grid.Column="0"
Text="{DynamicResource Text.Preference.Git.User}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<TextBox Grid.Row="3" Grid.Column="1"
Height="28"
CornerRadius="3"
Text="{Binding #me.DefaultUser, Mode=TwoWay}"
Watermark="{DynamicResource Text.Preference.Git.User.Placeholder}"/>
<TextBlock Grid.Row="4" Grid.Column="0" <TextBlock Grid.Row="4" Grid.Column="0"
Text="{DynamicResource Text.Preference.Git.Email}" Text="{DynamicResource Text.Preference.Git.User}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0,0,16,0"/> Margin="0,0,16,0"/>
<TextBox Grid.Row="4" Grid.Column="1" <TextBox Grid.Row="4" Grid.Column="1"
Height="28"
CornerRadius="3"
Text="{Binding #me.DefaultUser, Mode=TwoWay}"
Watermark="{DynamicResource Text.Preference.Git.User.Placeholder}"/>
<TextBlock Grid.Row="5" Grid.Column="0"
Text="{DynamicResource Text.Preference.Git.Email}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<TextBox Grid.Row="5" Grid.Column="1"
Height="28" Height="28"
CornerRadius="3" CornerRadius="3"
Text="{Binding #me.DefaultEmail, Mode=TwoWay}" Text="{Binding #me.DefaultEmail, Mode=TwoWay}"
Watermark="{DynamicResource Text.Preference.Git.Email.Placeholder}"/> Watermark="{DynamicResource Text.Preference.Git.Email.Placeholder}"/>
<TextBlock Grid.Row="5" Grid.Column="0" <TextBlock Grid.Row="6" Grid.Column="0"
Text="{DynamicResource Text.Preference.Git.CRLF}" Text="{DynamicResource Text.Preference.Git.CRLF}"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0,0,16,0"/> Margin="0,0,16,0"/>
<ComboBox Grid.Row="5" Grid.Column="1" <ComboBox Grid.Row="6" Grid.Column="1"
MinHeight="28" MinHeight="28"
Padding="8,0" Padding="8,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
@ -309,7 +348,7 @@
</ComboBox.ItemTemplate> </ComboBox.ItemTemplate>
</ComboBox> </ComboBox>
<CheckBox Grid.Row="6" Grid.Column="1" <CheckBox Grid.Row="7" Grid.Column="1"
Content="{DynamicResource Text.Preference.Git.AutoFetch}" Content="{DynamicResource Text.Preference.Git.AutoFetch}"
IsChecked="{Binding GitAutoFetch, Mode=TwoWay}"/> IsChecked="{Binding GitAutoFetch, Mode=TwoWay}"/>
</Grid> </Grid>
@ -333,7 +372,6 @@
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0,0,16,0"/> Margin="0,0,16,0"/>
<TextBox Grid.Row="1" Grid.Column="1" <TextBox Grid.Row="1" Grid.Column="1"
x:Name="txtGPGExecutable"
Height="28" Height="28"
CornerRadius="3" CornerRadius="3"
Text="{Binding #me.GPGExecutableFile, Mode=TwoWay}"> Text="{Binding #me.GPGExecutableFile, Mode=TwoWay}">
@ -370,9 +408,20 @@
MinHeight="28" MinHeight="28"
Padding="8,0" Padding="8,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
ItemsSource="{Binding Source={x:Static m:ExternalMergeTools.Supported}}" HorizontalContentAlignment="Left"
DisplayMemberBinding="{Binding Name, x:DataType=m:ExternalMergeTools}" RenderOptions.BitmapInterpolationMode="HighQuality"
SelectedIndex="{Binding ExternalMergeToolType, Mode=TwoWay}"/> FontSize="{Binding DefaultFontSize}"
ItemsSource="{Binding Source={x:Static m:ExternalMerger.Supported}}"
SelectedIndex="{Binding ExternalMergeToolType, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate x:DataType="{x:Type m:ExternalMerger}">
<Grid ColumnDefinitions="Auto,*">
<Image Grid.Column="0" Width="16" Height="16" Source="{Binding IconImage}" RenderOptions.BitmapInterpolationMode="HighQuality"/>
<TextBlock Grid.Column="1" Text="{Binding Name}" Margin="6,0,0,0"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="1" Grid.Column="0" <TextBlock Grid.Row="1" Grid.Column="0"
Text="{DynamicResource Text.Preference.Merger.Path}" Text="{DynamicResource Text.Preference.Merger.Path}"

View file

@ -1,9 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
@ -52,10 +51,13 @@ namespace SourceGit.Views
set; set;
} }
public static readonly StyledProperty<string> GPGExecutableFileProperty =
AvaloniaProperty.Register<Preference, string>(nameof(GPGExecutableFile));
public string GPGExecutableFile public string GPGExecutableFile
{ {
get; get => GetValue(GPGExecutableFileProperty);
set; set => SetValue(GPGExecutableFileProperty, value);
} }
public string GPGUserKey public string GPGUserKey
@ -222,13 +224,13 @@ namespace SourceGit.Views
private async void SelectExternalMergeTool(object sender, RoutedEventArgs e) private async void SelectExternalMergeTool(object sender, RoutedEventArgs e)
{ {
var type = ViewModels.Preference.Instance.ExternalMergeToolType; var type = ViewModels.Preference.Instance.ExternalMergeToolType;
if (type < 0 || type >= Models.ExternalMergeTools.Supported.Count) if (type < 0 || type >= Models.ExternalMerger.Supported.Count)
{ {
ViewModels.Preference.Instance.ExternalMergeToolType = 0; ViewModels.Preference.Instance.ExternalMergeToolType = 0;
type = 0; type = 0;
} }
var tool = Models.ExternalMergeTools.Supported[type]; var tool = Models.ExternalMerger.Supported[type];
var options = new FilePickerOpenOptions() var options = new FilePickerOpenOptions()
{ {
FileTypeFilter = [new FilePickerFileType(tool.Name) { Patterns = tool.GetPatterns() }], FileTypeFilter = [new FilePickerFileType(tool.Name) { Patterns = tool.GetPatterns() }],

View file

@ -22,7 +22,7 @@
<Path Width="13" Height="13" Data="{StaticResource Icons.Terminal}"/> <Path Width="13" Height="13" Data="{StaticResource Icons.Terminal}"/>
</Button> </Button>
<Button Classes="icon_button" Width="32" Click="OnOpenWithExternalEditor" ToolTip.Tip="{DynamicResource Text.Repository.OpenWithExternalTools}"> <Button Classes="icon_button" Width="32" Click="OnOpenWithExternalTools" ToolTip.Tip="{DynamicResource Text.Repository.OpenWithExternalTools}">
<Path Width="13" Height="13" Data="{StaticResource Icons.OpenWith}"/> <Path Width="13" Height="13" Data="{StaticResource Icons.OpenWith}"/>
</Button> </Button>

View file

@ -61,11 +61,11 @@ namespace SourceGit.Views
InitializeComponent(); InitializeComponent();
} }
private void OnOpenWithExternalEditor(object sender, RoutedEventArgs e) private void OnOpenWithExternalTools(object sender, RoutedEventArgs e)
{ {
if (sender is Button button && DataContext is ViewModels.Repository repo) if (sender is Button button && DataContext is ViewModels.Repository repo)
{ {
var menu = repo.CreateContextMenuForExternalEditors(); var menu = repo.CreateContextMenuForExternalTools();
if (menu != null) if (menu != null)
{ {
menu.Open(button); menu.Open(button);

View file

@ -121,6 +121,8 @@ namespace SourceGit.Views
TextArea.LeftMargins[0].Margin = new Thickness(8, 0); TextArea.LeftMargins[0].Margin = new Thickness(8, 0);
TextArea.TextView.Margin = new Thickness(4, 0); TextArea.TextView.Margin = new Thickness(4, 0);
TextArea.TextView.Options.EnableHyperlinks = false;
TextArea.TextView.Options.EnableEmailHyperlinks = false;
} }
protected override void OnLoaded(RoutedEventArgs e) protected override void OnLoaded(RoutedEventArgs e)

View file

@ -260,6 +260,8 @@ namespace SourceGit.Views
TextArea.TextView.Margin = new Thickness(4, 0); TextArea.TextView.Margin = new Thickness(4, 0);
TextArea.TextView.BackgroundRenderers.Add(new LineBackgroundRenderer(this)); TextArea.TextView.BackgroundRenderers.Add(new LineBackgroundRenderer(this));
TextArea.TextView.LineTransformers.Add(_lineStyleTransformer); TextArea.TextView.LineTransformers.Add(_lineStyleTransformer);
TextArea.TextView.Options.EnableHyperlinks = false;
TextArea.TextView.Options.EnableEmailHyperlinks = false;
} }
protected override void OnLoaded(RoutedEventArgs e) protected override void OnLoaded(RoutedEventArgs e)
@ -638,6 +640,8 @@ namespace SourceGit.Views
TextArea.TextView.Margin = new Thickness(4, 0); TextArea.TextView.Margin = new Thickness(4, 0);
TextArea.TextView.BackgroundRenderers.Add(new LineBackgroundRenderer(this)); TextArea.TextView.BackgroundRenderers.Add(new LineBackgroundRenderer(this));
TextArea.TextView.LineTransformers.Add(_lineStyleTransformer); TextArea.TextView.LineTransformers.Add(_lineStyleTransformer);
TextArea.TextView.Options.EnableHyperlinks = false;
TextArea.TextView.Options.EnableEmailHyperlinks = false;
} }
protected override void OnLoaded(RoutedEventArgs e) protected override void OnLoaded(RoutedEventArgs e)

View file

@ -20,12 +20,13 @@
<Grid Grid.Column="0" RowDefinitions="28,*,28,*"> <Grid Grid.Column="0" RowDefinitions="28,*,28,*">
<!-- Unstaged Toolbar --> <!-- Unstaged Toolbar -->
<Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}"> <Border Grid.Row="0" BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}">
<Grid ColumnDefinitions="Auto,Auto,Auto,*,Auto,Auto,Auto,Auto"> <Grid ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto,Auto,Auto">
<v:ChangeViewModeSwitcher Grid.Column="0" Width="14" Height="14" Margin="8,0,0,0" ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=UnstagedChangeViewMode, Mode=TwoWay}"/> <v:ChangeViewModeSwitcher Grid.Column="0" Width="14" Height="14" Margin="8,0,0,0" ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=UnstagedChangeViewMode, Mode=TwoWay}"/>
<TextBlock Grid.Column="1" Text="{DynamicResource Text.WorkingCopy.Unstaged}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="8,0,0,0"/> <TextBlock Grid.Column="1" Text="{DynamicResource Text.WorkingCopy.Unstaged}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="8,0,0,0"/>
<Path Grid.Column="2" Classes="rotating" Width="14" Height="14" Data="{StaticResource Icons.Loading}" Margin="8,0,0,0" IsVisible="{Binding IsStaging}"/> <TextBlock Grid.Column="2" FontWeight="Bold" Foreground="{DynamicResource Brush.FG2}" Text="{Binding Unstaged, Converter={x:Static c:ListConverters.ToCount}}"/>
<Path Grid.Column="3" Classes="rotating" Width="14" Height="14" Data="{StaticResource Icons.Loading}" Margin="8,0,0,0" IsVisible="{Binding IsStaging}"/>
<Button Grid.Column="4" <Button Grid.Column="5"
Classes="icon_button" Classes="icon_button"
Width="26" Height="14" Width="26" Height="14"
Padding="0" Padding="0"
@ -33,19 +34,19 @@
Click="ViewAssumeUnchanged"> Click="ViewAssumeUnchanged">
<Path Width="14" Height="14" Data="{StaticResource Icons.File.Ignore}"/> <Path Width="14" Height="14" Data="{StaticResource Icons.File.Ignore}"/>
</Button> </Button>
<ToggleButton Grid.Column="5" <ToggleButton Grid.Column="6"
Classes="toggle_untracked" Classes="toggle_untracked"
Width="26" Height="14" Width="26" Height="14"
ToolTip.Tip="{DynamicResource Text.WorkingCopy.IncludeUntracked}" ToolTip.Tip="{DynamicResource Text.WorkingCopy.IncludeUntracked}"
IsChecked="{Binding $parent[v:Repository].DataContext.(vm:Repository).IncludeUntracked, Mode=TwoWay}"/> IsChecked="{Binding $parent[v:Repository].DataContext.(vm:Repository).IncludeUntracked, Mode=TwoWay}"/>
<Button Grid.Column="6" <Button Grid.Column="7"
Classes="icon_button" Classes="icon_button"
Width="26" Height="14" Width="26" Height="14"
Padding="0" Padding="0"
ToolTip.Tip="{DynamicResource Text.WorkingCopy.Unstaged.Stage}" Click="StageSelected"> ToolTip.Tip="{DynamicResource Text.WorkingCopy.Unstaged.Stage}" Click="StageSelected">
<Path Width="14" Height="14" Margin="0,6,0,0" Data="{StaticResource Icons.Down}"/> <Path Width="14" Height="14" Margin="0,6,0,0" Data="{StaticResource Icons.Down}"/>
</Button> </Button>
<Button Grid.Column="7" <Button Grid.Column="8"
Classes="icon_button" Classes="icon_button"
Width="26" Height="14" Width="26" Height="14"
Padding="0" Padding="0"
@ -167,14 +168,15 @@
<!-- Staged Toolbar --> <!-- Staged Toolbar -->
<Border Grid.Row="2" BorderThickness="0,1" BorderBrush="{DynamicResource Brush.Border0}"> <Border Grid.Row="2" BorderThickness="0,1" BorderBrush="{DynamicResource Brush.Border0}">
<Grid ColumnDefinitions="Auto,Auto,Auto,*,Auto,Auto"> <Grid ColumnDefinitions="Auto,Auto,Auto,Auto,*,Auto,Auto">
<v:ChangeViewModeSwitcher Grid.Column="0" Width="14" Height="14" Margin="8,0,0,0" ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=StagedChangeViewMode, Mode=TwoWay}"/> <v:ChangeViewModeSwitcher Grid.Column="0" Width="14" Height="14" Margin="8,0,0,0" ViewMode="{Binding Source={x:Static vm:Preference.Instance}, Path=StagedChangeViewMode, Mode=TwoWay}"/>
<TextBlock Grid.Column="1" Text="{DynamicResource Text.WorkingCopy.Staged}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="8,0,0,0"/> <TextBlock Grid.Column="1" Text="{DynamicResource Text.WorkingCopy.Staged}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold" Margin="8,0,0,0"/>
<Path Grid.Column="2" Classes="rotating" Width="14" Height="14" Data="{StaticResource Icons.Loading}" Margin="8,0,0,0" IsVisible="{Binding IsUnstaging}"/> <TextBlock Grid.Column="2" FontWeight="Bold" Foreground="{DynamicResource Brush.FG2}" Text="{Binding Staged, Converter={x:Static c:ListConverters.ToCount}}"/>
<Button Grid.Column="4" Classes="icon_button" Width="26" Height="14" Padding="0" ToolTip.Tip="{DynamicResource Text.WorkingCopy.Staged.Unstage}" Click="UnstageSelected"> <Path Grid.Column="3" Classes="rotating" Width="14" Height="14" Data="{StaticResource Icons.Loading}" Margin="8,0,0,0" IsVisible="{Binding IsUnstaging}"/>
<Button Grid.Column="5" Classes="icon_button" Width="26" Height="14" Padding="0" ToolTip.Tip="{DynamicResource Text.WorkingCopy.Staged.Unstage}" Click="UnstageSelected">
<Path Width="14" Height="14" Margin="0,6,0,0" Data="{StaticResource Icons.Up}"/> <Path Width="14" Height="14" Margin="0,6,0,0" Data="{StaticResource Icons.Up}"/>
</Button> </Button>
<Button Grid.Column="5" Classes="icon_button" Width="26" Height="14" Padding="0" ToolTip.Tip="{DynamicResource Text.WorkingCopy.Staged.UnstageAll}" Click="UnstageAll"> <Button Grid.Column="6" Classes="icon_button" Width="26" Height="14" Padding="0" ToolTip.Tip="{DynamicResource Text.WorkingCopy.Staged.UnstageAll}" Click="UnstageAll">
<Path Width="14" Height="14" Data="{StaticResource Icons.DoubleUp}"/> <Path Width="14" Height="14" Data="{StaticResource Icons.DoubleUp}"/>
</Button> </Button>
</Grid> </Grid>
@ -184,21 +186,21 @@
<Grid Grid.Row="3" Background="{DynamicResource Brush.Contents}"> <Grid Grid.Row="3" Background="{DynamicResource Brush.Contents}">
<DataGrid x:Name="stagedList" <DataGrid x:Name="stagedList"
Background="Transparent" Background="Transparent"
ItemsSource="{Binding Staged}" ItemsSource="{Binding Staged}"
SelectedItem="{Binding SelectedStagedChange, Mode=TwoWay}" SelectedItem="{Binding SelectedStagedChange, Mode=TwoWay}"
SelectionMode="Extended" SelectionMode="Extended"
CanUserReorderColumns="False" CanUserReorderColumns="False"
CanUserResizeColumns="False" CanUserResizeColumns="False"
CanUserSortColumns="False" CanUserSortColumns="False"
IsReadOnly="True" IsReadOnly="True"
HeadersVisibility="None" HeadersVisibility="None"
Focusable="False" Focusable="False"
RowHeight="26" RowHeight="26"
HorizontalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
KeyDown="OnStagedListKeyDown" KeyDown="OnStagedListKeyDown"
ContextRequested="OnStagedListContextRequested" ContextRequested="OnStagedListContextRequested"
IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=StagedChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsList}}"> IsVisible="{Binding Source={x:Static vm:Preference.Instance}, Path=StagedChangeViewMode, Converter={x:Static c:ChangeViewModeConverters.IsList}}">
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTemplateColumn Header="ICON"> <DataGridTemplateColumn Header="ICON">
<DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellTemplate>
@ -319,12 +321,8 @@
<Border Background="{DynamicResource Brush.Window}" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"> <Border Background="{DynamicResource Brush.Window}" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center"> <StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Width="64" Height="64" Data="{StaticResource Icons.Conflict}" Fill="{DynamicResource Brush.FG2}"/> <Path Width="64" Height="64" Data="{StaticResource Icons.Conflict}" Fill="{DynamicResource Brush.FG2}"/>
<TextBlock Margin="0,16,0,28" FontSize="20" FontWeight="Bold" Text="{DynamicResource Text.WorkingCopy.Conflicts}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/> <TextBlock Margin="0,16,0,8" FontSize="20" FontWeight="Bold" Text="{DynamicResource Text.WorkingCopy.Conflicts}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <TextBlock Text="{DynamicResource Text.WorkingCopy.ResolveTip}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
<Button Classes="flat" FontWeight="Regular" Content="{DynamicResource Text.WorkingCopy.UseTheirs}" Height="26" Padding="16,0" Command="{Binding $parent[v:WorkingCopy].DataContext.(vm:WorkingCopy).UseTheirs}"/>
<Button Classes="flat" FontWeight="Regular" Content="{DynamicResource Text.WorkingCopy.UseMine}" Height="26" Padding="16,0" Margin="8,0" Command="{Binding $parent[v:WorkingCopy].DataContext.(vm:WorkingCopy).UseMine}"/>
<Button Classes="flat" FontWeight="Regular" Content="{DynamicResource Text.WorkingCopy.OpenMerger}" Height="26" Padding="16,0" Command="{Binding $parent[v:WorkingCopy].DataContext.(vm:WorkingCopy).UseExternalMergeTool}"/>
</StackPanel>
</StackPanel> </StackPanel>
</Border> </Border>
</DataTemplate> </DataTemplate>