Compare commits

..

No commits in common. "dcf50934061e1e9549b8073b7c87dede6873096c" and "fba84c82978efcacee91cffbbc455aba331e8e9b" have entirely different histories.

36 changed files with 163 additions and 343 deletions

View file

@ -47,7 +47,7 @@
## Translation Status
[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-95.63%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-96.65%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-87.34%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-90.39%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-99.56%25-yellow)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md)
[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-96.05%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-97.08%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-87.72%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-90.79%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-100.00%25-brightgreen)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md)
## How to Use
@ -119,15 +119,14 @@ For other AI service:
This app supports open repository in external tools listed in the table below.
| Tool | Windows | macOS | Linux |
|-------------------------------|---------|-------|-------|
| Visual Studio Code | YES | YES | YES |
| Visual Studio Code - Insiders | YES | YES | YES |
| VSCodium | YES | YES | YES |
| Fleet | YES | YES | YES |
| Sublime Text | YES | YES | YES |
| Zed | NO | YES | YES |
| Visual Studio | YES | NO | NO |
| Tool | Windows | macOS | Linux | KEY IN `external_editors.json` |
|-------------------------------|---------|-------|-------|--------------------------------|
| Visual Studio Code | YES | YES | YES | VSCODE |
| Visual Studio Code - Insiders | YES | YES | YES | VSCODE_INSIDERS |
| VSCodium | YES | YES | YES | VSCODIUM |
| JetBrains Fleet | YES | YES | YES | FLEET |
| Sublime Text | YES | YES | YES | SUBLIME_TEXT |
| Zed | NO | YES | YES | ZED |
> [!NOTE]
> This app will try to find those tools based on some pre-defined or expected locations automatically. If you are using one portable version of these tools, it will not be detected by this app.
@ -135,7 +134,7 @@ This app supports open repository in external tools listed in the table below.
```json
{
"tools": {
"Visual Studio Code": "D:\\VSCode\\Code.exe"
"VSCODE": "D:\\VSCode\\Code.exe"
}
}
```

View file

@ -1,4 +1,4 @@
### de_DE.axaml: 95.63%
### de_DE.axaml: 96.05%
<details>
@ -22,11 +22,8 @@
- Text.Configure.OpenAI
- Text.Configure.OpenAI.Prefered
- Text.Configure.OpenAI.Prefered.Tip
- Text.Diff.VisualLines.All
- Text.ExecuteCustomAction
- Text.ExecuteCustomAction.Name
- Text.IssueLinkCM.OpenInBrowser
- Text.IssueLinkCM.CopyLink
- Text.Preference.AI.AnalyzeDiffPrompt
- Text.Preference.AI.GenerateSubjectPrompt
- Text.Preference.AI.Name
@ -37,7 +34,7 @@
</details>
### es_ES.axaml: 96.65%
### es_ES.axaml: 97.08%
<details>
@ -57,11 +54,8 @@
- Text.Configure.OpenAI
- Text.Configure.OpenAI.Prefered
- Text.Configure.OpenAI.Prefered.Tip
- Text.Diff.VisualLines.All
- Text.ExecuteCustomAction
- Text.ExecuteCustomAction.Name
- Text.IssueLinkCM.OpenInBrowser
- Text.IssueLinkCM.CopyLink
- Text.Preference.AI.Name
- Text.Repository.CustomActions
- Text.Repository.CustomActions.Empty
@ -69,7 +63,7 @@
</details>
### fr_FR.axaml: 87.34%
### fr_FR.axaml: 87.72%
<details>
@ -114,7 +108,6 @@
- Text.ConventionalCommit.ShortDescription
- Text.ConventionalCommit.Type
- Text.Diff.IgnoreWhitespace
- Text.Diff.VisualLines.All
- Text.Discard.IncludeIgnored
- Text.ExecuteCustomAction
- Text.ExecuteCustomAction.Name
@ -126,8 +119,6 @@
- Text.Histories.Tips.Prefix
- Text.Hotkeys.Repo.CommitWithAutoStage
- Text.Hotkeys.Repo.DiscardSelected
- Text.IssueLinkCM.OpenInBrowser
- Text.IssueLinkCM.CopyLink
- Text.MoveRepositoryNode
- Text.MoveRepositoryNode.Target
- Text.Preference.AI
@ -165,7 +156,7 @@
</details>
### pt_BR.axaml: 90.39%
### pt_BR.axaml: 90.79%
<details>
@ -212,15 +203,12 @@
- Text.ConventionalCommit.ShortDescription
- Text.ConventionalCommit.Type
- Text.CopyAllText
- Text.Diff.VisualLines.All
- Text.Discard.IncludeIgnored
- Text.ExecuteCustomAction
- Text.ExecuteCustomAction.Name
- Text.FileHistory.FileContent
- Text.FileHistory.FileChange
- Text.GitLFS.Locks.OnlyMine
- Text.IssueLinkCM.OpenInBrowser
- Text.IssueLinkCM.CopyLink
- Text.MoveRepositoryNode
- Text.MoveRepositoryNode.Target
- Text.Preference.AI.Name
@ -240,15 +228,13 @@
</details>
### ru_RU.axaml: 99.56%
### ru_RU.axaml: 100.00%
<details>
<summary>Missing Keys</summary>
- Text.Diff.VisualLines.All
- Text.IssueLinkCM.OpenInBrowser
- Text.IssueLinkCM.CopyLink
</details>

View file

@ -1 +1 @@
8.37
8.36

View file

@ -8,10 +8,6 @@ namespace SourceGit.Commands
{
[GeneratedRegex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@")]
private static partial Regex REG_INDICATOR();
[GeneratedRegex(@"^index\s([0-9a-f]{6,40})\.\.([0-9a-f]{6,40})(\s[1-9]{6})?")]
private static partial Regex REG_HASH_CHANGE();
private const string PREFIX_LFS_NEW = "+version https://git-lfs.github.com/spec/";
private const string PREFIX_LFS_DEL = "-version https://git-lfs.github.com/spec/";
private const string PREFIX_LFS_MODIFY = " version https://git-lfs.github.com/spec/";
@ -105,31 +101,17 @@ namespace SourceGit.Commands
if (_result.TextDiff.Lines.Count == 0)
{
if (line.StartsWith("Binary", StringComparison.Ordinal))
var match = REG_INDICATOR().Match(line);
if (!match.Success)
{
_result.IsBinary = true;
if (line.StartsWith("Binary", StringComparison.Ordinal))
_result.IsBinary = true;
return;
}
if (string.IsNullOrEmpty(_result.OldHash))
{
var match = REG_HASH_CHANGE().Match(line);
if (!match.Success)
return;
_result.OldHash = match.Groups[1].Value;
_result.NewHash = match.Groups[2].Value;
}
else
{
var match = REG_INDICATOR().Match(line);
if (!match.Success)
return;
_oldLine = int.Parse(match.Groups[1].Value);
_newLine = int.Parse(match.Groups[2].Value);
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0));
}
_oldLine = int.Parse(match.Groups[1].Value);
_newLine = int.Parse(match.Groups[2].Value);
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0));
}
else
{

View file

@ -30,7 +30,7 @@ namespace SourceGit.Models
public string Arguments
{
get => _arguments;
get => _arguments;
set => SetProperty(ref _arguments, value);
}

View file

@ -63,7 +63,7 @@ namespace SourceGit.Models
{
public string File { get; set; } = string.Empty;
public List<TextDiffLine> Lines { get; set; } = new List<TextDiffLine>();
public Vector ScrollOffset { get; set; } = Vector.Zero;
public Vector SyncScrollOffset { get; set; } = Vector.Zero;
public int MaxLineNumber = 0;
public string Repo { get; set; } = null;
@ -674,8 +674,6 @@ namespace SourceGit.Models
{
public bool IsBinary { get; set; } = false;
public bool IsLFS { get; set; } = false;
public string OldHash { get; set; } = string.Empty;
public string NewHash { get; set; } = string.Empty;
public string OldMode { get; set; } = string.Empty;
public string NewMode { get; set; } = string.Empty;
public TextDiff TextDiff { get; set; } = null;

View file

@ -13,13 +13,15 @@ namespace SourceGit.Models
public class ExternalTool
{
public string Name { get; private set; }
public string Executable { get; private set; }
public string OpenCmdArgs { get; private set; }
public Bitmap IconImage { get; private set; } = null;
public ExternalTool(string name, string icon, string execFile, Func<string, string> execArgsGenerator = null)
public ExternalTool(string name, string icon, string executable, string openCmdArgs)
{
Name = name;
_execFile = execFile;
_execArgsGenerator = execArgsGenerator ?? (repo => $"\"{repo}\"");
Executable = executable;
OpenCmdArgs = openCmdArgs;
try
{
@ -38,14 +40,11 @@ namespace SourceGit.Models
Process.Start(new ProcessStartInfo()
{
WorkingDirectory = repo,
FileName = _execFile,
Arguments = _execArgsGenerator.Invoke(repo),
FileName = Executable,
Arguments = string.Format(OpenCmdArgs, repo),
UseShellExecute = false,
});
}
private string _execFile = string.Empty;
private Func<string, string> _execArgsGenerator = null;
}
public class JetBrainsState
@ -111,48 +110,48 @@ namespace SourceGit.Models
_customPaths = new ExternalToolPaths();
}
public void TryAdd(string name, string icon, Func<string> finder, Func<string, string> execArgsGenerator = null)
public void TryAdd(string name, string icon, string args, string key, Func<string> finder)
{
if (_customPaths.Tools.TryGetValue(name, out var customPath) && File.Exists(customPath))
if (_customPaths.Tools.TryGetValue(key, out var customPath) && File.Exists(customPath))
{
Founded.Add(new ExternalTool(name, icon, customPath, execArgsGenerator));
Founded.Add(new ExternalTool(name, icon, customPath, args));
}
else
{
var path = finder();
if (!string.IsNullOrEmpty(path) && File.Exists(path))
Founded.Add(new ExternalTool(name, icon, path, execArgsGenerator));
Founded.Add(new ExternalTool(name, icon, path, args));
}
}
public void VSCode(Func<string> platformFinder)
{
TryAdd("Visual Studio Code", "vscode", platformFinder);
TryAdd("Visual Studio Code", "vscode", "\"{0}\"", "VSCODE", platformFinder);
}
public void VSCodeInsiders(Func<string> platformFinder)
{
TryAdd("Visual Studio Code - Insiders", "vscode_insiders", platformFinder);
TryAdd("Visual Studio Code - Insiders", "vscode_insiders", "\"{0}\"", "VSCODE_INSIDERS", platformFinder);
}
public void VSCodium(Func<string> platformFinder)
{
TryAdd("VSCodium", "codium", platformFinder);
TryAdd("VSCodium", "codium", "\"{0}\"", "VSCODIUM", platformFinder);
}
public void Fleet(Func<string> platformFinder)
{
TryAdd("Fleet", "fleet", platformFinder);
TryAdd("Fleet", "fleet", "\"{0}\"", "FLEET", platformFinder);
}
public void SublimeText(Func<string> platformFinder)
{
TryAdd("Sublime Text", "sublime_text", platformFinder);
TryAdd("Sublime Text", "sublime_text", "\"{0}\"", "SUBLIME_TEXT", platformFinder);
}
public void Zed(Func<string> platformFinder)
{
TryAdd("Zed", "zed", platformFinder);
TryAdd("Zed", "zed", "\"{0}\"", "ZED", platformFinder);
}
public void FindJetBrainsFromToolbox(Func<string> platformFinder)
@ -171,7 +170,8 @@ namespace SourceGit.Models
Founded.Add(new ExternalTool(
$"{tool.DisplayName} {tool.DisplayVersion}",
supported_icons.Contains(tool.ProductCode) ? $"JetBrains/{tool.ProductCode}" : "JetBrains/JB",
Path.Combine(tool.InstallLocation, tool.LaunchCommand)));
Path.Combine(tool.InstallLocation, tool.LaunchCommand),
"\"{0}\""));
}
}
}

View file

@ -134,7 +134,6 @@ namespace SourceGit.Native
finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\Programs\\Fleet\\Fleet.exe");
finder.FindJetBrainsFromToolbox(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\JetBrains\\Toolbox");
finder.SublimeText(FindSublimeText);
finder.TryAdd("Visual Studio", "vs", FindVisualStudio, GenerateCommandlineArgsForVisualStudio);
return finder.Founded;
}
@ -314,27 +313,6 @@ namespace SourceGit.Native
return string.Empty;
}
private string FindVisualStudio()
{
var localMachine = Microsoft.Win32.RegistryKey.OpenBaseKey(
Microsoft.Win32.RegistryHive.LocalMachine,
Microsoft.Win32.RegistryView.Registry64);
// Get default class for VisualStudio.Launcher.sln - the handler for *.sln files
if (localMachine.OpenSubKey(@"SOFTWARE\Classes\VisualStudio.Launcher.sln\CLSID") is Microsoft.Win32.RegistryKey launcher)
{
// Get actual path to the executable
if (launcher.GetValue(string.Empty) is string CLSID &&
localMachine.OpenSubKey(@$"SOFTWARE\Classes\CLSID\{CLSID}\LocalServer32") is Microsoft.Win32.RegistryKey devenv &&
devenv.GetValue(string.Empty) is string localServer32)
{
return localServer32!.Trim('\"');
}
}
return string.Empty;
}
#endregion
private void OpenFolderAndSelectFile(string folderPath)
@ -350,34 +328,5 @@ namespace SourceGit.Native
ILFree(pidl);
}
}
private string GenerateCommandlineArgsForVisualStudio(string repo)
{
var sln = FindVSSolutionFile(new DirectoryInfo(repo), 4);
return string.IsNullOrEmpty(sln) ? $"\"{repo}\"" : $"\"{sln}\"";
}
private string FindVSSolutionFile(DirectoryInfo dir, int leftDepth)
{
var files = dir.GetFiles();
foreach (var f in files)
{
if (f.Name.EndsWith(".sln", StringComparison.OrdinalIgnoreCase))
return f.FullName;
}
if (leftDepth <= 0)
return null;
var subDirs = dir.GetDirectories();
foreach (var subDir in subDirs)
{
var first = FindVSSolutionFile(subDir, leftDepth - 1);
if (!string.IsNullOrEmpty(first))
return first;
}
return null;
}
}
}

View file

@ -65,7 +65,6 @@
<StreamGeometry x:Key="Icons.LayoutHorizontal">M875 117H149C109 117 75 151 75 192v640c0 41 34 75 75 75h725c41 0 75-34 75-75V192c0-41-34-75-75-75zM139 832V192c0-6 4-11 11-11h331v661H149c-6 0-11-4-11-11zm747 0c0 6-4 11-11 11H544v-661H875c6 0 11 4 11 11v640z</StreamGeometry>
<StreamGeometry x:Key="Icons.LayoutVertical">M875 117H149C109 117 75 151 75 192v640c0 41 34 75 75 75h725c41 0 75-34 75-75V192c0-41-34-75-75-75zm-725 64h725c6 0 11 4 11 11v288h-747V192c0-6 4-11 11-11zm725 661H149c-6 0-11-4-11-11V544h747V832c0 6-4 11-11 11z</StreamGeometry>
<StreamGeometry x:Key="Icons.LFS">M40 9 15 23 15 31 9 28 9 20 34 5 24 0 0 14 0 34 25 48 25 28 49 14zM26 29 26 48 49 34 49 15z</StreamGeometry>
<StreamGeometry x:Key="Icons.Lines.All">M416 192m32 0 448 0q32 0 32 32l0 0q0 32-32 32l-448 0q-32 0-32-32l0 0q0-32 32-32ZM416 448m32 0 448 0q32 0 32 32l0 0q0 32-32 32l-448 0q-32 0-32-32l0 0q0-32 32-32ZM416 704m32 0 448 0q32 0 32 32l0 0q0 32-32 32l-448 0q-32 0-32-32l0 0q0-32 32-32ZM96 320l128-192 128 192h-256zM96 640l128 192 128-192h-256zM190 320h64v320H190z</StreamGeometry>
<StreamGeometry x:Key="Icons.Lines.Incr">M408 232C408 210 426 192 448 192h416a40 40 0 110 80H448a40 40 0 01-40-40zM408 512c0-22 18-40 40-40h416a40 40 0 110 80H448A40 40 0 01408 512zM448 752A40 40 0 00448 832h416a40 40 0 100-80H448zM32 480l132 0 0-128 64 0 0 128 132 0 0 64-132 0 0 128-64 0 0-128-132 0Z</StreamGeometry>
<StreamGeometry x:Key="Icons.Lines.Decr">M408 232C408 210 426 192 448 192h416a40 40 0 110 80H448a40 40 0 01-40-40zM408 512c0-22 18-40 40-40h416a40 40 0 110 80H448A40 40 0 01408 512zM448 752A40 40 0 00448 832h416a40 40 0 100-80H448zM32 480l328 0 0 64-328 0Z</StreamGeometry>
<StreamGeometry x:Key="Icons.Link">M 968 418 l -95 94 c -59 59 -146 71 -218 37 L 874 331 a 64 64 0 0 0 0 -90 L 783 150 a 64 64 0 0 0 -90 0 L 475 368 c -34 -71 -22 -159 37 -218 l 94 -94 c 75 -75 196 -75 271 0 l 90 90 c 75 75 75 196 0 271 z M 332 693 a 64 64 0 0 1 0 -90 l 271 -271 c 25 -25 65 -25 90 0 s 25 65 0 90 L 422 693 a 64 64 0 0 1 -90 0 z M 151 783 l 90 90 a 64 64 0 0 0 90 0 l 218 -218 c 34 71 22 159 -37 218 l -86 94 a 192 192 0 0 1 -271 0 l -98 -98 a 192 192 0 0 1 0 -271 l 94 -86 c 59 -59 146 -71 218 -37 L 151 693 a 64 64 0 0 0 0 90 z</StreamGeometry>

View file

@ -18,8 +18,8 @@
<x:String x:Key="Text.AddWorktree.Name.Placeholder" xml:space="preserve">Optional. Default is the destination folder name.</x:String>
<x:String x:Key="Text.AddWorktree.Tracking" xml:space="preserve">Track Branch:</x:String>
<x:String x:Key="Text.AddWorktree.Tracking.Toggle" xml:space="preserve">Tracking remote branch</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">AI Assistant</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">Use AI to generate commit message</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">OpenAI Assistant</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">Use OpenAI to generate commit message</x:String>
<x:String x:Key="Text.Apply" xml:space="preserve">Patch</x:String>
<x:String x:Key="Text.Apply.Error" xml:space="preserve">Error</x:String>
<x:String x:Key="Text.Apply.Error.Desc" xml:space="preserve">Raise errors and refuses to apply the patch</x:String>
@ -166,7 +166,7 @@
<x:String x:Key="Text.Configure.IssueTracker.RuleName" xml:space="preserve">Rule Name:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate" xml:space="preserve">Result URL:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate.Tip" xml:space="preserve">Please use $1, $2 to access regex groups values.</x:String>
<x:String x:Key="Text.Configure.OpenAI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Configure.OpenAI" xml:space="preserve">OPEN AI</x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered" xml:space="preserve">Prefered Service:</x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered.Tip" xml:space="preserve">If the 'Prefered Service' is set, SourceGit will only use it in this repository. Otherwise, if there is more than one service available, a context menu to choose one of them will be shown.</x:String>
<x:String x:Key="Text.Configure.Proxy" xml:space="preserve">HTTP Proxy</x:String>
@ -246,7 +246,6 @@
<x:String x:Key="Text.Diff.SyntaxHighlight" xml:space="preserve">Syntax Highlighting</x:String>
<x:String x:Key="Text.Diff.ToggleWordWrap" xml:space="preserve">Line Word Wrap</x:String>
<x:String x:Key="Text.Diff.UseMerger" xml:space="preserve">Open in Merge Tool</x:String>
<x:String x:Key="Text.Diff.VisualLines.All" xml:space="preserve">Show All Lines</x:String>
<x:String x:Key="Text.Diff.VisualLines.Decr" xml:space="preserve">Decrease Number of Visible Lines</x:String>
<x:String x:Key="Text.Diff.VisualLines.Incr" xml:space="preserve">Increase Number of Visible Lines</x:String>
<x:String x:Key="Text.Diff.Welcome" xml:space="preserve">SELECT FILE TO VIEW CHANGES</x:String>
@ -275,7 +274,7 @@
<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.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.StageMulti" xml:space="preserve">Stage {0} files</x:String>
<x:String x:Key="Text.FileCM.StageSelectedLines" xml:space="preserve">Stage Changes in Selected Line(s)</x:String>
@ -388,8 +387,6 @@
<x:String x:Key="Text.InteractiveRebase" xml:space="preserve">Interactive Rebase</x:String>
<x:String x:Key="Text.InteractiveRebase.Target" xml:space="preserve">Target Branch:</x:String>
<x:String x:Key="Text.InteractiveRebase.On" xml:space="preserve">On:</x:String>
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">Open in Browser</x:String>
<x:String x:Key="Text.IssueLinkCM.CopyLink" xml:space="preserve">Copy Link</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">ERROR</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">NOTICE</x:String>
<x:String x:Key="Text.Merge" xml:space="preserve">Merge Branch</x:String>
@ -401,7 +398,7 @@
<x:String x:Key="Text.Name" xml:space="preserve">Name:</x:String>
<x:String x:Key="Text.NotConfigured" xml:space="preserve">Git has NOT been configured. Please to go [Preference] and configure it first.</x:String>
<x:String x:Key="Text.OpenAppDataDir" xml:space="preserve">Open App Data Dir</x:String>
<x:String x:Key="Text.OpenWith" xml:space="preserve">Open with...</x:String>
<x:String x:Key="Text.OpenWith" xml:space="preserve">Open With...</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">Optional.</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">Create New Page</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">Bookmark</x:String>
@ -421,7 +418,7 @@
<x:String x:Key="Text.Period.LastYear" xml:space="preserve">Last year</x:String>
<x:String x:Key="Text.Period.YearsAgo" xml:space="preserve">{0} years ago</x:String>
<x:String x:Key="Text.Preference" xml:space="preserve">Preference</x:String>
<x:String x:Key="Text.Preference.AI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Preference.AI" xml:space="preserve">OPEN AI</x:String>
<x:String x:Key="Text.Preference.AI.AnalyzeDiffPrompt" xml:space="preserve">Analyze Diff Prompt</x:String>
<x:String x:Key="Text.Preference.AI.ApiKey" xml:space="preserve">API Key</x:String>
<x:String x:Key="Text.Preference.AI.GenerateSubjectPrompt" xml:space="preserve">Generate Subject Prompt</x:String>
@ -533,15 +530,15 @@
<x:String x:Key="Text.Repository.CustomActions" xml:space="preserve">Custom Actions</x:String>
<x:String x:Key="Text.Repository.CustomActions.Empty" xml:space="preserve">No Custom Actions</x:String>
<x:String x:Key="Text.Repository.EnableReflog" xml:space="preserve">Enable '--reflog' Option</x:String>
<x:String x:Key="Text.Repository.Explore" xml:space="preserve">Open in File Browser</x:String>
<x:String x:Key="Text.Repository.Explore" xml:space="preserve">Open In File Browser</x:String>
<x:String x:Key="Text.Repository.Filter" xml:space="preserve">Search Branches/Tags/Submodules</x:String>
<x:String x:Key="Text.Repository.FilterCommitPrefix" xml:space="preserve">FILTERED BY:</x:String>
<x:String x:Key="Text.Repository.LocalBranches" xml:space="preserve">LOCAL BRANCHES</x:String>
<x:String x:Key="Text.Repository.NavigateToCurrentHead" xml:space="preserve">Navigate to HEAD</x:String>
<x:String x:Key="Text.Repository.NavigateToCurrentHead" xml:space="preserve">Navigate To HEAD</x:String>
<x:String x:Key="Text.Repository.FirstParentFilterToggle" xml:space="preserve">Enable '--first-parent' Option</x:String>
<x:String x:Key="Text.Repository.NewBranch" xml:space="preserve">Create Branch</x:String>
<x:String x:Key="Text.Repository.OpenIn" xml:space="preserve">Open in {0}</x:String>
<x:String x:Key="Text.Repository.OpenWithExternalTools" xml:space="preserve">Open in External Tools</x:String>
<x:String x:Key="Text.Repository.OpenIn" xml:space="preserve">Open In {0}</x:String>
<x:String x:Key="Text.Repository.OpenWithExternalTools" xml:space="preserve">Open In External Tools</x:String>
<x:String x:Key="Text.Repository.Refresh" xml:space="preserve">Refresh</x:String>
<x:String x:Key="Text.Repository.Remotes" xml:space="preserve">REMOTES</x:String>
<x:String x:Key="Text.Repository.Remotes.Add" xml:space="preserve">ADD REMOTE</x:String>
@ -559,7 +556,7 @@
<x:String x:Key="Text.Repository.Submodules.Update" xml:space="preserve">UPDATE SUBMODULE</x:String>
<x:String x:Key="Text.Repository.Tags" xml:space="preserve">TAGS</x:String>
<x:String x:Key="Text.Repository.Tags.Add" xml:space="preserve">NEW TAG</x:String>
<x:String x:Key="Text.Repository.Terminal" xml:space="preserve">Open in Terminal</x:String>
<x:String x:Key="Text.Repository.Terminal" xml:space="preserve">Open In Terminal</x:String>
<x:String x:Key="Text.Repository.Worktrees" xml:space="preserve">WORKTREES</x:String>
<x:String x:Key="Text.Repository.Worktrees.Add" xml:space="preserve">ADD WORKTREE</x:String>
<x:String x:Key="Text.Repository.Worktrees.Prune" xml:space="preserve">PRUNE</x:String>

View file

@ -21,8 +21,8 @@
<x:String x:Key="Text.AddWorktree.Name.Placeholder" xml:space="preserve">选填。默认使用目标文件夹名称。</x:String>
<x:String x:Key="Text.AddWorktree.Tracking" xml:space="preserve">跟踪分支</x:String>
<x:String x:Key="Text.AddWorktree.Tracking.Toggle" xml:space="preserve">设置上游跟踪分支</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">AI助手</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">使用AI助手生成提交信息</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">OpenAI助手</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">使用OpenAI助手生成提交信息</x:String>
<x:String x:Key="Text.Apply" xml:space="preserve">应用补丁(apply)</x:String>
<x:String x:Key="Text.Apply.Error" xml:space="preserve">错误</x:String>
<x:String x:Key="Text.Apply.Error.Desc" xml:space="preserve">输出错误,并终止应用补丁</x:String>
@ -169,9 +169,9 @@
<x:String x:Key="Text.Configure.IssueTracker.RuleName" xml:space="preserve">规则名 </x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate" xml:space="preserve">为ISSUE生成的URL链接 </x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate.Tip" xml:space="preserve">可在URL中使用$1$2等变量填入正则表达式匹配的内容</x:String>
<x:String x:Key="Text.Configure.OpenAI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Configure.OpenAI" xml:space="preserve">OPEN AI</x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered" xml:space="preserve">启用特定服务 </x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered.Tip" xml:space="preserve">当【启用特定服务】被设置时SourceGit将在本仓库中仅使用该服务。否则将弹出可用的AI服务列表供用户选择。</x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered.Tip" xml:space="preserve">当【启用特定服务】被设置时SourceGit将在本仓库中仅使用该服务。否则将弹出可用的OpenAI服务列表供用户选择。</x:String>
<x:String x:Key="Text.Configure.Proxy" xml:space="preserve">HTTP代理</x:String>
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">HTTP网络代理</x:String>
<x:String x:Key="Text.Configure.User" xml:space="preserve">用户名</x:String>
@ -249,7 +249,6 @@
<x:String x:Key="Text.Diff.SyntaxHighlight" xml:space="preserve">语法高亮</x:String>
<x:String x:Key="Text.Diff.ToggleWordWrap" xml:space="preserve">自动换行</x:String>
<x:String x:Key="Text.Diff.UseMerger" xml:space="preserve">使用外部合并工具查看</x:String>
<x:String x:Key="Text.Diff.VisualLines.All" xml:space="preserve">显示完整文件</x:String>
<x:String x:Key="Text.Diff.VisualLines.Decr" xml:space="preserve">减少可见的行数</x:String>
<x:String x:Key="Text.Diff.VisualLines.Incr" xml:space="preserve">增加可见的行数</x:String>
<x:String x:Key="Text.Diff.Welcome" xml:space="preserve">请选择需要对比的文件</x:String>
@ -391,8 +390,6 @@
<x:String x:Key="Text.InteractiveRebase" xml:space="preserve">交互式变基</x:String>
<x:String x:Key="Text.InteractiveRebase.Target" xml:space="preserve">目标分支 </x:String>
<x:String x:Key="Text.InteractiveRebase.On" xml:space="preserve">起始提交 </x:String>
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">在浏览器中访问</x:String>
<x:String x:Key="Text.IssueLinkCM.CopyLink" xml:space="preserve">复制链接地址</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">出错了</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">系统提示</x:String>
<x:String x:Key="Text.Merge" xml:space="preserve">合并分支</x:String>
@ -424,7 +421,7 @@
<x:String x:Key="Text.Period.LastYear" xml:space="preserve">一年前</x:String>
<x:String x:Key="Text.Period.YearsAgo" xml:space="preserve">{0}年前</x:String>
<x:String x:Key="Text.Preference" xml:space="preserve">偏好设置</x:String>
<x:String x:Key="Text.Preference.AI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Preference.AI" xml:space="preserve">OPEN AI</x:String>
<x:String x:Key="Text.Preference.AI.AnalyzeDiffPrompt" xml:space="preserve">Analyze Diff Prompt</x:String>
<x:String x:Key="Text.Preference.AI.ApiKey" xml:space="preserve">API密钥</x:String>
<x:String x:Key="Text.Preference.AI.GenerateSubjectPrompt" xml:space="preserve">Generate Subject Prompt</x:String>

View file

@ -21,8 +21,8 @@
<x:String x:Key="Text.AddWorktree.Name.Placeholder" xml:space="preserve">選填。預設使用目標資料夾名稱。</x:String>
<x:String x:Key="Text.AddWorktree.Tracking" xml:space="preserve">追蹤分支</x:String>
<x:String x:Key="Text.AddWorktree.Tracking.Toggle" xml:space="preserve">設定遠端追蹤分支</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">AI 助理</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">使用 AI 產生提交訊息</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">OpenAI 助理</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">使用 OpenAI 產生提交訊息</x:String>
<x:String x:Key="Text.Apply" xml:space="preserve">套用修補檔 (apply patch)</x:String>
<x:String x:Key="Text.Apply.Error" xml:space="preserve">錯誤</x:String>
<x:String x:Key="Text.Apply.Error.Desc" xml:space="preserve">輸出錯誤,並中止套用修補檔</x:String>
@ -169,9 +169,9 @@
<x:String x:Key="Text.Configure.IssueTracker.RuleName" xml:space="preserve">規則名稱:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate" xml:space="preserve">為 Issue 產生的網址連結:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate.Tip" xml:space="preserve">可在網址中使用 $1、$2 等變數填入正規表達式相符的內容</x:String>
<x:String x:Key="Text.Configure.OpenAI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Configure.OpenAI" xml:space="preserve">OpenAI</x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered" xml:space="preserve">偏好服務:</x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered.Tip" xml:space="preserve">設定 [偏好服務] 後SourceGit 將於此存放庫中使用該服務,否則會顯示 AI 服務列表供使用者選擇。</x:String>
<x:String x:Key="Text.Configure.OpenAI.Prefered.Tip" xml:space="preserve">設定 [偏好服務] 後SourceGit 將於此存放庫中使用該服務,否則會顯示 OpenAI 服務列表供使用者選擇。</x:String>
<x:String x:Key="Text.Configure.Proxy" xml:space="preserve">HTTP 代理</x:String>
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">HTTP 網路代理</x:String>
<x:String x:Key="Text.Configure.User" xml:space="preserve">使用者名稱</x:String>
@ -249,7 +249,6 @@
<x:String x:Key="Text.Diff.SyntaxHighlight" xml:space="preserve">語法上色</x:String>
<x:String x:Key="Text.Diff.ToggleWordWrap" xml:space="preserve">自動換行</x:String>
<x:String x:Key="Text.Diff.UseMerger" xml:space="preserve">使用外部合併工具檢視</x:String>
<x:String x:Key="Text.Diff.VisualLines.All" xml:space="preserve">顯示檔案的全部內容</x:String>
<x:String x:Key="Text.Diff.VisualLines.Decr" xml:space="preserve">減少可見的行數</x:String>
<x:String x:Key="Text.Diff.VisualLines.Incr" xml:space="preserve">增加可見的行數</x:String>
<x:String x:Key="Text.Diff.Welcome" xml:space="preserve">請選擇需要對比的檔案</x:String>
@ -391,8 +390,6 @@
<x:String x:Key="Text.InteractiveRebase" xml:space="preserve">互動式重定基底</x:String>
<x:String x:Key="Text.InteractiveRebase.Target" xml:space="preserve">目標分支:</x:String>
<x:String x:Key="Text.InteractiveRebase.On" xml:space="preserve">起始提交:</x:String>
<x:String x:Key="Text.IssueLinkCM.OpenInBrowser" xml:space="preserve">在瀏覽器中存取網址</x:String>
<x:String x:Key="Text.IssueLinkCM.CopyLink" xml:space="preserve">複製網址</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">發生錯誤</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">系統提示</x:String>
<x:String x:Key="Text.Merge" xml:space="preserve">合併分支</x:String>
@ -424,7 +421,7 @@
<x:String x:Key="Text.Period.LastYear" xml:space="preserve">一年前</x:String>
<x:String x:Key="Text.Period.YearsAgo" xml:space="preserve">{0} 年前</x:String>
<x:String x:Key="Text.Preference" xml:space="preserve">偏好設定</x:String>
<x:String x:Key="Text.Preference.AI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Preference.AI" xml:space="preserve">OpenAI</x:String>
<x:String x:Key="Text.Preference.AI.Server" xml:space="preserve">伺服器</x:String>
<x:String x:Key="Text.Preference.AI.ApiKey" xml:space="preserve">API 金鑰</x:String>
<x:String x:Key="Text.Preference.AI.Model" xml:space="preserve">模型</x:String>

View file

@ -33,6 +33,12 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _fileModeChange, value);
}
public bool IsLoading
{
get => _isLoading;
private set => SetProperty(ref _isLoading, value);
}
public bool IsTextDiff
{
get => _isTextDiff;
@ -62,7 +68,6 @@ namespace SourceGit.ViewModels
_content = previous._content;
_unifiedLines = previous._unifiedLines;
_ignoreWhitespace = previous._ignoreWhitespace;
_info = previous._info;
}
if (string.IsNullOrEmpty(_option.OrgPath) || _option.OrgPath == "/dev/null")
@ -73,12 +78,6 @@ namespace SourceGit.ViewModels
LoadDiffContent();
}
public void ToggleFullTextDiff()
{
Preference.Instance.UseFullTextDiff = !Preference.Instance.UseFullTextDiff;
LoadDiffContent();
}
public void IncrUnified()
{
UnifiedLines = _unifiedLines + 1;
@ -104,23 +103,15 @@ namespace SourceGit.ViewModels
{
Content = null;
IsTextDiff = false;
IsLoading = false;
return;
}
Task.Run(() =>
{
// NOTE: Here we override the UnifiedLines value (if UseFullTextDiff is on).
// There is no way to tell a git-diff to use "ALL lines of context",
// so instead we set a very high number for the "lines of context" parameter.
var numLines = Preference.Instance.UseFullTextDiff ? 999999999 : _unifiedLines;
var latest = new Commands.Diff(_repo, _option, numLines, _ignoreWhitespace).Result();
var info = new Info(_option, numLines, _ignoreWhitespace, latest);
if (_info != null && info.IsSame(_info))
return;
_info = info;
var latest = new Commands.Diff(_repo, _option, _unifiedLines, _ignoreWhitespace).Result();
var rs = null as object;
if (latest.TextDiff != null)
{
var count = latest.TextDiff.Lines.Count;
@ -212,11 +203,12 @@ namespace SourceGit.ViewModels
Dispatcher.UIThread.Post(() =>
{
if (_content is Models.TextDiff old && rs is Models.TextDiff cur && old.File == cur.File)
cur.ScrollOffset = old.ScrollOffset;
cur.SyncScrollOffset = old.SyncScrollOffset;
FileModeChange = latest.FileModeChange;
Content = rs;
IsTextDiff = rs is Models.TextDiff;
IsLoading = false;
});
});
}
@ -249,41 +241,14 @@ namespace SourceGit.ViewModels
".ico", ".bmp", ".jpg", ".png", ".jpeg", ".webp"
};
private class Info
{
public string Argument { get; set; }
public int UnifiedLines { get; set; }
public bool IgnoreWhitespace { get; set; }
public string OldHash { get; set; }
public string NewHash { get; set; }
public Info(Models.DiffOption option, int unifiedLines, bool ignoreWhitespace, Models.DiffResult result)
{
Argument = option.ToString();
UnifiedLines = unifiedLines;
IgnoreWhitespace = ignoreWhitespace;
OldHash = result.OldHash;
NewHash = result.NewHash;
}
public bool IsSame(Info other)
{
return Argument.Equals(other.Argument, StringComparison.Ordinal) &&
UnifiedLines == other.UnifiedLines &&
IgnoreWhitespace == other.IgnoreWhitespace &&
OldHash.Equals(other.OldHash, StringComparison.Ordinal) &&
NewHash.Equals(other.NewHash, StringComparison.Ordinal);
}
}
private readonly string _repo;
private readonly Models.DiffOption _option = null;
private string _title;
private string _fileModeChange = string.Empty;
private int _unifiedLines = 4;
private bool _isLoading = true;
private bool _isTextDiff = false;
private bool _ignoreWhitespace = false;
private object _content = null;
private Info _info = null;
}
}

View file

@ -186,12 +186,6 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _showHiddenSymbolsInDiffView, value);
}
public bool UseFullTextDiff
{
get => _useFullTextDiff;
set => SetProperty(ref _useFullTextDiff, value);
}
public Models.ChangeViewMode UnstagedChangeViewMode
{
get => _unstagedChangeViewMode;
@ -597,7 +591,6 @@ namespace SourceGit.ViewModels
private bool _useSyntaxHighlighting = false;
private bool _enableDiffViewWordWrap = false;
private bool _showHiddenSymbolsInDiffView = false;
private bool _useFullTextDiff = false;
private Models.ChangeViewMode _unstagedChangeViewMode = Models.ChangeViewMode.List;
private Models.ChangeViewMode _stagedChangeViewMode = Models.ChangeViewMode.List;

View file

@ -407,8 +407,8 @@ namespace SourceGit.Views
var menu = new ContextMenu();
menu.Items.Add(copy);
menu.Open(TextArea.TextView);
TextArea.TextView.OpenContextMenu(menu);
e.Handled = true;
}

View file

@ -15,7 +15,7 @@ namespace SourceGit.Views
if (DataContext is ViewModels.BranchCompare vm && sender is ChangeCollectionView view)
{
var menu = vm.CreateChangeContextMenu();
menu?.Open(view);
view.OpenContextMenu(menu);
}
e.Handled = true;

View file

@ -374,7 +374,7 @@ namespace SourceGit.Views
if (selected.Count == 1 && selected[0] is ViewModels.BranchTreeNode { Backend: Models.Remote remote })
{
var menu = repo.CreateContextMenuForRemote(remote);
menu?.Open(this);
this.OpenContextMenu(menu);
return;
}
@ -391,7 +391,7 @@ namespace SourceGit.Views
var menu = branch.IsLocal ?
repo.CreateContextMenuForLocalBranch(branch) :
repo.CreateContextMenuForRemoteBranch(branch);
menu?.Open(this);
this.OpenContextMenu(menu);
}
else if (branches.Find(x => x.IsCurrent) == null)
{
@ -405,7 +405,7 @@ namespace SourceGit.Views
ev.Handled = true;
};
menu.Items.Add(deleteMulti);
menu?.Open(this);
this.OpenContextMenu(menu);
}
}

View file

@ -68,7 +68,7 @@ namespace SourceGit.Views
private void OnOpenWebLink(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.CommitDetail detail && sender is Control control)
if (DataContext is ViewModels.CommitDetail detail)
{
var links = WebLinks;
if (links.Count > 1)
@ -88,7 +88,7 @@ namespace SourceGit.Views
menu.Items.Add(item);
}
menu?.Open(control);
(sender as Control)?.OpenContextMenu(menu);
}
else if (links.Count == 1)
{

View file

@ -16,7 +16,7 @@ namespace SourceGit.Views
DataContext is ViewModels.CommitDetail vm)
{
var menu = vm.CreateChangeContextMenu(selected[0]);
menu?.Open(view);
view.OpenContextMenu(menu);
}
e.Handled = true;

View file

@ -26,7 +26,7 @@ namespace SourceGit.Views
if (DataContext is ViewModels.CommitDetail detail && sender is Grid grid && grid.DataContext is Models.Change change)
{
var menu = detail.CreateChangeContextMenu(change);
menu?.Open(grid);
grid.OpenContextMenu(menu);
}
e.Handled = true;

View file

@ -176,38 +176,7 @@ namespace SourceGit.Views
}
else
{
var point = e.GetCurrentPoint(this);
var link = _lastHover.Link;
if (point.Properties.IsLeftButtonPressed)
{
Native.OS.OpenBrowser(link);
}
else if (point.Properties.IsRightButtonPressed)
{
var open = new MenuItem();
open.Header = App.Text("IssueLinkCM.OpenInBrowser");
open.Icon = App.CreateMenuIcon("Icons.OpenWith");
open.Click += (_, ev) =>
{
Native.OS.OpenBrowser(link);
ev.Handled = true;
};
var copy = new MenuItem();
copy.Header = App.Text("IssueLinkCM.CopyLink");
copy.Icon = App.CreateMenuIcon("Icons.Copy");
copy.Click += (_, ev) =>
{
App.CopyText(link);
ev.Handled = true;
};
var menu = new ContextMenu();
menu.Items.Add(open);
menu.Items.Add(copy);
menu.Open(this);
}
Native.OS.OpenBrowser(_lastHover.Link);
}
e.Handled = true;

View file

@ -0,0 +1,26 @@
using System.ComponentModel;
using Avalonia.Controls;
namespace SourceGit.Views
{
public static class ContextMenuExtension
{
public static void OpenContextMenu(this Control control, ContextMenu menu)
{
if (menu == null)
return;
menu.PlacementTarget = control;
menu.Closing += OnContextMenuClosing; // Clear context menu because it is dynamic.
control.ContextMenu = menu;
control.ContextMenu?.Open();
}
private static void OnContextMenuClosing(object sender, CancelEventArgs e)
{
if (sender is ContextMenu menu && menu.PlacementTarget != null)
menu.PlacementTarget.ContextMenu = null;
}
}
}

View file

@ -39,9 +39,6 @@
Command="{Binding IncrUnified}"
IsVisible="{Binding IsTextDiff}"
ToolTip.Tip="{DynamicResource Text.Diff.VisualLines.Incr}">
<Button.IsEnabled>
<Binding Source="{x:Static vm:Preference.Instance}" Path="UseFullTextDiff" Mode="OneWay" Converter="{x:Static BoolConverters.Not}"/>
</Button.IsEnabled>
<Path Width="12" Height="12" Stretch="Uniform" Margin="0,6,0,0" Data="{StaticResource Icons.Lines.Incr}"/>
</Button>
@ -49,26 +46,10 @@
Width="32"
Command="{Binding DecrUnified}"
IsVisible="{Binding IsTextDiff}"
ToolTip.Tip="{DynamicResource Text.Diff.VisualLines.Decr}">
<Button.IsEnabled>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="UnifiedLines" Mode="OneWay" Converter="{x:Static c:IntConverters.IsGreaterThanFour}"/>
<Binding Source="{x:Static vm:Preference.Instance}" Path="UseFullTextDiff" Mode="OneWay" Converter="{x:Static BoolConverters.Not}"/>
</MultiBinding>
</Button.IsEnabled>
ToolTip.Tip="{DynamicResource Text.Diff.VisualLines.Decr}"
IsEnabled="{Binding UnifiedLines, Converter={x:Static c:IntConverters.IsGreaterThanFour}}">
<Path Width="12" Height="12" Stretch="Uniform" Margin="0,6,0,0" Data="{StaticResource Icons.Lines.Decr}"/>
</Button>
<ToggleButton Classes="line_path"
Width="32" Height="18"
Background="Transparent"
Padding="9,6"
Command="{Binding ToggleFullTextDiff}"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=UseFullTextDiff, Mode=OneWay}"
IsVisible="{Binding IsTextDiff}"
ToolTip.Tip="{DynamicResource Text.Diff.VisualLines.All}">
<Path Width="13" Height="13" Data="{StaticResource Icons.Lines.All}" Margin="0,3,0,0"/>
</ToggleButton>
<ToggleButton Classes="line_path"
Width="32" Height="18"
@ -230,9 +211,7 @@
<!-- Text Diff -->
<DataTemplate DataType="m:TextDiff">
<v:TextDiffView
UseSideBySideDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=OneWay}"
UseFullTextDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseFullTextDiff, Mode=OneWay}"/>
<v:TextDiffView UseSideBySideDiff="{Binding Source={x:Static vm:Preference.Instance}, Path=UseSideBySideDiff, Mode=OneWay}"/>
</DataTemplate>
<!-- Empty or only EOL changes -->

View file

@ -706,7 +706,7 @@ namespace SourceGit.Views
if (DataContext is ViewModels.Histories histories && sender is ListBox { SelectedItems: { Count: > 0 } } list)
{
var menu = histories.MakeContextMenu(list);
menu?.Open(list);
list.OpenContextMenu(menu);
}
e.Handled = true;
}

View file

@ -250,7 +250,7 @@ namespace SourceGit.Views
if (sender is Button btn && DataContext is ViewModels.Launcher launcher)
{
var menu = launcher.CreateContextForWorkspace();
menu?.Open(btn);
btn.OpenContextMenu(menu);
}
e.Handled = true;

View file

@ -234,7 +234,7 @@ namespace SourceGit.Views
if (sender is Border border && DataContext is ViewModels.Launcher vm)
{
var menu = vm.CreateContextForPageTab(border.DataContext as ViewModels.LauncherPage);
menu?.Open(border);
border.OpenContextMenu(menu);
}
e.Handled = true;

View file

@ -189,7 +189,7 @@ namespace SourceGit.Views
if (sender is ListBox { SelectedItem: Models.Submodule submodule } grid && DataContext is ViewModels.Repository repo)
{
var menu = repo.CreateContextMenuForSubmodule(submodule.Path);
menu?.Open(grid);
grid.OpenContextMenu(menu);
}
e.Handled = true;
@ -210,7 +210,7 @@ namespace SourceGit.Views
if (sender is ListBox { SelectedItem: Models.Worktree worktree } grid && DataContext is ViewModels.Repository repo)
{
var menu = repo.CreateContextMenuForWorktree(worktree);
menu?.Open(grid);
grid.OpenContextMenu(menu);
}
e.Handled = true;

View file

@ -17,7 +17,7 @@ namespace SourceGit.Views
if (sender is Button button && DataContext is ViewModels.Repository repo)
{
var menu = repo.CreateContextMenuForExternalTools();
menu?.Open(button);
button.OpenContextMenu(menu);
e.Handled = true;
}
}
@ -72,10 +72,10 @@ namespace SourceGit.Views
private void OpenGitFlowMenu(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.Repository repo && sender is Control control)
if (DataContext is ViewModels.Repository repo)
{
var menu = repo.CreateContextMenuForGitFlow();
menu?.Open(control);
(sender as Control)?.OpenContextMenu(menu);
}
e.Handled = true;
@ -83,10 +83,10 @@ namespace SourceGit.Views
private void OpenGitLFSMenu(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.Repository repo && sender is Control control)
if (DataContext is ViewModels.Repository repo)
{
var menu = repo.CreateContextMenuForGitLFS();
menu?.Open(control);
(sender as Control)?.OpenContextMenu(menu);
}
e.Handled = true;
@ -94,10 +94,10 @@ namespace SourceGit.Views
private void OpenCustomActionMenu(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.Repository repo && sender is Control control)
if (DataContext is ViewModels.Repository repo)
{
var menu = repo.CreateContextMenuForCustomAction();
menu?.Open(control);
(sender as Control)?.OpenContextMenu(menu);
}
e.Handled = true;

View file

@ -15,7 +15,7 @@ namespace SourceGit.Views
if (DataContext is ViewModels.RevisionCompare vm && sender is ChangeCollectionView view)
{
var menu = vm.CreateChangeContextMenu();
menu?.Open(view);
view.OpenContextMenu(menu);
}
e.Handled = true;

View file

@ -229,7 +229,7 @@ namespace SourceGit.Views
if (obj.Type != Models.ObjectType.Tree)
{
var menu = vm.CreateRevisionFileContextMenu(obj);
menu?.Open(grid);
grid.OpenContextMenu(menu);
}
}

View file

@ -95,8 +95,8 @@ namespace SourceGit.Views
var menu = new ContextMenu();
menu.Items.Add(copy);
menu.Open(TextArea.TextView);
TextArea.TextView.OpenContextMenu(menu);
e.Handled = true;
}

View file

@ -28,7 +28,7 @@ namespace SourceGit.Views
if (DataContext is ViewModels.StashesPage vm && sender is Border border)
{
var menu = vm.MakeContextMenu(border.DataContext as Models.Stash);
menu?.Open(border);
border.OpenContextMenu(menu);
}
e.Handled = true;
}
@ -38,7 +38,7 @@ namespace SourceGit.Views
if (DataContext is ViewModels.StashesPage vm && sender is Grid grid)
{
var menu = vm.MakeContextMenuForChange(grid.DataContext as Models.Change);
menu?.Open(grid);
grid.OpenContextMenu(menu);
}
e.Handled = true;
}

View file

@ -225,7 +225,7 @@ namespace SourceGit.Views
if (selected != null && DataContext is ViewModels.Repository repo)
{
var menu = repo.CreateContextMenuForTag(selected);
menu?.Open(control);
control.OpenContextMenu(menu);
}
e.Handled = true;

View file

@ -589,8 +589,8 @@ namespace SourceGit.Views
var menu = new ContextMenu();
menu.Items.Add(copy);
menu.Open(TextArea.TextView);
TextArea.TextView.OpenContextMenu(menu);
e.Handled = true;
}
@ -902,7 +902,7 @@ namespace SourceGit.Views
var scroller = this.FindDescendantOfType<ScrollViewer>();
if (scroller != null)
{
scroller.Bind(ScrollViewer.OffsetProperty, new Binding("ScrollOffset", BindingMode.TwoWay));
scroller.Bind(ScrollViewer.OffsetProperty, new Binding("SyncScrollOffset", BindingMode.TwoWay));
scroller.GotFocus += OnTextViewScrollGotFocus;
}
}
@ -1205,15 +1205,6 @@ namespace SourceGit.Views
set => SetValue(UseSideBySideDiffProperty, value);
}
public static readonly StyledProperty<bool> UseFullTextDiffProperty =
AvaloniaProperty.Register<TextDiffView, bool>(nameof(UseFullTextDiff));
public bool UseFullTextDiff
{
get => GetValue(UseFullTextDiffProperty);
set => SetValue(UseFullTextDiffProperty, value);
}
public static readonly StyledProperty<TextDiffViewChunk> SelectedChunkProperty =
AvaloniaProperty.Register<TextDiffView, TextDiffViewChunk>(nameof(SelectedChunk));
@ -1245,12 +1236,15 @@ namespace SourceGit.Views
{
UseSideBySideDiffProperty.Changed.AddClassHandler<TextDiffView>((v, _) =>
{
v.RefreshContent(v.DataContext as Models.TextDiff, false);
});
if (v.DataContext is Models.TextDiff diff)
{
diff.SyncScrollOffset = Vector.Zero;
UseFullTextDiffProperty.Changed.AddClassHandler<TextDiffView>((v, _) =>
{
v.RefreshContent(v.DataContext as Models.TextDiff, false);
if (v.UseSideBySideDiff)
v.Editor.Content = new ViewModels.TwoSideTextDiff(diff);
else
v.Editor.Content = diff;
}
});
SelectedChunkProperty.Changed.AddClassHandler<TextDiffView>((v, _) =>
@ -1277,22 +1271,11 @@ namespace SourceGit.Views
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
RefreshContent(DataContext as Models.TextDiff, true);
}
protected override void OnPointerExited(PointerEventArgs e)
{
base.OnPointerExited(e);
if (SelectedChunk != null)
SetCurrentValue(SelectedChunkProperty, null);
}
private void RefreshContent(Models.TextDiff diff, bool keepScrollOffset = true)
{
if (SelectedChunk != null)
SetCurrentValue(SelectedChunkProperty, null);
var diff = DataContext as Models.TextDiff;
if (diff == null)
{
Editor.Content = null;
@ -1301,21 +1284,22 @@ namespace SourceGit.Views
}
if (UseSideBySideDiff)
{
var previousContent = Editor.Content as ViewModels.TwoSideTextDiff;
Editor.Content = new ViewModels.TwoSideTextDiff(diff, keepScrollOffset ? previousContent : null);
}
Editor.Content = new ViewModels.TwoSideTextDiff(diff, Editor.Content as ViewModels.TwoSideTextDiff);
else
{
if (!keepScrollOffset)
diff.ScrollOffset = Vector.Zero;
Editor.Content = diff;
}
IsUnstagedChange = diff.Option.IsUnstaged;
EnableChunkSelection = diff.Option.WorkingCopyChange != null;
}
protected override void OnPointerExited(PointerEventArgs e)
{
base.OnPointerExited(e);
if (SelectedChunk != null)
SetCurrentValue(SelectedChunkProperty, null);
}
private void OnStageChunk(object _1, RoutedEventArgs _2)
{
var chunk = SelectedChunk;

View file

@ -117,7 +117,7 @@ namespace SourceGit.Views
if (sender is Grid { DataContext: ViewModels.RepositoryNode node } grid)
{
var menu = ViewModels.Welcome.Instance.CreateContextMenu(node);
menu?.Open(grid);
grid.OpenContextMenu(menu);
e.Handled = true;
}
}

View file

@ -31,27 +31,27 @@ namespace SourceGit.Views
{
var menu = vm.CreateContextMenuForCommitMessages();
menu.Placement = PlacementMode.TopEdgeAlignedLeft;
menu?.Open(button);
button.OpenContextMenu(menu);
e.Handled = true;
}
}
private void OnUnstagedContextRequested(object sender, ContextRequestedEventArgs e)
{
if (DataContext is ViewModels.WorkingCopy vm && sender is Control control)
if (DataContext is ViewModels.WorkingCopy vm)
{
var menu = vm.CreateContextMenuForUnstagedChanges();
menu?.Open(control);
(sender as Control)?.OpenContextMenu(menu);
e.Handled = true;
}
}
private void OnStagedContextRequested(object sender, ContextRequestedEventArgs e)
{
if (DataContext is ViewModels.WorkingCopy vm && sender is Control control)
if (DataContext is ViewModels.WorkingCopy vm)
{
var menu = vm.CreateContextMenuForStagedChanges();
menu?.Open(control);
(sender as Control)?.OpenContextMenu(menu);
e.Handled = true;
}
}
@ -136,10 +136,10 @@ namespace SourceGit.Views
private void OnOpenOpenAIHelper(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.WorkingCopy vm && sender is Control control)
if (DataContext is ViewModels.WorkingCopy vm)
{
var menu = vm.CreateContextForOpenAI();
menu?.Open(control);
(sender as Button)?.OpenContextMenu(menu);
}
e.Handled = true;