Merge branch 'release/v2026.08'

This commit is contained in:
leo
2026-04-13 10:42:30 +08:00
126 changed files with 1930 additions and 1101 deletions

View File

@@ -7,7 +7,7 @@ The project uses the following third-party libraries or assets
### AvaloniaUI
- **Source**: https://github.com/AvaloniaUI/Avalonia
- **Version**: 11.3.12
- **Version**: 11.3.13
- **License**: MIT License
- **License Link**: https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md
@@ -22,7 +22,7 @@ The project uses the following third-party libraries or assets
### LiveChartsCore.SkiaSharpView.Avalonia
- **Source**: https://github.com/beto-rodriguez/LiveCharts2
- **Version**: 2.0.0-rc6.1
- **Version**: 2.0.0
- **License**: MIT License
- **License Link**: https://github.com/beto-rodriguez/LiveCharts2/blob/master/LICENSE
@@ -36,7 +36,7 @@ The project uses the following third-party libraries or assets
### OpenAI .NET SDK
- **Source**: https://github.com/openai/openai-dotnet
- **Version**: 2.9.1
- **Version**: 2.10.0
- **License**: MIT License
- **License Link**: https://github.com/openai/openai-dotnet/blob/main/LICENSE

View File

@@ -6,19 +6,23 @@ This document shows the translation status of each locale file in the repository
### ![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen)
### ![de__DE](https://img.shields.io/badge/de__DE-98.04%25-yellow)
### ![de__DE](https://img.shields.io/badge/de__DE-97.53%25-yellow)
<details>
<summary>Missing keys in de_DE.axaml</summary>
- Text.AIAssistant.Use
- Text.Apply.3Way
- Text.CheckoutBranchFromStash
- Text.CheckoutBranchFromStash.Branch
- Text.CheckoutBranchFromStash.Stash
- Text.CommandPalette.Branches
- Text.CommandPalette.BranchesAndTags
- Text.CommandPalette.RepositoryActions
- Text.CommandPalette.RevisionFiles
- Text.CommitMessageTextBox.Column
- Text.ConfirmEmptyCommit.StageSelectedThenCommit
- Text.Discard.IncludeModified
- Text.GotoRevisionSelector
- Text.Hotkeys.Repo.CreateBranch
- Text.Hotkeys.Repo.GoToChild
@@ -27,6 +31,7 @@ This document shows the translation status of each locale file in the repository
- Text.Preferences.AI.AdditionalPrompt
- Text.Preferences.General.Use24Hours
- Text.StashCM.ApplyFileChanges
- Text.StashCM.Branch
- Text.Worktree.Branch
- Text.Worktree.Head
- Text.Worktree.Path
@@ -35,7 +40,7 @@ This document shows the translation status of each locale file in the repository
### ![es__ES](https://img.shields.io/badge/es__ES-%E2%88%9A-brightgreen)
### ![fr__FR](https://img.shields.io/badge/fr__FR-91.84%25-yellow)
### ![fr__FR](https://img.shields.io/badge/fr__FR-91.37%25-yellow)
<details>
<summary>Missing keys in fr_FR.axaml</summary>
@@ -52,6 +57,9 @@ This document shows the translation status of each locale file in the repository
- Text.ChangeCM.MergeExternal
- Text.ChangeCM.ResetFileTo
- Text.Checkout.WarnUpdatingSubmodules
- Text.CheckoutBranchFromStash
- Text.CheckoutBranchFromStash.Branch
- Text.CheckoutBranchFromStash.Stash
- Text.CommandPalette.Branches
- Text.CommandPalette.BranchesAndTags
- Text.CommandPalette.RepositoryActions
@@ -61,6 +69,7 @@ This document shows the translation status of each locale file in the repository
- Text.Compare.WithHead
- Text.Configure.Git.AskBeforeAutoUpdatingSubmodules
- Text.ConfirmEmptyCommit.StageSelectedThenCommit
- Text.Discard.IncludeModified
- Text.EditBranchDescription
- Text.EditBranchDescription.Target
- Text.FileCM.CustomAction
@@ -110,6 +119,7 @@ This document shows the translation status of each locale file in the repository
- Text.SquashOrFixup.Fixup
- Text.SquashOrFixup.Into
- Text.StashCM.ApplyFileChanges
- Text.StashCM.Branch
- Text.TagCM.CompareTwo
- Text.TagCM.CompareWith
- Text.TagCM.CompareWithHead
@@ -122,7 +132,7 @@ This document shows the translation status of each locale file in the repository
</details>
### ![id__ID](https://img.shields.io/badge/id__ID-89.67%25-yellow)
### ![id__ID](https://img.shields.io/badge/id__ID-89.21%25-yellow)
<details>
<summary>Missing keys in id_ID.axaml</summary>
@@ -143,6 +153,9 @@ This document shows the translation status of each locale file in the repository
- Text.ChangeCM.MergeExternal
- Text.ChangeCM.ResetFileTo
- Text.Checkout.WarnUpdatingSubmodules
- Text.CheckoutBranchFromStash
- Text.CheckoutBranchFromStash.Branch
- Text.CheckoutBranchFromStash.Stash
- Text.CommandPalette.Branches
- Text.CommandPalette.BranchesAndTags
- Text.CommandPalette.RepositoryActions
@@ -157,6 +170,7 @@ This document shows the translation status of each locale file in the repository
- Text.ConfigureCustomActionControls.StringValue.Tip
- Text.ConfirmEmptyCommit.StageSelectedThenCommit
- Text.DealWithLocalChanges.DoNothing
- Text.Discard.IncludeModified
- Text.DropHead
- Text.DropHead.Commit
- Text.DropHead.NewHead
@@ -218,6 +232,7 @@ This document shows the translation status of each locale file in the repository
- Text.SquashOrFixup.Fixup
- Text.SquashOrFixup.Into
- Text.StashCM.ApplyFileChanges
- Text.StashCM.Branch
- Text.TagCM.CompareTwo
- Text.TagCM.CompareWith
- Text.TagCM.CompareWithHead
@@ -230,7 +245,7 @@ This document shows the translation status of each locale file in the repository
</details>
### ![it__IT](https://img.shields.io/badge/it__IT-97.42%25-yellow)
### ![it__IT](https://img.shields.io/badge/it__IT-96.92%25-yellow)
<details>
<summary>Missing keys in it_IT.axaml</summary>
@@ -238,12 +253,16 @@ This document shows the translation status of each locale file in the repository
- Text.AIAssistant.Use
- Text.Apply.3Way
- Text.ChangeCM.ResetFileTo
- Text.CheckoutBranchFromStash
- Text.CheckoutBranchFromStash.Branch
- Text.CheckoutBranchFromStash.Stash
- Text.CommandPalette.Branches
- Text.CommandPalette.BranchesAndTags
- Text.CommandPalette.RepositoryActions
- Text.CommandPalette.RevisionFiles
- Text.CommitMessageTextBox.Column
- Text.ConfirmEmptyCommit.StageSelectedThenCommit
- Text.Discard.IncludeModified
- Text.GotoRevisionSelector
- Text.Histories.Header.DateTime
- Text.Histories.ShowColumns
@@ -257,37 +276,43 @@ This document shows the translation status of each locale file in the repository
- Text.SelfUpdate.CurrentVersion
- Text.SelfUpdate.ReleaseDate
- Text.StashCM.ApplyFileChanges
- Text.StashCM.Branch
- Text.Worktree.Branch
- Text.Worktree.Head
- Text.Worktree.Path
</details>
### ![ja__JP](https://img.shields.io/badge/ja__JP-98.35%25-yellow)
### ![ja__JP](https://img.shields.io/badge/ja__JP-97.84%25-yellow)
<details>
<summary>Missing keys in ja_JP.axaml</summary>
- Text.AIAssistant.Use
- Text.Apply.3Way
- Text.CheckoutBranchFromStash
- Text.CheckoutBranchFromStash.Branch
- Text.CheckoutBranchFromStash.Stash
- Text.CommandPalette.Branches
- Text.CommandPalette.BranchesAndTags
- Text.CommandPalette.RepositoryActions
- Text.CommandPalette.RevisionFiles
- Text.ConfirmEmptyCommit.StageSelectedThenCommit
- Text.DealWithLocalChanges.DoNothing
- Text.Discard.IncludeModified
- Text.Hotkeys.Repo.CreateBranch
- Text.Init.CommandTip
- Text.Init.ErrorMessageTip
- Text.Preferences.AI.AdditionalPrompt
- Text.Preferences.General.Use24Hours
- Text.StashCM.Branch
- Text.Worktree.Branch
- Text.Worktree.Head
- Text.Worktree.Path
</details>
### ![ko__KR](https://img.shields.io/badge/ko__KR-89.98%25-yellow)
### ![ko__KR](https://img.shields.io/badge/ko__KR-89.52%25-yellow)
<details>
<summary>Missing keys in ko_KR.axaml</summary>
@@ -308,6 +333,9 @@ This document shows the translation status of each locale file in the repository
- Text.ChangeCM.MergeExternal
- Text.ChangeCM.ResetFileTo
- Text.Checkout.WarnUpdatingSubmodules
- Text.CheckoutBranchFromStash
- Text.CheckoutBranchFromStash.Branch
- Text.CheckoutBranchFromStash.Stash
- Text.CommandPalette.Branches
- Text.CommandPalette.BranchesAndTags
- Text.CommandPalette.RepositoryActions
@@ -320,6 +348,7 @@ This document shows the translation status of each locale file in the repository
- Text.ConfigureCustomActionControls.StringValue.Tip
- Text.ConfirmEmptyCommit.StageSelectedThenCommit
- Text.DealWithLocalChanges.DoNothing
- Text.Discard.IncludeModified
- Text.EditBranchDescription
- Text.EditBranchDescription.Target
- Text.FileCM.CustomAction
@@ -379,6 +408,7 @@ This document shows the translation status of each locale file in the repository
- Text.SquashOrFixup.Fixup
- Text.SquashOrFixup.Into
- Text.StashCM.ApplyFileChanges
- Text.StashCM.Branch
- Text.Submodule.Status.Unmerged
- Text.TagCM.CompareTwo
- Text.TagCM.CompareWith
@@ -392,7 +422,7 @@ This document shows the translation status of each locale file in the repository
</details>
### ![pt__BR](https://img.shields.io/badge/pt__BR-68.08%25-red)
### ![pt__BR](https://img.shields.io/badge/pt__BR-67.73%25-red)
<details>
<summary>Missing keys in pt_BR.axaml</summary>
@@ -416,6 +446,9 @@ This document shows the translation status of each locale file in the repository
- Text.Checkout.WarnUpdatingSubmodules
- Text.Checkout.WithFastForward
- Text.Checkout.WithFastForward.Upstream
- Text.CheckoutBranchFromStash
- Text.CheckoutBranchFromStash.Branch
- Text.CheckoutBranchFromStash.Stash
- Text.Clone.RecurseSubmodules
- Text.CommandPalette.Branches
- Text.CommandPalette.BranchesAndTags
@@ -509,6 +542,7 @@ This document shows the translation status of each locale file in the repository
- Text.DirtyState.HasLocalChanges
- Text.DirtyState.HasPendingPullOrPush
- Text.DirtyState.UpToDate
- Text.Discard.IncludeModified
- Text.Discard.IncludeUntracked
- Text.DropHead
- Text.DropHead.Commit
@@ -655,6 +689,7 @@ This document shows the translation status of each locale file in the repository
- Text.SquashOrFixup.Into
- Text.Stash.Mode
- Text.StashCM.ApplyFileChanges
- Text.StashCM.Branch
- Text.StashCM.CopyMessage
- Text.StashCM.SaveAsPatch
- Text.Submodule.Branch
@@ -711,7 +746,7 @@ This document shows the translation status of each locale file in the repository
### ![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen)
### ![ta__IN](https://img.shields.io/badge/ta__IN-70.14%25-red)
### ![ta__IN](https://img.shields.io/badge/ta__IN-69.78%25-red)
<details>
<summary>Missing keys in ta_IN.axaml</summary>
@@ -764,6 +799,9 @@ This document shows the translation status of each locale file in the repository
- Text.Checkout.WarnUpdatingSubmodules
- Text.Checkout.WithFastForward
- Text.Checkout.WithFastForward.Upstream
- Text.CheckoutBranchFromStash
- Text.CheckoutBranchFromStash.Branch
- Text.CheckoutBranchFromStash.Stash
- Text.CommandPalette.Branches
- Text.CommandPalette.BranchesAndTags
- Text.CommandPalette.RepositoryActions
@@ -843,6 +881,7 @@ This document shows the translation status of each locale file in the repository
- Text.DirtyState.HasLocalChanges
- Text.DirtyState.HasPendingPullOrPush
- Text.DirtyState.UpToDate
- Text.Discard.IncludeModified
- Text.Discard.IncludeUntracked
- Text.DropHead
- Text.DropHead.Commit
@@ -957,6 +996,7 @@ This document shows the translation status of each locale file in the repository
- Text.SquashOrFixup.Into
- Text.Stash.Mode
- Text.StashCM.ApplyFileChanges
- Text.StashCM.Branch
- Text.StashCM.CopyMessage
- Text.Submodule.Branch
- Text.Submodule.CopyBranch
@@ -1008,7 +1048,7 @@ This document shows the translation status of each locale file in the repository
</details>
### ![uk__UA](https://img.shields.io/badge/uk__UA-70.97%25-red)
### ![uk__UA](https://img.shields.io/badge/uk__UA-70.61%25-red)
<details>
<summary>Missing keys in uk_UA.axaml</summary>
@@ -1061,6 +1101,9 @@ This document shows the translation status of each locale file in the repository
- Text.Checkout.WarnUpdatingSubmodules
- Text.Checkout.WithFastForward
- Text.Checkout.WithFastForward.Upstream
- Text.CheckoutBranchFromStash
- Text.CheckoutBranchFromStash.Branch
- Text.CheckoutBranchFromStash.Stash
- Text.CommandPalette.Branches
- Text.CommandPalette.BranchesAndTags
- Text.CommandPalette.RepositoryActions
@@ -1136,6 +1179,7 @@ This document shows the translation status of each locale file in the repository
- Text.DirtyState.HasLocalChanges
- Text.DirtyState.HasPendingPullOrPush
- Text.DirtyState.UpToDate
- Text.Discard.IncludeModified
- Text.Discard.IncludeUntracked
- Text.DropHead
- Text.DropHead.Commit
@@ -1250,6 +1294,7 @@ This document shows the translation status of each locale file in the repository
- Text.SquashOrFixup.Into
- Text.Stash.Mode
- Text.StashCM.ApplyFileChanges
- Text.StashCM.Branch
- Text.StashCM.CopyMessage
- Text.Submodule.Branch
- Text.Submodule.CopyBranch

View File

@@ -1 +1 @@
2026.07
2026.08

View File

@@ -3,8 +3,6 @@ using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Azure.AI.OpenAI;
using OpenAI;
using OpenAI.Chat;
namespace SourceGit.AI
@@ -18,12 +16,10 @@ namespace SourceGit.AI
public async Task GenerateCommitMessageAsync(string repo, string changeList, Action<string> onUpdate, CancellationToken cancellation)
{
var endPoint = new Uri(_service.Server);
var client = _service.Server.Contains("openai.azure.com/", StringComparison.Ordinal)
? new AzureOpenAIClient(endPoint, _service.Credential)
: new OpenAIClient(_service.Credential, new() { Endpoint = endPoint });
var chatClient = _service.GetChatClient();
if (chatClient == null)
throw new Exception("Failed to fetch available models from this service. Please check your configuration and try again.");
var chatClient = client.GetChatClient(_service.Model);
var options = new ChatCompletionOptions() { Tools = { ChatTools.GetDetailChangesInFile } };
var userMessageBuilder = new StringBuilder();
@@ -31,8 +27,8 @@ namespace SourceGit.AI
.AppendLine("Generate a commit message (follow the rule of conventional commit message) for given git repository.")
.AppendLine("- Read all given changed files before generating. Only binary files (such as images, audios ...) can be skipped.")
.AppendLine("- Output the conventional commit message (with detail changes in list) directly. Do not explain your output nor introduce your answer.")
.AppendLine(string.IsNullOrEmpty(_service.AdditionalPrompt) ? string.Empty : _service.AdditionalPrompt)
.Append("Reposiory path: ").AppendLine(repo.Quoted())
.AppendLine(_service.AdditionalPrompt)
.Append("Repository path: ").AppendLine(repo.Quoted())
.AppendLine("Changed files ('A' means added, 'M' means modified, 'D' means deleted, 'T' means type changed, 'R' means renamed, 'C' means copied): ")
.Append(changeList);
@@ -65,7 +61,7 @@ namespace SourceGit.AI
foreach (var call in completion.ToolCalls)
{
var result = await ChatTools.Process(call, onUpdate);
var result = await ChatTools.ProcessAsync(call, onUpdate);
messages.Add(result);
}
@@ -73,7 +69,7 @@ namespace SourceGit.AI
break;
}
case ChatFinishReason.ContentFilter:
throw new Exception("Ommitted content due to a content filter flag");
throw new Exception("Omitted content due to a content filter flag");
default:
break;
}

View File

@@ -32,7 +32,7 @@ namespace SourceGit.AI
}
""")), false);
public static async Task<ToolChatMessage> Process(ChatToolCall call, Action<string> output)
public static async Task<ToolChatMessage> ProcessAsync(ChatToolCall call, Action<string> output)
{
using var doc = JsonDocument.Parse(call.FunctionArguments);

View File

@@ -1,16 +1,93 @@
using System;
using System.ClientModel;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Azure.AI.OpenAI;
using CommunityToolkit.Mvvm.ComponentModel;
using OpenAI;
using OpenAI.Chat;
namespace SourceGit.AI
{
public class Service
public class Service : ObservableObject
{
public string Name { get; set; }
public string Server { get; set; }
public string Model { get; set; }
public string ApiKey { get; set; }
public bool ReadApiKeyFromEnv { get; set; }
public string AdditionalPrompt { get; set; }
public ApiKeyCredential Credential => new ApiKeyCredential(ReadApiKeyFromEnv ? Environment.GetEnvironmentVariable(ApiKey) : ApiKey);
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
public string Server
{
get;
set;
} = string.Empty;
public string ApiKey
{
get;
set;
} = string.Empty;
public bool ReadApiKeyFromEnv
{
get;
set;
} = false;
public string AdditionalPrompt
{
get;
set;
} = string.Empty;
[JsonIgnore]
public List<string> AvailableModels
{
get;
private set;
} = [];
public string Model
{
get;
set;
} = string.Empty;
public async Task<List<string>> FetchAvailableModelsAsync()
{
var allModels = GetOpenAIClient().GetOpenAIModelClient().GetModels();
AvailableModels = new List<string>();
foreach (var model in allModels.Value)
AvailableModels.Add(model.Id);
if (AvailableModels.Count > 0)
{
if (string.IsNullOrEmpty(Model) || !AvailableModels.Contains(Model))
Model = AvailableModels[0];
}
else
{
Model = null;
}
return AvailableModels;
}
public ChatClient GetChatClient()
{
return !string.IsNullOrEmpty(Model) ? GetOpenAIClient().GetChatClient(Model) : null;
}
private OpenAIClient GetOpenAIClient()
{
var credential = new ApiKeyCredential(ReadApiKeyFromEnv ? Environment.GetEnvironmentVariable(ApiKey) : ApiKey);
return Server.Contains("openai.azure.com/", StringComparison.Ordinal)
? new AzureOpenAIClient(new Uri(Server), credential, new AzureOpenAIClientOptions() { UserAgentApplicationId = string.Empty })
: new OpenAIClient(credential, new() { Endpoint = new Uri(Server), UserAgentApplicationId = string.Empty });
}
private string _name = string.Empty;
}
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
namespace SourceGit
@@ -45,16 +43,6 @@ namespace SourceGit
public static readonly Command OpenAboutCommand = new Command(async _ => await ShowDialog(new Views.About()));
public static readonly Command CheckForUpdateCommand = new Command(_ => (Current as App)?.Check4Update(true));
public static readonly Command QuitCommand = new Command(_ => Quit(0));
public static readonly Command CopyTextBlockCommand = new Command(async p =>
{
if (p is not TextBlock textBlock)
return;
if (textBlock.Inlines is { Count: > 0 } inlines)
await CopyTextAsync(inlines.Text);
else if (!string.IsNullOrEmpty(textBlock.Text))
await CopyTextAsync(textBlock.Text);
});
public static readonly Command HideAppCommand = new Command(_ =>
{

View File

@@ -1,19 +1,15 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Input.Platform;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.Fonts;
@@ -32,7 +28,7 @@ namespace SourceGit
AppDomain.CurrentDomain.UnhandledException += (_, e) =>
{
LogException(e.ExceptionObject as Exception);
Native.OS.LogException(e.ExceptionObject as Exception);
};
TaskScheduler.UnobservedTaskException += (_, e) =>
@@ -51,7 +47,7 @@ namespace SourceGit
}
catch (Exception ex)
{
LogException(ex);
Native.OS.LogException(ex);
}
}
@@ -76,52 +72,9 @@ namespace SourceGit
Native.OS.SetupApp(builder);
return builder;
}
public static void LogException(Exception ex)
{
if (ex == null)
return;
var crashDir = Path.Combine(Native.OS.DataDir, "crashes");
if (!Directory.Exists(crashDir))
Directory.CreateDirectory(crashDir);
var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
var file = Path.Combine(crashDir, $"{time}.log");
using var writer = new StreamWriter(file);
writer.WriteLine($"Crash::: {ex.GetType().FullName}: {ex.Message}");
writer.WriteLine();
writer.WriteLine("----------------------------");
writer.WriteLine($"Version: {Assembly.GetExecutingAssembly().GetName().Version}");
writer.WriteLine($"OS: {Environment.OSVersion}");
writer.WriteLine($"Framework: {AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}");
writer.WriteLine($"Source: {ex.Source}");
writer.WriteLine($"Thread Name: {Thread.CurrentThread.Name ?? "Unnamed"}");
writer.WriteLine($"App Start Time: {Process.GetCurrentProcess().StartTime}");
writer.WriteLine($"Exception Time: {DateTime.Now}");
writer.WriteLine($"Memory Usage: {Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024} MB");
writer.WriteLine("----------------------------");
writer.WriteLine();
writer.WriteLine(ex);
writer.Flush();
}
#endregion
#region Utility Functions
public static Control CreateViewForViewModel(object data)
{
var dataTypeName = data.GetType().FullName;
if (string.IsNullOrEmpty(dataTypeName) || !dataTypeName.Contains(".ViewModels.", StringComparison.Ordinal))
return null;
var viewTypeName = dataTypeName.Replace(".ViewModels.", ".Views.");
var viewType = Type.GetType(viewTypeName);
if (viewType != null)
return Activator.CreateInstance(viewType) as Control;
return null;
}
public static Task ShowDialog(object data, Window owner = null)
{
if (owner == null)
@@ -135,7 +88,7 @@ namespace SourceGit
if (data is Views.ChromelessWindow window)
return window.ShowDialog(owner);
window = CreateViewForViewModel(data) as Views.ChromelessWindow;
window = Views.ControlExtensions.CreateFromViewModels(data) as Views.ChromelessWindow;
if (window != null)
{
window.DataContext = data;
@@ -149,7 +102,7 @@ namespace SourceGit
{
if (data is not Views.ChromelessWindow window)
{
window = CreateViewForViewModel(data) as Views.ChromelessWindow;
window = Views.ControlExtensions.CreateFromViewModels(data) as Views.ChromelessWindow;
if (window == null)
return;
@@ -221,18 +174,6 @@ namespace SourceGit
return Models.ConfirmEmptyCommitResult.Cancel;
}
public static void RaiseException(string context, string message)
{
if (Current is App { _launcher: not null } app)
app._launcher.DispatchNotification(context, message, true);
}
public static void SendNotification(string context, string message)
{
if (Current is App { _launcher: not null } app)
app._launcher.DispatchNotification(context, message, false);
}
public static void SetLocale(string localeKey)
{
if (Current is not App app ||
@@ -342,19 +283,6 @@ namespace SourceGit
}
}
public static async Task CopyTextAsync(string data)
{
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow.Clipboard: { } clipboard })
await clipboard.SetTextAsync(data ?? "");
}
public static async Task<string> GetClipboardTextAsync()
{
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow.Clipboard: { } clipboard })
return await clipboard.TryGetTextAsync();
return null;
}
public static string Text(string key, params object[] args)
{
var fmt = Current?.FindResource($"Text.{key}") as string;
@@ -367,19 +295,6 @@ namespace SourceGit
return string.Format(fmt, args);
}
public static Avalonia.Controls.Shapes.Path CreateMenuIcon(string key)
{
var icon = new Avalonia.Controls.Shapes.Path();
icon.Width = 12;
icon.Height = 12;
icon.Stretch = Stretch.Uniform;
if (Current?.FindResource(key) is StreamGeometry geo)
icon.Data = geo;
return icon;
}
public static ViewModels.Launcher GetLauncher()
{
return Current is App app ? app._launcher : null;
@@ -439,32 +354,12 @@ namespace SourceGit
if (TryLaunchAsAskpass(desktop))
return;
_ipcChannel = new Models.IpcChannel();
if (!_ipcChannel.IsFirstInstance)
{
var arg = desktop.Args is { Length: > 0 } ? desktop.Args[0].Trim() : string.Empty;
if (!string.IsNullOrEmpty(arg))
{
if (arg.StartsWith('"') && arg.EndsWith('"'))
arg = arg.Substring(1, arg.Length - 2).Trim();
if (arg.Length > 0 && !Path.IsPathFullyQualified(arg))
arg = Path.GetFullPath(arg);
}
_ipcChannel.SendToFirstInstance(arg);
Environment.Exit(0);
}
else
{
_ipcChannel.MessageReceived += TryOpenRepository;
desktop.Exit += (_, _) => _ipcChannel.Dispose();
TryLaunchAsNormal(desktop);
}
TryLaunchAsNormal(desktop);
}
}
#endregion
#region Launch Ways
private static bool TryLaunchAsRebaseTodoEditor(string[] args, out int exitCode)
{
exitCode = -1;
@@ -625,6 +520,24 @@ namespace SourceGit
private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
{
_ipcChannel = new Models.IpcChannel();
if (!_ipcChannel.IsFirstInstance)
{
var arg = desktop.Args is { Length: > 0 } ? desktop.Args[0].Trim() : string.Empty;
if (!string.IsNullOrEmpty(arg))
{
if (arg.StartsWith('"') && arg.EndsWith('"'))
arg = arg.Substring(1, arg.Length - 2).Trim();
if (arg.Length > 0 && !Path.IsPathFullyQualified(arg))
arg = Path.GetFullPath(arg);
}
_ipcChannel.SendToFirstInstance(arg);
Environment.Exit(0);
return;
}
Native.OS.SetupExternalTools();
Models.AvatarManager.Instance.Start();
@@ -639,40 +552,26 @@ namespace SourceGit
desktop.MainWindow = new Views.Launcher() { DataContext = _launcher };
desktop.ShutdownMode = ShutdownMode.OnMainWindowClose;
_ipcChannel.MessageReceived += repo =>
{
Dispatcher.UIThread.Invoke(() =>
{
_launcher.TryOpenRepositoryFromPath(repo);
if (desktop.MainWindow is Views.Launcher main)
main.BringToTop();
});
};
desktop.Exit += (_, _) => _ipcChannel.Dispose();
#if !DISABLE_UPDATE_DETECTION
if (pref.ShouldCheck4UpdateOnStartup())
Check4Update();
#endif
}
#endregion
private void TryOpenRepository(string repo)
{
if (!string.IsNullOrEmpty(repo) && Directory.Exists(repo))
{
var test = new Commands.QueryRepositoryRootPath(repo).GetResult();
if (test.IsSuccess && !string.IsNullOrEmpty(test.StdOut))
{
Dispatcher.UIThread.Invoke(() =>
{
var node = ViewModels.Preferences.Instance.FindOrAddNodeByRepositoryPath(test.StdOut.Trim(), null, false);
ViewModels.Welcome.Instance.Refresh();
_launcher?.OpenRepositoryInTab(node, null);
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: Views.Launcher wnd })
wnd.BringToTop();
});
return;
}
}
Dispatcher.UIThread.Invoke(() =>
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: Views.Launcher launcher })
launcher.BringToTop();
});
}
#region Check for Updates
private void Check4Update(bool manually = false)
{
Task.Run(async () =>
@@ -728,6 +627,7 @@ namespace SourceGit
// Ignore exceptions.
}
}
#endregion
private string FixFontFamilyName(string input)
{

View File

@@ -70,7 +70,7 @@ namespace SourceGit.Commands
catch (Exception e)
{
if (RaiseError)
App.RaiseException(Context, e.Message);
RaiseException(e.Message);
Log?.AppendLine(string.Empty);
return false;
@@ -101,7 +101,7 @@ namespace SourceGit.Commands
{
var errMsg = string.Join("\n", errs).Trim();
if (!string.IsNullOrEmpty(errMsg))
App.RaiseException(Context, errMsg);
RaiseException(errMsg);
}
return false;
@@ -219,6 +219,11 @@ namespace SourceGit.Commands
return start;
}
protected void RaiseException(string error)
{
Models.Notification.Send(Context, error, true);
}
private void HandleOutput(string line, List<string> errs)
{
if (line == null)

View File

@@ -17,7 +17,7 @@ namespace SourceGit.Commands
var tool = Native.OS.GetDiffMergeTool(true);
if (tool == null)
{
App.RaiseException(Context, "Invalid diff/merge tool in preference setting!");
RaiseException("Invalid diff/merge tool in preference setting!");
return;
}
@@ -40,7 +40,7 @@ namespace SourceGit.Commands
}
catch (Exception ex)
{
App.RaiseException(Context, ex.Message);
RaiseException(ex.Message);
}
}
@@ -56,7 +56,7 @@ namespace SourceGit.Commands
if (config.TryGetValue("merge.tool", out var mergeTool))
return CheckCLIBasedTool(mergeTool);
App.RaiseException(Context, "Missing git configuration: diff.guitool");
RaiseException("Missing git configuration: diff.guitool");
return false;
}
@@ -65,7 +65,7 @@ namespace SourceGit.Commands
if (tool.StartsWith("vimdiff", StringComparison.Ordinal) ||
tool.StartsWith("nvimdiff", StringComparison.Ordinal))
{
App.RaiseException(Context, $"CLI based diff tool \"{tool}\" is not supported by this app!");
RaiseException($"CLI based diff tool \"{tool}\" is not supported by this app!");
return false;
}

View File

@@ -10,7 +10,7 @@ namespace SourceGit.Commands
/// <summary>
/// Discard all local changes (unstaged & staged)
/// </summary>
public static async Task AllAsync(string repo, bool includeUntracked, bool includeIgnored, Models.ICommandLog log)
public static async Task AllAsync(string repo, bool includeModified, bool includeUntracked, bool includeIgnored, Models.ICommandLog log)
{
if (includeUntracked)
{
@@ -33,11 +33,11 @@ namespace SourceGit.Commands
}
catch (Exception e)
{
App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}");
Models.Notification.Send(repo, $"Failed to discard changes. Reason: {e.Message}", true);
}
if (includeIgnored)
await new Clean(repo, Models.CleanMode.All).Use(log).ExecAsync().ConfigureAwait(false);
await new Clean(repo, Models.CleanMode.UntrackedAndIgnoredFiles).Use(log).ExecAsync().ConfigureAwait(false);
else
await new Clean(repo, Models.CleanMode.OnlyUntrackedFiles).Use(log).ExecAsync().ConfigureAwait(false);
}
@@ -46,7 +46,8 @@ namespace SourceGit.Commands
await new Clean(repo, Models.CleanMode.OnlyIgnoredFiles).Use(log).ExecAsync().ConfigureAwait(false);
}
await new Reset(repo, "", "--hard").Use(log).ExecAsync().ConfigureAwait(false);
if (includeModified)
await new Reset(repo, "", "--hard").Use(log).ExecAsync().ConfigureAwait(false);
}
/// <summary>
@@ -79,7 +80,7 @@ namespace SourceGit.Commands
}
catch (Exception e)
{
App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}");
Models.Notification.Send(repo, $"Failed to discard changes. Reason: {e.Message}", true);
}
if (restores.Count > 0)

View File

@@ -42,7 +42,7 @@ namespace SourceGit.Commands
start.Args = $"flow hotfix start {name}";
break;
default:
App.RaiseException(repo, "Bad git-flow branch type!!!");
Models.Notification.Send(repo, "Bad git-flow branch type!!!", true);
return false;
}
@@ -66,7 +66,7 @@ namespace SourceGit.Commands
builder.Append("hotfix");
break;
default:
App.RaiseException(repo, "Bad git-flow branch type!!!");
Models.Notification.Send(repo, "Bad git-flow branch type!!!", true);
return false;
}

View File

@@ -17,7 +17,7 @@ namespace SourceGit.Commands
var tool = Native.OS.GetDiffMergeTool(false);
if (tool == null)
{
App.RaiseException(Context, "Invalid diff/merge tool in preference setting!");
RaiseException("Invalid diff/merge tool in preference setting!");
return false;
}
@@ -46,14 +46,14 @@ namespace SourceGit.Commands
if (string.IsNullOrEmpty(tool))
{
App.RaiseException(Context, "Missing git configuration: merge.guitool");
RaiseException("Missing git configuration: merge.guitool");
return false;
}
if (tool.StartsWith("vimdiff", StringComparison.Ordinal) ||
tool.StartsWith("nvimdiff", StringComparison.Ordinal))
{
App.RaiseException(Context, $"CLI based merge tool \"{tool}\" is not supported by this app!");
RaiseException($"CLI based merge tool \"{tool}\" is not supported by this app!");
return false;
}

View File

@@ -103,7 +103,7 @@ namespace SourceGit.Commands
}
catch (Exception e)
{
App.RaiseException(Context, $"Failed to query commits. Reason: {e.Message}");
RaiseException($"Failed to query commits. Reason: {e.Message}");
}
return commits;

View File

@@ -21,7 +21,7 @@ namespace SourceGit.Commands
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
{
App.RaiseException(Context, $"Failed to query commits for interactive-rebase. Reason: {rs.StdErr}");
RaiseException($"Failed to query commits for interactive-rebase. Reason: {rs.StdErr}");
return commits;
}

View File

@@ -27,7 +27,7 @@ namespace SourceGit.Commands
}
catch (Exception e)
{
App.RaiseException(repo, $"Failed to query file content: {e}");
Models.Notification.Send(repo, $"Failed to query file content: {e}", true);
}
stream.Position = 0;
@@ -58,7 +58,7 @@ namespace SourceGit.Commands
}
catch (Exception e)
{
App.RaiseException(repo, $"Failed to query file content: {e}");
Models.Notification.Send(repo, $"Failed to query file content: {e}", true);
}
stream.Position = 0;

View File

@@ -69,7 +69,7 @@ namespace SourceGit.Commands
}
catch (Exception e)
{
App.RaiseException(repo, "Save change to patch failed: " + e.Message);
Models.Notification.Send(repo, "Save change to patch failed: " + e.Message, true);
return false;
}
}

View File

@@ -55,7 +55,7 @@ namespace SourceGit.Commands
}
catch (Exception e)
{
App.RaiseException(repo, "Save file failed: " + e.Message);
Models.Notification.Send(repo, "Save file failed: " + e.Message, true);
}
}
}

View File

@@ -76,6 +76,12 @@ namespace SourceGit.Commands
return await ExecAsync().ConfigureAwait(false);
}
public async Task<bool> CheckoutBranchAsync(string name, string branch)
{
Args = $"stash branch {branch.Quoted()} {name.Quoted()}";
return await ExecAsync().ConfigureAwait(false);
}
public async Task<bool> PopAsync(string name)
{
Args = $"stash pop -q --index {name.Quoted()}";

View File

@@ -74,13 +74,13 @@ namespace SourceGit.Commands
var rs = proc.ExitCode == 0;
if (!rs)
App.RaiseException(_repo, err);
Models.Notification.Send(_repo, err, true);
return rs;
}
catch (Exception e)
{
App.RaiseException(_repo, "Failed to update index: " + e.Message);
Models.Notification.Send(_repo, "Failed to update index: " + e.Message, true);
return false;
}
}

View File

@@ -4,6 +4,6 @@
{
OnlyUntrackedFiles = 0,
OnlyIgnoredFiles,
All,
UntrackedAndIgnoredFiles,
}
}

View File

@@ -1,8 +1,23 @@
namespace SourceGit.Models
using System;
namespace SourceGit.Models
{
public class Notification
{
public bool IsError { get; set; } = false;
public string Message { get; set; } = string.Empty;
public static event Action<Notification> Raised;
public string Group { get; set; }
public string Message { get; set; }
public bool IsError { get; set; }
public static void Send(string group, string message, bool isError = false)
{
Raised?.Invoke(new Notification
{
Group = group,
Message = message,
IsError = isError
});
}
}
}

View File

@@ -66,7 +66,12 @@ namespace SourceGit.Models
if (URL.StartsWith("http://", StringComparison.Ordinal) || URL.StartsWith("https://", StringComparison.Ordinal))
{
url = URL.EndsWith(".git", StringComparison.Ordinal) ? URL.Substring(0, URL.Length - 4) : URL;
var trimmed = URL.EndsWith(".git", StringComparison.Ordinal) ? URL.Substring(0, URL.Length - 4) : URL;
var uri = new Uri(trimmed);
if (uri.Port != 80 && uri.Port != 443)
url = $"{uri.Scheme}://{uri.Host}:{uri.Port}{uri.AbsolutePath}";
else
url = $"{uri.Scheme}://{uri.Host}{uri.AbsolutePath}";
return true;
}

View File

@@ -30,18 +30,6 @@ namespace SourceGit.Models
set;
} = string.Empty;
public bool EnableAutoFetch
{
get;
set;
} = false;
public int AutoFetchInterval
{
get;
set;
} = 10;
public bool AskBeforeAutoUpdatingSubmodules
{
get;

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Globalization;
namespace SourceGit.Models
{
@@ -26,6 +27,13 @@ namespace SourceGit.Models
AddedLeft,
}
private enum CharCategory : byte
{
Other, // default: whitespace, control, punctuation, symbols, etc.
Letter, // Ll/Lu/Lt/Lm + digit: ASCII and euro letters (latin, greek, cyrillic, etc.)
OtherLetter, // Lo: CJK, hiragana, katakana, hangul, Thai, Arabic, etc.
}
private class EditResult
{
public Edit State;
@@ -100,22 +108,25 @@ namespace SourceGit.Models
var start = 0;
var size = text.Length;
var chunks = new List<Chunk>();
var delims = new HashSet<char>(" \t+-*/=!,:;.'\"/?|&#@%`<>()[]{}\\".ToCharArray());
if (size == 0)
return chunks;
for (int i = 0; i < size; i++)
var prev = GetCategory(text[0]);
for (var i = 1; i < size; i++)
{
var ch = text[i];
if (delims.Contains(ch))
var category = GetCategory(ch);
if (prev != category || category == CharCategory.Other)
{
if (start != i)
AddChunk(chunks, hashes, text.Substring(start, i - start), start);
AddChunk(chunks, hashes, text.Substring(i, 1), i);
start = i + 1;
AddChunk(chunks, hashes, text[start..i], start);
start = i;
}
prev = category;
}
if (start < size)
AddChunk(chunks, hashes, text.Substring(start), start);
AddChunk(chunks, hashes, text[start..], start);
return chunks;
}
@@ -302,5 +313,33 @@ namespace SourceGit.Models
}
chunks.Add(new Chunk(hash, start, data.Length));
}
private static CharCategory[] BuildCategoryCache()
{
// Pre-compute category for all char values.
// All entries default to Other (0).
var cache = new CharCategory[65536];
for (int i = 0; i < 65536; i++)
{
var ch = (char)i;
// Unicode Lo: CJK, hiragana, katakana, hangul, Thai, Arabic, Hebrew, etc.
// → group consecutive chars into one chunk (no space delimiter in these languages)
if (char.GetUnicodeCategory(ch) == UnicodeCategory.OtherLetter)
cache[i] = CharCategory.OtherLetter;
// Unicode Ll/Lu/Lt/Lm + digit: latin, greek, cyrillic and their diacritic variants
// → group consecutive chars into one chunk (words in space-delimited languages)
else if (char.IsLetterOrDigit(ch))
cache[i] = CharCategory.Letter;
// everything else (whitespace, control, punctuation, symbols) → Other (default)
}
return cache;
}
private static CharCategory GetCategory(char ch) => s_charCategoryCache[ch];
private static readonly CharCategory[] s_charCategoryCache = BuildCategoryCache();
}
}

View File

@@ -136,7 +136,7 @@ namespace SourceGit.Native
}
catch (Exception e)
{
App.RaiseException(workdir, $"Failed to start '{OS.ShellOrTerminal}'. Reason: {e.Message}");
Models.Notification.Send(workdir, $"Failed to start '{OS.ShellOrTerminal}'. Reason: {e.Message}", true);
}
}
@@ -148,7 +148,7 @@ namespace SourceGit.Native
proc.WaitForExit();
if (proc.ExitCode != 0)
App.RaiseException("", $"Failed to open: {file}");
Models.Notification.Send("", $"Failed to open: {file}", true);
proc.Close();
}

View File

@@ -2,9 +2,10 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Avalonia;
using Avalonia.Controls;
@@ -153,6 +154,35 @@ namespace SourceGit.Native
_backend.SetupWindow(window);
}
public static void LogException(Exception ex)
{
if (ex == null)
return;
var crashDir = Path.Combine(DataDir, "crashes");
if (!Directory.Exists(crashDir))
Directory.CreateDirectory(crashDir);
var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
var file = Path.Combine(crashDir, $"{time}.log");
using var writer = new StreamWriter(file);
writer.WriteLine($"Crash::: {ex.GetType().FullName}: {ex.Message}");
writer.WriteLine();
writer.WriteLine("----------------------------");
writer.WriteLine($"Version: {Assembly.GetExecutingAssembly().GetName().Version}");
writer.WriteLine($"OS: {Environment.OSVersion}");
writer.WriteLine($"Framework: {AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}");
writer.WriteLine($"Source: {ex.Source}");
writer.WriteLine($"Thread Name: {Thread.CurrentThread.Name ?? "Unnamed"}");
writer.WriteLine($"App Start Time: {Process.GetCurrentProcess().StartTime}");
writer.WriteLine($"Exception Time: {DateTime.Now}");
writer.WriteLine($"Memory Usage: {Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024} MB");
writer.WriteLine("----------------------------");
writer.WriteLine();
writer.WriteLine(ex);
writer.Flush();
}
public static string FindGitExecutable()
{
return _backend.FindGitExecutable();
@@ -217,7 +247,7 @@ namespace SourceGit.Native
public static void OpenTerminal(string workdir)
{
if (string.IsNullOrEmpty(ShellOrTerminal))
App.RaiseException(workdir, "Terminal is not specified! Please confirm that the correct shell/terminal has been configured.");
Models.Notification.Send(workdir, "Terminal is not specified! Please confirm that the correct shell/terminal has been configured.", true);
else
_backend.OpenTerminal(workdir, ShellOrTerminalArgs);
}

View File

@@ -166,7 +166,7 @@ namespace SourceGit.Native
if (!File.Exists(terminal))
{
App.RaiseException(workdir, "Terminal is not specified! Please confirm that the correct shell/terminal has been configured.");
Models.Notification.Send(workdir, "Terminal is not specified! Please confirm that the correct shell/terminal has been configured.", true);
return;
}

View File

@@ -22,6 +22,7 @@
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">Neuen Branch erstellen</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">Existierender Branch</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">AI-Assistent</x:String>
<x:String x:Key="Text.AIAssistant.Model" xml:space="preserve">Modell</x:String>
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">NEU GENERIEREN</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">Verwende AI, um Commit-Nachrichten zu generieren</x:String>
<x:String x:Key="Text.App.Hide" xml:space="preserve">SourceGit minimieren</x:String>
@@ -580,11 +581,11 @@ $1, $2, … Werte der Eingabe-Steuerelemente</x:String>
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">Öffnen in externem Merge-Tool</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">Optional.</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">Neue Registerkarte erstellen</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">Lesezeichen</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">Registerkarte schließen</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">Andere Registerkarten schließen</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">Registerkarten zur Rechten schließen</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">Kopiere Repository-Pfad</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Edit" xml:space="preserve">Bearbeiten</x:String>
<x:String x:Key="Text.PageTabBar.Tab.MoveToWorkspace" xml:space="preserve">In Arbeitsumgebung verschieben</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Refresh" xml:space="preserve">Aktualisieren</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">Repositorys</x:String>
@@ -602,7 +603,6 @@ $1, $2, … Werte der Eingabe-Steuerelemente</x:String>
<x:String x:Key="Text.Preferences" xml:space="preserve">Einstellungen</x:String>
<x:String x:Key="Text.Preferences.AI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Preferences.AI.ApiKey" xml:space="preserve">API-Schlüssel</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">Modell</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">Name</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">Der eingegebene Wert ist der Name der Umgebungsvariable, aus der der API-Schlüssel gelesen wird</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">Server</x:String>

View File

@@ -18,6 +18,7 @@
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">Create New Branch</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">Existing Branch</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">AI Assistant</x:String>
<x:String x:Key="Text.AIAssistant.Model" xml:space="preserve">MODEL</x:String>
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">RE-GENERATE</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.Use" xml:space="preserve">Use</x:String>
@@ -115,6 +116,9 @@
<x:String x:Key="Text.Checkout.WarnUpdatingSubmodules" xml:space="preserve">The following submodules need to be updated:{0}Do you want to update them?</x:String>
<x:String x:Key="Text.Checkout.WithFastForward" xml:space="preserve">Checkout &amp; Fast-Forward</x:String>
<x:String x:Key="Text.Checkout.WithFastForward.Upstream" xml:space="preserve">Fast-Forward to:</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash" xml:space="preserve">Checkout Branch From Stash</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Branch" xml:space="preserve">New Branch:</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Stash" xml:space="preserve">Stash:</x:String>
<x:String x:Key="Text.CherryPick" xml:space="preserve">Cherry Pick</x:String>
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">Append source to commit message</x:String>
<x:String x:Key="Text.CherryPick.Commit" xml:space="preserve">Commit(s):</x:String>
@@ -382,6 +386,7 @@
<x:String x:Key="Text.Discard.All" xml:space="preserve">All local changes in working copy.</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">Changes:</x:String>
<x:String x:Key="Text.Discard.IncludeIgnored" xml:space="preserve">Include ignored files</x:String>
<x:String x:Key="Text.Discard.IncludeModified" xml:space="preserve">Include modified/deleted files</x:String>
<x:String x:Key="Text.Discard.IncludeUntracked" xml:space="preserve">Include untracked files</x:String>
<x:String x:Key="Text.Discard.Total" xml:space="preserve">{0} changes will be discarded</x:String>
<x:String x:Key="Text.Discard.Warning" xml:space="preserve">You can't undo this action!!!</x:String>
@@ -589,11 +594,11 @@
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">Open in External Merge Tool</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 Tab</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">Bookmark</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">Close Tab</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">Close Other Tabs</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">Close Tabs to the Right</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">Copy Repository Path</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Edit" xml:space="preserve">Edit</x:String>
<x:String x:Key="Text.PageTabBar.Tab.MoveToWorkspace" xml:space="preserve">Move to Workspace</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Refresh" xml:space="preserve">Refresh</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">Repositories</x:String>
@@ -612,7 +617,6 @@
<x:String x:Key="Text.Preferences.AI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Preferences.AI.AdditionalPrompt" xml:space="preserve">Additional Prompt (Use `-` to list your requirements)</x:String>
<x:String x:Key="Text.Preferences.AI.ApiKey" xml:space="preserve">API Key</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">Model</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">Name</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">Entered value is the name to load API key from ENV</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">Server</x:String>
@@ -857,6 +861,7 @@
<x:String x:Key="Text.Stash.Title" xml:space="preserve">Stash Local Changes</x:String>
<x:String x:Key="Text.StashCM.Apply" xml:space="preserve">Apply</x:String>
<x:String x:Key="Text.StashCM.ApplyFileChanges" xml:space="preserve">Apply Changes</x:String>
<x:String x:Key="Text.StashCM.Branch" xml:space="preserve">Checkout New Branch</x:String>
<x:String x:Key="Text.StashCM.CopyMessage" xml:space="preserve">Copy Message</x:String>
<x:String x:Key="Text.StashCM.Drop" xml:space="preserve">Drop</x:String>
<x:String x:Key="Text.StashCM.SaveAsPatch" xml:space="preserve">Save as Patch...</x:String>

View File

@@ -22,6 +22,7 @@
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">Crear Nueva Rama</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">Rama Existente</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">Asistente OpenAI</x:String>
<x:String x:Key="Text.AIAssistant.Model" xml:space="preserve">Modelo</x:String>
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">RE-GENERAR</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">Usar OpenAI para generar mensaje de commit</x:String>
<x:String x:Key="Text.AIAssistant.Use" xml:space="preserve">Usar</x:String>
@@ -119,6 +120,9 @@
<x:String x:Key="Text.Checkout.WarnUpdatingSubmodules" xml:space="preserve">Los siguientes submódulos necesitan ser actualizados:{0} ¿Quieres actualizarlos?</x:String>
<x:String x:Key="Text.Checkout.WithFastForward" xml:space="preserve">Checkout &amp; Fast-Forward</x:String>
<x:String x:Key="Text.Checkout.WithFastForward.Upstream" xml:space="preserve">Fast-Forward a:</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash" xml:space="preserve">Checkout a la Rama desde el Stash</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Branch" xml:space="preserve">Nueva Rama:</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Stash" xml:space="preserve">Stash:</x:String>
<x:String x:Key="Text.CherryPick" xml:space="preserve">Cherry Pick</x:String>
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">Añadir fuente al mensaje de commit</x:String>
<x:String x:Key="Text.CherryPick.Commit" xml:space="preserve">Commit(s):</x:String>
@@ -386,6 +390,7 @@
<x:String x:Key="Text.Discard.All" xml:space="preserve">Todos los cambios locales en la copia de trabajo.</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">Cambios:</x:String>
<x:String x:Key="Text.Discard.IncludeIgnored" xml:space="preserve">Incluir archivos ignorados</x:String>
<x:String x:Key="Text.Discard.IncludeModified" xml:space="preserve">Incluir archivos modificados/eliminados</x:String>
<x:String x:Key="Text.Discard.IncludeUntracked" xml:space="preserve">Incluir archivos no rastreados</x:String>
<x:String x:Key="Text.Discard.Total" xml:space="preserve">Total {0} cambios serán descartados</x:String>
<x:String x:Key="Text.Discard.Warning" xml:space="preserve">¡No puedes deshacer esta acción!</x:String>
@@ -593,11 +598,11 @@
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">Abrir en Herramienta de Merge</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">Opcional.</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">Crear Nueva Página</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">Marcador</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">Cerrar Pestaña</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">Cerrar Otras Pestañas</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">Cerrar Pestañas a la Derecha</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">Copiar Ruta del Repositorio</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Edit" xml:space="preserve">Editar</x:String>
<x:String x:Key="Text.PageTabBar.Tab.MoveToWorkspace" xml:space="preserve">Mover al Espacio de trabajo</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Refresh" xml:space="preserve">Actualizar</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">Repositorios</x:String>
@@ -616,7 +621,6 @@
<x:String x:Key="Text.Preferences.AI" xml:space="preserve">OPEN AI</x:String>
<x:String x:Key="Text.Preferences.AI.AdditionalPrompt" xml:space="preserve">Prompt adicional (Usa `-` para listar tus requerimientos)</x:String>
<x:String x:Key="Text.Preferences.AI.ApiKey" xml:space="preserve">Clave API</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">Modelo</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">Nombre</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">El valor ingresado es el nombre de la clave API a cargar desde ENV</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">Servidor</x:String>
@@ -861,6 +865,7 @@
<x:String x:Key="Text.Stash.Title" xml:space="preserve">Stash Cambios Locales</x:String>
<x:String x:Key="Text.StashCM.Apply" xml:space="preserve">Aplicar</x:String>
<x:String x:Key="Text.StashCM.ApplyFileChanges" xml:space="preserve">Aplicar Cambios</x:String>
<x:String x:Key="Text.StashCM.Branch" xml:space="preserve">Checkout a Nueva Rama</x:String>
<x:String x:Key="Text.StashCM.CopyMessage" xml:space="preserve">Copiar Mensaje</x:String>
<x:String x:Key="Text.StashCM.Drop" xml:space="preserve">Eliminar</x:String>
<x:String x:Key="Text.StashCM.SaveAsPatch" xml:space="preserve">Guardar como Parche...</x:String>

View File

@@ -21,6 +21,7 @@
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">Créer une nouvelle branche</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">Branche existante</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">Assistant IA</x:String>
<x:String x:Key="Text.AIAssistant.Model" xml:space="preserve">Modèle</x:String>
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">RE-GÉNÉRER</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">Utiliser l'IA pour générer un message de commit</x:String>
<x:String x:Key="Text.App.Hide" xml:space="preserve">Masquer SourceGit</x:String>
@@ -541,11 +542,11 @@
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">Ouvrir dans l'outil de fusion</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">Optionnel.</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">Créer un nouvel onglet</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">Bookmark</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">Fermer l'onglet</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">Fermer les autres onglets</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">Fermer les onglets à droite</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">Copier le chemin vers le dépôt</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Edit" xml:space="preserve">Éditer</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">Dépôts</x:String>
<x:String x:Key="Text.Paste" xml:space="preserve">Coller</x:String>
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">il y a {0} jours</x:String>
@@ -561,7 +562,6 @@
<x:String x:Key="Text.Preferences" xml:space="preserve">Préférences</x:String>
<x:String x:Key="Text.Preferences.AI" xml:space="preserve">IA</x:String>
<x:String x:Key="Text.Preferences.AI.ApiKey" xml:space="preserve">Clé d'API</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">Modèle</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">Nom</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">La valeur saisie est le nom pour charger la clé API depuis l'ENV</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">Serveur</x:String>

View File

@@ -20,6 +20,7 @@
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">Buat Branch Baru</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">Branch Yang Ada</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">Asisten AI</x:String>
<x:String x:Key="Text.AIAssistant.Model" xml:space="preserve">Model</x:String>
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">BUAT ULANG</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">Gunakan AI untuk membuat pesan commit</x:String>
<x:String x:Key="Text.App.Hide" xml:space="preserve">Sembunyikan SourceGit</x:String>
@@ -515,11 +516,11 @@
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">Buka di Merge Tool</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">Opsional.</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">Buat Tab Baru</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">Bookmark</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">Tutup Tab</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">Tutup Tab Lain</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">Tutup Tab di Kanan</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">Salin Jalur Repositori</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Edit" xml:space="preserve">Sunting</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">Repositori</x:String>
<x:String x:Key="Text.Paste" xml:space="preserve">Tempel</x:String>
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">{0} hari lalu</x:String>
@@ -535,7 +536,6 @@
<x:String x:Key="Text.Preferences" xml:space="preserve">Preferensi</x:String>
<x:String x:Key="Text.Preferences.AI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Preferences.AI.ApiKey" xml:space="preserve">API Key</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">Model</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">Nama</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">Nilai yang dimasukkan adalah nama untuk memuat API key dari ENV</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">Server</x:String>

View File

@@ -22,6 +22,7 @@
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">Crea nuovo branch</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">Branch esistente</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">Assistente AI</x:String>
<x:String x:Key="Text.AIAssistant.Model" xml:space="preserve">Modello</x:String>
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">RIGENERA</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">Usa AI per generare il messaggio di commit</x:String>
<x:String x:Key="Text.App.Hide" xml:space="preserve">Nascondi SourceGit</x:String>
@@ -576,11 +577,11 @@ ${pure_files:N} Come ${files:N}, ma senza cartelle</x:String>
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">Apri nello Strumento di Merge</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">Opzionale.</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">Crea Nuova Pagina</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">Segnalibro</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">Chiudi Tab</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">Chiudi Altri Tab</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">Chiudi i Tab a Destra</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">Copia Percorso Repository</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Edit" xml:space="preserve">Modifica</x:String>
<x:String x:Key="Text.PageTabBar.Tab.MoveToWorkspace" xml:space="preserve">Sposta nel Workspace</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Refresh" xml:space="preserve">Aggiorna</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">Repository</x:String>
@@ -598,7 +599,6 @@ ${pure_files:N} Come ${files:N}, ma senza cartelle</x:String>
<x:String x:Key="Text.Preferences" xml:space="preserve">Preferenze</x:String>
<x:String x:Key="Text.Preferences.AI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Preferences.AI.ApiKey" xml:space="preserve">Chiave API</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">Modello</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">Nome</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">Il valore inserito è il nome per caricare la chiave API da ENV</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">Server</x:String>

View File

@@ -22,6 +22,7 @@
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">新しいブランチを作成</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">既存のブランチ</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">AI アシスタント</x:String>
<x:String x:Key="Text.AIAssistant.Model" xml:space="preserve">モデル</x:String>
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">再生成</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">AI を使用してコミットメッセージを生成</x:String>
<x:String x:Key="Text.App.Hide" xml:space="preserve">SourceGit を隠す</x:String>
@@ -582,11 +583,11 @@
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">外部のマージツールで開く</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">省略可能</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">新しいタブを作成</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">ブックマーク</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">タブを閉じる</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">他のタブを閉じる</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">右側のタブを閉じる</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">リポジトリへのパスをコピー</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Edit" xml:space="preserve">編集</x:String>
<x:String x:Key="Text.PageTabBar.Tab.MoveToWorkspace" xml:space="preserve">ワークスペースに移動</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Refresh" xml:space="preserve">再読み込み</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">リポジトリ</x:String>
@@ -604,7 +605,6 @@
<x:String x:Key="Text.Preferences" xml:space="preserve">設定</x:String>
<x:String x:Key="Text.Preferences.AI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Preferences.AI.ApiKey" xml:space="preserve">API キー</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">モデル</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">名前</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">この値を環境変数の名前とし、そこから API キーを読み込む</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">サーバー</x:String>

View File

@@ -18,6 +18,7 @@
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">새 브랜치 생성</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">기존 브랜치</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">AI 어시스턴트</x:String>
<x:String x:Key="Text.AIAssistant.Model" xml:space="preserve">모델</x:String>
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">재생성</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">AI를 사용하여 커밋 메시지 생성</x:String>
<x:String x:Key="Text.App.Hide" xml:space="preserve">SourceGit 숨기기</x:String>
@@ -517,11 +518,11 @@
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">병합 도구에서 열기</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">선택 사항.</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">새 탭 만들기</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">북마크</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">탭 닫기</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">다른 탭 닫기</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">오른쪽 탭 닫기</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">저장소 경로 복사</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Edit" xml:space="preserve">편집</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">저장소</x:String>
<x:String x:Key="Text.Paste" xml:space="preserve">붙여넣기</x:String>
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">{0}일 전</x:String>
@@ -537,7 +538,6 @@
<x:String x:Key="Text.Preferences" xml:space="preserve">환경설정</x:String>
<x:String x:Key="Text.Preferences.AI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Preferences.AI.ApiKey" xml:space="preserve">API 키</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">모델</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">이름</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">입력된 값은 환경변수(ENV)에서 API 키를 불러올 이름입니다</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">서버</x:String>

View File

@@ -22,6 +22,7 @@
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">Criar Novo Branch</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">Branch Existente</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">Assietente IA</x:String>
<x:String x:Key="Text.AIAssistant.Model" xml:space="preserve">Modelo</x:String>
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">Gerar novamente</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">Utilizar IA para gerar mensagem de commit</x:String>
<x:String x:Key="Text.App.Hide" xml:space="preserve">Esconder SourceGit</x:String>
@@ -398,11 +399,11 @@
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">Abrir na Ferramenta de Mesclagem</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">Opcional.</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">Criar Nova Página</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">Adicionar aos Favoritos</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">Fechar Aba</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">Fechar Outras Abas</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">Fechar Abas à Direita</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">Copiar Caminho do Repositório</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Edit" xml:space="preserve">Editar</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">Repositórios</x:String>
<x:String x:Key="Text.Paste" xml:space="preserve">Colar</x:String>
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">{0} dias atrás</x:String>
@@ -418,7 +419,6 @@
<x:String x:Key="Text.Preferences" xml:space="preserve">Preferências</x:String>
<x:String x:Key="Text.Preferences.AI" xml:space="preserve">INTELIGÊNCIA ARTIFICIAL</x:String>
<x:String x:Key="Text.Preferences.AI.ApiKey" xml:space="preserve">Chave da API</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">Modelo</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">Nome</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">Servidor</x:String>
<x:String x:Key="Text.Preferences.Appearance" xml:space="preserve">APARÊNCIA</x:String>

View File

@@ -22,6 +22,7 @@
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">Создать новую ветку</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">Ветку из списка</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">Помощник OpenAI</x:String>
<x:String x:Key="Text.AIAssistant.Model" xml:space="preserve">Модель</x:String>
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">ПЕРЕСОЗДАТЬ</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">Использовать OpenAI для создания сообщения о ревизии</x:String>
<x:String x:Key="Text.AIAssistant.Use" xml:space="preserve">Использовать</x:String>
@@ -119,6 +120,9 @@
<x:String x:Key="Text.Checkout.WarnUpdatingSubmodules" xml:space="preserve">Подмодулям требуется обновление:{0}Обновить их?</x:String>
<x:String x:Key="Text.Checkout.WithFastForward" xml:space="preserve">Переключиться и перемотать</x:String>
<x:String x:Key="Text.Checkout.WithFastForward.Upstream" xml:space="preserve">Перемотать к:</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash" xml:space="preserve">Переключить ветку из отложенного</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Branch" xml:space="preserve">Новая ветка:</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Stash" xml:space="preserve">Отложенный:</x:String>
<x:String x:Key="Text.CherryPick" xml:space="preserve"> Частичный выбор</x:String>
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">Добавить источник для ревизии сообщения</x:String>
<x:String x:Key="Text.CherryPick.Commit" xml:space="preserve">Ревизия(и):</x:String>
@@ -386,6 +390,7 @@
<x:String x:Key="Text.Discard.All" xml:space="preserve">Все локальные изменения в рабочей копии.</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">Изменения:</x:String>
<x:String x:Key="Text.Discard.IncludeIgnored" xml:space="preserve">Включить игнорируемые файлы</x:String>
<x:String x:Key="Text.Discard.IncludeModified" xml:space="preserve">Включить изменённые/удалённые файлы</x:String>
<x:String x:Key="Text.Discard.IncludeUntracked" xml:space="preserve">Включить неотслеживаемые файлы</x:String>
<x:String x:Key="Text.Discard.Total" xml:space="preserve">{0} изменений будут отменены</x:String>
<x:String x:Key="Text.Discard.Warning" xml:space="preserve">Вы не можете отменить это действие!!!</x:String>
@@ -593,11 +598,11 @@
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">Открыть в инструменте слияния</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">Необязательно.</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">Создать новую вкладку</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">Закладка</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">Закрыть вкладку</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">Закрыть другие вкладки</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">Закрыть вкладки справа</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">Копировать путь репозитория</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Edit" xml:space="preserve">Редактировать...</x:String>
<x:String x:Key="Text.PageTabBar.Tab.MoveToWorkspace" xml:space="preserve">Переместить в рабочее пространство</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Refresh" xml:space="preserve">Обновить</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">Репозитории</x:String>
@@ -616,7 +621,6 @@
<x:String x:Key="Text.Preferences.AI" xml:space="preserve">ОТКРЫТЬ ИИ</x:String>
<x:String x:Key="Text.Preferences.AI.AdditionalPrompt" xml:space="preserve">Дополнительная подсказка (Для перечисления ваших требований используйте `-`)</x:String>
<x:String x:Key="Text.Preferences.AI.ApiKey" xml:space="preserve">Ключ API</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">Модель</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">Имя:</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">Введённое значение — это имя для загрузки API-ключа из ENV</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">Сервер</x:String>
@@ -861,6 +865,7 @@
<x:String x:Key="Text.Stash.Title" xml:space="preserve">Отложить локальные изменения</x:String>
<x:String x:Key="Text.StashCM.Apply" xml:space="preserve">Принять</x:String>
<x:String x:Key="Text.StashCM.ApplyFileChanges" xml:space="preserve">Применить изменения</x:String>
<x:String x:Key="Text.StashCM.Branch" xml:space="preserve">Переключить на новую ветку</x:String>
<x:String x:Key="Text.StashCM.CopyMessage" xml:space="preserve">Копировать сообщение</x:String>
<x:String x:Key="Text.StashCM.Drop" xml:space="preserve">Отбросить</x:String>
<x:String x:Key="Text.StashCM.SaveAsPatch" xml:space="preserve">Сохранить как заплатку...</x:String>

View File

@@ -17,6 +17,7 @@
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">புதிய கிளையை உருவாக்கு</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">ஏற்கனவே உள்ள கிளை</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">செநு உதவியாளர்</x:String>
<x:String x:Key="Text.AIAssistant.Model" xml:space="preserve">மாதிரி</x:String>
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">மறு-உருவாக்கு</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">உறுதிமொழி செய்தியை உருவாக்க செநுவைப் பயன்படுத்து</x:String>
<x:String x:Key="Text.Apply" xml:space="preserve">ஒட்டு</x:String>
@@ -395,11 +396,11 @@
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">ஒன்றிணை கருவியில் திற</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">விருப்பத்தேர்வு.</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">புதிய பக்கத்தை உருவாக்கு</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">புத்தகக்குறி</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">மூடு தாவல்</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">பிற தாவல்களை மூடு</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">வலதுபுறத்தில் உள்ள தாவல்களை மூடு</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">களஞ்சிய பாதை நகலெடு</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Edit" xml:space="preserve">திருத்து</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">களஞ்சியங்கள்</x:String>
<x:String x:Key="Text.Paste" xml:space="preserve">ஒட்டு</x:String>
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">{0} நாட்களுக்கு முன்பு</x:String>
@@ -415,7 +416,6 @@
<x:String x:Key="Text.Preferences" xml:space="preserve">விருப்பத்தேர்வுகள்</x:String>
<x:String x:Key="Text.Preferences.AI" xml:space="preserve">செநு</x:String>
<x:String x:Key="Text.Preferences.AI.ApiKey" xml:space="preserve">பநிஇ திறவுகோல்</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">மாதிரி</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">பெயர்</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">சேவையகம்</x:String>
<x:String x:Key="Text.Preferences.Appearance" xml:space="preserve">தோற்றம்</x:String>

View File

@@ -17,6 +17,7 @@
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">Створити нову гілку</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">Наявна гілка</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">AI Асистент</x:String>
<x:String x:Key="Text.AIAssistant.Model" xml:space="preserve">Модель</x:String>
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">ПЕРЕГЕНЕРУВАТИ</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">Використати AI для генерації повідомлення коміту</x:String>
<x:String x:Key="Text.Apply" xml:space="preserve">Застосувати</x:String>
@@ -399,11 +400,11 @@
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">Відкрити в інструменті злиття</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">Необов'язково.</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">Створити нову вкладку</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">Закладка</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">Закрити вкладку</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">Закрити інші вкладки</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">Закрити вкладки праворуч</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">Копіювати шлях до сховища</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Edit" xml:space="preserve">Редагувати</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">Сховища</x:String>
<x:String x:Key="Text.Paste" xml:space="preserve">Вставити</x:String>
<x:String x:Key="Text.Period.DaysAgo" xml:space="preserve">{0} днів тому</x:String>
@@ -419,7 +420,6 @@
<x:String x:Key="Text.Preferences" xml:space="preserve">Налаштування</x:String>
<x:String x:Key="Text.Preferences.AI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Preferences.AI.ApiKey" xml:space="preserve">Ключ API</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">Модель</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">Назва</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">Сервер</x:String>
<x:String x:Key="Text.Preferences.Appearance" xml:space="preserve">ВИГЛЯД</x:String>

View File

@@ -22,6 +22,7 @@
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">创建新分支</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">已有分支</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">AI助手</x:String>
<x:String x:Key="Text.AIAssistant.Model" xml:space="preserve">模型</x:String>
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">重新生成</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">使用AI助手生成提交信息</x:String>
<x:String x:Key="Text.AIAssistant.Use" xml:space="preserve">应用所选</x:String>
@@ -119,6 +120,9 @@
<x:String x:Key="Text.Checkout.WarnUpdatingSubmodules" xml:space="preserve">以下子模块需要更新:{0}是否立即更新?</x:String>
<x:String x:Key="Text.Checkout.WithFastForward" xml:space="preserve">检出分支并快进</x:String>
<x:String x:Key="Text.Checkout.WithFastForward.Upstream" xml:space="preserve">上游分支 </x:String>
<x:String x:Key="Text.CheckoutBranchFromStash" xml:space="preserve">从所选贮藏检出分支</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Branch" xml:space="preserve">新分支 </x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Stash" xml:space="preserve">所选贮藏 </x:String>
<x:String x:Key="Text.CherryPick" xml:space="preserve">挑选提交</x:String>
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">提交信息中追加来源信息</x:String>
<x:String x:Key="Text.CherryPick.Commit" xml:space="preserve">提交列表 </x:String>
@@ -386,6 +390,7 @@
<x:String x:Key="Text.Discard.All" xml:space="preserve">所有本仓库未提交的修改。</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">变更 </x:String>
<x:String x:Key="Text.Discard.IncludeIgnored" xml:space="preserve">包括所有已忽略的文件</x:String>
<x:String x:Key="Text.Discard.IncludeModified" xml:space="preserve">包括已修改或删除的文件</x:String>
<x:String x:Key="Text.Discard.IncludeUntracked" xml:space="preserve">包括未跟踪的文件</x:String>
<x:String x:Key="Text.Discard.Total" xml:space="preserve">总计{0}项选中更改</x:String>
<x:String x:Key="Text.Discard.Warning" xml:space="preserve">本操作不支持回退,请确认后继续!!!</x:String>
@@ -593,11 +598,11 @@
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">使用外部对比工具查看</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">选填。</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">新建空白页</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">设置书签</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">关闭标签页</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">关闭其他标签页</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">关闭右侧标签页</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">复制仓库路径</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Edit" xml:space="preserve">编辑</x:String>
<x:String x:Key="Text.PageTabBar.Tab.MoveToWorkspace" xml:space="preserve">移至工作区</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Refresh" xml:space="preserve">刷新</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">新标签页</x:String>
@@ -616,7 +621,6 @@
<x:String x:Key="Text.Preferences.AI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Preferences.AI.AdditionalPrompt" xml:space="preserve">附加提示词 (请使用 `-` 列出您的要求)</x:String>
<x:String x:Key="Text.Preferences.AI.ApiKey" xml:space="preserve">API密钥</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">模型</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">配置名称</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">从环境变量填写环境变量名中读取API密钥</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">服务地址</x:String>
@@ -861,6 +865,7 @@
<x:String x:Key="Text.Stash.Title" xml:space="preserve">贮藏本地变更</x:String>
<x:String x:Key="Text.StashCM.Apply" xml:space="preserve">应用(apply)</x:String>
<x:String x:Key="Text.StashCM.ApplyFileChanges" xml:space="preserve">应用(apply)选中变更</x:String>
<x:String x:Key="Text.StashCM.Branch" xml:space="preserve">检出新分支</x:String>
<x:String x:Key="Text.StashCM.CopyMessage" xml:space="preserve">复制描述信息</x:String>
<x:String x:Key="Text.StashCM.Drop" xml:space="preserve">删除(drop)</x:String>
<x:String x:Key="Text.StashCM.SaveAsPatch" xml:space="preserve">另存为补丁...</x:String>

View File

@@ -22,6 +22,7 @@
<x:String x:Key="Text.AddWorktree.WhatToCheckout.CreateNew" xml:space="preserve">建立新分支</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">已有分支</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">AI 助理</x:String>
<x:String x:Key="Text.AIAssistant.Model" xml:space="preserve">模型</x:String>
<x:String x:Key="Text.AIAssistant.Regen" xml:space="preserve">重新產生</x:String>
<x:String x:Key="Text.AIAssistant.Tip" xml:space="preserve">使用 AI 產生提交訊息</x:String>
<x:String x:Key="Text.AIAssistant.Use" xml:space="preserve">套用選取</x:String>
@@ -119,6 +120,9 @@
<x:String x:Key="Text.Checkout.WarnUpdatingSubmodules" xml:space="preserve">以下子模組需要更新: {0},您要立即更新嗎?</x:String>
<x:String x:Key="Text.Checkout.WithFastForward" xml:space="preserve">簽出分支並快轉</x:String>
<x:String x:Key="Text.Checkout.WithFastForward.Upstream" xml:space="preserve">上游分支: </x:String>
<x:String x:Key="Text.CheckoutBranchFromStash" xml:space="preserve">從所選擱置簽出分支</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Branch" xml:space="preserve">新分支:</x:String>
<x:String x:Key="Text.CheckoutBranchFromStash.Stash" xml:space="preserve">所選擱置:</x:String>
<x:String x:Key="Text.CherryPick" xml:space="preserve">揀選提交</x:String>
<x:String x:Key="Text.CherryPick.AppendSourceToMessage" xml:space="preserve">提交資訊中追加來源資訊</x:String>
<x:String x:Key="Text.CherryPick.Commit" xml:space="preserve">提交列表:</x:String>
@@ -386,6 +390,7 @@
<x:String x:Key="Text.Discard.All" xml:space="preserve">所有本機未提交的變更。</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">變更:</x:String>
<x:String x:Key="Text.Discard.IncludeIgnored" xml:space="preserve">包括所有已忽略的檔案</x:String>
<x:String x:Key="Text.Discard.IncludeModified" xml:space="preserve">包括已變更或刪除的檔案</x:String>
<x:String x:Key="Text.Discard.IncludeUntracked" xml:space="preserve">包含未追蹤檔案</x:String>
<x:String x:Key="Text.Discard.Total" xml:space="preserve">將捨棄總計 {0} 項已選取的變更</x:String>
<x:String x:Key="Text.Discard.Warning" xml:space="preserve">您無法復原此操作,請確認後再繼續!</x:String>
@@ -593,11 +598,11 @@
<x:String x:Key="Text.OpenInExternalMergeTool" xml:space="preserve">使用外部比對工具檢視</x:String>
<x:String x:Key="Text.Optional" xml:space="preserve">選填。</x:String>
<x:String x:Key="Text.PageTabBar.New" xml:space="preserve">新增分頁</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">設定書籤</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">關閉分頁</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">關閉其他分頁</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">關閉右側分頁</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">複製存放庫路徑</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Edit" xml:space="preserve">編輯</x:String>
<x:String x:Key="Text.PageTabBar.Tab.MoveToWorkspace" xml:space="preserve">移至工作區</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Refresh" xml:space="preserve">重新整理</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">新分頁</x:String>
@@ -616,7 +621,6 @@
<x:String x:Key="Text.Preferences.AI" xml:space="preserve">AI</x:String>
<x:String x:Key="Text.Preferences.AI.AdditionalPrompt" xml:space="preserve">附加提示詞 (請使用 '-' 列出您的要求)</x:String>
<x:String x:Key="Text.Preferences.AI.ApiKey" xml:space="preserve">API 金鑰</x:String>
<x:String x:Key="Text.Preferences.AI.Model" xml:space="preserve">模型</x:String>
<x:String x:Key="Text.Preferences.AI.Name" xml:space="preserve">名稱</x:String>
<x:String x:Key="Text.Preferences.AI.ReadApiKeyFromEnv" xml:space="preserve">從環境變數中 (輸入環境變數名稱) 讀取 API 金鑰</x:String>
<x:String x:Key="Text.Preferences.AI.Server" xml:space="preserve">伺服器</x:String>
@@ -861,6 +865,7 @@
<x:String x:Key="Text.Stash.Title" xml:space="preserve">擱置本機變更</x:String>
<x:String x:Key="Text.StashCM.Apply" xml:space="preserve">套用 (apply)</x:String>
<x:String x:Key="Text.StashCM.ApplyFileChanges" xml:space="preserve">套用 (apply) 所選變更</x:String>
<x:String x:Key="Text.StashCM.Branch" xml:space="preserve">簽出分支</x:String>
<x:String x:Key="Text.StashCM.CopyMessage" xml:space="preserve">複製描述訊息</x:String>
<x:String x:Key="Text.StashCM.Drop" xml:space="preserve">刪除 (drop)</x:String>
<x:String x:Key="Text.StashCM.SaveAsPatch" xml:space="preserve">另存為修補檔 (patch)...</x:String>

View File

@@ -1,6 +1,5 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="using:SourceGit"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
xmlns:c="using:SourceGit.Converters"
@@ -540,36 +539,6 @@
<Setter Property="Foreground" Value="{DynamicResource Brush.InlineCodeFG}"/>
</Style>
<Style Selector="SelectableTextBlock">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="{DynamicResource Brush.FG1}"/>
</Style>
<Style Selector="SelectableTextBlock[IsEnabled=True]">
<Setter Property="ContextFlyout">
<Setter.Value>
<MenuFlyout Placement="Bottom">
<MenuItem Header="{DynamicResource Text.Copy}"
Command="{Binding $parent[SelectableTextBlock].Copy}"
IsEnabled="{Binding $parent[SelectableTextBlock].CanCopy}"
InputGesture="{x:Static TextBox.CopyGesture}">
<MenuItem.Icon>
<Path Width="11" Height="11" Data="{StaticResource Icons.Copy}" VerticalAlignment="Center"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource Text.CopyAllText}"
Command="{x:Static s:App.CopyTextBlockCommand}"
CommandParameter="{Binding $parent[SelectableTextBlock]}">
<MenuItem.Icon>
<Path Width="11" Height="11" Data="{StaticResource Icons.Copy}" VerticalAlignment="Center"/>
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Setter.Value>
</Setter>
</Style>
<Style Selector="TextBox">
<Setter Property="CornerRadius" Value="0"/>
<Setter Property="Padding" Value="4,0"/>

View File

@@ -47,17 +47,17 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.12" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.12" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.12" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.12" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.12" Condition="'$(Configuration)' == 'Debug'" />
<PackageReference Include="Avalonia" Version="11.3.13" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.13" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.13" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.13" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.13" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.13" Condition="'$(Configuration)' == 'Debug'" />
<PackageReference Include="Azure.AI.OpenAI" Version="2.9.0-beta.1" />
<PackageReference Include="BitMiracle.LibTiff.NET" Version="2.4.660" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" Version="2.0.0-rc6.1" />
<PackageReference Include="OpenAI" Version="2.9.1" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
<PackageReference Include="LiveChartsCore.SkiaSharpView.Avalonia" Version="2.0.0" />
<PackageReference Include="OpenAI" Version="2.10.0" />
<PackageReference Include="Pfim" Version="0.11.4" />
<ProjectReference Include="../depends/AvaloniaEdit/src/AvaloniaEdit.TextMate/AvaloniaEdit.TextMate.csproj" />

View File

@@ -10,6 +10,17 @@ namespace SourceGit.ViewModels
{
public class AIAssistant : ObservableObject
{
public List<string> AvailableModels
{
get => _service.AvailableModels;
}
public string CurrentModel
{
get => _service.Model;
set => _service.Model = value;
}
public bool IsGenerating
{
get => _isGenerating;

View File

@@ -37,12 +37,6 @@ namespace SourceGit.ViewModels
private set;
}
public List<string> RemoteBranches
{
get;
private set;
}
public string SelectedBranch
{
get => _selectedBranch;
@@ -59,10 +53,16 @@ namespace SourceGit.ViewModels
}
}
public string SelectedTrackingBranch
public List<Models.Branch> RemoteBranches
{
get;
set;
private set;
}
public Models.Branch SelectedTrackingBranch
{
get => _selectedTrackingBranch;
set => SetProperty(ref _selectedTrackingBranch, value);
}
public AddWorktree(Repository repo)
@@ -70,13 +70,13 @@ namespace SourceGit.ViewModels
_repo = repo;
LocalBranches = new List<string>();
RemoteBranches = new List<string>();
RemoteBranches = new List<Models.Branch>();
foreach (var branch in repo.Branches)
{
if (branch.IsLocal)
LocalBranches.Add(branch.Name);
else
RemoteBranches.Add(branch.FriendlyName);
RemoteBranches.Add(branch);
}
}
@@ -110,7 +110,7 @@ namespace SourceGit.ViewModels
ProgressDescription = "Adding worktree ...";
var branchName = _selectedBranch;
var tracking = _setTrackingBranch ? SelectedTrackingBranch : string.Empty;
var tracking = (_setTrackingBranch && _selectedTrackingBranch != null) ? _selectedTrackingBranch.FriendlyName : string.Empty;
var log = _repo.CreateLog("Add Worktree");
Use(log);
@@ -129,15 +129,11 @@ namespace SourceGit.ViewModels
return;
var name = string.IsNullOrEmpty(_selectedBranch) ? System.IO.Path.GetFileName(_path.TrimEnd('/', '\\')) : _selectedBranch;
var remoteBranch = RemoteBranches.Find(b => b.EndsWith(name, StringComparison.Ordinal));
if (string.IsNullOrEmpty(remoteBranch))
var remoteBranch = RemoteBranches.Find(b => b.Name.EndsWith(name, StringComparison.Ordinal));
if (remoteBranch == null)
remoteBranch = RemoteBranches[0];
if (!remoteBranch.Equals(SelectedTrackingBranch, StringComparison.Ordinal))
{
SelectedTrackingBranch = remoteBranch;
OnPropertyChanged(nameof(SelectedTrackingBranch));
}
SelectedTrackingBranch = remoteBranch;
}
private Repository _repo = null;
@@ -145,5 +141,6 @@ namespace SourceGit.ViewModels
private bool _createNewBranch = true;
private string _selectedBranch = string.Empty;
private bool _setTrackingBranch = false;
private Models.Branch _selectedTrackingBranch = null;
}
}

View File

@@ -59,7 +59,7 @@ namespace SourceGit.ViewModels
log.Complete();
if (succ)
App.SendNotification(_repo.FullPath, $"Save archive to : {_saveFile}");
_repo.SendNotification($"Save archive to : {_saveFile}");
return succ;
}

View File

@@ -0,0 +1,70 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace SourceGit.ViewModels
{
public class CheckoutBranchFromStash : Popup
{
public Models.Stash Target
{
get;
}
[Required(ErrorMessage = "Branch name is required!")]
[RegularExpression(@"^[\w\-/\.#\+]+$", ErrorMessage = "Bad branch name format!")]
[CustomValidation(typeof(CheckoutBranchFromStash), nameof(ValidateBranchName))]
public string BranchName
{
get => _branchName;
set => SetProperty(ref _branchName, value, true);
}
public CheckoutBranchFromStash(Repository repo, Models.Stash stash)
{
_repo = repo;
Target = stash;
}
public static ValidationResult ValidateBranchName(string name, ValidationContext ctx)
{
if (ctx.ObjectInstance is CheckoutBranchFromStash caller)
{
foreach (var b in caller._repo.Branches)
{
if (b.FriendlyName.Equals(name, StringComparison.Ordinal))
return new ValidationResult("A branch with same name already exists!");
}
return ValidationResult.Success;
}
return new ValidationResult("Missing runtime context to create branch!");
}
public override async Task<bool> Sure()
{
using var lockWatcher = _repo.LockWatcher();
ProgressDescription = "Checkout branch from stash...";
var log = _repo.CreateLog($"Checkout Branch '{_branchName}'");
Use(log);
var succ = await new Commands.Stash(_repo.FullPath)
.Use(log)
.CheckoutBranchAsync(Target.Name, _branchName);
if (succ)
{
_repo.MarkWorkingCopyDirtyManually();
_repo.MarkStashesDirtyManually();
}
log.Complete();
return true;
}
private readonly Repository _repo;
private string _branchName = string.Empty;
}
}

View File

@@ -2,7 +2,6 @@
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Threading;
namespace SourceGit.ViewModels
{
@@ -66,20 +65,6 @@ namespace SourceGit.ViewModels
_parentFolder = activeWorkspace?.DefaultCloneDir;
if (string.IsNullOrEmpty(ParentFolder))
_parentFolder = Preferences.Instance.GitDefaultCloneDir;
Task.Run(async () =>
{
try
{
var text = await App.GetClipboardTextAsync();
if (Models.Remote.IsValidURL(text))
Dispatcher.UIThread.Post(() => Remote = text);
}
catch
{
// Ignore
}
});
}
public static ValidationResult ValidateRemote(string remote, ValidationContext _)
@@ -127,7 +112,7 @@ namespace SourceGit.ViewModels
if (!Directory.Exists(path))
{
App.RaiseException(_pageId, $"Folder '{path}' can NOT be found");
Models.Notification.Send(_pageId, $"Folder '{path}' can NOT be found", true);
return false;
}

View File

@@ -248,7 +248,7 @@ namespace SourceGit.ViewModels
saveTo);
if (succ)
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
_repo.SendNotification(App.Text("SaveAsPatchSuccess"));
}
public async Task ResetToThisRevisionAsync(string path)

View File

@@ -256,11 +256,9 @@ namespace SourceGit.ViewModels
await new Commands.Checkout(_repo).MultipleFilesWithRevisionAsync(checkouts, _toHead.SHA);
}
public async Task SaveChangesAsPatchAsync(List<Models.Change> changes, string saveTo)
public async Task<bool> SaveChangesAsPatchAsync(List<Models.Change> changes, string saveTo)
{
var succ = await Commands.SaveChangesAsPatch.ProcessRevisionCompareChangesAsync(_repo, changes, _based, _to, saveTo);
if (succ)
App.SendNotification(_repo, App.Text("SaveAsPatchSuccess"));
return await Commands.SaveChangesAsPatch.ProcessRevisionCompareChangesAsync(_repo, changes, _based, _to, saveTo);
}
private void Refresh()

View File

@@ -5,6 +5,12 @@ namespace SourceGit.ViewModels
{
public class DiscardAllMode
{
public bool IncludeModified
{
get;
set;
} = true;
public bool IncludeUntracked
{
get;
@@ -72,7 +78,7 @@ namespace SourceGit.ViewModels
if (Mode is DiscardAllMode all)
{
await Commands.Discard.AllAsync(_repo.FullPath, all.IncludeUntracked, all.IncludeIgnored, log);
await Commands.Discard.AllAsync(_repo.FullPath, all.IncludeModified, all.IncludeUntracked, all.IncludeIgnored, log);
_repo.ClearCommitMessage();
}
else

View File

@@ -210,7 +210,7 @@ namespace SourceGit.ViewModels
}
catch (Exception e)
{
App.RaiseException(_repo.FullPath, e.Message);
_repo.SendNotification(e.Message, true);
}
}
@@ -258,12 +258,12 @@ namespace SourceGit.ViewModels
{
var errMsg = builder.ToString().Trim();
if (!string.IsNullOrEmpty(errMsg))
App.RaiseException(_repo.FullPath, errMsg);
_repo.SendNotification(errMsg, true);
}
}
catch (Exception e)
{
App.RaiseException(_repo.FullPath, e.Message);
_repo.SendNotification(e.Message, true);
}
}

View File

@@ -66,7 +66,7 @@ namespace SourceGit.ViewModels
if (SetProperty(ref _commits, value))
{
if (value.Count > 0 && lastSelected != null)
SelectedCommit = value.Find(x => x.SHA == lastSelected.SHA);
SelectedCommit = value.Find(x => x.SHA.Equals(lastSelected.SHA, StringComparison.Ordinal));
}
}
}
@@ -222,8 +222,8 @@ namespace SourceGit.ViewModels
else if (commits.Count == 1)
{
var commit = (commits[0] as Models.Commit)!;
if (_repo.SearchCommitContext.Selected == null || _repo.SearchCommitContext.Selected.SHA != commit.SHA)
_repo.SearchCommitContext.Selected = _repo.SearchCommitContext.Results?.Find(x => x.SHA == commit.SHA);
if (_repo.SearchCommitContext.Selected == null || !_repo.SearchCommitContext.Selected.SHA.Equals(commit.SHA, StringComparison.Ordinal))
_repo.SearchCommitContext.Selected = _repo.SearchCommitContext.Results?.Find(x => x.SHA.Equals(commit.SHA, StringComparison.Ordinal));
SelectedCommit = commit;
NavigationId = _navigationId + 1;
@@ -366,7 +366,7 @@ namespace SourceGit.ViewModels
var parents = new List<Models.Commit>();
foreach (var sha in commit.Parents)
{
var parent = _commits.Find(x => x.SHA == sha);
var parent = _commits.Find(x => x.SHA.Equals(sha, StringComparison.Ordinal));
if (parent == null)
parent = await new Commands.QuerySingleCommit(_repo.FullPath, sha).GetResultAsync();
@@ -429,7 +429,7 @@ namespace SourceGit.ViewModels
var on = await new Commands.QuerySingleCommit(_repo.FullPath, start).GetResultAsync();
if (on == null)
App.RaiseException(_repo.FullPath, $"Can not squash current commit into parent!");
_repo.SendNotification($"Can not squash current commit into parent!", true);
else
await App.ShowDialog(new InteractiveRebase(_repo, on, prefill));
}

View File

@@ -47,6 +47,7 @@ namespace SourceGit.ViewModels
public Launcher(string startupRepo)
{
Models.Notification.Raised += DispatchNotification;
_ignoreIndexChange = true;
Pages = new AvaloniaList<LauncherPage>();
@@ -72,6 +73,9 @@ namespace SourceGit.ViewModels
_ignoreIndexChange = false;
if (TryOpenRepositoryFromPath(startupRepo))
return;
if (!string.IsNullOrEmpty(startupRepo))
{
var test = new Commands.QueryRepositoryRootPath(startupRepo).GetResult();
@@ -96,6 +100,23 @@ namespace SourceGit.ViewModels
PostActivePageChanged();
}
public bool TryOpenRepositoryFromPath(string repo)
{
if (!string.IsNullOrEmpty(repo) && Directory.Exists(repo))
{
var test = new Commands.QueryRepositoryRootPath(repo).GetResult();
if (test.IsSuccess && !string.IsNullOrEmpty(test.StdOut))
{
var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(test.StdOut.Trim(), null, false);
Welcome.Instance.Refresh();
OpenRepositoryInTab(node, null);
return true;
}
}
return false;
}
public void Quit()
{
_ignoreIndexChange = true;
@@ -111,15 +132,6 @@ namespace SourceGit.ViewModels
if (to == null || to.IsActive)
return;
foreach (var one in Pages)
{
if (!one.CanCreatePopup() || one.Data is Repository { IsAutoFetching: true })
{
App.RaiseException(null, "You have unfinished task(s) in opened pages. Please wait!!!");
return;
}
}
_ignoreIndexChange = true;
var pref = Preferences.Instance;
@@ -297,9 +309,14 @@ namespace SourceGit.ViewModels
}
}
if (!Path.Exists(node.Id))
if (!Directory.Exists(node.Id))
{
App.RaiseException(node.Id, "Repository does NOT exist any more. Please remove it.");
ActivePage.Notifications.Add(new Models.Notification
{
Group = node.Id,
Message = "Repository does NOT exist any more. Please remove it.",
IsError = true,
});
return;
}
@@ -307,7 +324,12 @@ namespace SourceGit.ViewModels
var gitDir = isBare ? node.Id : GetRepositoryGitDir(node.Id);
if (string.IsNullOrEmpty(gitDir))
{
App.RaiseException(node.Id, "Given path is not a valid git repository!");
ActivePage.Notifications.Add(new Models.Notification
{
Group = node.Id,
Message = "Given path is not a valid git repository!",
IsError = true,
});
return;
}
@@ -347,24 +369,24 @@ namespace SourceGit.ViewModels
ActivePage = page;
}
public void DispatchNotification(string pageId, string message, bool isError)
private void DispatchNotification(Models.Notification notification)
{
if (!Dispatcher.UIThread.CheckAccess())
{
Dispatcher.UIThread.Invoke(() => DispatchNotification(pageId, message, isError));
Dispatcher.UIThread.Invoke(() => DispatchNotification(notification));
return;
}
var notification = new Models.Notification()
if (string.IsNullOrEmpty(notification.Group))
{
IsError = isError,
Message = message,
};
_activePage?.Notifications.Add(notification);
return;
}
foreach (var page in Pages)
{
var id = page.Node.Id.Replace('\\', '/').TrimEnd('/');
if (id == pageId)
if (id.Equals(notification.Group, StringComparison.OrdinalIgnoreCase))
{
page.Notifications.Add(notification);
return;

View File

@@ -1,8 +1,6 @@
using System;
using System.Threading.Tasks;
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
@@ -59,12 +57,6 @@ namespace SourceGit.ViewModels
Notifications.Clear();
}
public async Task CopyPathAsync()
{
if (_node.IsRepository)
await App.CopyTextAsync(_node.Id);
}
public void ChangeDirtyState(Models.DirtyState flag, bool remove)
{
var state = _dirtyState;
@@ -106,7 +98,7 @@ namespace SourceGit.ViewModels
}
catch (Exception e)
{
App.LogException(e);
Native.OS.LogException(e);
}
dump.InProgress = false;

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -253,6 +254,18 @@ namespace SourceGit.ViewModels
}
}
public bool EnableAutoFetch
{
get;
set;
} = false;
public int AutoFetchInterval
{
get;
set;
} = 10;
public bool IgnoreWhitespaceChangesInDiff
{
get => _ignoreWhitespaceChangesInDiff;
@@ -616,6 +629,21 @@ namespace SourceGit.ViewModels
RemoveInvalidRepositoriesRecursive(RepositoryNodes);
}
public async Task UpdateAvailableAIModelsAsync()
{
foreach (var service in OpenAIServices)
{
try
{
await service.FetchAvailableModelsAsync();
}
catch
{
// Ignore errors.
}
}
}
public void Save()
{
if (_isLoading || _isReadonly)

View File

@@ -134,7 +134,7 @@ namespace SourceGit.ViewModels
}
else
{
await Commands.Discard.AllAsync(_repo.FullPath, false, false, log);
await Commands.Discard.AllAsync(_repo.FullPath, true, false, false, log);
}
}

View File

@@ -456,7 +456,7 @@ namespace SourceGit.ViewModels
}
catch (Exception ex)
{
App.RaiseException(string.Empty, $"Failed to start watcher for repository: '{FullPath}'. You may need to press 'F5' to refresh repository manually!\n\nReason: {ex.Message}");
SendNotification($"Failed to start watcher for repository: '{FullPath}'. You may need to press 'F5' to refresh repository manually!\n\nReason: {ex.Message}", true);
}
_historyFilterMode = _uiStates.GetHistoryFilterMode();
@@ -530,6 +530,11 @@ namespace SourceGit.ViewModels
_visibleSubmodules = null;
}
public void SendNotification(string message, bool isError = false)
{
Models.Notification.Send(FullPath, message, isError);
}
public bool CanCreatePopup()
{
var page = GetOwnerPage();
@@ -599,7 +604,7 @@ namespace SourceGit.ViewModels
var log = CreateLog("Install LFS");
var succ = await new Commands.LFS(FullPath).Use(log).InstallAsync();
if (succ)
App.SendNotification(FullPath, "LFS enabled successfully!");
SendNotification("LFS enabled successfully!");
log.Complete();
}
@@ -612,7 +617,7 @@ namespace SourceGit.ViewModels
.TrackAsync(pattern, isFilenameMode);
if (succ)
App.SendNotification(FullPath, $"Tracking successfully! Pattern: {pattern}");
SendNotification($"Tracking successfully! Pattern: {pattern}");
log.Complete();
return succ;
@@ -626,7 +631,7 @@ namespace SourceGit.ViewModels
.LockAsync(remote, path);
if (succ)
App.SendNotification(FullPath, $"Lock file successfully! File: {path}");
SendNotification($"Lock file successfully! File: {path}");
log.Complete();
return succ;
@@ -640,7 +645,7 @@ namespace SourceGit.ViewModels
.UnlockAsync(remote, path, force);
if (succ && notify)
App.SendNotification(FullPath, $"Unlock file successfully! File: {path}");
SendNotification($"Unlock file successfully! File: {path}");
log.Complete();
return succ;
@@ -697,7 +702,7 @@ namespace SourceGit.ViewModels
if (_remotes.Count == 0)
{
App.RaiseException(FullPath, "No remotes added to this repository!!!");
SendNotification("No remotes added to this repository!!!", true);
return;
}
@@ -714,13 +719,13 @@ namespace SourceGit.ViewModels
if (_remotes.Count == 0)
{
App.RaiseException(FullPath, "No remotes added to this repository!!!");
SendNotification("No remotes added to this repository!!!", true);
return;
}
if (_currentBranch == null)
{
App.RaiseException(FullPath, "Can NOT find current branch!!!");
SendNotification("Can NOT find current branch!!!", true);
return;
}
@@ -738,13 +743,13 @@ namespace SourceGit.ViewModels
if (_remotes.Count == 0)
{
App.RaiseException(FullPath, "No remotes added to this repository!!!");
SendNotification("No remotes added to this repository!!!", true);
return;
}
if (_currentBranch == null)
{
App.RaiseException(FullPath, "Can NOT find current branch!!!");
SendNotification("Can NOT find current branch!!!", true);
return;
}
@@ -1120,9 +1125,9 @@ namespace SourceGit.ViewModels
var head = await new Commands.QueryRevisionByRefName(FullPath, "HEAD").GetResultAsync();
if (!succ)
App.RaiseException(FullPath, log.Content.Substring(log.Content.IndexOf('\n')).Trim());
SendNotification(log.Content.Substring(log.Content.IndexOf('\n')).Trim(), true);
else if (log.Content.Contains("is the first bad commit"))
App.SendNotification(FullPath, log.Content.Substring(log.Content.IndexOf('\n')).Trim());
SendNotification(log.Content.Substring(log.Content.IndexOf('\n')).Trim());
MarkBranchesDirtyManually();
NavigateToCommit(head, true);
@@ -1380,7 +1385,7 @@ namespace SourceGit.ViewModels
{
if (_currentBranch == null)
{
App.RaiseException(FullPath, "Git cannot create a branch before your first commit.");
SendNotification("Git cannot create a branch before your first commit.", true);
return;
}
@@ -1463,7 +1468,7 @@ namespace SourceGit.ViewModels
{
if (_currentBranch == null)
{
App.RaiseException(FullPath, "Git cannot create a branch before your first commit.");
SendNotification("Git cannot create a tag before your first commit.", true);
return;
}
@@ -1882,7 +1887,7 @@ namespace SourceGit.ViewModels
try
{
if (_settings is not { EnableAutoFetch: true } || !CanCreatePopup())
if (Preferences.Instance.EnableAutoFetch || !CanCreatePopup())
{
_lastFetchTime = DateTime.Now;
return;
@@ -1893,7 +1898,7 @@ namespace SourceGit.ViewModels
return;
var now = DateTime.Now;
var desire = _lastFetchTime.AddMinutes(_settings.AutoFetchInterval);
var desire = _lastFetchTime.AddMinutes(Preferences.Instance.AutoFetchInterval);
if (desire > now)
return;

View File

@@ -101,26 +101,6 @@ namespace SourceGit.ViewModels
set => _repo.Settings.AskBeforeAutoUpdatingSubmodules = value;
}
public bool EnableAutoFetch
{
get => _repo.Settings.EnableAutoFetch;
set => _repo.Settings.EnableAutoFetch = value;
}
public int? AutoFetchInterval
{
get => _repo.Settings.AutoFetchInterval;
set
{
if (value is null || value < 1)
return;
var interval = (int)value;
if (_repo.Settings.AutoFetchInterval != interval)
_repo.Settings.AutoFetchInterval = interval;
}
}
public AvaloniaList<Models.CommitTemplate> CommitTemplates
{
get => _repo.Settings.CommitTemplates;

View File

@@ -306,7 +306,7 @@ namespace SourceGit.ViewModels
{
var succ = await Commands.SaveChangesAsPatch.ProcessRevisionCompareChangesAsync(_repo.FullPath, changes ?? _changes, GetSHA(_startPoint), GetSHA(_endPoint), saveTo);
if (succ)
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
_repo.SendNotification(App.Text("SaveAsPatchSuccess"));
}
public void ClearSearchFilter()

View File

@@ -68,10 +68,19 @@ namespace SourceGit.ViewModels
.Use(log)
.RunAsync();
if (succ && needAutoStash)
await new Commands.Stash(_repo.FullPath)
.Use(log)
.PopAsync("stash@{0}");
if (succ)
{
if (needAutoStash)
await new Commands.Stash(_repo.FullPath)
.Use(log)
.PopAsync("stash@{0}");
if (_repo.SelectedViewIndex == 0)
{
var head = await new Commands.QueryRevisionByRefName(_repo.FullPath, "HEAD").GetResultAsync();
_repo.NavigateToCommit(head, true);
}
}
log.Complete();
return succ;

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
@@ -56,7 +57,7 @@ namespace SourceGit.ViewModels
{
if (string.IsNullOrEmpty(_customDir))
{
App.RaiseException(null, "Missing root directory to scan!");
Models.Notification.Send(null, "Missing root directory to scan!", true);
return false;
}
@@ -66,7 +67,7 @@ namespace SourceGit.ViewModels
{
if (_selected == null || string.IsNullOrEmpty(_selected.Path))
{
App.RaiseException(null, "Missing root directory to scan!");
Models.Notification.Send(null, "Missing root directory to scan!", true);
return false;
}
@@ -95,12 +96,12 @@ namespace SourceGit.ViewModels
foreach (var f in found)
{
var parent = new DirectoryInfo(f).Parent!.FullName.Replace('\\', '/').TrimEnd('/');
if (parent.Equals(normalizedRoot, StringComparison.Ordinal))
if (parent.Equals(normalizedRoot, StringComparison.OrdinalIgnoreCase))
{
var node = Preferences.Instance.FindOrAddNodeByRepositoryPath(f, null, false, false);
await node.UpdateStatusAsync(false, null);
}
else if (parent.StartsWith(normalizedRoot, StringComparison.Ordinal))
else if (parent.StartsWith(normalizedRoot, StringComparison.OrdinalIgnoreCase))
{
var relative = parent.Substring(normalizedRoot.Length).TrimStart('/');
var group = FindOrCreateGroupRecursive(Preferences.Instance.RepositoryNodes, relative);
@@ -120,7 +121,7 @@ namespace SourceGit.ViewModels
foreach (var node in group)
{
if (node.IsRepository)
repos.Add(node.Id);
repos.Add(OperatingSystem.IsLinux() ? node.Id : node.Id.ToLower(CultureInfo.CurrentCulture));
else
GetManagedRepositories(node.SubNodes, repos);
}
@@ -138,7 +139,7 @@ namespace SourceGit.ViewModels
ProgressDescription = $"Scanning {subdir.FullName}...";
var normalizedSelf = subdir.FullName.Replace('\\', '/').TrimEnd('/');
if (_managed.Contains(normalizedSelf))
if (IsManaged(normalizedSelf))
continue;
var gitDir = Path.Combine(subdir.FullName, ".git");
@@ -148,7 +149,7 @@ namespace SourceGit.ViewModels
if (test.IsSuccess && !string.IsNullOrEmpty(test.StdOut))
{
var normalized = test.StdOut.Trim().Replace('\\', '/').TrimEnd('/');
if (!_managed.Contains(normalized))
if (!IsManaged(normalized))
outs.Add(normalized);
}
@@ -200,6 +201,14 @@ namespace SourceGit.ViewModels
return added;
}
private bool IsManaged(string path)
{
if (OperatingSystem.IsLinux())
return _managed.Contains(path);
return _managed.Contains(path.ToLower(CultureInfo.CurrentCulture));
}
private HashSet<string> _managed = new();
private bool _useCustomDir = false;
private string _customDir = string.Empty;

View File

@@ -153,6 +153,12 @@ namespace SourceGit.ViewModels
_repo.ShowPopup(new ApplyStash(_repo, stash));
}
public void CheckoutBranch(Models.Stash stash)
{
if (_repo.CanCreatePopup())
_repo.ShowPopup(new CheckoutBranchFromStash(_repo, stash));
}
public void Drop(Models.Stash stash)
{
if (_repo.CanCreatePopup())
@@ -183,7 +189,7 @@ namespace SourceGit.ViewModels
var succ = await Commands.SaveChangesAsPatch.ProcessStashChangesAsync(_repo.FullPath, opts, saveTo);
if (succ)
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
_repo.SendNotification(App.Text("SaveAsPatchSuccess"));
}
public void OpenChangeWithExternalDiffTool(Models.Change change)

View File

@@ -104,7 +104,7 @@ namespace SourceGit.ViewModels
{
if (!Preferences.Instance.IsGitConfigured())
{
App.RaiseException(string.Empty, App.Text("NotConfigured"));
Models.Notification.Send(null, App.Text("NotConfigured"), true);
return null;
}
@@ -132,7 +132,7 @@ namespace SourceGit.ViewModels
{
if (!Preferences.Instance.IsGitConfigured())
{
App.RaiseException(string.Empty, App.Text("NotConfigured"));
Models.Notification.Send(null, App.Text("NotConfigured"), true);
return;
}
@@ -154,7 +154,7 @@ namespace SourceGit.ViewModels
{
if (!Preferences.Instance.IsGitConfigured())
{
App.RaiseException(string.Empty, App.Text("NotConfigured"));
Models.Notification.Send(null, App.Text("NotConfigured"), true);
return;
}
@@ -166,7 +166,7 @@ namespace SourceGit.ViewModels
public void OpenTerminal()
{
if (!Preferences.Instance.IsGitConfigured())
App.RaiseException(string.Empty, App.Text("NotConfigured"));
Models.Notification.Send(null, App.Text("NotConfigured"), true);
else
Native.OS.OpenTerminal(null);
}
@@ -175,7 +175,7 @@ namespace SourceGit.ViewModels
{
if (!Preferences.Instance.IsGitConfigured())
{
App.RaiseException(string.Empty, App.Text("NotConfigured"));
Models.Notification.Send(null, App.Text("NotConfigured"), true);
return;
}

View File

@@ -92,7 +92,7 @@ namespace SourceGit.ViewModels
var currentBranch = _repo.CurrentBranch;
if (currentBranch == null)
{
App.RaiseException(_repo.FullPath, "No commits to amend!!!");
_repo.SendNotification("No commits to amend!!!", true);
_useAmend = false;
OnPropertyChanged();
return;
@@ -415,7 +415,7 @@ namespace SourceGit.ViewModels
{
var succ = await Commands.SaveChangesAsPatch.ProcessLocalChangesAsync(_repo.FullPath, changes, isUnstaged, saveTo);
if (succ)
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
_repo.SendNotification(App.Text("SaveAsPatchSuccess"));
}
public void Discard(List<Models.Change> changes)
@@ -614,13 +614,13 @@ namespace SourceGit.ViewModels
if (!_repo.CanCreatePopup())
{
App.RaiseException(_repo.FullPath, "Repository has an unfinished job! Please wait!");
_repo.SendNotification("Repository has an unfinished job! Please wait!", true);
return;
}
if (autoStage && HasUnsolvedConflicts)
{
App.RaiseException(_repo.FullPath, "Repository has unsolved conflict(s). Auto-stage and commit is disabled!");
_repo.SendNotification("Repository has unsolved conflict(s). Auto-stage and commit is disabled!", true);
return;
}
@@ -665,18 +665,15 @@ namespace SourceGit.ViewModels
var log = _repo.CreateLog("Commit");
var succ = await new Commands.Commit(_repo.FullPath, _commitMessage, EnableSignOff, NoVerifyOnCommit, _useAmend, _resetAuthor)
.Use(log)
.RunAsync()
.ConfigureAwait(false);
.RunAsync();
log.Complete();
if (succ)
{
// Do not use property `UseAmend` but manually trigger property changed to avoid refreshing staged changes here.
_useAmend = false;
OnPropertyChanged(nameof(UseAmend));
UseAmend = false;
CommitMessage = string.Empty;
if (autoPush && _repo.Remotes.Count > 0)
{
Models.Branch pushBranch = null;

View File

@@ -46,18 +46,33 @@
Content="{Binding Text}"/>
<!-- Options -->
<Border Grid.Row="2" Margin="0,0,0,8">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<v:LoadingIcon Width="14" Height="14"
Margin="0,0,8,0"
IsVisible="{Binding IsGenerating}"/>
<Button Classes="flat"
<Grid Grid.Row="2" Margin="8,0,8,8" ColumnDefinitions="Auto,*,18,Auto">
<TextBlock Grid.Column="0"
Classes="group_header_label"
Text="{DynamicResource Text.AIAssistant.Model}"/>
<ComboBox Grid.Column="1"
Height="28"
Padding="12,0"
Content="{DynamicResource Text.AIAssistant.Regen}"
IsEnabled="{Binding !IsGenerating}"
Click="OnRegenClicked"/>
</StackPanel>
</Border>
Margin="6,0" Padding="4,0"
BorderThickness="0"
Background="Transparent"
VerticalAlignment="Center"
ItemsSource="{Binding AvailableModels, Mode=OneWay}"
SelectedItem="{Binding CurrentModel, Mode=TwoWay}"
SelectionChanged="OnModelChanged"/>
<v:LoadingIcon Grid.Column="2"
Width="14" Height="14"
Margin="0,0,8,0"
IsVisible="{Binding IsGenerating}"/>
<Button Grid.Column="3"
Classes="flat"
Height="28"
Padding="12,0"
Content="{DynamicResource Text.AIAssistant.Regen}"
IsEnabled="{Binding !IsGenerating}"
Click="OnRegenClicked"/>
</Grid>
</Grid>
</v:ChromelessWindow>

View File

@@ -101,7 +101,7 @@ namespace SourceGit.Views
return;
var apply = new MenuItem() { Header = App.Text("AIAssistant.Use") };
apply.Icon = App.CreateMenuIcon("Icons.Check");
apply.Icon = this.CreateMenuIcon("Icons.Check");
apply.Click += (_, ev) =>
{
vm.Use(selected);
@@ -109,10 +109,10 @@ namespace SourceGit.Views
};
var copy = new MenuItem() { Header = App.Text("Copy") };
copy.Icon = App.CreateMenuIcon("Icons.Copy");
copy.Icon = this.CreateMenuIcon("Icons.Copy");
copy.Click += async (_, ev) =>
{
await App.CopyTextAsync(selected);
await this.CopyTextAsync(selected);
ev.Handled = true;
};
@@ -149,6 +149,13 @@ namespace SourceGit.Views
(DataContext as ViewModels.AIAssistant)?.Cancel();
}
private async void OnModelChanged(object sender, SelectionChangedEventArgs e)
{
if (DataContext is ViewModels.AIAssistant vm && IsLoaded)
await vm.GenAsync();
e.Handled = true;
}
private async void OnRegenClicked(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.AIAssistant vm)

View File

@@ -22,7 +22,7 @@ namespace SourceGit.Views
{
if (attr.Key.Equals("BuildDate", StringComparison.OrdinalIgnoreCase) && DateTime.TryParse(attr.Value, out var date))
{
TxtReleaseDate.Text = App.Text("About.ReleaseDate", date.ToLocalTime().ToString("MMM d yyyy"));
TxtReleaseDate.Text = App.Text("About.ReleaseDate", Models.DateTimeFormat.Format(date, true));
break;
}
}

View File

@@ -3,6 +3,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.AddWorktree"
@@ -84,22 +85,12 @@
Margin="0,0,8,0"
Text="{DynamicResource Text.AddWorktree.Tracking}"/>
</Border>
<ComboBox Grid.Row="3" Grid.Column="1"
Height="28" Padding="8,0"
VerticalAlignment="Center" HorizontalAlignment="Stretch"
ItemsSource="{Binding RemoteBranches}"
IsTextSearchEnabled="True"
SelectedItem="{Binding SelectedTrackingBranch, Mode=TwoWay}"
IsVisible="{Binding SetTrackingBranch, Mode=OneWay}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="20" VerticalAlignment="Center">
<Path Margin="0,0,8,0" Width="14" Height="14" Fill="{DynamicResource Brush.FG1}" Data="{StaticResource Icons.Branch}"/>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<v:BranchSelector Grid.Row="3" Grid.Column="1"
Height="28"
VerticalAlignment="Center" HorizontalAlignment="Stretch"
Branches="{Binding RemoteBranches}"
SelectedBranch="{Binding SelectedTrackingBranch, Mode=TwoWay}"
IsVisible="{Binding SetTrackingBranch, Mode=OneWay}"/>
<CheckBox Grid.Row="4" Grid.Column="1"
Height="32"

View File

@@ -32,7 +32,7 @@ namespace SourceGit.Views
}
catch (Exception exception)
{
App.RaiseException(string.Empty, $"Failed to select location: {exception.Message}");
Models.Notification.Send(null, $"Failed to select location: {exception.Message}", true);
}
e.Handled = true;

51
src/Views/Alert.axaml Normal file
View File

@@ -0,0 +1,51 @@
<v:ChromelessWindow xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:v="using:SourceGit.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.Alert"
x:Name="ThisControl"
Icon="/App.ico"
SizeToContent="WidthAndHeight"
CanResize="False"
WindowStartupLocation="CenterOwner">
<Grid RowDefinitions="Auto,Auto,Auto">
<!-- TitleBar -->
<Grid Grid.Row="0" Height="28" IsVisible="{Binding !#ThisControl.UseSystemWindowFrame}">
<Border Background="{DynamicResource Brush.TitleBar}"
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}"
PointerPressed="BeginMoveWindow"/>
<Path Width="14" Height="14"
Margin="10,0,0,0"
HorizontalAlignment="Left"
Data="{StaticResource Icons.Error}"
IsVisible="{OnPlatform True, macOS=False}"/>
<TextBlock x:Name="TxtTitle"
Classes="bold"
HorizontalAlignment="Center" VerticalAlignment="Center"
IsHitTestVisible="False"/>
<v:CaptionButtons HorizontalAlignment="Right"
IsCloseButtonOnly="True"
IsVisible="{OnPlatform True, macOS=False}"/>
</Grid>
<!-- Body -->
<Border Grid.Row="1" Margin="16">
<TextBlock x:Name="Message" MaxWidth="520" MinWidth="240" TextWrapping="Wrap"/>
</Border>
<!-- Buttons -->
<Button Grid.Row="2"
Classes="flat primary"
Width="80" Height="30"
Margin="0,0,0,16"
HorizontalAlignment="Center"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
Content="{DynamicResource Text.Sure}"
Click="OnOk"/>
</Grid>
</v:ChromelessWindow>

29
src/Views/Alert.axaml.cs Normal file
View File

@@ -0,0 +1,29 @@
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace SourceGit.Views
{
public partial class Alert : ChromelessWindow
{
public Alert()
{
CloseOnESC = true;
InitializeComponent();
}
public async Task ShowAsync(Window owner, string message, bool isError)
{
var title = isError ? App.Text("Launcher.Error") : App.Text("Launcher.Info");
Title = title;
TxtTitle.Text = title;
Message.Text = message;
await ShowDialog(owner);
}
private void OnOk(object sender, RoutedEventArgs e)
{
Close();
}
}
}

View File

@@ -193,7 +193,7 @@ namespace SourceGit.Views
}
var refetch = new MenuItem();
refetch.Icon = App.CreateMenuIcon("Icons.Loading");
refetch.Icon = this.CreateMenuIcon("Icons.Loading");
refetch.Header = App.Text("Avatar.Refetch");
refetch.Click += (_, ev) =>
{
@@ -204,7 +204,7 @@ namespace SourceGit.Views
};
var load = new MenuItem();
load.Icon = App.CreateMenuIcon("Icons.Folder.Open");
load.Icon = this.CreateMenuIcon("Icons.Folder.Open");
load.Header = App.Text("Avatar.Load");
load.Click += async (_, ev) =>
{
@@ -225,7 +225,7 @@ namespace SourceGit.Views
};
var saveAs = new MenuItem();
saveAs.Icon = App.CreateMenuIcon("Icons.Save");
saveAs.Icon = this.CreateMenuIcon("Icons.Save");
saveAs.Header = App.Text("SaveAs");
saveAs.Click += async (_, ev) =>
{

View File

@@ -427,10 +427,10 @@ namespace SourceGit.Views
var copy = new MenuItem();
copy.Header = App.Text("Copy");
copy.Icon = App.CreateMenuIcon("Icons.Copy");
copy.Icon = this.CreateMenuIcon("Icons.Copy");
copy.Click += async (_, ev) =>
{
await App.CopyTextAsync(selected);
await this.CopyTextAsync(selected);
ev.Handled = true;
};

View File

@@ -0,0 +1,152 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:m="using:SourceGit.Models"
xmlns:v="using:SourceGit.Views"
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
x:Class="SourceGit.Views.BranchSelector">
<UserControl.Styles>
<Style Selector="v|BranchSelector">
<Setter Property="FocusAdorner">
<FocusAdornerTemplate>
<Border/>
</FocusAdornerTemplate>
</Setter>
<Setter Property="Template">
<ControlTemplate>
<Grid Background="Transparent">
<Border x:Name="PART_Background"
Background="{DynamicResource Brush.Contents}"
BorderThickness="1"
BorderBrush="{DynamicResource Brush.Border1}"
CornerRadius="3"/>
<Grid x:Name="PART_Selected"
Background="Transparent"
ColumnDefinitions="Auto,*,32"
PointerPressed="OnToggleDropDown">
<Path Grid.Column="0"
Margin="8,0,0,0"
Width="14" Height="14"
Data="{StaticResource Icons.Branch}"
IsHitTestVisible="False"/>
<ContentControl Grid.Column="1"
Margin="8,0,0,0"
Content="{TemplateBinding SelectedBranch, Mode=OneWay}">
<ContentControl.DataTemplates>
<DataTemplate DataType="m:Branch">
<TextBlock Text="{Binding FriendlyName, Mode=OneWay}" VerticalAlignment="Center"/>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
<Path Grid.Column="2"
Width="12" Height="12"
Margin="0,0,10,0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Data="M0 0 M1939 486L2029 576L1024 1581L19 576L109 486L1024 1401L1939 486Z"
IsHitTestVisible="False"/>
</Grid>
<Popup x:Name="PART_Popup"
WindowManagerAddShadowHint="False"
IsOpen="{TemplateBinding IsDropDownOpened, Mode=TwoWay}"
Width="{Binding Bounds.Width, ElementName=PART_Background}"
MaxHeight="600"
PlacementTarget="PART_Background"
Placement="BottomEdgeAlignedLeft"
VerticalOffset="2"
IsLightDismissEnabled="True"
InheritsTransform="True">
<Border Background="{DynamicResource Brush.Contents}"
BorderThickness="1"
BorderBrush="{DynamicResource Brush.Accent}"
CornerRadius="4"
Padding="4"
HorizontalAlignment="Stretch">
<Grid RowDefinitions="36,Auto">
<TextBox Grid.Row="0"
x:Name="PART_TextFilter"
Height="24"
Margin="6,0"
BorderThickness="1"
CornerRadius="12"
Text="{TemplateBinding SearchFilter, Mode=TwoWay}"
BorderBrush="{DynamicResource Brush.Border2}"
VerticalContentAlignment="Center"
KeyDown="OnSearchBoxKeyDown">
<TextBox.InnerLeftContent>
<Path Width="14" Height="14"
Margin="6,0,0,0"
Fill="{DynamicResource Brush.FG2}"
Data="{StaticResource Icons.Search}"/>
</TextBox.InnerLeftContent>
<TextBox.InnerRightContent>
<Button Classes="icon_button"
Width="16"
Margin="0,0,6,0"
Click="OnClearSearchFilter"
IsVisible="{Binding ElementName=PART_TextFilter, Path=Text, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
HorizontalAlignment="Right">
<Path Width="14" Height="14"
Margin="0,1,0,0"
Fill="{DynamicResource Brush.FG1}"
Data="{StaticResource Icons.Clear}"/>
</Button>
</TextBox.InnerRightContent>
</TextBox>
<ListBox Grid.Row="1"
Focusable="True"
Margin="0,4"
MaxHeight="360"
BorderThickness="0"
Background="Transparent"
ItemsSource="{TemplateBinding VisibleBranches, Mode=OneWay}"
SelectedItem="{TemplateBinding SelectedBranch, Mode=TwoWay}"
KeyDown="OnDropDownListKeyDown">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Height" Value="28"/>
<Setter Property="CornerRadius" Value="3"/>
</Style>
</ListBox.Styles>
<ListBox.ItemTemplate>
<DataTemplate DataType="m:Branch">
<StackPanel Orientation="Horizontal" Background="Transparent" Height="28" PointerPressed="OnDropDownItemPointerPressed">
<Path Width="14" Height="14" Fill="{DynamicResource Brush.FG1}" Data="{StaticResource Icons.Branch}"/>
<TextBlock Margin="8,0,0,0" Text="{Binding FriendlyName, Mode=OneWay}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Border>
</Popup>
</Grid>
</ControlTemplate>
</Setter>
<Style Selector="^:pointerover /template/ Border#PART_Background">
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Accent}" />
</Style>
<Style Selector="^:focus-visible">
<Style Selector="^ /template/ Border#PART_Background">
<Setter Property="Background" Value="{DynamicResource ComboBoxBackgroundUnfocused}" />
</Style>
</Style>
</Style>
</UserControl.Styles>
</UserControl>

View File

@@ -0,0 +1,224 @@
using System;
using System.Collections.Generic;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace SourceGit.Views
{
public partial class BranchSelector : UserControl
{
public static readonly StyledProperty<List<Models.Branch>> BranchesProperty =
AvaloniaProperty.Register<BranchSelector, List<Models.Branch>>(nameof(Branches));
public List<Models.Branch> Branches
{
get => GetValue(BranchesProperty);
set => SetValue(BranchesProperty, value);
}
public static readonly StyledProperty<List<Models.Branch>> VisibleBranchesProperty =
AvaloniaProperty.Register<BranchSelector, List<Models.Branch>>(nameof(VisibleBranches));
public List<Models.Branch> VisibleBranches
{
get => GetValue(VisibleBranchesProperty);
set => SetValue(VisibleBranchesProperty, value);
}
public static readonly StyledProperty<Models.Branch> SelectedBranchProperty =
AvaloniaProperty.Register<BranchSelector, Models.Branch>(nameof(SelectedBranch));
public Models.Branch SelectedBranch
{
get => GetValue(SelectedBranchProperty);
set => SetValue(SelectedBranchProperty, value);
}
public static readonly StyledProperty<bool> IsDropDownOpenedProperty =
AvaloniaProperty.Register<BranchSelector, bool>(nameof(IsDropDownOpened));
public bool IsDropDownOpened
{
get => GetValue(IsDropDownOpenedProperty);
set => SetValue(IsDropDownOpenedProperty, value);
}
public static readonly StyledProperty<string> SearchFilterProperty =
AvaloniaProperty.Register<BranchSelector, string>(nameof(SearchFilter));
public string SearchFilter
{
get => GetValue(SearchFilterProperty);
set => SetValue(SearchFilterProperty, value);
}
public BranchSelector()
{
Focusable = true;
InitializeComponent();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == BranchesProperty || change.Property == SearchFilterProperty)
{
var branches = Branches;
var filter = SearchFilter;
if (branches is not { Count: > 0 })
{
SetCurrentValue(VisibleBranchesProperty, []);
}
else if (string.IsNullOrEmpty(filter))
{
SetCurrentValue(VisibleBranchesProperty, Branches);
}
else
{
var visible = new List<Models.Branch>();
var oldSelection = SelectedBranch;
var keepSelection = false;
foreach (var b in Branches)
{
if (b.FriendlyName.Contains(SearchFilter, StringComparison.OrdinalIgnoreCase))
{
visible.Add(b);
if (!keepSelection)
keepSelection = (b == oldSelection);
}
}
SetCurrentValue(VisibleBranchesProperty, visible);
if (!keepSelection && visible.Count > 0)
SetCurrentValue(SelectedBranchProperty, visible[0]);
}
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
if (_popup != null)
{
_popup.Opened -= OnPopupOpened;
_popup.Closed -= OnPopupClosed;
}
_popup = e.NameScope.Get<Popup>("PART_Popup");
_popup.Opened += OnPopupOpened;
_popup.Closed += OnPopupClosed;
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Space && !IsDropDownOpened)
{
IsDropDownOpened = true;
e.Handled = true;
}
else if (e.Key == Key.Escape && IsDropDownOpened)
{
IsDropDownOpened = false;
e.Handled = true;
}
}
private void OnPopupOpened(object sender, EventArgs e)
{
var listBox = _popup?.Child?.FindDescendantOfType<ListBox>();
listBox?.Focus();
}
private void OnPopupClosed(object sender, EventArgs e)
{
Focus(NavigationMethod.Directional);
}
private void OnToggleDropDown(object sender, PointerPressedEventArgs e)
{
IsDropDownOpened = !IsDropDownOpened;
e.Handled = true;
}
private void OnSearchBoxKeyDown(object _, KeyEventArgs e)
{
if (e.Key == Key.Tab)
{
var listBox = _popup?.Child?.FindDescendantOfType<ListBox>();
listBox?.Focus();
e.Handled = true;
}
else if (e.Key == Key.Up)
{
var listBox = _popup?.Child?.FindDescendantOfType<ListBox>();
if (listBox != null)
{
if (listBox.SelectedIndex > 0)
listBox.SelectedIndex--;
listBox.Focus();
}
e.Handled = true;
}
else if (e.Key == Key.Down)
{
var listBox = _popup?.Child?.FindDescendantOfType<ListBox>();
if (listBox != null)
{
if (listBox.SelectedIndex < listBox.Items.Count - 1)
listBox.SelectedIndex++;
listBox.Focus();
}
e.Handled = true;
}
}
private void OnClearSearchFilter(object sender, RoutedEventArgs e)
{
SearchFilter = string.Empty;
e.Handled = true;
}
private void OnDropDownListKeyDown(object _, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
IsDropDownOpened = false;
e.Handled = true;
}
else if (e.Key == Key.F && e.KeyModifiers == (OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control))
{
var searchBox = _popup?.Child?.FindDescendantOfType<TextBox>();
if (searchBox != null)
{
searchBox.CaretIndex = SearchFilter?.Length ?? 0;
searchBox.Focus();
}
e.Handled = true;
}
}
private void OnDropDownItemPointerPressed(object sender, PointerPressedEventArgs e)
{
if (sender is Control { DataContext: Models.Branch branch })
SelectedBranch = branch;
IsDropDownOpened = false;
e.Handled = true;
}
private Popup _popup = null;
}
}

View File

@@ -534,7 +534,7 @@ namespace SourceGit.Views
{
var compare = new MenuItem();
compare.Header = App.Text("BranchCM.CompareTwo");
compare.Icon = App.CreateMenuIcon("Icons.Compare");
compare.Icon = this.CreateMenuIcon("Icons.Compare");
compare.Click += (_, ev) =>
{
App.ShowWindow(new ViewModels.Compare(repo, branches[0], branches[1]));
@@ -547,7 +547,7 @@ namespace SourceGit.Views
{
var mergeMulti = new MenuItem();
mergeMulti.Header = App.Text("BranchCM.MergeMultiBranches", branches.Count);
mergeMulti.Icon = App.CreateMenuIcon("Icons.Merge");
mergeMulti.Icon = this.CreateMenuIcon("Icons.Merge");
mergeMulti.Click += (_, ev) =>
{
repo.MergeMultipleBranches(branches);
@@ -556,7 +556,7 @@ namespace SourceGit.Views
var deleteMulti = new MenuItem();
deleteMulti.Header = App.Text("BranchCM.DeleteMultiBranches", branches.Count);
deleteMulti.Icon = App.CreateMenuIcon("Icons.Clear");
deleteMulti.Icon = this.CreateMenuIcon("Icons.Clear");
deleteMulti.Click += (_, ev) =>
{
repo.DeleteMultipleBranches(branches, branches[0].IsLocal);
@@ -689,7 +689,7 @@ namespace SourceGit.Views
var push = new MenuItem();
push.Header = App.Text("BranchCM.Push", branch.Name);
push.Icon = App.CreateMenuIcon("Icons.Push");
push.Icon = this.CreateMenuIcon("Icons.Push");
push.IsEnabled = repo.Remotes.Count > 0;
push.Click += (_, e) =>
{
@@ -706,7 +706,7 @@ namespace SourceGit.Views
{
var fastForward = new MenuItem();
fastForward.Header = App.Text("BranchCM.FastForward", upstream.FriendlyName);
fastForward.Icon = App.CreateMenuIcon("Icons.FastForward");
fastForward.Icon = this.CreateMenuIcon("Icons.FastForward");
fastForward.IsEnabled = branch.Ahead.Count == 0 && branch.Behind.Count > 0;
fastForward.Click += async (_, e) =>
{
@@ -717,7 +717,7 @@ namespace SourceGit.Views
var pull = new MenuItem();
pull.Header = App.Text("BranchCM.Pull", upstream.FriendlyName);
pull.Icon = App.CreateMenuIcon("Icons.Pull");
pull.Icon = this.CreateMenuIcon("Icons.Pull");
pull.Click += (_, e) =>
{
if (repo.CanCreatePopup())
@@ -735,7 +735,7 @@ namespace SourceGit.Views
var compareWith = new MenuItem();
compareWith.Header = App.Text("BranchCM.CompareWith");
compareWith.Icon = App.CreateMenuIcon("Icons.Compare");
compareWith.Icon = this.CreateMenuIcon("Icons.Compare");
compareWith.Click += (_, _) =>
{
new ViewModels.CompareCommandPalette(repo, branch).Open();
@@ -749,7 +749,7 @@ namespace SourceGit.Views
var checkout = new MenuItem();
checkout.Header = App.Text(hasNoWorktree ? "BranchCM.Checkout" : "BranchCM.SwitchToWorktree", branch.Name);
checkout.Icon = App.CreateMenuIcon("Icons.Check");
checkout.Icon = this.CreateMenuIcon("Icons.Check");
checkout.IsEnabled = !repo.IsBare || !hasNoWorktree;
checkout.Click += async (_, e) =>
{
@@ -763,7 +763,7 @@ namespace SourceGit.Views
{
var fastForward = new MenuItem();
fastForward.Header = App.Text("BranchCM.FastForward", upstream.FriendlyName);
fastForward.Icon = App.CreateMenuIcon("Icons.FastForward");
fastForward.Icon = this.CreateMenuIcon("Icons.FastForward");
fastForward.IsEnabled = branch.Ahead.Count == 0 && branch.Behind.Count > 0;
fastForward.Click += async (_, e) =>
{
@@ -775,7 +775,7 @@ namespace SourceGit.Views
var fetchInto = new MenuItem();
fetchInto.Header = App.Text("BranchCM.FetchInto", upstream.FriendlyName, branch.Name);
fetchInto.Icon = App.CreateMenuIcon("Icons.Fetch");
fetchInto.Icon = this.CreateMenuIcon("Icons.Fetch");
fetchInto.IsEnabled = branch.Ahead.Count == 0;
fetchInto.Click += async (_, e) =>
{
@@ -794,7 +794,7 @@ namespace SourceGit.Views
{
var merge = new MenuItem();
merge.Header = App.Text("BranchCM.Merge", branch.Name, current.Name);
merge.Icon = App.CreateMenuIcon("Icons.Merge");
merge.Icon = this.CreateMenuIcon("Icons.Merge");
merge.Click += (_, e) =>
{
if (repo.CanCreatePopup())
@@ -804,7 +804,7 @@ namespace SourceGit.Views
var rebase = new MenuItem();
rebase.Header = App.Text("BranchCM.Rebase", current.Name, branch.Name);
rebase.Icon = App.CreateMenuIcon("Icons.Rebase");
rebase.Icon = this.CreateMenuIcon("Icons.Rebase");
rebase.Click += (_, e) =>
{
if (repo.CanCreatePopup())
@@ -814,7 +814,7 @@ namespace SourceGit.Views
var interactiveRebase = new MenuItem();
interactiveRebase.Header = App.Text("BranchCM.InteractiveRebase.Manually", current.Name, branch.Name);
interactiveRebase.Icon = App.CreateMenuIcon("Icons.InteractiveRebase");
interactiveRebase.Icon = this.CreateMenuIcon("Icons.InteractiveRebase");
interactiveRebase.IsEnabled = !current.Head.Equals(branch.Head, StringComparison.Ordinal);
interactiveRebase.Click += async (_, e) =>
{
@@ -836,7 +836,7 @@ namespace SourceGit.Views
{
var move = new MenuItem();
move.Header = App.Text("BranchCM.ResetToSelectedCommit", branch.Name, selectedCommit.SHA.Substring(0, 10));
move.Icon = App.CreateMenuIcon("Icons.Reset");
move.Icon = this.CreateMenuIcon("Icons.Reset");
move.Click += (_, e) =>
{
if (repo.CanCreatePopup())
@@ -850,7 +850,7 @@ namespace SourceGit.Views
var compareWithCurrent = new MenuItem();
compareWithCurrent.Header = App.Text("BranchCM.CompareWithHead");
compareWithCurrent.Icon = App.CreateMenuIcon("Icons.Compare");
compareWithCurrent.Icon = this.CreateMenuIcon("Icons.Compare");
compareWithCurrent.Click += (_, _) =>
{
App.ShowWindow(new ViewModels.Compare(repo, branch, current));
@@ -858,7 +858,7 @@ namespace SourceGit.Views
var compareWith = new MenuItem();
compareWith.Header = App.Text("BranchCM.CompareWith");
compareWith.Icon = App.CreateMenuIcon("Icons.Compare");
compareWith.Icon = this.CreateMenuIcon("Icons.Compare");
compareWith.Click += (_, _) =>
{
new ViewModels.CompareCommandPalette(repo, branch).Open();
@@ -875,7 +875,7 @@ namespace SourceGit.Views
{
var finish = new MenuItem();
finish.Header = App.Text("BranchCM.Finish", branch.Name);
finish.Icon = App.CreateMenuIcon("Icons.GitFlow");
finish.Icon = this.CreateMenuIcon("Icons.GitFlow");
finish.Click += (_, e) =>
{
if (repo.CanCreatePopup())
@@ -891,7 +891,7 @@ namespace SourceGit.Views
{
var editDescription = new MenuItem();
editDescription.Header = App.Text("BranchCM.EditDescription", branch.Name);
editDescription.Icon = App.CreateMenuIcon("Icons.Edit");
editDescription.Icon = this.CreateMenuIcon("Icons.Edit");
editDescription.Click += async (_, e) =>
{
var desc = await new Commands.Config(repo.FullPath).GetAsync($"branch.{branch.Name}.description");
@@ -902,7 +902,7 @@ namespace SourceGit.Views
var rename = new MenuItem();
rename.Header = App.Text("BranchCM.Rename", branch.Name);
rename.Icon = App.CreateMenuIcon("Icons.Rename");
rename.Icon = this.CreateMenuIcon("Icons.Rename");
rename.Click += (_, e) =>
{
if (repo.CanCreatePopup())
@@ -912,7 +912,7 @@ namespace SourceGit.Views
var delete = new MenuItem();
delete.Header = App.Text("BranchCM.Delete", branch.Name);
delete.Icon = App.CreateMenuIcon("Icons.Clear");
delete.Icon = this.CreateMenuIcon("Icons.Clear");
delete.IsEnabled = !branch.IsCurrent;
delete.Click += (_, e) =>
{
@@ -928,7 +928,7 @@ namespace SourceGit.Views
}
var createBranch = new MenuItem();
createBranch.Icon = App.CreateMenuIcon("Icons.Branch.Add");
createBranch.Icon = this.CreateMenuIcon("Icons.Branch.Add");
createBranch.Header = App.Text("CreateBranch");
createBranch.Click += (_, e) =>
{
@@ -938,7 +938,7 @@ namespace SourceGit.Views
};
var createTag = new MenuItem();
createTag.Icon = App.CreateMenuIcon("Icons.Tag.Add");
createTag.Icon = this.CreateMenuIcon("Icons.Tag.Add");
createTag.Header = App.Text("CreateTag");
createTag.Click += (_, e) =>
{
@@ -958,7 +958,7 @@ namespace SourceGit.Views
{
var createPR = new MenuItem();
createPR.Header = App.Text("BranchCM.CreatePRForUpstream", upstream.FriendlyName);
createPR.Icon = App.CreateMenuIcon("Icons.CreatePR");
createPR.Icon = this.CreateMenuIcon("Icons.CreatePR");
createPR.Click += (_, e) =>
{
Native.OS.OpenBrowser(prURL);
@@ -984,7 +984,7 @@ namespace SourceGit.Views
{
var tracking = new MenuItem();
tracking.Header = App.Text("BranchCM.Tracking");
tracking.Icon = App.CreateMenuIcon("Icons.Track");
tracking.Icon = this.CreateMenuIcon("Icons.Track");
tracking.Click += (_, e) =>
{
if (repo.CanCreatePopup())
@@ -996,7 +996,7 @@ namespace SourceGit.Views
}
var archive = new MenuItem();
archive.Icon = App.CreateMenuIcon("Icons.Archive");
archive.Icon = this.CreateMenuIcon("Icons.Archive");
archive.Header = App.Text("Archive");
archive.Click += (_, e) =>
{
@@ -1009,10 +1009,10 @@ namespace SourceGit.Views
var copy = new MenuItem();
copy.Header = App.Text("BranchCM.CopyName");
copy.Icon = App.CreateMenuIcon("Icons.Copy");
copy.Icon = this.CreateMenuIcon("Icons.Copy");
copy.Click += async (_, e) =>
{
await App.CopyTextAsync(branch.Name);
await this.CopyTextAsync(branch.Name);
e.Handled = true;
};
menu.Items.Add(copy);
@@ -1028,7 +1028,7 @@ namespace SourceGit.Views
{
var visit = new MenuItem();
visit.Header = App.Text("RemoteCM.OpenInBrowser");
visit.Icon = App.CreateMenuIcon("Icons.OpenWith");
visit.Icon = this.CreateMenuIcon("Icons.OpenWith");
visit.Click += (_, e) =>
{
Native.OS.OpenBrowser(visitURL);
@@ -1041,7 +1041,7 @@ namespace SourceGit.Views
var fetch = new MenuItem();
fetch.Header = App.Text("RemoteCM.Fetch");
fetch.Icon = App.CreateMenuIcon("Icons.Fetch");
fetch.Icon = this.CreateMenuIcon("Icons.Fetch");
fetch.Click += (_, e) =>
{
if (repo.CanCreatePopup())
@@ -1051,7 +1051,7 @@ namespace SourceGit.Views
var prune = new MenuItem();
prune.Header = App.Text("RemoteCM.Prune");
prune.Icon = App.CreateMenuIcon("Icons.Clean");
prune.Icon = this.CreateMenuIcon("Icons.Clean");
prune.Click += async (_, e) =>
{
if (repo.CanCreatePopup())
@@ -1061,7 +1061,7 @@ namespace SourceGit.Views
var edit = new MenuItem();
edit.Header = App.Text("RemoteCM.Edit");
edit.Icon = App.CreateMenuIcon("Icons.Edit");
edit.Icon = this.CreateMenuIcon("Icons.Edit");
edit.Click += (_, e) =>
{
if (repo.CanCreatePopup())
@@ -1071,7 +1071,7 @@ namespace SourceGit.Views
var delete = new MenuItem();
delete.Header = App.Text("RemoteCM.Delete");
delete.Icon = App.CreateMenuIcon("Icons.Clear");
delete.Icon = this.CreateMenuIcon("Icons.Clear");
delete.Click += (_, e) =>
{
if (repo.CanCreatePopup())
@@ -1081,10 +1081,10 @@ namespace SourceGit.Views
var copy = new MenuItem();
copy.Header = App.Text("RemoteCM.CopyURL");
copy.Icon = App.CreateMenuIcon("Icons.Copy");
copy.Icon = this.CreateMenuIcon("Icons.Copy");
copy.Click += async (_, e) =>
{
await App.CopyTextAsync(remote.URL);
await this.CopyTextAsync(remote.URL);
e.Handled = true;
};
@@ -1106,7 +1106,7 @@ namespace SourceGit.Views
var checkout = new MenuItem();
checkout.Header = App.Text("BranchCM.Checkout", name);
checkout.Icon = App.CreateMenuIcon("Icons.Check");
checkout.Icon = this.CreateMenuIcon("Icons.Check");
checkout.Click += async (_, e) =>
{
await repo.CheckoutBranchAsync(branch);
@@ -1119,7 +1119,7 @@ namespace SourceGit.Views
{
var pull = new MenuItem();
pull.Header = App.Text("BranchCM.PullInto", name, current.Name);
pull.Icon = App.CreateMenuIcon("Icons.Pull");
pull.Icon = this.CreateMenuIcon("Icons.Pull");
pull.Click += (_, e) =>
{
if (repo.CanCreatePopup())
@@ -1129,7 +1129,7 @@ namespace SourceGit.Views
var merge = new MenuItem();
merge.Header = App.Text("BranchCM.Merge", name, current.Name);
merge.Icon = App.CreateMenuIcon("Icons.Merge");
merge.Icon = this.CreateMenuIcon("Icons.Merge");
merge.Click += (_, e) =>
{
if (repo.CanCreatePopup())
@@ -1139,7 +1139,7 @@ namespace SourceGit.Views
var rebase = new MenuItem();
rebase.Header = App.Text("BranchCM.Rebase", current.Name, name);
rebase.Icon = App.CreateMenuIcon("Icons.Rebase");
rebase.Icon = this.CreateMenuIcon("Icons.Rebase");
rebase.Click += (_, e) =>
{
if (repo.CanCreatePopup())
@@ -1149,7 +1149,7 @@ namespace SourceGit.Views
var interactiveRebase = new MenuItem();
interactiveRebase.Header = App.Text("BranchCM.InteractiveRebase.Manually", current.Name, name);
interactiveRebase.Icon = App.CreateMenuIcon("Icons.InteractiveRebase");
interactiveRebase.Icon = this.CreateMenuIcon("Icons.InteractiveRebase");
interactiveRebase.IsEnabled = !current.Head.Equals(branch.Head, StringComparison.Ordinal);
interactiveRebase.Click += async (_, e) =>
{
@@ -1160,7 +1160,7 @@ namespace SourceGit.Views
var compareWithHead = new MenuItem();
compareWithHead.Header = App.Text("BranchCM.CompareWithHead");
compareWithHead.Icon = App.CreateMenuIcon("Icons.Compare");
compareWithHead.Icon = this.CreateMenuIcon("Icons.Compare");
compareWithHead.Click += (_, _) =>
{
App.ShowWindow(new ViewModels.Compare(repo, branch, current));
@@ -1168,7 +1168,7 @@ namespace SourceGit.Views
var compareWith = new MenuItem();
compareWith.Header = App.Text("BranchCM.CompareWith");
compareWith.Icon = App.CreateMenuIcon("Icons.Compare");
compareWith.Icon = this.CreateMenuIcon("Icons.Compare");
compareWith.Click += (_, _) =>
{
new ViewModels.CompareCommandPalette(repo, branch).Open();
@@ -1188,7 +1188,7 @@ namespace SourceGit.Views
var editDescription = new MenuItem();
editDescription.Header = App.Text("BranchCM.EditDescription", branch.Name);
editDescription.Icon = App.CreateMenuIcon("Icons.Edit");
editDescription.Icon = this.CreateMenuIcon("Icons.Edit");
editDescription.Click += async (_, e) =>
{
var desc = await new Commands.Config(repo.FullPath).GetAsync($"branch.{branch.Name}.description");
@@ -1199,7 +1199,7 @@ namespace SourceGit.Views
var delete = new MenuItem();
delete.Header = App.Text("BranchCM.Delete", name);
delete.Icon = App.CreateMenuIcon("Icons.Clear");
delete.Icon = this.CreateMenuIcon("Icons.Clear");
delete.Click += (_, e) =>
{
if (repo.CanCreatePopup())
@@ -1212,7 +1212,7 @@ namespace SourceGit.Views
menu.Items.Add(new MenuItem() { Header = "-" });
var createBranch = new MenuItem();
createBranch.Icon = App.CreateMenuIcon("Icons.Branch.Add");
createBranch.Icon = this.CreateMenuIcon("Icons.Branch.Add");
createBranch.Header = App.Text("CreateBranch");
createBranch.Click += (_, e) =>
{
@@ -1222,7 +1222,7 @@ namespace SourceGit.Views
};
var createTag = new MenuItem();
createTag.Icon = App.CreateMenuIcon("Icons.Tag.Add");
createTag.Icon = this.CreateMenuIcon("Icons.Tag.Add");
createTag.Header = App.Text("CreateTag");
createTag.Click += (_, e) =>
{
@@ -1239,7 +1239,7 @@ namespace SourceGit.Views
{
var createPR = new MenuItem();
createPR.Header = App.Text("BranchCM.CreatePR");
createPR.Icon = App.CreateMenuIcon("Icons.CreatePR");
createPR.Icon = this.CreateMenuIcon("Icons.CreatePR");
createPR.Click += (_, e) =>
{
Native.OS.OpenBrowser(prURL);
@@ -1252,7 +1252,7 @@ namespace SourceGit.Views
menu.Items.Add(new MenuItem() { Header = "-" });
var archive = new MenuItem();
archive.Icon = App.CreateMenuIcon("Icons.Archive");
archive.Icon = this.CreateMenuIcon("Icons.Archive");
archive.Header = App.Text("Archive");
archive.Click += (_, e) =>
{
@@ -1263,10 +1263,10 @@ namespace SourceGit.Views
var copy = new MenuItem();
copy.Header = App.Text("BranchCM.CopyName");
copy.Icon = App.CreateMenuIcon("Icons.Copy");
copy.Icon = this.CreateMenuIcon("Icons.Copy");
copy.Click += async (_, e) =>
{
await App.CopyTextAsync(name);
await this.CopyTextAsync(name);
e.Handled = true;
};
@@ -1285,13 +1285,13 @@ namespace SourceGit.Views
var custom = new MenuItem();
custom.Header = App.Text("BranchCM.CustomAction");
custom.Icon = App.CreateMenuIcon("Icons.Action");
custom.Icon = this.CreateMenuIcon("Icons.Action");
foreach (var action in actions)
{
var (dup, label) = action;
var item = new MenuItem();
item.Icon = App.CreateMenuIcon("Icons.Action");
item.Icon = this.CreateMenuIcon("Icons.Action");
item.Header = label;
item.Click += async (_, e) =>
{
@@ -1314,13 +1314,13 @@ namespace SourceGit.Views
var custom = new MenuItem();
custom.Header = App.Text("RemoteCM.CustomAction");
custom.Icon = App.CreateMenuIcon("Icons.Action");
custom.Icon = this.CreateMenuIcon("Icons.Action");
foreach (var action in actions)
{
var (dup, label) = action;
var item = new MenuItem();
item.Icon = App.CreateMenuIcon("Icons.Action");
item.Icon = this.CreateMenuIcon("Icons.Action");
item.Header = label;
item.Click += async (_, e) =>
{

View File

@@ -37,38 +37,43 @@
SelectionChanged="OnRowSelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:ChangeTreeNode">
<Grid ColumnDefinitions="16,16,Auto,*"
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
Background="Transparent"
DoubleTapped="OnRowDoubleTapped"
DataContextChanged="OnRowDataContextChanged">
<v:ChangeTreeNodeToggleButton Grid.Column="0"
Classes="tree_expander"
Focusable="False"
HorizontalAlignment="Center"
IsChecked="{Binding IsExpanded, Mode=OneWay}"
IsVisible="{Binding IsFolder}"/>
<ToggleButton Grid.Column="1"
Classes="folder"
<StackPanel Orientation="Horizontal"
Height="24"
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
Background="Transparent"
DoubleTapped="OnRowDoubleTapped"
DataContextChanged="OnRowDataContextChanged">
<Border Width="16">
<v:ChangeTreeNodeToggleButton Classes="tree_expander"
Focusable="False"
HorizontalAlignment="Center"
IsChecked="{Binding IsExpanded, Mode=OneWay}"
IsVisible="{Binding IsFolder}"/>
</Border>
<ToggleButton Classes="folder"
Focusable="False"
Width="14" Height="14"
Margin="0,1,0,0"
Margin="1,1,1,0"
Foreground="Goldenrod"
IsChecked="{Binding IsExpanded}"
IsVisible="{Binding IsFolder}"/>
<v:ChangeStatusIcon Grid.Column="1"
Width="14" Height="14"
<v:ChangeStatusIcon Width="14" Height="14"
Margin="1,0"
IsUnstagedChange="{Binding #ThisControl.IsUnstagedChange}"
Change="{Binding Change}"
IsVisible="{Binding !IsFolder}"/>
<StackPanel Grid.Column="2" Orientation="Horizontal" Margin="4,0,0,0">
<TextBlock Text="{Binding ConflictMarker, Mode=OneWay}" Foreground="DarkOrange" FontWeight="Bold" Margin="0,0,4,0" IsVisible="{Binding ShowConflictMarker}"/>
<TextBlock Text="{Binding DisplayName, Mode=OneWay}"/>
</StackPanel>
</Grid>
<TextBlock Text="{Binding ConflictMarker, Mode=OneWay}"
Foreground="DarkOrange"
FontWeight="Bold"
Margin="4,0,0,0"
IsVisible="{Binding ShowConflictMarker}"/>
<TextBlock Text="{Binding DisplayName, Mode=OneWay}"
Margin="4,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</v:ChangeCollectionContainer>
@@ -82,25 +87,28 @@
SelectionChanged="OnRowSelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate DataType="m:Change">
<Grid ColumnDefinitions="Auto,Auto,Auto,*"
Background="Transparent"
DoubleTapped="OnRowDoubleTapped"
DataContextChanged="OnRowDataContextChanged">
<v:ChangeStatusIcon Grid.Column="0"
Width="14" Height="14"
<StackPanel Orientation="Horizontal"
Height="24"
Background="Transparent"
DoubleTapped="OnRowDoubleTapped"
DataContextChanged="OnRowDataContextChanged">
<v:ChangeStatusIcon Width="14" Height="14"
Margin="4,0,0,0"
IsUnstagedChange="{Binding #ThisControl.IsUnstagedChange}"
Change="{Binding}" />
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="4,0">
<TextBlock Text="{Binding ConflictMarker}" Foreground="DarkOrange" FontWeight="Bold" Margin="0,0,4,0" IsVisible="{Binding IsConflicted}"/>
<TextBlock Text="{Binding Path, Converter={x:Static c:PathConverters.PureFileName}}"/>
</StackPanel>
<TextBlock Text="{Binding ConflictMarker}"
Foreground="DarkOrange"
FontWeight="Bold"
Margin="4,0,0,0" IsVisible="{Binding IsConflicted}"/>
<TextBlock Text="{Binding Path, Converter={x:Static c:PathConverters.PureFileName}}"
Margin="4,0,0,0"/>
<TextBlock Grid.Column="2"
Text="{Binding Path, Converter={x:Static c:PathConverters.PureDirectoryName}}"
Foreground="{DynamicResource Brush.FG2}"/>
</Grid>
<TextBlock Text="{Binding Path, Converter={x:Static c:PathConverters.PureDirectoryName}}"
Foreground="{DynamicResource Brush.FG2}"
Margin="4,0,0,0"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</v:ChangeCollectionContainer>
@@ -114,21 +122,25 @@
SelectionChanged="OnRowSelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate DataType="m:Change">
<Grid ColumnDefinitions="Auto,Auto,*"
Background="Transparent"
DoubleTapped="OnRowDoubleTapped"
DataContextChanged="OnRowDataContextChanged">
<v:ChangeStatusIcon Grid.Column="0"
Width="14" Height="14"
<StackPanel Orientation="Horizontal"
Height="24"
Background="Transparent"
DoubleTapped="OnRowDoubleTapped"
DataContextChanged="OnRowDataContextChanged">
<v:ChangeStatusIcon Width="14" Height="14"
Margin="4,0,0,0"
IsUnstagedChange="{Binding #ThisControl.IsUnstagedChange}"
Change="{Binding}" />
<StackPanel Grid.Column="1" Orientation="Horizontal" Margin="4,0">
<TextBlock Text="{Binding ConflictMarker}" Foreground="DarkOrange" FontWeight="Bold" Margin="0,0,4,0" IsVisible="{Binding IsConflicted}"/>
<TextBlock Text="{Binding Path}"/>
</StackPanel>
</Grid>
<TextBlock Text="{Binding ConflictMarker}"
Foreground="DarkOrange"
FontWeight="Bold"
Margin="4,0,0,0"
IsVisible="{Binding IsConflicted}"/>
<TextBlock Margin="4,0,0,0"
Text="{Binding Path}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</v:ChangeCollectionContainer>

View File

@@ -231,17 +231,17 @@ namespace SourceGit.Views
private void OnRowDataContextChanged(object sender, EventArgs e)
{
if (sender is not Control control)
if (sender is not Control { DataContext: { } ctx } control)
return;
if (control.DataContext is ViewModels.ChangeTreeNode node)
if (ctx is ViewModels.ChangeTreeNode node)
{
if (node.Change is { } c)
UpdateRowTips(control, c);
else
ToolTip.SetTip(control, node.FullPath);
}
else if (control.DataContext is Models.Change change)
else if (ctx is Models.Change change)
{
UpdateRowTips(control, change);
}
@@ -253,8 +253,10 @@ namespace SourceGit.Views
private void OnRowDoubleTapped(object sender, TappedEventArgs e)
{
var grid = sender as Grid;
if (grid?.DataContext is ViewModels.ChangeTreeNode node)
if (sender is not Control { DataContext: { } ctx })
return;
if (ctx is ViewModels.ChangeTreeNode node)
{
if (node.IsFolder)
{
@@ -269,7 +271,7 @@ namespace SourceGit.Views
RaiseEvent(new RoutedEventArgs(ChangeDoubleTappedEvent));
}
}
else if (grid?.DataContext is Models.Change)
else if (ctx is Models.Change)
{
RaiseEvent(new RoutedEventArgs(ChangeDoubleTappedEvent));
}

View File

@@ -0,0 +1,48 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.CheckoutBranchFromStash"
x:DataType="vm:CheckoutBranchFromStash">
<StackPanel Orientation="Vertical" Margin="8,0,0,0">
<StackPanel Orientation="Horizontal">
<Path Width="16" Height="16"
Data="{StaticResource Icons.Branch.Add}"/>
<TextBlock FontSize="18"
Margin="8,0,0,0"
Classes="bold"
Text="{DynamicResource Text.CheckoutBranchFromStash}"/>
</StackPanel>
<Grid Margin="0,16,8,0" RowDefinitions="32,32" ColumnDefinitions="120,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.CheckoutBranchFromStash.Stash}"/>
<Grid Grid.Row="0" Grid.Column="1" ColumnDefinitions="Auto,Auto,*">
<Path Grid.Column="0"
Width="12" Height="12"
Margin="2,0,8,0"
HorizontalAlignment="Left" VerticalAlignment="Center"
Data="{StaticResource Icons.Stashes}"/>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Text="{Binding Target.Name}" Foreground="DarkOrange"/>
<TextBlock Grid.Column="2" VerticalAlignment="Center" Text="{Binding Target.Subject}" TextTrimming="CharacterEllipsis" Margin="4,0,0,0"/>
</Grid>
<TextBlock Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.CheckoutBranchFromStash.Branch}"/>
<v:BranchOrTagNameTextBox Grid.Row="1" Grid.Column="1"
Height="26"
VerticalAlignment="Center"
CornerRadius="3"
Text="{Binding BranchName, Mode=TwoWay}"/>
</Grid>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,12 @@
using Avalonia.Controls;
namespace SourceGit.Views
{
public partial class CheckoutBranchFromStash : UserControl
{
public CheckoutBranchFromStash()
{
InitializeComponent();
}
}
}

View File

@@ -1,5 +1,6 @@
using System;
using Avalonia.Controls;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Platform.Storage;
@@ -12,6 +13,29 @@ namespace SourceGit.Views
InitializeComponent();
}
protected override async void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
if (DataContext is not ViewModels.Clone vm)
return;
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard != null)
{
try
{
var text = await clipboard.TryGetTextAsync();
if (Models.Remote.IsValidURL(text))
vm.Remote = text;
}
catch
{
// Ignore exceptions here.
}
}
}
private async void SelectParentFolder(object _, RoutedEventArgs e)
{
var options = new FolderPickerOpenOptions() { AllowMultiple = false };
@@ -31,7 +55,7 @@ namespace SourceGit.Views
}
catch (Exception exception)
{
App.RaiseException(string.Empty, $"Failed to select parent folder: {exception.Message}");
Models.Notification.Send(null, $"Failed to select parent folder: {exception.Message}", true);
}
e.Handled = true;

View File

@@ -5,7 +5,7 @@ namespace SourceGit.Views
{
public class CommandPaletteDataTemplates : IDataTemplate
{
public Control Build(object param) => App.CreateViewForViewModel(param);
public Control Build(object param) => ControlExtensions.CreateFromViewModels(param);
public bool Match(object data) => data is ViewModels.ICommandPalette;
}
}

View File

@@ -203,6 +203,7 @@
<TextBlock Grid.Row="4" Grid.Column="0" Classes="info_label" VerticalAlignment="Top" Margin="0,4,0,0" Text="{DynamicResource Text.CommitDetail.Info.Message}" />
<v:CommitMessagePresenter Grid.Row="4" Grid.Column="1"
Margin="12,4,0,0"
Foreground="{DynamicResource Brush.FG1}"
FullMessage="{Binding #ThisControl.FullMessage}"
HorizontalAlignment="Stretch"
TextWrapping="Wrap">
@@ -213,6 +214,26 @@
</MultiBinding>
</ToolTip.IsOpen>
<v:CommitMessagePresenter.ContextFlyout>
<MenuFlyout Placement="Bottom">
<MenuItem Header="{DynamicResource Text.Copy}"
Command="{Binding $parent[SelectableTextBlock].Copy}"
IsEnabled="{Binding $parent[SelectableTextBlock].CanCopy}"
InputGesture="{x:Static TextBox.CopyGesture}">
<MenuItem.Icon>
<Path Width="11" Height="11" Data="{StaticResource Icons.Copy}" VerticalAlignment="Center"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource Text.CopyAllText}"
Click="OnCopyAllCommitMessage">
<MenuItem.Icon>
<Path Width="11" Height="11" Data="{StaticResource Icons.Copy}" VerticalAlignment="Center"/>
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</v:CommitMessagePresenter.ContextFlyout>
<v:CommitMessagePresenter.DataTemplates>
<DataTemplate DataType="m:Commit">
<StackPanel MinWidth="400" Orientation="Vertical">

View File

@@ -63,7 +63,7 @@ namespace SourceGit.Views
private async void OnCopyCommitSHA(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: Models.Commit commit })
await App.CopyTextAsync(commit.SHA);
await this.CopyTextAsync(commit.SHA);
e.Handled = true;
}
@@ -151,28 +151,28 @@ namespace SourceGit.Views
var copyName = new MenuItem();
copyName.Header = App.Text("CommitDetail.Info.CopyName");
copyName.Icon = App.CreateMenuIcon("Icons.Copy");
copyName.Icon = this.CreateMenuIcon("Icons.Copy");
copyName.Click += async (_, ev) =>
{
await App.CopyTextAsync(user.Name);
await this.CopyTextAsync(user.Name);
ev.Handled = true;
};
var copyEmail = new MenuItem();
copyEmail.Header = App.Text("CommitDetail.Info.CopyEmail");
copyEmail.Icon = App.CreateMenuIcon("Icons.Email");
copyEmail.Icon = this.CreateMenuIcon("Icons.Email");
copyEmail.Click += async (_, ev) =>
{
await App.CopyTextAsync(user.Email);
await this.CopyTextAsync(user.Email);
ev.Handled = true;
};
var copyUser = new MenuItem();
copyUser.Header = App.Text("CommitDetail.Info.CopyNameAndEmail");
copyUser.Icon = App.CreateMenuIcon("Icons.User");
copyUser.Icon = this.CreateMenuIcon("Icons.User");
copyUser.Click += async (_, ev) =>
{
await App.CopyTextAsync(user.ToString());
await this.CopyTextAsync(user.ToString());
ev.Handled = true;
};
@@ -183,5 +183,12 @@ namespace SourceGit.Views
menu.Open(control);
e.Handled = true;
}
private async void OnCopyAllCommitMessage(object sender, RoutedEventArgs e)
{
if (DataContext is ViewModels.CommitDetail detail)
await this.CopyTextAsync(detail.FullMessage.Message);
e.Handled = true;
}
}
}

View File

@@ -62,7 +62,7 @@ namespace SourceGit.Views
builder.AppendLine(copyAbsPath ? vm.GetAbsPath(c.Path) : c.Path);
}
await App.CopyTextAsync(builder.ToString());
await this.CopyTextAsync(builder.ToString());
e.Handled = true;
}
else if (e.Key == Key.F && e.KeyModifiers == cmdKey)

View File

@@ -25,7 +25,7 @@ namespace SourceGit.Views
var fullPath = Native.OS.GetAbsPath(repo.FullPath, node.FullPath);
var explore = new MenuItem();
explore.Header = App.Text("RevealFile");
explore.Icon = App.CreateMenuIcon("Icons.Explore");
explore.Icon = this.CreateMenuIcon("Icons.Explore");
explore.IsEnabled = Directory.Exists(fullPath);
explore.Click += (_, ev) =>
{
@@ -35,7 +35,7 @@ namespace SourceGit.Views
var history = new MenuItem();
history.Header = App.Text("DirHistories");
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Icon = this.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) =>
{
App.ShowWindow(new ViewModels.DirHistories(repo, node.FullPath, commit.SHA));
@@ -44,7 +44,7 @@ namespace SourceGit.Views
var patch = new MenuItem();
patch.Header = App.Text("FileCM.SaveAsPatch");
patch.Icon = App.CreateMenuIcon("Icons.Save");
patch.Icon = this.CreateMenuIcon("Icons.Save");
patch.Click += async (_, e) =>
{
var storageProvider = TopLevel.GetTopLevel(this)?.StorageProvider;
@@ -67,7 +67,7 @@ namespace SourceGit.Views
}
catch (Exception exception)
{
App.RaiseException(repo.FullPath, $"Failed to save as patch: {exception.Message}");
repo.SendNotification($"Failed to save as patch: {exception.Message}", true);
}
e.Handled = true;
@@ -75,21 +75,21 @@ namespace SourceGit.Views
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyPath.Icon = this.CreateMenuIcon("Icons.Copy");
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
copyPath.Click += async (_, ev) =>
{
await App.CopyTextAsync(node.FullPath);
await this.CopyTextAsync(node.FullPath);
ev.Handled = true;
};
var copyFullPath = new MenuItem();
copyFullPath.Header = App.Text("CopyFullPath");
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyFullPath.Icon = this.CreateMenuIcon("Icons.Copy");
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
copyFullPath.Click += async (_, e) =>
{
await App.CopyTextAsync(fullPath);
await this.CopyTextAsync(fullPath);
e.Handled = true;
};
@@ -112,7 +112,7 @@ namespace SourceGit.Views
var patch = new MenuItem();
patch.Header = App.Text("FileCM.SaveAsPatch");
patch.Icon = App.CreateMenuIcon("Icons.Save");
patch.Icon = this.CreateMenuIcon("Icons.Save");
patch.Click += async (_, e) =>
{
var storageProvider = TopLevel.GetTopLevel(this)?.StorageProvider;
@@ -135,7 +135,7 @@ namespace SourceGit.Views
}
catch (Exception exception)
{
App.RaiseException(repo.FullPath, $"Failed to save as patch: {exception.Message}");
repo.SendNotification($"Failed to save as patch: {exception.Message}", true);
}
e.Handled = true;
@@ -149,7 +149,7 @@ namespace SourceGit.Views
{
var resetToThisRevision = new MenuItem();
resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToThisRevision.Icon = this.CreateMenuIcon("Icons.File.Checkout");
resetToThisRevision.Click += async (_, ev) =>
{
await vm.ResetMultipleToThisRevisionAsync(changes);
@@ -158,7 +158,7 @@ namespace SourceGit.Views
var resetToFirstParent = new MenuItem();
resetToFirstParent.Header = App.Text("ChangeCM.CheckoutFirstParentRevision");
resetToFirstParent.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToFirstParent.Icon = this.CreateMenuIcon("Icons.File.Checkout");
resetToFirstParent.IsEnabled = commit.Parents.Count > 0;
resetToFirstParent.Click += async (_, ev) =>
{
@@ -173,7 +173,7 @@ namespace SourceGit.Views
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyPath.Icon = this.CreateMenuIcon("Icons.Copy");
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
copyPath.Click += async (_, ev) =>
{
@@ -181,13 +181,13 @@ namespace SourceGit.Views
foreach (var c in changes)
builder.AppendLine(c.Path);
await App.CopyTextAsync(builder.ToString());
await this.CopyTextAsync(builder.ToString());
ev.Handled = true;
};
var copyFullPath = new MenuItem();
copyFullPath.Header = App.Text("CopyFullPath");
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyFullPath.Icon = this.CreateMenuIcon("Icons.Copy");
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
copyFullPath.Click += async (_, e) =>
{
@@ -195,7 +195,7 @@ namespace SourceGit.Views
foreach (var c in changes)
builder.AppendLine(Native.OS.GetAbsPath(repo.FullPath, c.Path));
await App.CopyTextAsync(builder.ToString());
await this.CopyTextAsync(builder.ToString());
e.Handled = true;
};
@@ -211,7 +211,7 @@ namespace SourceGit.Views
var openWith = new MenuItem();
openWith.Header = App.Text("Open");
openWith.Icon = App.CreateMenuIcon("Icons.OpenWith");
openWith.Icon = this.CreateMenuIcon("Icons.OpenWith");
openWith.IsEnabled = change.Index != Models.ChangeState.Deleted;
if (openWith.IsEnabled)
{
@@ -249,7 +249,7 @@ namespace SourceGit.Views
var openWithMerger = new MenuItem();
openWithMerger.Header = App.Text("OpenInExternalMergeTool");
openWithMerger.Icon = App.CreateMenuIcon("Icons.OpenWith");
openWithMerger.Icon = this.CreateMenuIcon("Icons.OpenWith");
openWithMerger.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+D" : "Ctrl+Shift+D";
openWithMerger.Click += (_, ev) =>
{
@@ -260,7 +260,7 @@ namespace SourceGit.Views
var fullPath = Native.OS.GetAbsPath(repo.FullPath, change.Path);
var explore = new MenuItem();
explore.Header = App.Text("RevealFile");
explore.Icon = App.CreateMenuIcon("Icons.Explore");
explore.Icon = this.CreateMenuIcon("Icons.Explore");
explore.IsEnabled = File.Exists(fullPath);
explore.Click += (_, ev) =>
{
@@ -270,7 +270,7 @@ namespace SourceGit.Views
var history = new MenuItem();
history.Header = App.Text("FileHistory");
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Icon = this.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) =>
{
App.ShowWindow(new ViewModels.FileHistories(repo.FullPath, change.Path, commit.SHA));
@@ -279,7 +279,7 @@ namespace SourceGit.Views
var blame = new MenuItem();
blame.Header = App.Text("Blame");
blame.Icon = App.CreateMenuIcon("Icons.Blame");
blame.Icon = this.CreateMenuIcon("Icons.Blame");
blame.IsEnabled = change.Index != Models.ChangeState.Deleted;
blame.Click += (_, ev) =>
{
@@ -289,7 +289,7 @@ namespace SourceGit.Views
var patch = new MenuItem();
patch.Header = App.Text("FileCM.SaveAsPatch");
patch.Icon = App.CreateMenuIcon("Icons.Save");
patch.Icon = this.CreateMenuIcon("Icons.Save");
patch.Click += async (_, e) =>
{
var storageProvider = TopLevel.GetTopLevel(this)?.StorageProvider;
@@ -312,7 +312,7 @@ namespace SourceGit.Views
}
catch (Exception exception)
{
App.RaiseException(repo.FullPath, $"Failed to save as patch: {exception.Message}");
repo.SendNotification($"Failed to save as patch: {exception.Message}", true);
}
e.Handled = true;
@@ -332,7 +332,7 @@ namespace SourceGit.Views
{
var resetToThisRevision = new MenuItem();
resetToThisRevision.Header = App.Text("ChangeCM.CheckoutThisRevision");
resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToThisRevision.Icon = this.CreateMenuIcon("Icons.File.Checkout");
resetToThisRevision.Click += async (_, ev) =>
{
await vm.ResetToThisRevisionAsync(change);
@@ -341,7 +341,7 @@ namespace SourceGit.Views
var resetToFirstParent = new MenuItem();
resetToFirstParent.Header = App.Text("ChangeCM.CheckoutFirstParentRevision");
resetToFirstParent.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToFirstParent.Icon = this.CreateMenuIcon("Icons.File.Checkout");
resetToFirstParent.IsEnabled = commit.Parents.Count > 0;
resetToFirstParent.Click += async (_, ev) =>
{
@@ -357,11 +357,11 @@ namespace SourceGit.Views
{
var lfs = new MenuItem();
lfs.Header = App.Text("GitLFS");
lfs.Icon = App.CreateMenuIcon("Icons.LFS");
lfs.Icon = this.CreateMenuIcon("Icons.LFS");
var lfsLock = new MenuItem();
lfsLock.Header = App.Text("GitLFS.Locks.Lock");
lfsLock.Icon = App.CreateMenuIcon("Icons.Lock");
lfsLock.Icon = this.CreateMenuIcon("Icons.Lock");
if (repo.Remotes.Count == 1)
{
lfsLock.Click += async (_, e) =>
@@ -389,7 +389,7 @@ namespace SourceGit.Views
var lfsUnlock = new MenuItem();
lfsUnlock.Header = App.Text("GitLFS.Locks.Unlock");
lfsUnlock.Icon = App.CreateMenuIcon("Icons.Unlock");
lfsUnlock.Icon = this.CreateMenuIcon("Icons.Unlock");
if (repo.Remotes.Count == 1)
{
lfsUnlock.Click += async (_, e) =>
@@ -426,13 +426,13 @@ namespace SourceGit.Views
var target = new Models.CustomActionTargetFile(change.Path, vm.Commit);
var custom = new MenuItem();
custom.Header = App.Text("FileCM.CustomAction");
custom.Icon = App.CreateMenuIcon("Icons.Action");
custom.Icon = this.CreateMenuIcon("Icons.Action");
foreach (var action in actions)
{
var (dup, label) = action;
var item = new MenuItem();
item.Icon = App.CreateMenuIcon("Icons.Action");
item.Icon = this.CreateMenuIcon("Icons.Action");
item.Header = label;
item.Click += async (_, e) =>
{
@@ -449,21 +449,21 @@ namespace SourceGit.Views
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyPath.Icon = this.CreateMenuIcon("Icons.Copy");
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
copyPath.Click += async (_, ev) =>
{
await App.CopyTextAsync(change.Path);
await this.CopyTextAsync(change.Path);
ev.Handled = true;
};
var copyFullPath = new MenuItem();
copyFullPath.Header = App.Text("CopyFullPath");
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyFullPath.Icon = this.CreateMenuIcon("Icons.Copy");
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
copyFullPath.Click += async (_, e) =>
{
await App.CopyTextAsync(fullPath);
await this.CopyTextAsync(fullPath);
e.Handled = true;
};
@@ -484,9 +484,9 @@ namespace SourceGit.Views
e.KeyModifiers.HasFlag(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control))
{
if (e.KeyModifiers.HasFlag(KeyModifiers.Shift))
await App.CopyTextAsync(vm.GetAbsPath(change.Path));
await this.CopyTextAsync(vm.GetAbsPath(change.Path));
else
await App.CopyTextAsync(change.Path);
await this.CopyTextAsync(change.Path);
e.Handled = true;
return;

View File

@@ -134,7 +134,7 @@ namespace SourceGit.Views
{
var open = new MenuItem();
open.Header = App.Text("SHALinkCM.NavigateTo");
open.Icon = App.CreateMenuIcon("Icons.Commit");
open.Icon = this.CreateMenuIcon("Icons.Commit");
open.Click += (_, ev) =>
{
detail.NavigateTo(link);
@@ -143,10 +143,10 @@ namespace SourceGit.Views
var copy = new MenuItem();
copy.Header = App.Text("SHALinkCM.CopySHA");
copy.Icon = App.CreateMenuIcon("Icons.Copy");
copy.Icon = this.CreateMenuIcon("Icons.Copy");
copy.Click += async (_, ev) =>
{
await App.CopyTextAsync(link);
await this.CopyTextAsync(link);
ev.Handled = true;
};
@@ -167,7 +167,7 @@ namespace SourceGit.Views
{
var open = new MenuItem();
open.Header = App.Text("IssueLinkCM.OpenInBrowser");
open.Icon = App.CreateMenuIcon("Icons.OpenWith");
open.Icon = this.CreateMenuIcon("Icons.OpenWith");
open.Click += (_, ev) =>
{
Native.OS.OpenBrowser(link);
@@ -176,10 +176,10 @@ namespace SourceGit.Views
var copy = new MenuItem();
copy.Header = App.Text("IssueLinkCM.CopyLink");
copy.Icon = App.CreateMenuIcon("Icons.Copy");
copy.Icon = this.CreateMenuIcon("Icons.Copy");
copy.Click += async (_, ev) =>
{
await App.CopyTextAsync(link);
await this.CopyTextAsync(link);
ev.Handled = true;
};
@@ -268,7 +268,7 @@ namespace SourceGit.Views
{
var currentParent = this.FindAncestorOfType<CommitBaseInfo>();
if (currentParent is { DataContext: ViewModels.CommitDetail currentDetail } &&
currentDetail.Commit.SHA == lastDetailCommit)
currentDetail.Commit.SHA.Equals(lastDetailCommit, StringComparison.Ordinal))
{
_inlineCommits.TryAdd(sha, c);
if (_lastHover == link && c != null)

View File

@@ -337,7 +337,7 @@ namespace SourceGit.Views
var copy = new MenuItem();
copy.Header = App.Text("Copy");
copy.Icon = App.CreateMenuIcon("Icons.Copy");
copy.Icon = this.CreateMenuIcon("Icons.Copy");
copy.IsEnabled = hasSelected;
copy.Click += (_, ev) =>
{
@@ -347,7 +347,7 @@ namespace SourceGit.Views
var cut = new MenuItem();
cut.Header = App.Text("Cut");
cut.Icon = App.CreateMenuIcon("Icons.Cut");
cut.Icon = this.CreateMenuIcon("Icons.Cut");
cut.IsEnabled = hasSelected;
cut.Click += (_, ev) =>
{
@@ -357,7 +357,7 @@ namespace SourceGit.Views
var paste = new MenuItem();
paste.Header = App.Text("Paste");
paste.Icon = App.CreateMenuIcon("Icons.Paste");
paste.Icon = this.CreateMenuIcon("Icons.Paste");
paste.Click += (_, ev) =>
{
Paste();
@@ -447,7 +447,7 @@ namespace SourceGit.Views
menu.Items.Add(new MenuItem()
{
Header = App.Text("WorkingCopy.NoCommitTemplates"),
Icon = App.CreateMenuIcon("Icons.Code"),
Icon = this.CreateMenuIcon("Icons.Code"),
IsEnabled = false
});
}
@@ -455,7 +455,7 @@ namespace SourceGit.Views
{
for (int i = 0; i < templateCount; i++)
{
var icon = App.CreateMenuIcon("Icons.Code");
var icon = this.CreateMenuIcon("Icons.Code");
icon.Fill = foreground;
var template = repo.Settings.CommitTemplates[i];
@@ -484,7 +484,7 @@ namespace SourceGit.Views
friendlyName = $"~{gitTemplate.AsSpan(prefixLen)}";
}
var icon = App.CreateMenuIcon("Icons.Code");
var icon = this.CreateMenuIcon("Icons.Code");
icon.Fill = foreground;
var gitTemplateItem = new MenuItem();
@@ -508,7 +508,7 @@ namespace SourceGit.Views
menu.Items.Add(new MenuItem()
{
Header = App.Text("WorkingCopy.NoCommitHistories"),
Icon = App.CreateMenuIcon("Icons.Histories"),
Icon = this.CreateMenuIcon("Icons.Histories"),
IsEnabled = false
});
}
@@ -524,7 +524,7 @@ namespace SourceGit.Views
TextTrimming = TextTrimming.CharacterEllipsis
};
var icon = App.CreateMenuIcon("Icons.Histories");
var icon = this.CreateMenuIcon("Icons.Histories");
icon.Fill = foreground;
var item = new MenuItem();
@@ -541,7 +541,7 @@ namespace SourceGit.Views
menu.Items.Add(new MenuItem() { Header = "-" });
var clearIcon = App.CreateMenuIcon("Icons.Clear");
var clearIcon = this.CreateMenuIcon("Icons.Clear");
clearIcon.Fill = foreground;
var clearHistoryItem = new MenuItem();
@@ -573,7 +573,7 @@ namespace SourceGit.Views
if (vm.Staged == null || vm.Staged.Count == 0)
{
App.RaiseException(repo.FullPath, "No files added to commit!");
repo.SendNotification("No files added to commit!", true);
e.Handled = true;
return;
}
@@ -581,7 +581,7 @@ namespace SourceGit.Views
var services = repo.GetPreferredOpenAIServices();
if (services.Count == 0)
{
App.RaiseException(repo.FullPath, "Bad configuration for OpenAI");
repo.SendNotification("Bad configuration for OpenAI", true);
e.Handled = true;
return;
}

View File

@@ -24,7 +24,7 @@ namespace SourceGit.Views
var patch = new MenuItem();
patch.Header = App.Text("FileCM.SaveAsPatch");
patch.Icon = App.CreateMenuIcon("Icons.Save");
patch.Icon = this.CreateMenuIcon("Icons.Save");
patch.Click += async (_, e) =>
{
var storageProvider = this.StorageProvider;
@@ -42,12 +42,14 @@ namespace SourceGit.Views
if (storageFile != null)
{
var saveTo = storageFile.Path.LocalPath;
await vm.SaveChangesAsPatchAsync(selected, saveTo);
var succ = await vm.SaveChangesAsPatchAsync(selected, saveTo);
if (succ)
await new Alert().ShowAsync(this, "Save patch successfully.", false);
}
}
catch (Exception exception)
{
App.RaiseException(null, $"Failed to save as patch: {exception.Message}");
await new Alert().ShowAsync(this, $"Failed to save as patch: {exception.Message}", true);
}
e.Handled = true;
@@ -58,7 +60,7 @@ namespace SourceGit.Views
var change = selected[0];
var openWithMerger = new MenuItem();
openWithMerger.Header = App.Text("OpenInExternalMergeTool");
openWithMerger.Icon = App.CreateMenuIcon("Icons.OpenWith");
openWithMerger.Icon = this.CreateMenuIcon("Icons.OpenWith");
openWithMerger.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+D" : "Ctrl+Shift+D";
openWithMerger.Click += (_, ev) =>
{
@@ -72,7 +74,7 @@ namespace SourceGit.Views
var full = vm.GetAbsPath(change.Path);
var explore = new MenuItem();
explore.Header = App.Text("RevealFile");
explore.Icon = App.CreateMenuIcon("Icons.Explore");
explore.Icon = this.CreateMenuIcon("Icons.Explore");
explore.IsEnabled = File.Exists(full);
explore.Click += (_, ev) =>
{
@@ -89,7 +91,7 @@ namespace SourceGit.Views
{
var resetToLeft = new MenuItem();
resetToLeft.Header = App.Text("ChangeCM.ResetFileTo", vm.BaseName);
resetToLeft.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToLeft.Icon = this.CreateMenuIcon("Icons.File.Checkout");
resetToLeft.Click += async (_, ev) =>
{
await vm.ResetToLeftAsync(change);
@@ -98,7 +100,7 @@ namespace SourceGit.Views
var resetToRight = new MenuItem();
resetToRight.Header = App.Text("ChangeCM.ResetFileTo", vm.ToName);
resetToRight.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToRight.Icon = this.CreateMenuIcon("Icons.File.Checkout");
resetToRight.Click += async (_, ev) =>
{
await vm.ResetToRightAsync(change);
@@ -112,21 +114,21 @@ namespace SourceGit.Views
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyPath.Icon = this.CreateMenuIcon("Icons.Copy");
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
copyPath.Click += async (_, ev) =>
{
await App.CopyTextAsync(change.Path);
await this.CopyTextAsync(change.Path);
ev.Handled = true;
};
var copyFullPath = new MenuItem();
copyFullPath.Header = App.Text("CopyFullPath");
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyFullPath.Icon = this.CreateMenuIcon("Icons.Copy");
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
copyFullPath.Click += async (_, ev) =>
{
await App.CopyTextAsync(vm.GetAbsPath(change.Path));
await this.CopyTextAsync(vm.GetAbsPath(change.Path));
ev.Handled = true;
};
@@ -142,7 +144,7 @@ namespace SourceGit.Views
{
var resetToLeft = new MenuItem();
resetToLeft.Header = App.Text("ChangeCM.ResetFileTo", vm.BaseName);
resetToLeft.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToLeft.Icon = this.CreateMenuIcon("Icons.File.Checkout");
resetToLeft.Click += async (_, ev) =>
{
await vm.ResetMultipleToLeftAsync(selected);
@@ -151,7 +153,7 @@ namespace SourceGit.Views
var resetToRight = new MenuItem();
resetToRight.Header = App.Text("ChangeCM.ResetFileTo", vm.ToName);
resetToRight.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToRight.Icon = this.CreateMenuIcon("Icons.File.Checkout");
resetToRight.Click += async (_, ev) =>
{
await vm.ResetMultipleToRightAsync(selected);
@@ -165,7 +167,7 @@ namespace SourceGit.Views
var copyPath = new MenuItem();
copyPath.Header = App.Text("CopyPath");
copyPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyPath.Icon = this.CreateMenuIcon("Icons.Copy");
copyPath.Tag = OperatingSystem.IsMacOS() ? "⌘+C" : "Ctrl+C";
copyPath.Click += async (_, ev) =>
{
@@ -173,13 +175,13 @@ namespace SourceGit.Views
foreach (var c in selected)
builder.AppendLine(c.Path);
await App.CopyTextAsync(builder.ToString());
await this.CopyTextAsync(builder.ToString());
ev.Handled = true;
};
var copyFullPath = new MenuItem();
copyFullPath.Header = App.Text("CopyFullPath");
copyFullPath.Icon = App.CreateMenuIcon("Icons.Copy");
copyFullPath.Icon = this.CreateMenuIcon("Icons.Copy");
copyFullPath.Tag = OperatingSystem.IsMacOS() ? "⌘+⇧+C" : "Ctrl+Shift+C";
copyFullPath.Click += async (_, ev) =>
{
@@ -187,7 +189,7 @@ namespace SourceGit.Views
foreach (var c in selected)
builder.AppendLine(vm.GetAbsPath(c.Path));
await App.CopyTextAsync(builder.ToString());
await this.CopyTextAsync(builder.ToString());
ev.Handled = true;
};
@@ -233,7 +235,7 @@ namespace SourceGit.Views
builder.AppendLine(copyAbsPath ? vm.GetAbsPath(c.Path) : c.Path);
}
await App.CopyTextAsync(builder.ToString());
await this.CopyTextAsync(builder.ToString());
e.Handled = true;
}
else if (e.Key == Key.F && e.KeyModifiers == cmdKey)

View File

@@ -40,7 +40,7 @@ namespace SourceGit.Views
}
catch (Exception ex)
{
App.RaiseException(string.Empty, $"Failed to select default clone directory: {ex.Message}");
await new Alert().ShowAsync(this, $"Failed to select default clone directory: {ex.Message}", true);
}
e.Handled = true;

View File

@@ -0,0 +1,48 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
namespace SourceGit.Views
{
public static class ControlExtensions
{
public static Control CreateFromViewModels(object data)
{
var dataTypeName = data.GetType().FullName;
if (string.IsNullOrEmpty(dataTypeName) || !dataTypeName.Contains(".ViewModels.", StringComparison.Ordinal))
return null;
var viewTypeName = dataTypeName.Replace(".ViewModels.", ".Views.");
var viewType = Type.GetType(viewTypeName);
if (viewType != null)
return Activator.CreateInstance(viewType) as Control;
return null;
}
public static async Task CopyTextAsync(this Control control, string text)
{
var clipboard = TopLevel.GetTopLevel(control)?.Clipboard;
if (clipboard != null)
await clipboard.SetTextAsync(text);
}
public static Path CreateMenuIcon(this Control control, string iconKey)
{
if (control?.FindResource(iconKey) is StreamGeometry geo)
{
return new Path()
{
Data = geo,
Width = 12,
Height = 12,
Stretch = Stretch.Uniform
};
}
return null;
}
}
}

View File

@@ -35,7 +35,7 @@
<DataTemplate DataType="m:Branch">
<StackPanel Orientation="Horizontal">
<Path Width="14" Height="14" Data="{StaticResource Icons.Branch}"/>
<SelectableTextBlock VerticalAlignment="Center" Text="{Binding FriendlyName}" Margin="8,0,0,0"/>
<TextBlock VerticalAlignment="Center" Text="{Binding FriendlyName}" Margin="8,0,0,0"/>
</StackPanel>
</DataTemplate>

View File

@@ -21,7 +21,7 @@
<ContentControl Margin="0,16,0,8" Content="{Binding Mode}">
<ContentControl.DataTemplates>
<DataTemplate DataType="vm:DiscardAllMode">
<Grid RowDefinitions="32,32,32,Auto" ColumnDefinitions="120,*">
<Grid RowDefinitions="32,32,32,32,Auto" ColumnDefinitions="120,*">
<TextBlock Grid.Row="0" Grid.Column="0"
Margin="0,0,8,0"
HorizontalAlignment="Right"
@@ -30,14 +30,18 @@
Text="{DynamicResource Text.Discard.All}"/>
<CheckBox Grid.Row="1" Grid.Column="1"
Content="{DynamicResource Text.Discard.IncludeModified}"
IsChecked="{Binding IncludeModified, Mode=TwoWay}"/>
<CheckBox Grid.Row="2" Grid.Column="1"
Content="{DynamicResource Text.Discard.IncludeUntracked}"
IsChecked="{Binding IncludeUntracked, Mode=TwoWay}"/>
<CheckBox Grid.Row="2" Grid.Column="1"
<CheckBox Grid.Row="3" Grid.Column="1"
Content="{DynamicResource Text.Discard.IncludeIgnored}"
IsChecked="{Binding IncludeIgnored, Mode=TwoWay}"/>
<Grid Grid.Row="3" Grid.Column="1" ColumnDefinitions="Auto,*" Margin="0,6,0,0">
<Grid Grid.Row="4" Grid.Column="1" ColumnDefinitions="Auto,*" Margin="0,6,0,0">
<Path Grid.Column="0"
Width="14" Height="14"
Data="{StaticResource Icons.Error}"

View File

@@ -30,14 +30,14 @@
<DataTemplate DataType="m:Null">
<StackPanel Orientation="Horizontal">
<Path Width="14" Height="14" Data="{StaticResource Icons.Repositories}"/>
<SelectableTextBlock VerticalAlignment="Center" Text="{DynamicResource Text.ExecuteCustomAction.Repository}" Margin="8,0,0,0" IsTabStop="False"/>
<TextBlock VerticalAlignment="Center" Text="{DynamicResource Text.ExecuteCustomAction.Repository}" Margin="8,0,0,0" IsTabStop="False"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="m:Branch">
<StackPanel Orientation="Horizontal">
<Path Width="14" Height="14" Data="{StaticResource Icons.Branch}"/>
<SelectableTextBlock VerticalAlignment="Center" Text="{Binding FriendlyName}" Margin="8,0,0,0" IsTabStop="False"/>
<TextBlock VerticalAlignment="Center" Text="{Binding FriendlyName}" Margin="8,0,0,0" IsTabStop="False"/>
</StackPanel>
</DataTemplate>

View File

@@ -39,7 +39,7 @@ namespace SourceGit.Views
}
catch (Exception exception)
{
App.RaiseException(string.Empty, $"Failed to select parent folder: {exception.Message}");
Models.Notification.Send(null, $"Failed to select parent folder: {exception.Message}", true);
}
}
else

View File

@@ -268,13 +268,5 @@
HorizontalAlignment="Center" VerticalAlignment="Center"
IsVisible="{Binding IsLoading}"/>
</Grid>
<Border Grid.Row="3" x:Name="NotifyDonePanel" Background="Transparent" IsVisible="False" PointerPressed="OnCloseNotifyPanel">
<Border HorizontalAlignment="Center" VerticalAlignment="Center" Effect="drop-shadow(0 0 12 #80000000)">
<Border CornerRadius="8" Background="{DynamicResource Brush.Popup}" Padding="32">
<Path Width="52" Height="52" Data="{StaticResource Icons.Check}" Fill="Green"/>
</Border>
</Border>
</Border>
</Grid>
</v:ChromelessWindow>

View File

@@ -60,18 +60,12 @@ namespace SourceGit.Views
if (sender is Button { DataContext: ViewModels.FileHistoriesSingleRevision single })
{
await single.ResetToSelectedRevisionAsync();
NotifyDonePanel.IsVisible = true;
await new Alert().ShowAsync(this, "Reset to selected revision successfully.", false);
}
e.Handled = true;
}
private void OnCloseNotifyPanel(object _, PointerPressedEventArgs e)
{
NotifyDonePanel.IsVisible = false;
e.Handled = true;
}
private async void OnSaveAsPatch(object sender, RoutedEventArgs e)
{
if (sender is Button { DataContext: ViewModels.FileHistoriesCompareRevisions compare })
@@ -84,14 +78,16 @@ namespace SourceGit.Views
try
{
var storageFile = await StorageProvider.SaveFilePickerAsync(options);
if (storageFile != null)
await compare.SaveAsPatch(storageFile.Path.LocalPath);
if (storageFile == null)
return;
NotifyDonePanel.IsVisible = true;
var succ = await compare.SaveAsPatch(storageFile.Path.LocalPath);
if (succ)
await new Alert().ShowAsync(this, "Saved as patch successfully.", false);
}
catch (Exception exception)
{
App.RaiseException(string.Empty, $"Failed to save as patch: {exception.Message}");
await new Alert().ShowAsync(this, $"Failed to save as patch: {exception.Message}", true);
}
e.Handled = true;

Some files were not shown because too many files have changed in this diff Show More