mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-05-20 11:44:59 +00:00
feat: implement IPC for opening repositories in new tabs (#1185)
* refactor: improve diff handling for EOL changes and enhance text diff display - Updated `Diff.cs` to streamline whitespace handling in diff arguments. - Enhanced `DiffContext.cs` to check for EOL changes when old and new hashes differ, creating a text diff if necessary. - Added support for showing end-of-line symbols in `TextDiffView.axaml.cs` options. * localization: update translations to include EOF handling in ignore whitespace messages - Modified the ignore whitespace text in multiple language files to specify that EOF changes are also ignored. - Ensured consistency across all localization files for the patch application feature. * revert: Typo in DiffResult comment * revert: update diff arguments to ignore CR at EOL in whitespace handling (like before changes) * revert: update translations to remove EOF references in Text.Apply.IgnoreWS and fixed typo in Text.Diff.IgnoreWhitespace (EOF => EOL) * feat: add workspace-specific default clone directory functionality - Implemented logic in Clone.cs to set ParentFolder based on the active workspace's DefaultCloneDir if available, falling back to the global GitDefaultCloneDir. - Added DefaultCloneDir property to Workspace.cs to store the default clone directory for each workspace. - Updated ConfigureWorkspace.axaml to include a TextBox and Button for setting the DefaultCloneDir in the UI. - Implemented folder selection functionality in ConfigureWorkspace.axaml.cs to allow users to choose a directory for cloning. - This closes issue #1145 * feat: implement IPC for opening repositories in new tabs - Added functionality to send repository paths to an existing instance of the application using named pipes. - Introduced a new preference option to open repositories in a new tab instead of a new window. - Updated UI to include a checkbox for the new preference. - Enhanced the handling of IPC server lifecycle based on the new preference setting. - This closes issue #1184 --------- Co-authored-by: mpagani <massimo.pagani@unitec-group.com>
This commit is contained in:
parent
558eb7c9ac
commit
09c0edef8e
4 changed files with 157 additions and 3 deletions
145
src/App.axaml.cs
145
src/App.axaml.cs
|
@ -2,12 +2,14 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
@ -46,6 +48,8 @@ namespace SourceGit
|
||||||
Environment.Exit(exitTodo);
|
Environment.Exit(exitTodo);
|
||||||
else if (TryLaunchAsRebaseMessageEditor(args, out int exitMessage))
|
else if (TryLaunchAsRebaseMessageEditor(args, out int exitMessage))
|
||||||
Environment.Exit(exitMessage);
|
Environment.Exit(exitMessage);
|
||||||
|
else if (TrySendArgsToExistingInstance(args))
|
||||||
|
Environment.Exit(0);
|
||||||
else
|
else
|
||||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||||
}
|
}
|
||||||
|
@ -77,6 +81,44 @@ namespace SourceGit
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TrySendArgsToExistingInstance(string[] args)
|
||||||
|
{
|
||||||
|
if (args == null || args.Length != 1 || !Directory.Exists(args[0]))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var pref = ViewModels.Preferences.Instance;
|
||||||
|
|
||||||
|
if (!pref.OpenReposInNewTab)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var processes = Process.GetProcessesByName("SourceGit");
|
||||||
|
|
||||||
|
if (processes.Length <= 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
using var client = new NamedPipeClientStream(".", "SourceGitIPC", PipeDirection.Out);
|
||||||
|
|
||||||
|
client.Connect(1000);
|
||||||
|
|
||||||
|
if (client.IsConnected)
|
||||||
|
{
|
||||||
|
using var writer = new StreamWriter(client);
|
||||||
|
|
||||||
|
writer.WriteLine(args[0]);
|
||||||
|
writer.Flush();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static void LogException(Exception ex)
|
private static void LogException(Exception ex)
|
||||||
{
|
{
|
||||||
if (ex == null)
|
if (ex == null)
|
||||||
|
@ -328,7 +370,13 @@ namespace SourceGit
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
|
||||||
var pref = ViewModels.Preferences.Instance;
|
var pref = ViewModels.Preferences.Instance;
|
||||||
pref.PropertyChanged += (_, _) => pref.Save();
|
|
||||||
|
pref.PropertyChanged += (s, e) => {
|
||||||
|
pref.Save();
|
||||||
|
|
||||||
|
if (e.PropertyName.Equals(nameof(ViewModels.Preferences.OpenReposInNewTab)))
|
||||||
|
HandleOpenReposInNewTabChanged();
|
||||||
|
};
|
||||||
|
|
||||||
SetLocale(pref.Locale);
|
SetLocale(pref.Locale);
|
||||||
SetTheme(pref.Theme, pref.ThemeOverrides);
|
SetTheme(pref.Theme, pref.ThemeOverrides);
|
||||||
|
@ -488,13 +536,104 @@ namespace SourceGit
|
||||||
_launcher = new ViewModels.Launcher(startupRepo);
|
_launcher = new ViewModels.Launcher(startupRepo);
|
||||||
desktop.MainWindow = new Views.Launcher() { DataContext = _launcher };
|
desktop.MainWindow = new Views.Launcher() { DataContext = _launcher };
|
||||||
|
|
||||||
#if !DISABLE_UPDATE_DETECTION
|
|
||||||
var pref = ViewModels.Preferences.Instance;
|
var pref = ViewModels.Preferences.Instance;
|
||||||
|
|
||||||
|
HandleOpenReposInNewTabChanged();
|
||||||
|
|
||||||
|
#if !DISABLE_UPDATE_DETECTION
|
||||||
if (pref.ShouldCheck4UpdateOnStartup())
|
if (pref.ShouldCheck4UpdateOnStartup())
|
||||||
Check4Update();
|
Check4Update();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleOpenReposInNewTabChanged()
|
||||||
|
{
|
||||||
|
var pref = ViewModels.Preferences.Instance;
|
||||||
|
|
||||||
|
if (pref.OpenReposInNewTab)
|
||||||
|
{
|
||||||
|
if (_ipcServerTask == null || _ipcServerTask.IsCompleted)
|
||||||
|
{
|
||||||
|
// Start IPC server
|
||||||
|
_ipcServerCts = new CancellationTokenSource();
|
||||||
|
_ipcServerTask = Task.Run(() => StartIPCServer(_ipcServerCts.Token));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Stop IPC server if running
|
||||||
|
if (_ipcServerCts != null && !_ipcServerCts.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
_ipcServerCts.Cancel();
|
||||||
|
_ipcServerCts.Dispose();
|
||||||
|
_ipcServerCts = null;
|
||||||
|
}
|
||||||
|
_ipcServerTask = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartIPCServer(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
using var server = new NamedPipeServerStream("SourceGitIPC", PipeDirection.In);
|
||||||
|
|
||||||
|
// Use WaitForConnectionAsync with cancellation token
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Task connectionTask = server.WaitForConnectionAsync(cancellationToken);
|
||||||
|
connectionTask.Wait(cancellationToken);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (AggregateException ae) when (ae.InnerExceptions.Any(e => e is OperationCanceledException))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the connection
|
||||||
|
using var reader = new StreamReader(server);
|
||||||
|
var repoPath = reader.ReadLine();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(repoPath) && Directory.Exists(repoPath))
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var test = new Commands.QueryRepositoryRootPath(repoPath).ReadToEnd();
|
||||||
|
|
||||||
|
if (test.IsSuccess && !string.IsNullOrEmpty(test.StdOut))
|
||||||
|
{
|
||||||
|
var repoRootPath = test.StdOut.Trim();
|
||||||
|
var pref = ViewModels.Preferences.Instance;
|
||||||
|
var node = pref.FindOrAddNodeByRepositoryPath(repoRootPath, null, false);
|
||||||
|
|
||||||
|
ViewModels.Welcome.Instance.Refresh();
|
||||||
|
|
||||||
|
_launcher?.OpenRepositoryInTab(node, null);
|
||||||
|
|
||||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && desktop.MainWindow != null)
|
||||||
|
desktop.MainWindow.Activate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// Pipe server failed, we can just exit the thread
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void Check4Update(bool manually = false)
|
private void Check4Update(bool manually = false)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
|
@ -584,5 +723,7 @@ namespace SourceGit
|
||||||
private ResourceDictionary _activeLocale = null;
|
private ResourceDictionary _activeLocale = null;
|
||||||
private ResourceDictionary _themeOverrides = null;
|
private ResourceDictionary _themeOverrides = null;
|
||||||
private ResourceDictionary _fontsOverrides = null;
|
private ResourceDictionary _fontsOverrides = null;
|
||||||
|
private Task _ipcServerTask = null;
|
||||||
|
private CancellationTokenSource _ipcServerCts = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -752,4 +752,5 @@
|
||||||
<x:String x:Key="Text.Worktree.Lock" xml:space="preserve">Lock</x:String>
|
<x:String x:Key="Text.Worktree.Lock" xml:space="preserve">Lock</x:String>
|
||||||
<x:String x:Key="Text.Worktree.Remove" xml:space="preserve">Remove</x:String>
|
<x:String x:Key="Text.Worktree.Remove" xml:space="preserve">Remove</x:String>
|
||||||
<x:String x:Key="Text.Worktree.Unlock" xml:space="preserve">Unlock</x:String>
|
<x:String x:Key="Text.Worktree.Unlock" xml:space="preserve">Unlock</x:String>
|
||||||
|
<x:String x:Key="Text.Preferences.General.OpenReposInNewTab" xml:space="preserve">Open repositories in new tab instead of new window</x:String>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|
|
@ -90,6 +90,12 @@ namespace SourceGit.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool OpenReposInNewTab
|
||||||
|
{
|
||||||
|
get => _openReposInNewTab;
|
||||||
|
set => SetProperty(ref _openReposInNewTab, value);
|
||||||
|
}
|
||||||
|
|
||||||
public bool UseSystemWindowFrame
|
public bool UseSystemWindowFrame
|
||||||
{
|
{
|
||||||
get => _useSystemWindowFrame;
|
get => _useSystemWindowFrame;
|
||||||
|
@ -632,6 +638,7 @@ namespace SourceGit.ViewModels
|
||||||
private string _defaultFontFamily = string.Empty;
|
private string _defaultFontFamily = string.Empty;
|
||||||
private string _monospaceFontFamily = string.Empty;
|
private string _monospaceFontFamily = string.Empty;
|
||||||
private bool _onlyUseMonoFontInEditor = false;
|
private bool _onlyUseMonoFontInEditor = false;
|
||||||
|
private bool _openReposInNewTab = false;
|
||||||
private bool _useSystemWindowFrame = false;
|
private bool _useSystemWindowFrame = false;
|
||||||
private double _defaultFontSize = 13;
|
private double _defaultFontSize = 13;
|
||||||
private double _editorFontSize = 13;
|
private double _editorFontSize = 13;
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
<TabItem.Header>
|
<TabItem.Header>
|
||||||
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preferences.General}"/>
|
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preferences.General}"/>
|
||||||
</TabItem.Header>
|
</TabItem.Header>
|
||||||
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32,32,Auto" ColumnDefinitions="Auto,*">
|
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32,32,32,Auto" ColumnDefinitions="Auto,*">
|
||||||
<TextBlock Grid.Row="0" Grid.Column="0"
|
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||||
Text="{DynamicResource Text.Preferences.General.Locale}"
|
Text="{DynamicResource Text.Preferences.General.Locale}"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
|
@ -142,6 +142,11 @@
|
||||||
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowChildren, Mode=TwoWay}"/>
|
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowChildren, Mode=TwoWay}"/>
|
||||||
|
|
||||||
<CheckBox Grid.Row="8" Grid.Column="1"
|
<CheckBox Grid.Row="8" Grid.Column="1"
|
||||||
|
Height="32"
|
||||||
|
Content="{DynamicResource Text.Preferences.General.OpenReposInNewTab}"
|
||||||
|
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=OpenReposInNewTab, Mode=TwoWay}"/>
|
||||||
|
|
||||||
|
<CheckBox Grid.Row="9" Grid.Column="1"
|
||||||
Height="32"
|
Height="32"
|
||||||
Content="{DynamicResource Text.Preferences.General.Check4UpdatesOnStartup}"
|
Content="{DynamicResource Text.Preferences.General.Check4UpdatesOnStartup}"
|
||||||
IsVisible="{x:Static s:App.IsCheckForUpdateCommandVisible}"
|
IsVisible="{x:Static s:App.IsCheckForUpdateCommandVisible}"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue