Merge branch 'release/v2025.08'

This commit is contained in:
leo 2025-03-10 09:36:44 +08:00
commit 18d3b9560b
No known key found for this signature in database
63 changed files with 781 additions and 495 deletions

View file

@ -19,14 +19,25 @@ jobs:
os: macos-latest
runtime: osx-arm64
- name : Linux
os: ubuntu-20.04
os: ubuntu-latest
runtime: linux-x64
container: ubuntu:20.04
- name : Linux (arm64)
os: ubuntu-20.04
os: ubuntu-latest
runtime: linux-arm64
container: ubuntu:20.04
name: Build ${{ matrix.name }}
runs-on: ${{ matrix.os }}
container: ${{ matrix.container || '' }}
steps:
- name: Install common CLI tools
if: ${{ startsWith(matrix.runtime, 'linux-') }}
run: |
export DEBIAN_FRONTEND=noninteractive
ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime
apt-get update
apt-get install -y sudo
sudo apt-get install -y curl wget git unzip zip libicu66 tzdata clang
- name: Checkout sources
uses: actions/checkout@v4
- name: Setup .NET
@ -47,7 +58,7 @@ jobs:
if: ${{ matrix.runtime == 'linux-arm64' }}
run: |
sudo apt-get update
sudo apt-get install clang llvm gcc-aarch64-linux-gnu zlib1g-dev:arm64
sudo apt-get install -y llvm gcc-aarch64-linux-gnu zlib1g-dev:arm64
- name: Build
run: dotnet build -c Release
- name: Publish

View file

@ -9,10 +9,10 @@ on:
jobs:
windows:
name: Package Windows
runs-on: ubuntu-latest
runs-on: windows-2019
strategy:
matrix:
runtime: [win-x64, win-arm64]
runtime: [ win-x64, win-arm64 ]
steps:
- name: Checkout sources
uses: actions/checkout@v4
@ -22,6 +22,7 @@ jobs:
name: sourcegit.${{ matrix.runtime }}
path: build/SourceGit
- name: Package
shell: bash
env:
VERSION: ${{ inputs.version }}
RUNTIME: ${{ matrix.runtime }}
@ -69,6 +70,7 @@ jobs:
linux:
name: Package Linux
runs-on: ubuntu-latest
container: ubuntu:20.04
strategy:
matrix:
runtime: [linux-x64, linux-arm64]
@ -77,9 +79,10 @@ jobs:
uses: actions/checkout@v4
- name: Download package dependencies
run: |
sudo add-apt-repository universe
sudo apt-get update
sudo apt-get install desktop-file-utils rpm libfuse2
export DEBIAN_FRONTEND=noninteractive
ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime
apt-get update
apt-get install -y curl wget git dpkg-dev fakeroot tzdata zip unzip desktop-file-utils rpm libfuse2 file build-essential binutils
- name: Download build
uses: actions/download-artifact@v4
with:
@ -89,6 +92,7 @@ jobs:
env:
VERSION: ${{ inputs.version }}
RUNTIME: ${{ matrix.runtime }}
APPIMAGE_EXTRACT_AND_RUN: 1
run: |
mkdir build/SourceGit
tar -xf "build/sourcegit.${{ matrix.runtime }}.tar" -C build/SourceGit

View file

@ -47,7 +47,7 @@
## Translation Status
[![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.21%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-99.87%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-91.80%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-%E2%88%9A-brightgreen)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-91.53%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-99.07%25-yellow)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-%E2%88%9A-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-%E2%88%9A-brightgreen)](TRANSLATION.md)
[![en_US](https://img.shields.io/badge/en__US-%E2%88%9A-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.08%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-%E2%88%9A-brightgreen)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-91.68%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-99.87%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-91.41%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-%E2%88%9A-brightgreen)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-%E2%88%9A-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-%E2%88%9A-brightgreen)](TRANSLATION.md)
> [!NOTE]
> You can find the missing keys in [TRANSLATION.md](TRANSLATION.md)

View file

@ -60,6 +60,8 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DEBIAN", "DEBIAN", "{F101849D-BDB7-40D4-A516-751150C3CCFC}"
ProjectSection(SolutionItems) = preProject
build\resources\deb\DEBIAN\control = build\resources\deb\DEBIAN\control
build\resources\deb\DEBIAN\preinst = build\resources\deb\DEBIAN\preinst
build\resources\deb\DEBIAN\prerm = build\resources\deb\DEBIAN\prerm
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "rpm", "rpm", "{9BA0B044-0CC9-46F8-B551-204F149BF45D}"

View file

@ -1,4 +1,4 @@
### de_DE.axaml: 99.21%
### de_DE.axaml: 99.08%
<details>
@ -9,21 +9,22 @@
- Text.Diff.First
- Text.Diff.Last
- Text.Preferences.AI.Streaming
- Text.Preferences.Appearance.EditorTabWidth
- Text.StashCM.SaveAsPatch
</details>
### es_ES.axaml: 99.87%
### es_ES.axaml: 100.00%
<details>
<summary>Missing Keys</summary>
- Text.StashCM.SaveAsPatch
</details>
### fr_FR.axaml: 91.80%
### fr_FR.axaml: 91.68%
<details>
@ -56,6 +57,7 @@
- Text.MergeMultiple.Strategy
- Text.MergeMultiple.Targets
- Text.Preferences.AI.Streaming
- Text.Preferences.Appearance.EditorTabWidth
- Text.Preferences.Appearance.FontSize
- Text.Preferences.Appearance.FontSize.Default
- Text.Preferences.Appearance.FontSize.Editor
@ -94,17 +96,17 @@
</details>
### it_IT.axaml: 100.00%
### it_IT.axaml: 99.87%
<details>
<summary>Missing Keys</summary>
- Text.Preferences.Appearance.EditorTabWidth
</details>
### pt_BR.axaml: 91.53%
### pt_BR.axaml: 91.41%
<details>
@ -148,6 +150,7 @@
- Text.MergeMultiple.Strategy
- Text.MergeMultiple.Targets
- Text.Preferences.AI.Streaming
- Text.Preferences.Appearance.EditorTabWidth
- Text.Preferences.General.DateFormat
- Text.Preferences.General.ShowChildren
- Text.Preferences.Git.SSLVerify
@ -177,19 +180,13 @@
</details>
### ru_RU.axaml: 99.07%
### ru_RU.axaml: 100.00%
<details>
<summary>Missing Keys</summary>
- Text.BranchCM.CustomAction
- Text.BranchUpstreamInvalid
- Text.Configure.CustomAction.Scope.Branch
- Text.Diff.First
- Text.Diff.Last
- Text.Preferences.AI.Streaming
- Text.StashCM.SaveAsPatch
</details>

View file

@ -1 +1 @@
2025.07
2025.08

View file

@ -0,0 +1,32 @@
#!/bin/sh
set -e
# summary of how this script can be called:
# * <new-preinst> `install'
# * <new-preinst> `install' <old-version>
# * <new-preinst> `upgrade' <old-version>
# * <old-preinst> `abort-upgrade' <new-version>
# for details, see http://www.debian.org/doc/debian-policy/
case "$1" in
install|upgrade)
# Check if SourceGit is running and stop it
if pgrep -f '/opt/sourcegit/sourcegit' > /dev/null; then
echo "Stopping running SourceGit instance..."
pkill -f '/opt/sourcegit/sourcegit' || true
# Give the process a moment to terminate
sleep 1
fi
;;
abort-upgrade)
;;
*)
echo "preinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
exit 0

View file

@ -0,0 +1,35 @@
#!/bin/sh
set -e
# summary of how this script can be called:
# * <prerm> `remove'
# * <old-prerm> `upgrade' <new-version>
# * <new-prerm> `failed-upgrade' <old-version>
# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
# * <deconfigured's-prerm> `deconfigure' `in-favour'
# <package-being-installed> <version> `removing'
# <conflicting-package> <version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package
case "$1" in
remove|upgrade|deconfigure)
if pgrep -f '/opt/sourcegit/sourcegit' > /dev/null; then
echo "Stopping running SourceGit instance..."
pkill -f '/opt/sourcegit/sourcegit' || true
# Give the process a moment to terminate
sleep 1
fi
;;
failed-upgrade)
;;
*)
echo "prerm called with unknown argument \`$1'" >&2
exit 1
;;
esac
exit 0

View file

@ -9,4 +9,8 @@ cd build
rm -rf SourceGit/*.pdb
zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then
powershell -Command "Compress-Archive -Path SourceGit\\* -DestinationPath \"sourcegit_$VERSION.$RUNTIME.zip\" -Force"
else
zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit
fi

View file

@ -445,10 +445,14 @@ namespace SourceGit
var file = args[1];
if (!File.Exists(file))
{
desktop.Shutdown(-1);
else
desktop.MainWindow = new Views.StandaloneCommitMessageEditor(file);
return true;
}
var editor = new Views.StandaloneCommitMessageEditor();
editor.SetFile(file);
desktop.MainWindow = editor;
return true;
}
@ -461,7 +465,9 @@ namespace SourceGit
var args = desktop.Args;
if (args?.Length > 0)
{
desktop.MainWindow = new Views.Askpass(args[0]);
var askpass = new Views.Askpass();
askpass.TxtDescription.Text = args[0];
desktop.MainWindow = askpass;
return true;
}

View file

@ -68,6 +68,18 @@ namespace SourceGit.Commands
return;
}
if (line.StartsWith("deleted file mode ", StringComparison.Ordinal))
{
_result.OldMode = line.Substring(18);
return;
}
if (line.StartsWith("new file mode ", StringComparison.Ordinal))
{
_result.NewMode = line.Substring(14);
return;
}
if (_result.IsBinary)
return;

View file

@ -0,0 +1,17 @@
namespace SourceGit.Commands
{
public class IsCommitSHA : Command
{
public IsCommitSHA(string repo, string hash)
{
WorkingDirectory = repo;
Args = $"cat-file -t {hash}";
}
public bool Result()
{
var rs = ReadToEnd();
return rs.IsSuccess && rs.StdOut.Trim().Equals("commit");
}
}
}

View file

@ -11,10 +11,11 @@ namespace SourceGit.Commands
Context = repo;
TraitErrorAsOutput = true;
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
Args = "pull --verbose --progress --tags ";
Args = "pull --verbose --progress ";
if (useRebase)
Args += "--rebase ";
Args += "--rebase=true ";
if (noTags)
Args += "--no-tags ";

View file

@ -3,18 +3,18 @@ using System.Collections.Generic;
namespace SourceGit.Commands
{
public class QueryCommitsWithFullMessage : Command
public class QueryCommitsForInteractiveRebase : Command
{
public QueryCommitsWithFullMessage(string repo, string args)
public QueryCommitsForInteractiveRebase(string repo, string on)
{
_boundary = $"----- BOUNDARY OF COMMIT {Guid.NewGuid()} -----";
WorkingDirectory = repo;
Context = repo;
Args = $"log --date-order --no-show-signature --decorate=full --pretty=format:\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_boundary}\" {args}";
Args = $"log --date-order --no-show-signature --decorate=full --pretty=format:\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_boundary}\" {on}..HEAD";
}
public List<Models.CommitWithMessage> Result()
public List<Models.InteractiveCommit> Result()
{
var rs = ReadToEnd();
if (!rs.IsSuccess)
@ -29,7 +29,7 @@ namespace SourceGit.Commands
switch (nextPartIdx)
{
case 0:
_current = new Models.CommitWithMessage();
_current = new Models.InteractiveCommit();
_current.Commit.SHA = line;
_commits.Add(_current);
break;
@ -52,7 +52,7 @@ namespace SourceGit.Commands
_current.Commit.CommitterTime = ulong.Parse(line);
break;
default:
var boundary = rs.StdOut.IndexOf(_boundary, end + 1);
var boundary = rs.StdOut.IndexOf(_boundary, end + 1, StringComparison.Ordinal);
if (boundary > end)
{
_current.Message = rs.StdOut.Substring(start, boundary - start - 1);
@ -88,8 +88,8 @@ namespace SourceGit.Commands
_current.Commit.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
}
private List<Models.CommitWithMessage> _commits = new List<Models.CommitWithMessage>();
private Models.CommitWithMessage _current = null;
private List<Models.InteractiveCommit> _commits = [];
private Models.InteractiveCommit _current = null;
private string _boundary = "";
}
}

View file

@ -0,0 +1,21 @@
namespace SourceGit.Commands
{
public class QueryRevisionByRefName : Command
{
public QueryRevisionByRefName(string repo, string refname)
{
WorkingDirectory = repo;
Context = repo;
Args = $"rev-parse {refname}";
}
public string Result()
{
var rs = ReadToEnd();
if (rs.IsSuccess && !string.IsNullOrEmpty(rs.StdOut))
return rs.StdOut.Trim();
return null;
}
}
}

View file

@ -112,9 +112,9 @@ namespace SourceGit.Models
}
}
public class CommitWithMessage
public class CommitFullMessage
{
public Commit Commit { get; set; } = new Commit();
public string Message { get; set; } = "";
public string Message { get; set; } = string.Empty;
public List<Hyperlink> Links { get; set; } = [];
}
}

View file

@ -681,6 +681,18 @@ namespace SourceGit.Models
public TextDiff TextDiff { get; set; } = null;
public LFSDiff LFSDiff { get; set; } = null;
public string FileModeChange => string.IsNullOrEmpty(OldMode) ? string.Empty : $"{OldMode} → {NewMode}";
public string FileModeChange
{
get
{
if (string.IsNullOrEmpty(OldMode) && string.IsNullOrEmpty(NewMode))
return string.Empty;
var oldDisplay = string.IsNullOrEmpty(OldMode) ? "0" : OldMode;
var newDisplay = string.IsNullOrEmpty(NewMode) ? "0" : NewMode;
return $"{oldDisplay} → {newDisplay}";
}
}
}
}

View file

@ -12,6 +12,12 @@ namespace SourceGit.Models
Drop,
}
public class InteractiveCommit
{
public Commit Commit { get; set; } = new Commit();
public string Message { get; set; } = string.Empty;
}
public class InteractiveRebaseJob
{
public string SHA { get; set; } = string.Empty;

View file

@ -29,6 +29,6 @@ namespace SourceGit.Models
public class RevisionSubmodule
{
public Commit Commit { get; set; } = null;
public string FullMessage { get; set; } = string.Empty;
public CommitFullMessage FullMessage { get; set; } = null;
}
}

View file

@ -464,6 +464,7 @@
<x:String x:Key="Text.Preferences.AI.Streaming" xml:space="preserve">Enable Streaming</x:String>
<x:String x:Key="Text.Preferences.Appearance" xml:space="preserve">APPEARANCE</x:String>
<x:String x:Key="Text.Preferences.Appearance.DefaultFont" xml:space="preserve">Default Font</x:String>
<x:String x:Key="Text.Preferences.Appearance.EditorTabWidth" xml:space="preserve">Editor Tab Width</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize" xml:space="preserve">Font Size</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize.Default" xml:space="preserve">Default</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize.Editor" xml:space="preserve">Editor</x:String>

View file

@ -24,17 +24,17 @@
<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">APLICAR CÓMO MENSAJE DE COMMIT</x:String>
<x:String x:Key="Text.Apply" xml:space="preserve">Aplicar Patch</x:String>
<x:String x:Key="Text.Apply" xml:space="preserve">Aplicar Parche</x:String>
<x:String x:Key="Text.Apply.Error" xml:space="preserve">Error</x:String>
<x:String x:Key="Text.Apply.Error.Desc" xml:space="preserve">Genera errores y se niega a aplicar el patch</x:String>
<x:String x:Key="Text.Apply.Error.Desc" xml:space="preserve">Genera errores y se niega a aplicar el parche</x:String>
<x:String x:Key="Text.Apply.ErrorAll" xml:space="preserve">Error Todo</x:String>
<x:String x:Key="Text.Apply.ErrorAll.Desc" xml:space="preserve">Similar a 'error', pero muestra más</x:String>
<x:String x:Key="Text.Apply.File" xml:space="preserve">Archivo Patch:</x:String>
<x:String x:Key="Text.Apply.File" xml:space="preserve">Archivo del Parche:</x:String>
<x:String x:Key="Text.Apply.File.Placeholder" xml:space="preserve">Seleccionar archivo .patch para aplicar</x:String>
<x:String x:Key="Text.Apply.IgnoreWS" xml:space="preserve">Ignorar cambios de espacios en blanco</x:String>
<x:String x:Key="Text.Apply.NoWarn" xml:space="preserve">Sin Advertencia</x:String>
<x:String x:Key="Text.Apply.NoWarn.Desc" xml:space="preserve">Desactiva la advertencia de espacios en blanco al final</x:String>
<x:String x:Key="Text.Apply.Title" xml:space="preserve">Aplicar Patch</x:String>
<x:String x:Key="Text.Apply.Title" xml:space="preserve">Aplicar Parche</x:String>
<x:String x:Key="Text.Apply.Warn" xml:space="preserve">Advertencia</x:String>
<x:String x:Key="Text.Apply.Warn.Desc" xml:space="preserve">Genera advertencias para algunos de estos errores, pero aplica</x:String>
<x:String x:Key="Text.Apply.WS" xml:space="preserve">Espacios en Blanco:</x:String>
@ -126,7 +126,7 @@
<x:String x:Key="Text.CommitCM.Reset" xml:space="preserve">Reset ${0}$ hasta Aquí</x:String>
<x:String x:Key="Text.CommitCM.Revert" xml:space="preserve">Revertir Commit</x:String>
<x:String x:Key="Text.CommitCM.Reword" xml:space="preserve">Reescribir</x:String>
<x:String x:Key="Text.CommitCM.SaveAsPatch" xml:space="preserve">Guardar como Patch...</x:String>
<x:String x:Key="Text.CommitCM.SaveAsPatch" xml:space="preserve">Guardar como Parche...</x:String>
<x:String x:Key="Text.CommitCM.Squash" xml:space="preserve">Squash en Parent</x:String>
<x:String x:Key="Text.CommitCM.SquashCommitsSinceThis" xml:space="preserve">Squash Commits Hijos hasta Aquí</x:String>
<x:String x:Key="Text.CommitDetail.Changes" xml:space="preserve">CAMBIOS</x:String>
@ -259,7 +259,7 @@
<x:String x:Key="Text.Diff.Next" xml:space="preserve">Siguiente Diferencia</x:String>
<x:String x:Key="Text.Diff.NoChange" xml:space="preserve">SIN CAMBIOS O SOLO CAMBIOS DE EOL</x:String>
<x:String x:Key="Text.Diff.Prev" xml:space="preserve">Diferencia Anterior</x:String>
<x:String x:Key="Text.Diff.SaveAsPatch" xml:space="preserve">Guardar como Patch</x:String>
<x:String x:Key="Text.Diff.SaveAsPatch" xml:space="preserve">Guardar como Parche</x:String>
<x:String x:Key="Text.Diff.ShowHiddenSymbols" xml:space="preserve">Mostrar símbolos ocultos</x:String>
<x:String x:Key="Text.Diff.SideBySide" xml:space="preserve">Diferencia Lado a Lado</x:String>
<x:String x:Key="Text.Diff.Submodule" xml:space="preserve">SUBMÓDULO</x:String>
@ -300,7 +300,7 @@
<x:String x:Key="Text.FileCM.DiscardSelectedLines" xml:space="preserve">Descartar Cambios en Línea(s) Seleccionada(s)</x:String>
<x:String x:Key="Text.FileCM.OpenWithExternalMerger" xml:space="preserve">Abrir Herramienta de Merge Externa</x:String>
<x:String x:Key="Text.FileCM.ResolveUsing" xml:space="preserve">Resolver usando ${0}$</x:String>
<x:String x:Key="Text.FileCM.SaveAsPatch" xml:space="preserve">Guardar Como Patch...</x:String>
<x:String x:Key="Text.FileCM.SaveAsPatch" xml:space="preserve">Guardar como Parche...</x:String>
<x:String x:Key="Text.FileCM.Stage" xml:space="preserve">Stage</x:String>
<x:String x:Key="Text.FileCM.StageMulti" xml:space="preserve">Stage {0} archivos</x:String>
<x:String x:Key="Text.FileCM.StageSelectedLines" xml:space="preserve">Stage Cambios en Línea(s) Seleccionada(s)</x:String>
@ -468,6 +468,7 @@
<x:String x:Key="Text.Preferences.AI.Streaming" xml:space="preserve">Activar Transmisión</x:String>
<x:String x:Key="Text.Preferences.Appearance" xml:space="preserve">APARIENCIA</x:String>
<x:String x:Key="Text.Preferences.Appearance.DefaultFont" xml:space="preserve">Fuente por defecto</x:String>
<x:String x:Key="Text.Preferences.Appearance.EditorTabWidth" xml:space="preserve">Ancho de la Pestaña del Editor</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize" xml:space="preserve">Tamaño de fuente</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize.Default" xml:space="preserve">Por defecto</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize.Editor" xml:space="preserve">Editor</x:String>
@ -638,7 +639,7 @@
<x:String x:Key="Text.Running" xml:space="preserve">Ejecutando. Por favor espera...</x:String>
<x:String x:Key="Text.Save" xml:space="preserve">GUARDAR</x:String>
<x:String x:Key="Text.SaveAs" xml:space="preserve">Guardar Como...</x:String>
<x:String x:Key="Text.SaveAsPatchSuccess" xml:space="preserve">¡El patch se ha guardado exitosamente!</x:String>
<x:String x:Key="Text.SaveAsPatchSuccess" xml:space="preserve">¡El parche se ha guardado exitosamente!</x:String>
<x:String x:Key="Text.ScanRepositories" xml:space="preserve">Escanear Repositorios</x:String>
<x:String x:Key="Text.ScanRepositories.RootDir" xml:space="preserve">Directorio Raíz:</x:String>
<x:String x:Key="Text.SelfUpdate" xml:space="preserve">Buscar Actualizaciones...</x:String>
@ -672,6 +673,7 @@
<x:String x:Key="Text.StashCM.Apply" xml:space="preserve">Aplicar</x:String>
<x:String x:Key="Text.StashCM.Drop" xml:space="preserve">Eliminar</x:String>
<x:String x:Key="Text.StashCM.Pop" xml:space="preserve">Pop</x:String>
<x:String x:Key="Text.StashCM.SaveAsPatch" xml:space="preserve">Guardar como Parche...</x:String>
<x:String x:Key="Text.StashDropConfirm" xml:space="preserve">Eliminar Stash</x:String>
<x:String x:Key="Text.StashDropConfirm.Label" xml:space="preserve">Eliminar:</x:String>
<x:String x:Key="Text.Stashes" xml:space="preserve">Stashes</x:String>

View file

@ -20,21 +20,21 @@
<x:String x:Key="Text.AddWorktree.Name.Placeholder" xml:space="preserve">Имя целевого каталога по умолчанию. (необязательно)</x:String>
<x:String x:Key="Text.AddWorktree.Tracking" xml:space="preserve">Отслеживание ветки:</x:String>
<x:String x:Key="Text.AddWorktree.Tracking.Toggle" xml:space="preserve">Отслеживание внешней ветки</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">OpenAI Ассистент</x:String>
<x:String x:Key="Text.AIAssistant" xml:space="preserve">Помощник OpenAI</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>
<x:String x:Key="Text.Apply" xml:space="preserve">Исправить </x:String>
<x:String x:Key="Text.Apply.Error" xml:space="preserve">Ошибка</x:String>
<x:String x:Key="Text.Apply.Error.Desc" xml:space="preserve">Выдает ошибки и отказывается применять исправление</x:String>
<x:String x:Key="Text.Apply.Error.Desc" xml:space="preserve">Выдает ошибки и отказывается применять заплатку</x:String>
<x:String x:Key="Text.Apply.ErrorAll" xml:space="preserve">Все ошибки</x:String>
<x:String x:Key="Text.Apply.ErrorAll.Desc" xml:space="preserve">Аналогично «ошибке», но показывает больше</x:String>
<x:String x:Key="Text.Apply.File" xml:space="preserve">Файл исправлений:</x:String>
<x:String x:Key="Text.Apply.File" xml:space="preserve">Файл заплатки:</x:String>
<x:String x:Key="Text.Apply.File.Placeholder" xml:space="preserve">Выберите файл .patch для применения</x:String>
<x:String x:Key="Text.Apply.IgnoreWS" xml:space="preserve">Игнорировать изменения пробелов</x:String>
<x:String x:Key="Text.Apply.NoWarn" xml:space="preserve">Нет предупреждений</x:String>
<x:String x:Key="Text.Apply.NoWarn.Desc" xml:space="preserve">Отключить предупреждения о пробелах в конце</x:String>
<x:String x:Key="Text.Apply.Title" xml:space="preserve">Применить исправление</x:String>
<x:String x:Key="Text.Apply.Title" xml:space="preserve">Применить заплатку</x:String>
<x:String x:Key="Text.Apply.Warn" xml:space="preserve">Предупреждать</x:String>
<x:String x:Key="Text.Apply.Warn.Desc" xml:space="preserve">Выдавать предупреждения о нескольких таких ошибках, но применять</x:String>
<x:String x:Key="Text.Apply.WS" xml:space="preserve">Пробел:</x:String>
@ -58,6 +58,7 @@
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">Сравнить с ГОЛОВОЙ (HEAD)</x:String>
<x:String x:Key="Text.BranchCM.CompareWithWorktree" xml:space="preserve">Сравнить с рабочим каталогом</x:String>
<x:String x:Key="Text.BranchCM.CopyName" xml:space="preserve">Копировать имя ветки</x:String>
<x:String x:Key="Text.BranchCM.CustomAction" xml:space="preserve">Изменить действие</x:String>
<x:String x:Key="Text.BranchCM.Delete" xml:space="preserve">Удалить ${0}$...</x:String>
<x:String x:Key="Text.BranchCM.DeleteMultiBranches" xml:space="preserve">Удалить выбранные {0} ветки</x:String>
<x:String x:Key="Text.BranchCM.DiscardAll" xml:space="preserve">Отклонить все изменения.</x:String>
@ -73,6 +74,7 @@
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">Переименовать ${0}$...</x:String>
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">Отслеживать ветку...</x:String>
<x:String x:Key="Text.BranchCompare" xml:space="preserve">Сравнение веток</x:String>
<x:String x:Key="Text.BranchUpstreamInvalid" xml:space="preserve">Недопустимая основная ветка!</x:String>
<x:String x:Key="Text.Bytes" xml:space="preserve">Байты</x:String>
<x:String x:Key="Text.Cancel" xml:space="preserve">ОТМЕНА</x:String>
<x:String x:Key="Text.ChangeCM.CheckoutThisRevision" xml:space="preserve">Сбросить эту ревизию</x:String>
@ -105,7 +107,7 @@
<x:String x:Key="Text.Clone.LocalName" xml:space="preserve">Локальное имя:</x:String>
<x:String x:Key="Text.Clone.LocalName.Placeholder" xml:space="preserve">Имя репозитория. (необязательно).</x:String>
<x:String x:Key="Text.Clone.ParentFolder" xml:space="preserve">Родительский каталог:</x:String>
<x:String x:Key="Text.Clone.RecurseSubmodules" xml:space="preserve">Инициализировать и обновить подмодуль</x:String>
<x:String x:Key="Text.Clone.RecurseSubmodules" xml:space="preserve">Создать и обновить подмодуль</x:String>
<x:String x:Key="Text.Clone.RemoteURL" xml:space="preserve">Адрес репозитория:</x:String>
<x:String x:Key="Text.Close" xml:space="preserve">ЗАКРЫТЬ</x:String>
<x:String x:Key="Text.CodeEditor" xml:space="preserve">Редактор</x:String>
@ -119,7 +121,7 @@
<x:String x:Key="Text.CommitCM.CustomAction" xml:space="preserve">Пользовательское действие</x:String>
<x:String x:Key="Text.CommitCM.InteractiveRebase" xml:space="preserve">Интерактивное перемещение (rebase -i) ${0}$ сюда</x:String>
<x:String x:Key="Text.CommitCM.Merge" xml:space="preserve">Влить в ${0}$</x:String>
<x:String x:Key="Text.CommitCM.MergeMultiple" xml:space="preserve">Влить ветки...</x:String>
<x:String x:Key="Text.CommitCM.MergeMultiple" xml:space="preserve">Влить ...</x:String>
<x:String x:Key="Text.CommitCM.Rebase" xml:space="preserve">Переместить ${0}$ сюда</x:String>
<x:String x:Key="Text.CommitCM.Reset" xml:space="preserve">Сбросить ${0}$ сюда</x:String>
<x:String x:Key="Text.CommitCM.Revert" xml:space="preserve">Отменить ревизию</x:String>
@ -137,7 +139,7 @@
<x:String x:Key="Text.CommitDetail.Info.Author" xml:space="preserve">АВТОР</x:String>
<x:String x:Key="Text.CommitDetail.Info.Changed" xml:space="preserve">ИЗМЕНЁННЫЙ</x:String>
<x:String x:Key="Text.CommitDetail.Info.Children" xml:space="preserve">ДОЧЕРНИЙ</x:String>
<x:String x:Key="Text.CommitDetail.Info.Committer" xml:space="preserve">РЕВИЗОР(ИСПОЛНИТЕЛЬ)</x:String>
<x:String x:Key="Text.CommitDetail.Info.Committer" xml:space="preserve">РЕВИЗОР (ИСПОЛНИТЕЛЬ)</x:String>
<x:String x:Key="Text.CommitDetail.Info.ContainsIn" xml:space="preserve">Найти все ветки с этой ревизией</x:String>
<x:String x:Key="Text.CommitDetail.Info.ContainsIn.Title" xml:space="preserve">ВЕТКИ С ЭТОЙ РЕВИЗИЕЙ</x:String>
<x:String x:Key="Text.CommitDetail.Info.GotoChangesPage" xml:space="preserve">Отображаются только первые 100 изменений. Смотрите все изменения на вкладке ИЗМЕНЕНИЯ.</x:String>
@ -158,6 +160,7 @@
<x:String x:Key="Text.Configure.CustomAction.Executable" xml:space="preserve">Исполняемый файл:</x:String>
<x:String x:Key="Text.Configure.CustomAction.Name" xml:space="preserve">Имя:</x:String>
<x:String x:Key="Text.Configure.CustomAction.Scope" xml:space="preserve">Диапазон:</x:String>
<x:String x:Key="Text.Configure.CustomAction.Scope.Branch" xml:space="preserve">Ветка</x:String>
<x:String x:Key="Text.Configure.CustomAction.Scope.Commit" xml:space="preserve">Ревизия</x:String>
<x:String x:Key="Text.Configure.CustomAction.Scope.Repository" xml:space="preserve">Репозиторий</x:String>
<x:String x:Key="Text.Configure.CustomAction.WaitForExit" xml:space="preserve">Ждать для выполения выхода</x:String>
@ -250,13 +253,15 @@
<x:String x:Key="Text.Diff.Binary.Old" xml:space="preserve">СТАРЫЙ</x:String>
<x:String x:Key="Text.Diff.Copy" xml:space="preserve">Копировать</x:String>
<x:String x:Key="Text.Diff.FileModeChanged" xml:space="preserve">Режим файла изменён</x:String>
<x:String x:Key="Text.Diff.First" xml:space="preserve">Первое различие</x:String>
<x:String x:Key="Text.Diff.IgnoreWhitespace" xml:space="preserve">Игнорировать изменение пробелов</x:String>
<x:String x:Key="Text.Diff.Last" xml:space="preserve">Последнее различие</x:String>
<x:String x:Key="Text.Diff.ShowHiddenSymbols" xml:space="preserve">Показывать скрытые символы</x:String>
<x:String x:Key="Text.Diff.LFS" xml:space="preserve">ИЗМЕНЕНИЕ ОБЪЕКТА LFS</x:String>
<x:String x:Key="Text.Diff.Next" xml:space="preserve">Следующее различие</x:String>
<x:String x:Key="Text.Diff.NoChange" xml:space="preserve">НИКАКИХ ИЗМЕНЕНИЙ ИЛИ МЕНЯЕТСЯ ТОЛЬКО EOL</x:String>
<x:String x:Key="Text.Diff.Prev" xml:space="preserve">Предыдущее различие</x:String>
<x:String x:Key="Text.Diff.SaveAsPatch" xml:space="preserve">Сохранить как исправление</x:String>
<x:String x:Key="Text.Diff.SaveAsPatch" xml:space="preserve">Сохранить как заплатку</x:String>
<x:String x:Key="Text.Diff.SideBySide" xml:space="preserve">Различие бок о бок</x:String>
<x:String x:Key="Text.Diff.Submodule" xml:space="preserve">ПОДМОДУЛЬ</x:String>
<x:String x:Key="Text.Diff.Submodule.New" xml:space="preserve">НОВЫЙ</x:String>
@ -286,7 +291,7 @@
<x:String x:Key="Text.FastForwardWithoutCheck" xml:space="preserve">Быстрая перемотка вперёд (без проверки)</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">Извлечь</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">Извлечь все внешние репозитории</x:String>
<x:String x:Key="Text.Fetch.Force" xml:space="preserve">Разрешить опцию '--force'</x:String>
<x:String x:Key="Text.Fetch.Force" xml:space="preserve">Разрешить опцию (--force)</x:String>
<x:String x:Key="Text.Fetch.NoTags" xml:space="preserve">Извлечь без меток</x:String>
<x:String x:Key="Text.Fetch.Remote" xml:space="preserve">Внешний репозиторий:</x:String>
<x:String x:Key="Text.Fetch.Title" xml:space="preserve">Извлечь внешние изменения</x:String>
@ -296,7 +301,7 @@
<x:String x:Key="Text.FileCM.DiscardSelectedLines" xml:space="preserve">Отменить изменения в выбранной(ых) строке(ах)</x:String>
<x:String x:Key="Text.FileCM.OpenWithExternalMerger" xml:space="preserve">Открыть расширенный инструмент слияния</x:String>
<x:String x:Key="Text.FileCM.ResolveUsing" xml:space="preserve">Взять версию ${0}$</x:String>
<x:String x:Key="Text.FileCM.SaveAsPatch" xml:space="preserve">Сохранить как patch-файл...</x:String>
<x:String x:Key="Text.FileCM.SaveAsPatch" xml:space="preserve">Сохранить как файл заплатки...</x:String>
<x:String x:Key="Text.FileCM.Stage" xml:space="preserve">Подготовить</x:String>
<x:String x:Key="Text.FileCM.StageMulti" xml:space="preserve">Подготовленные {0} файлы</x:String>
<x:String x:Key="Text.FileCM.StageSelectedLines" xml:space="preserve">Подготовленные изменения в выбранной(ых) строке(ах)</x:String>
@ -320,7 +325,7 @@
<x:String x:Key="Text.GitFlow.FinishTarget" xml:space="preserve">Цель:</x:String>
<x:String x:Key="Text.GitFlow.Hotfix" xml:space="preserve">Исправление:</x:String>
<x:String x:Key="Text.GitFlow.HotfixPrefix" xml:space="preserve">Префикс исправлений:</x:String>
<x:String x:Key="Text.GitFlow.Init" xml:space="preserve">Инициализировать Git-поток</x:String>
<x:String x:Key="Text.GitFlow.Init" xml:space="preserve">Создать Git-поток</x:String>
<x:String x:Key="Text.GitFlow.KeepBranchAfterFinish" xml:space="preserve">Держать ветку</x:String>
<x:String x:Key="Text.GitFlow.ProductionBranch" xml:space="preserve">Производственная ветка:</x:String>
<x:String x:Key="Text.GitFlow.Release" xml:space="preserve">Выпуск:</x:String>
@ -403,7 +408,7 @@
<x:String x:Key="Text.Hunk.Stage" xml:space="preserve">Подготовить</x:String>
<x:String x:Key="Text.Hunk.Unstage" xml:space="preserve">Снять из подготовленных</x:String>
<x:String x:Key="Text.Hunk.Discard" xml:space="preserve">Отклонить</x:String>
<x:String x:Key="Text.Init" xml:space="preserve">Инициализировать репозиторий</x:String>
<x:String x:Key="Text.Init" xml:space="preserve">Создать репозиторий</x:String>
<x:String x:Key="Text.Init.Path" xml:space="preserve">Путь:</x:String>
<x:String x:Key="Text.InProgress.CherryPick" xml:space="preserve">Выполняется частичный перенос ревизий (cherry-pick).</x:String>
<x:String x:Key="Text.InProgress.CherryPick.Head" xml:space="preserve">Обрабтка ревизии.</x:String>
@ -460,10 +465,12 @@
<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.AI.Streaming" xml:space="preserve">Разрешить потоковую передачу</x:String>
<x:String x:Key="Text.Preferences.Appearance" xml:space="preserve">ВИД</x:String>
<x:String x:Key="Text.Preferences.Appearance.DefaultFont" xml:space="preserve">Шрифт по умолчанию</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize" xml:space="preserve">Размер шрифта</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize.Default" xml:space="preserve">По умолчанию</x:String>
<x:String x:Key="Text.Preferences.Appearance.EditorTabWidth" xml:space="preserve">Редактировать ширину вкладки</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize.Editor" xml:space="preserve">Редактор</x:String>
<x:String x:Key="Text.Preferences.Appearance.MonospaceFont" xml:space="preserve">Моноширный шрифт</x:String>
<x:String x:Key="Text.Preferences.Appearance.OnlyUseMonoFontInEditor" xml:space="preserve">В текстовом редакторе используется только моноширный шрифт</x:String>
@ -488,7 +495,7 @@
<x:String x:Key="Text.Preferences.Git.DefaultCloneDir" xml:space="preserve">Каталог клонирования по умолчанию</x:String>
<x:String x:Key="Text.Preferences.Git.Email" xml:space="preserve">Электроная почта пользователя</x:String>
<x:String x:Key="Text.Preferences.Git.Email.Placeholder" xml:space="preserve">Общая электроная почта пользователя git</x:String>
<x:String x:Key="Text.Preferences.Git.EnablePruneOnFetch" xml:space="preserve">Разрешить '--prune' при скачивании</x:String>
<x:String x:Key="Text.Preferences.Git.EnablePruneOnFetch" xml:space="preserve">Разрешить (--prune) при скачивании</x:String>
<x:String x:Key="Text.Preferences.Git.Path" xml:space="preserve">Путь установки</x:String>
<x:String x:Key="Text.Preferences.Git.SSLVerify" xml:space="preserve">Разрешить верификацию HTTP SSL</x:String>
<x:String x:Key="Text.Preferences.Git.User" xml:space="preserve">Имя пользователя</x:String>
@ -555,7 +562,7 @@
<x:String x:Key="Text.RemoteCM.OpenInBrowser" xml:space="preserve">Открыть в браузере</x:String>
<x:String x:Key="Text.RemoteCM.Prune" xml:space="preserve">Удалить</x:String>
<x:String x:Key="Text.RemoveWorktree" xml:space="preserve">Подтвердить удаление рабочего каталога</x:String>
<x:String x:Key="Text.RemoveWorktree.Force" xml:space="preserve">Включить опцию --force</x:String>
<x:String x:Key="Text.RemoveWorktree.Force" xml:space="preserve">Включить опцию (--force)</x:String>
<x:String x:Key="Text.RemoveWorktree.Target" xml:space="preserve">Цель:</x:String>
<x:String x:Key="Text.RenameBranch" xml:space="preserve">Переименовать ветку</x:String>
<x:String x:Key="Text.RenameBranch.Name" xml:space="preserve">Новое имя:</x:String>
@ -578,7 +585,7 @@
<x:String x:Key="Text.Repository.FilterCommits.Exclude" xml:space="preserve">Скрыть в графе ревизии</x:String>
<x:String x:Key="Text.Repository.FilterCommits.Include" xml:space="preserve">Фильтр в графе ревизии</x:String>
<x:String x:Key="Text.Repository.FilterCommits.Prefix" xml:space="preserve">ОТФИЛЬТРОВАНО:</x:String>
<x:String x:Key="Text.Repository.FirstParentFilterToggle" xml:space="preserve">Включить опцию --first-parent</x:String>
<x:String x:Key="Text.Repository.FirstParentFilterToggle" xml:space="preserve">Включить опцию (--first-parent)</x:String>
<x:String x:Key="Text.Repository.HistoriesLayout" xml:space="preserve">РАСПОЛОЖЕНИЕ</x:String>
<x:String x:Key="Text.Repository.HistoriesLayout.Horizontal" xml:space="preserve">Горизонтально</x:String>
<x:String x:Key="Text.Repository.HistoriesLayout.Vertical" xml:space="preserve">Вертикально</x:String>
@ -633,7 +640,7 @@
<x:String x:Key="Text.Running" xml:space="preserve">Запуск. Подождите пожалуйста...</x:String>
<x:String x:Key="Text.Save" xml:space="preserve">СОХРАНИТЬ</x:String>
<x:String x:Key="Text.SaveAs" xml:space="preserve">Сохранить как...</x:String>
<x:String x:Key="Text.SaveAsPatchSuccess" xml:space="preserve">Исправление успешно сохранено!</x:String>
<x:String x:Key="Text.SaveAsPatchSuccess" xml:space="preserve">Заплатка успешно сохранена!</x:String>
<x:String x:Key="Text.ScanRepositories" xml:space="preserve">Сканирование репозиторий</x:String>
<x:String x:Key="Text.ScanRepositories.RootDir" xml:space="preserve">Корневой каталог:</x:String>
<x:String x:Key="Text.SHALinkCM.CopySHA" xml:space="preserve">Копировать SHA</x:String>
@ -667,6 +674,7 @@
<x:String x:Key="Text.StashCM.Apply" xml:space="preserve">Принять</x:String>
<x:String x:Key="Text.StashCM.Drop" xml:space="preserve">Отбросить</x:String>
<x:String x:Key="Text.StashCM.Pop" xml:space="preserve">Применить</x:String>
<x:String x:Key="Text.StashCM.SaveAsPatch" xml:space="preserve">Сохранить как заплатку...</x:String>
<x:String x:Key="Text.StashDropConfirm" xml:space="preserve">Отбросить тайник</x:String>
<x:String x:Key="Text.StashDropConfirm.Label" xml:space="preserve">Отбросить:</x:String>
<x:String x:Key="Text.Stashes" xml:space="preserve">Отложенные</x:String>
@ -674,7 +682,7 @@
<x:String x:Key="Text.Stashes.Stashes" xml:space="preserve">ОТЛОЖЕННЫЕ</x:String>
<x:String x:Key="Text.Statistics" xml:space="preserve">Статистика</x:String>
<x:String x:Key="Text.Statistics.CommitAmount" xml:space="preserve">РЕВИЗИИ</x:String>
<x:String x:Key="Text.Statistics.Committer" xml:space="preserve">РЕВИЗОРЫ(ИСПОЛНИТЕЛИ)</x:String>
<x:String x:Key="Text.Statistics.Committer" xml:space="preserve">РЕВИЗОРЫ (ИСПОЛНИТЕЛИ)</x:String>
<x:String x:Key="Text.Statistics.ThisMonth" xml:space="preserve">МЕСЯЦ</x:String>
<x:String x:Key="Text.Statistics.ThisWeek" xml:space="preserve">НЕДЕЛЯ</x:String>
<x:String x:Key="Text.Statistics.TotalCommits" xml:space="preserve">РЕВИЗИИ: </x:String>
@ -697,10 +705,10 @@
<x:String x:Key="Text.URL" xml:space="preserve">Сетевой адрес:</x:String>
<x:String x:Key="Text.UpdateSubmodules" xml:space="preserve">Обновление подмодулей</x:String>
<x:String x:Key="Text.UpdateSubmodules.All" xml:space="preserve">Все подмодули</x:String>
<x:String x:Key="Text.UpdateSubmodules.Init" xml:space="preserve">Инициализировать по необходимости</x:String>
<x:String x:Key="Text.UpdateSubmodules.Init" xml:space="preserve">Создавать по необходимости</x:String>
<x:String x:Key="Text.UpdateSubmodules.Recursive" xml:space="preserve">Рекурсивно</x:String>
<x:String x:Key="Text.UpdateSubmodules.Target" xml:space="preserve">Подмодуль:</x:String>
<x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">Использовать опцию '--remote'</x:String>
<x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">Использовать опцию (--remote)</x:String>
<x:String x:Key="Text.Warn" xml:space="preserve">Предупреждение</x:String>
<x:String x:Key="Text.Welcome" xml:space="preserve">Приветствие</x:String>
<x:String x:Key="Text.Welcome.AddRootFolder" xml:space="preserve">Создать группу</x:String>

View file

@ -467,6 +467,7 @@
<x:String x:Key="Text.Preferences.AI.Streaming" xml:space="preserve">启用流式输出</x:String>
<x:String x:Key="Text.Preferences.Appearance" xml:space="preserve">外观配置</x:String>
<x:String x:Key="Text.Preferences.Appearance.DefaultFont" xml:space="preserve">缺省字体</x:String>
<x:String x:Key="Text.Preferences.Appearance.EditorTabWidth" xml:space="preserve">编辑器制表符宽度</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize" xml:space="preserve">字体大小</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize.Default" xml:space="preserve">默认</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize.Editor" xml:space="preserve">代码编辑器</x:String>

View file

@ -467,6 +467,7 @@
<x:String x:Key="Text.Preferences.AI.Streaming" xml:space="preserve">啟用串流輸出</x:String>
<x:String x:Key="Text.Preferences.Appearance" xml:space="preserve">外觀設定</x:String>
<x:String x:Key="Text.Preferences.Appearance.DefaultFont" xml:space="preserve">預設字型</x:String>
<x:String x:Key="Text.Preferences.Appearance.EditorTabWidth" xml:space="preserve">編輯器制表符寬度</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize" xml:space="preserve">字型大小</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize.Default" xml:space="preserve">預設</x:String>
<x:String x:Key="Text.Preferences.Appearance.FontSize.Editor" xml:space="preserve">程式碼</x:String>

View file

@ -1299,6 +1299,8 @@
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Height" Value="28"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled"/>
</Style>
<Style Selector="^:focus-within /template/ ButtonSpinner#PART_Spinner">
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Accent}"/>

View file

@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Threading;
@ -57,7 +58,18 @@ namespace SourceGit.ViewModels
}
}
public string GetCommitMessage(string sha)
{
if (_commitMessages.TryGetValue(sha, out var msg))
return msg;
msg = new Commands.QueryCommitFullMessage(_repo, sha).Result();
_commitMessages[sha] = msg;
return msg;
}
private readonly string _repo;
private Models.BlameData _data = null;
private Dictionary<string, string> _commitMessages = new Dictionary<string, string>();
}
}

View file

@ -5,7 +5,6 @@ using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage;
@ -17,12 +16,6 @@ namespace SourceGit.ViewModels
{
public partial class CommitDetail : ObservableObject
{
public DiffContext DiffContext
{
get => _diffContext;
private set => SetProperty(ref _diffContext, value);
}
public int ActivePageIndex
{
get => _repo.CommitDetailActivePageIndex;
@ -46,7 +39,7 @@ namespace SourceGit.ViewModels
}
}
public string FullMessage
public Models.CommitFullMessage FullMessage
{
get => _fullMessage;
private set => SetProperty(ref _fullMessage, value);
@ -58,6 +51,18 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _signInfo, value);
}
public List<Models.CommitLink> WebLinks
{
get;
private set;
} = [];
public List<string> Children
{
get => _children;
private set => SetProperty(ref _children, value);
}
public List<Models.Change> Changes
{
get => _changes;
@ -85,11 +90,11 @@ namespace SourceGit.ViewModels
}
}
public AvaloniaList<string> Children
public DiffContext DiffContext
{
get;
private set;
} = [];
get => _diffContext;
private set => SetProperty(ref _diffContext, value);
}
public string SearchChangeFilter
{
@ -109,73 +114,20 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _viewRevisionFileContent, value);
}
public AvaloniaList<Models.CommitLink> WebLinks
{
get;
private set;
} = [];
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{
get => _repo.Settings?.IssueTrackerRules;
}
public string RevisionFileSearchFilter
{
get => _revisionFileSearchFilter;
set
{
if (SetProperty(ref _revisionFileSearchFilter, value))
{
RevisionFileSearchSuggestion.Clear();
if (!string.IsNullOrEmpty(value))
{
if (_revisionFiles.Count == 0)
{
var sha = Commit.SHA;
Task.Run(() =>
{
var files = new Commands.QueryRevisionFileNames(_repo.FullPath, sha).Result();
Dispatcher.UIThread.Invoke(() =>
{
if (sha == Commit.SHA)
{
_revisionFiles.Clear();
_revisionFiles.AddRange(files);
if (!string.IsNullOrEmpty(_revisionFileSearchFilter))
UpdateRevisionFileSearchSuggestion();
}
});
});
}
else
{
UpdateRevisionFileSearchSuggestion();
}
}
else
{
IsRevisionFileSearchSuggestionOpen = false;
GC.Collect();
}
}
RefreshRevisionSearchSuggestion();
}
}
public AvaloniaList<string> RevisionFileSearchSuggestion
public List<string> RevisionFileSearchSuggestion
{
get;
private set;
} = [];
public bool IsRevisionFileSearchSuggestionOpen
{
get => _isRevisionFileSearchSuggestionOpen;
set => SetProperty(ref _isRevisionFileSearchSuggestionOpen, value);
get => _revisionFileSearchSuggestion;
private set => SetProperty(ref _revisionFileSearchSuggestion, value);
}
public CommitDetail(Repository repo)
@ -212,23 +164,17 @@ namespace SourceGit.ViewModels
{
_repo = null;
_commit = null;
if (_changes != null)
_changes.Clear();
if (_visibleChanges != null)
_visibleChanges.Clear();
if (_selectedChanges != null)
_selectedChanges.Clear();
_changes = null;
_visibleChanges = null;
_selectedChanges = null;
_signInfo = null;
_searchChangeFilter = null;
_diffContext = null;
_viewRevisionFileContent = null;
_cancelToken = null;
WebLinks.Clear();
_revisionFiles.Clear();
RevisionFileSearchSuggestion.Clear();
_revisionFiles = null;
_revisionFileSearchSuggestion = null;
}
public void NavigateTo(string commitSHA)
@ -251,6 +197,11 @@ namespace SourceGit.ViewModels
RevisionFileSearchFilter = string.Empty;
}
public void CancelRevisionFileSuggestions()
{
RevisionFileSearchSuggestion = null;
}
public Models.Commit GetParent(string sha)
{
return new Commands.QuerySingleCommit(_repo.FullPath, sha).Result();
@ -322,7 +273,12 @@ namespace SourceGit.ViewModels
if (commit != null)
{
var body = new Commands.QueryCommitFullMessage(submoduleRoot, file.SHA).Result();
var submodule = new Models.RevisionSubmodule() { Commit = commit, FullMessage = body };
var submodule = new Models.RevisionSubmodule()
{
Commit = commit,
FullMessage = new Models.CommitFullMessage { Message = body }
};
Dispatcher.UIThread.Invoke(() => ViewRevisionFileContent = submodule);
}
else
@ -332,7 +288,7 @@ namespace SourceGit.ViewModels
ViewRevisionFileContent = new Models.RevisionSubmodule()
{
Commit = new Models.Commit() { SHA = file.SHA },
FullMessage = string.Empty,
FullMessage = null,
};
});
}
@ -622,23 +578,22 @@ namespace SourceGit.ViewModels
private void Refresh()
{
_changes = null;
_revisionFiles.Clear();
_revisionFiles = null;
SignInfo = null;
ViewRevisionFileContent = null;
Children.Clear();
Children = null;
RevisionFileSearchFilter = string.Empty;
IsRevisionFileSearchSuggestionOpen = false;
GC.Collect();
RevisionFileSearchSuggestion = null;
if (_commit == null)
return;
Task.Run(() =>
{
var fullMessage = new Commands.QueryCommitFullMessage(_repo.FullPath, _commit.SHA).Result();
Dispatcher.UIThread.Invoke(() => FullMessage = fullMessage);
var message = new Commands.QueryCommitFullMessage(_repo.FullPath, _commit.SHA).Result();
var links = ParseLinksInMessage(message);
Dispatcher.UIThread.Invoke(() => FullMessage = new Models.CommitFullMessage { Message = message, Links = links });
});
Task.Run(() =>
@ -694,6 +649,49 @@ namespace SourceGit.ViewModels
});
}
private List<Models.Hyperlink> ParseLinksInMessage(string message)
{
var links = new List<Models.Hyperlink>();
if (_repo.Settings.IssueTrackerRules is { Count: > 0 } rules)
{
foreach (var rule in rules)
rule.Matches(links, message);
}
var matches = REG_SHA_FORMAT().Matches(message);
for (int i = 0; i < matches.Count; i++)
{
var match = matches[i];
if (!match.Success)
continue;
var start = match.Index;
var len = match.Length;
var intersect = false;
foreach (var link in links)
{
if (link.Intersect(start, len))
{
intersect = true;
break;
}
}
if (intersect)
continue;
var sha = match.Groups[1].Value;
var isCommitSHA = new Commands.IsCommitSHA(_repo.FullPath, sha).Result();
if (isCommitSHA)
links.Add(new Models.Hyperlink(start, len, sha, true));
}
if (links.Count > 0)
links.Sort((l, r) => l.Start - r.Start);
return links;
}
private void RefreshVisibleChanges()
{
if (_changes == null)
@ -800,7 +798,44 @@ namespace SourceGit.ViewModels
menu.Items.Add(new MenuItem() { Header = "-" });
}
private void UpdateRevisionFileSearchSuggestion()
private void RefreshRevisionSearchSuggestion()
{
if (!string.IsNullOrEmpty(_revisionFileSearchFilter))
{
if (_revisionFiles == null)
{
var sha = Commit.SHA;
Task.Run(() =>
{
var files = new Commands.QueryRevisionFileNames(_repo.FullPath, sha).Result();
var filesList = new List<string>();
filesList.AddRange(files);
Dispatcher.UIThread.Invoke(() =>
{
if (sha == Commit.SHA)
{
_revisionFiles = filesList;
if (!string.IsNullOrEmpty(_revisionFileSearchFilter))
CalcRevisionFileSearchSuggestion();
}
});
});
}
else
{
CalcRevisionFileSearchSuggestion();
}
}
else
{
RevisionFileSearchSuggestion = null;
GC.Collect();
}
}
private void CalcRevisionFileSearchSuggestion()
{
var suggestion = new List<string>();
foreach (var file in _revisionFiles)
@ -813,11 +848,12 @@ namespace SourceGit.ViewModels
break;
}
RevisionFileSearchSuggestion.Clear();
RevisionFileSearchSuggestion.AddRange(suggestion);
IsRevisionFileSearchSuggestionOpen = suggestion.Count > 0;
RevisionFileSearchSuggestion = suggestion;
}
[GeneratedRegex(@"\b([0-9a-fA-F]{6,40})\b")]
private static partial Regex REG_SHA_FORMAT();
[GeneratedRegex(@"^version https://git-lfs.github.com/spec/v\d+\r?\noid sha256:([0-9a-f]+)\r?\nsize (\d+)[\r\n]*$")]
private static partial Regex REG_LFS_FORMAT();
@ -828,8 +864,9 @@ namespace SourceGit.ViewModels
private Repository _repo = null;
private Models.Commit _commit = null;
private string _fullMessage = string.Empty;
private Models.CommitFullMessage _fullMessage = null;
private Models.CommitSignInfo _signInfo = null;
private List<string> _children = null;
private List<Models.Change> _changes = null;
private List<Models.Change> _visibleChanges = null;
private List<Models.Change> _selectedChanges = null;
@ -837,8 +874,8 @@ namespace SourceGit.ViewModels
private DiffContext _diffContext = null;
private object _viewRevisionFileContent = null;
private Commands.Command.CancelToken _cancelToken = null;
private List<string> _revisionFiles = [];
private List<string> _revisionFiles = null;
private string _revisionFileSearchFilter = string.Empty;
private bool _isRevisionFileSearchSuggestionOpen = false;
private List<string> _revisionFileSearchSuggestion = null;
}
}

View file

@ -35,14 +35,8 @@
}
else if (context is RebaseInProgress rebase)
{
Theirs = repo.Branches.Find(x => x.IsLocal && x.Name == rebase.HeadName) ??
new Models.Branch()
{
IsLocal = true,
Name = rebase.HeadName,
FullName = $"refs/heads/{rebase.HeadName}"
};
var b = repo.Branches.Find(x => x.IsLocal && x.Name == rebase.HeadName);
Theirs = (object)b ?? rebase.StoppedAt;
Mine = rebase.Onto;
}
else if (context is RevertInProgress revert)

View file

@ -60,6 +60,7 @@ namespace SourceGit.ViewModels
{
_isTextDiff = previous._isTextDiff;
_content = previous._content;
_fileModeChange = previous._fileModeChange;
_unifiedLines = previous._unifiedLines;
_ignoreWhitespace = previous._ignoreWhitespace;
_info = previous._info;
@ -234,13 +235,17 @@ namespace SourceGit.ViewModels
if (commit != null)
{
var body = new Commands.QueryCommitFullMessage(repo, sha).Result();
return new Models.RevisionSubmodule() { Commit = commit, FullMessage = body };
return new Models.RevisionSubmodule()
{
Commit = commit,
FullMessage = new Models.CommitFullMessage { Message = body }
};
}
return new Models.RevisionSubmodule()
{
Commit = new Models.Commit() { SHA = sha },
FullMessage = string.Empty,
FullMessage = null,
};
}

View file

@ -123,12 +123,20 @@ namespace SourceGit.ViewModels
if (commit != null)
{
var message = new Commands.QueryCommitFullMessage(submoduleRoot, obj.SHA).Result();
var module = new Models.RevisionSubmodule() { Commit = commit, FullMessage = message };
var module = new Models.RevisionSubmodule()
{
Commit = commit,
FullMessage = new Models.CommitFullMessage { Message = message }
};
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, module));
}
else
{
var module = new Models.RevisionSubmodule() { Commit = new Models.Commit() { SHA = obj.SHA }, FullMessage = "" };
var module = new Models.RevisionSubmodule()
{
Commit = new Models.Commit() { SHA = obj.SHA },
FullMessage = null
};
Dispatcher.UIThread.Invoke(() => ViewContent = new FileHistoriesRevisionFile(_file, module));
}
});

View file

@ -107,19 +107,24 @@ namespace SourceGit.ViewModels
{
_gitDir = repo.GitDir;
var stoppedSHAPath = Path.Combine(repo.GitDir, "rebase-merge", "stopped-sha");
if (File.Exists(stoppedSHAPath))
{
var stoppedSHA = File.ReadAllText(stoppedSHAPath).Trim();
StoppedAt = new Commands.QuerySingleCommit(repo.FullPath, stoppedSHA).Result() ?? new Models.Commit() { SHA = stoppedSHA };
}
var ontoSHA = File.ReadAllText(Path.Combine(repo.GitDir, "rebase-merge", "onto")).Trim();
Onto = new Commands.QuerySingleCommit(repo.FullPath, ontoSHA).Result() ?? new Models.Commit() { SHA = ontoSHA };
HeadName = File.ReadAllText(Path.Combine(repo.GitDir, "rebase-merge", "head-name")).Trim();
if (HeadName.StartsWith("refs/heads/"))
HeadName = HeadName.Substring(11);
else if (HeadName.StartsWith("refs/tags/"))
HeadName = HeadName.Substring(10);
var stoppedSHAPath = Path.Combine(repo.GitDir, "rebase-merge", "stopped-sha");
var stoppedSHA = string.Empty;
if (File.Exists(stoppedSHAPath))
stoppedSHA = File.ReadAllText(stoppedSHAPath).Trim();
else
stoppedSHA = new Commands.QueryRevisionByRefName(repo.FullPath, HeadName).Result();
if (!string.IsNullOrEmpty(stoppedSHA))
StoppedAt = new Commands.QuerySingleCommit(repo.FullPath, stoppedSHA).Result() ?? new Models.Commit() { SHA = stoppedSHA };
var ontoSHA = File.ReadAllText(Path.Combine(repo.GitDir, "rebase-merge", "onto")).Trim();
Onto = new Commands.QuerySingleCommit(repo.FullPath, ontoSHA).Result() ?? new Models.Commit() { SHA = ontoSHA };
}
public override bool Continue()

View file

@ -118,7 +118,7 @@ namespace SourceGit.ViewModels
Task.Run(() =>
{
var commits = new Commands.QueryCommitsWithFullMessage(repoPath, $"{on.SHA}..HEAD").Result();
var commits = new Commands.QueryCommitsForInteractiveRebase(repoPath, on.SHA).Result();
var list = new List<InteractiveRebaseItem>();
foreach (var c in commits)

View file

@ -111,6 +111,12 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _editorFontSize, value);
}
public int EditorTabWidth
{
get => _editorTabWidth;
set => SetProperty(ref _editorTabWidth, value);
}
public LayoutInfo Layout
{
get => _layout;
@ -649,6 +655,7 @@ namespace SourceGit.ViewModels
private bool _useSystemWindowFrame = false;
private double _defaultFontSize = 13;
private double _editorFontSize = 13;
private int _editorTabWidth = 4;
private LayoutInfo _layout = new LayoutInfo();
private int _maxHistoryCommits = 20000;

View file

@ -35,7 +35,7 @@ namespace SourceGit.ViewModels
public Models.Branch SelectedBranch
{
get => _selectedBranch;
set => SetProperty(ref _selectedBranch, value);
set => SetProperty(ref _selectedBranch, value, true);
}
public Models.DealWithLocalChanges PreAction

View file

@ -18,7 +18,7 @@ namespace SourceGit.ViewModels
get => _selectedLocalBranch;
set
{
if (SetProperty(ref _selectedLocalBranch, value))
if (SetProperty(ref _selectedLocalBranch, value, true))
AutoSelectBranchByRemote();
}
}
@ -39,7 +39,7 @@ namespace SourceGit.ViewModels
get => _selectedRemote;
set
{
if (SetProperty(ref _selectedRemote, value))
if (SetProperty(ref _selectedRemote, value, true))
AutoSelectBranchByRemote();
}
}
@ -56,7 +56,7 @@ namespace SourceGit.ViewModels
get => _selectedRemoteBranch;
set
{
if (SetProperty(ref _selectedRemoteBranch, value))
if (SetProperty(ref _selectedRemoteBranch, value, true))
IsSetTrackOptionVisible = value != null && _selectedLocalBranch.Upstream != value.FullName;
}
}

View file

@ -1012,7 +1012,7 @@ namespace SourceGit.ViewModels
var filters = _settings.BuildHistoriesFilter();
if (string.IsNullOrEmpty(filters))
builder.Append("--branches --remotes --tags");
builder.Append("--branches --remotes --tags HEAD");
else
builder.Append(filters);

View file

@ -440,6 +440,8 @@ namespace SourceGit.ViewModels
public void ContinueMerge()
{
IsCommitting = true;
if (_inProgressContext != null)
{
_repo.SetWatcherEnabled(false);
@ -456,17 +458,21 @@ namespace SourceGit.ViewModels
CommitMessage = string.Empty;
_repo.SetWatcherEnabled(true);
IsCommitting = false;
});
});
}
else
{
_repo.MarkWorkingCopyDirtyManually();
IsCommitting = false;
}
}
public void SkipMerge()
{
IsCommitting = true;
if (_inProgressContext != null)
{
_repo.SetWatcherEnabled(false);
@ -479,17 +485,21 @@ namespace SourceGit.ViewModels
CommitMessage = string.Empty;
_repo.SetWatcherEnabled(true);
IsCommitting = false;
});
});
}
else
{
_repo.MarkWorkingCopyDirtyManually();
IsCommitting = false;
}
}
public void AbortMerge()
{
IsCommitting = true;
if (_inProgressContext != null)
{
_repo.SetWatcherEnabled(false);
@ -502,12 +512,14 @@ namespace SourceGit.ViewModels
CommitMessage = string.Empty;
_repo.SetWatcherEnabled(true);
IsCommitting = false;
});
});
}
else
{
_repo.MarkWorkingCopyDirtyManually();
IsCommitting = false;
}
}

View file

@ -5,7 +5,7 @@
xmlns:v="using:SourceGit.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.About"
x:DataType="v:About"
x:Name="ThisControl"
Icon="/App.ico"
Title="{DynamicResource Text.About}"
SizeToContent="WidthAndHeight"
@ -13,7 +13,7 @@
WindowStartupLocation="CenterScreen">
<Grid RowDefinitions="Auto,*">
<!-- TitleBar -->
<Grid Grid.Row="0" Height="28" IsVisible="{Binding !UseSystemWindowFrame}">
<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"/>
@ -46,13 +46,13 @@
<StackPanel Height="48" Orientation="Horizontal">
<TextBlock Classes="bold" Text="SourceGit" FontSize="32" />
<Border Margin="12,0,0,0" Height="20" CornerRadius="10" Background="{DynamicResource Brush.Accent}" Effect="drop-shadow(0 0 6 #40000000)">
<TextBlock Classes="primary" Margin="8,0" Text="{Binding Version}" FontSize="12" Foreground="White"/>
<TextBlock x:Name="TxtVersion" Classes="primary" Margin="8,0" FontSize="12" Foreground="White"/>
</Border>
</StackPanel>
<TextBlock Margin="2,0,0,0" Text="{DynamicResource Text.About.SubTitle}" FontSize="16"/>
<TextBlock Margin="2,8,0,0" Text="{Binding Copyright}" Foreground="{DynamicResource Brush.FG2}"/>
<TextBlock x:Name="TxtCopyright" Margin="2,8,0,0" Foreground="{DynamicResource Brush.FG2}"/>
<StackPanel Orientation="Vertical" Margin="0,24,0,0">
<StackPanel Orientation="Horizontal" Height="18">

View file

@ -5,30 +5,18 @@ namespace SourceGit.Views
{
public partial class About : ChromelessWindow
{
public string Version
{
get;
private set;
}
public string Copyright
{
get;
private set;
}
public About()
{
var ver = Assembly.GetExecutingAssembly().GetName().Version;
if (ver != null)
Version = $"{ver.Major}.{ver.Minor}";
var attributes = Assembly.GetExecutingAssembly()
.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false);
if (attributes.Length > 0)
Copyright = ((AssemblyCopyrightAttribute)attributes[0]).Copyright;
DataContext = this;
InitializeComponent();
var assembly = Assembly.GetExecutingAssembly();
var ver = assembly.GetName().Version;
if (ver != null)
TxtVersion.Text = $"{ver.Major}.{ver.Minor:D2}";
var copyright = assembly.GetCustomAttribute<AssemblyCopyrightAttribute>();
if (copyright != null)
TxtCopyright.Text = copyright.Copyright;
}
private void OnVisitAvaloniaUI(object _, PointerPressedEventArgs e)

View file

@ -5,7 +5,7 @@
xmlns:v="using:SourceGit.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.Askpass"
x:DataType="v:Askpass"
x:Name="ThisControl"
Icon="/App.ico"
Title="{DynamicResource Text.Askpass}"
SizeToContent="WidthAndHeight"
@ -13,7 +13,7 @@
WindowStartupLocation="CenterScreen">
<Grid RowDefinitions="Auto,*">
<!-- TitleBar -->
<Grid Grid.Row="0" Height="28" IsVisible="{Binding !UseSystemWindowFrame}">
<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"/>
@ -36,23 +36,23 @@
<StackPanel Grid.Row="1" Margin="0,16" Orientation="Vertical">
<Border Margin="16,0">
<TextBlock Text="{Binding Description}" TextWrapping="Wrap"/>
<TextBlock x:Name="TxtDescription" Text="Enter passphrase:" TextWrapping="Wrap"/>
</Border>
<TextBox Margin="16"
MinWidth="300"
Height="32"
<TextBox x:Name="TxtPassphrase"
Margin="16"
MinWidth="300"
Height="32"
Focusable="True"
Text="{Binding Passphrase, Mode=TwoWay}"
PasswordChar="*"
RevealPassword="{Binding ShowPassword, Mode=OneWay}"
RevealPassword="{Binding #ToggleShowPassword.IsChecked, Mode=OneWay}"
HorizontalAlignment="Stretch"
v:AutoFocusBehaviour.IsEnabled="True">
<TextBox.InnerRightContent>
<ToggleButton Grid.Column="6"
x:Name="ToggleShowPassword"
Classes="toggle_untracked"
Width="26" Height="14"
IsChecked="{Binding ShowPassword, Mode=TwoWay}"/>
Width="26" Height="14"/>
</TextBox.InnerRightContent>
</TextBox>

View file

@ -1,43 +1,12 @@
using System;
using Avalonia;
using Avalonia.Interactivity;
namespace SourceGit.Views
{
public partial class Askpass : ChromelessWindow
{
public static readonly StyledProperty<bool> ShowPasswordProperty =
AvaloniaProperty.Register<Askpass, bool>(nameof(ShowPassword));
public bool ShowPassword
{
get => GetValue(ShowPasswordProperty);
set => SetValue(ShowPasswordProperty, value);
}
public string Description
{
get;
private set;
} = string.Empty;
public string Passphrase
{
get;
set;
} = string.Empty;
public Askpass()
{
DataContext = this;
InitializeComponent();
}
public Askpass(string description)
{
Description = description;
DataContext = this;
InitializeComponent();
}
@ -49,7 +18,8 @@ namespace SourceGit.Views
private void EnterPassword(object _1, RoutedEventArgs _2)
{
Console.Out.Write($"{Passphrase}\n");
var passphrase = TxtPassphrase.Text ?? string.Empty;
Console.Out.Write($"{passphrase}\n");
App.Quit(0);
}
}

View file

@ -58,8 +58,9 @@
Foreground="{DynamicResource Brush.FG1}"
FontFamily="{DynamicResource Fonts.Monospace}"
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}"
TabWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorTabWidth}"
BlameData="{Binding Data}"/>
<!-- Not supported mask (for binary files) -->
<StackPanel Orientation="Vertical"
HorizontalAlignment="Center" VerticalAlignment="Center"

View file

@ -175,12 +175,21 @@ namespace SourceGit.Views
if (rect.Contains(pos))
{
Cursor = Cursor.Parse("Hand");
if (DataContext is ViewModels.Blame blame)
{
var msg = blame.GetCommitMessage(info.CommitSHA);
ToolTip.SetTip(this, msg);
ToolTip.SetIsOpen(this, true);
}
return;
}
}
}
Cursor = Cursor.Default;
Cursor = Cursor.Default;
ToolTip.SetIsOpen(this, false);
}
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
@ -230,9 +239,9 @@ namespace SourceGit.Views
private readonly BlameTextEditor _editor = null;
}
public class VerticalSeperatorMargin : AbstractMargin
public class VerticalSeparatorMargin : AbstractMargin
{
public VerticalSeperatorMargin(BlameTextEditor editor)
public VerticalSeparatorMargin(BlameTextEditor editor)
{
_editor = editor;
}
@ -260,6 +269,15 @@ namespace SourceGit.Views
set => SetValue(BlameDataProperty, value);
}
public static readonly StyledProperty<int> TabWidthProperty =
AvaloniaProperty.Register<BlameTextEditor, int>(nameof(TabWidth), 4);
public int TabWidth
{
get => GetValue(TabWidthProperty);
set => SetValue(TabWidthProperty, value);
}
protected override Type StyleKeyOverride => typeof(TextEditor);
public BlameTextEditor() : base(new TextArea(), new TextDocument())
@ -268,20 +286,22 @@ namespace SourceGit.Views
ShowLineNumbers = false;
WordWrap = false;
Options.IndentationSize = TabWidth;
Options.EnableHyperlinks = false;
Options.EnableEmailHyperlinks = false;
_textMate = Models.TextMateHelper.CreateForEditor(this);
TextArea.LeftMargins.Add(new LineNumberMargin() { Margin = new Thickness(8, 0) });
TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this));
TextArea.LeftMargins.Add(new VerticalSeparatorMargin(this));
TextArea.LeftMargins.Add(new CommitInfoMargin(this) { Margin = new Thickness(8, 0) });
TextArea.LeftMargins.Add(new VerticalSeperatorMargin(this));
TextArea.LeftMargins.Add(new VerticalSeparatorMargin(this));
TextArea.Caret.PositionChanged += OnTextAreaCaretPositionChanged;
TextArea.LayoutUpdated += OnTextAreaLayoutUpdated;
TextArea.PointerWheelChanged += OnTextAreaPointerWheelChanged;
TextArea.TextView.ContextRequested += OnTextViewContextRequested;
TextArea.TextView.VisualLinesChanged += OnTextViewVisualLinesChanged;
TextArea.TextView.Margin = new Thickness(4, 0);
TextArea.TextView.Options.EnableHyperlinks = false;
TextArea.TextView.Options.EnableEmailHyperlinks = false;
}
public override void Render(DrawingContext context)
@ -350,6 +370,10 @@ namespace SourceGit.Views
Text = string.Empty;
}
}
else if (change.Property == TabWidthProperty)
{
Options.IndentationSize = TabWidth;
}
else if (change.Property.Name == "ActualThemeVariant" && change.NewValue != null)
{
Models.TextMateHelper.SetThemeByApp(_textMate);

View file

@ -6,21 +6,21 @@
xmlns:v="using:SourceGit.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.ChangeViewModeSwitcher"
x:DataType="v:ChangeViewModeSwitcher">
x:Name="ThisControl">
<Button Classes="icon_button" ToolTip.Tip="{DynamicResource Text.ChangeDisplayMode}">
<Button.Flyout>
<MenuFlyout Placement="BottomEdgeAlignedLeft">
<MenuItem Header="{DynamicResource Text.ChangeDisplayMode.List}" Command="{Binding SwitchMode}" CommandParameter="{x:Static m:ChangeViewMode.List}">
<MenuItem Header="{DynamicResource Text.ChangeDisplayMode.List}" Click="SwitchToList">
<MenuItem.Icon>
<Path Width="12" Height="12" Data="{StaticResource Icons.List}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource Text.ChangeDisplayMode.Grid}" Command="{Binding SwitchMode}" CommandParameter="{x:Static m:ChangeViewMode.Grid}">
<MenuItem Header="{DynamicResource Text.ChangeDisplayMode.Grid}" Click="SwitchToGrid">
<MenuItem.Icon>
<Path Width="12" Height="12" Data="{StaticResource Icons.Grid}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource Text.ChangeDisplayMode.Tree}" Command="{Binding SwitchMode}" CommandParameter="{x:Static m:ChangeViewMode.Tree}">
<MenuItem Header="{DynamicResource Text.ChangeDisplayMode.Tree}" Click="SwitchToTree">
<MenuItem.Icon>
<Path Width="12" Height="12" Data="{StaticResource Icons.Tree}"/>
</MenuItem.Icon>
@ -31,13 +31,13 @@
<Grid Width="14" Height="14" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Width="14" Height="14"
Data="{StaticResource Icons.List}"
IsVisible="{Binding ViewMode, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:ChangeViewMode.List}}"/>
IsVisible="{Binding #ThisControl.ViewMode, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:ChangeViewMode.List}}"/>
<Path Width="14" Height="14"
Data="{StaticResource Icons.Grid}"
IsVisible="{Binding ViewMode, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:ChangeViewMode.Grid}}"/>
IsVisible="{Binding #ThisControl.ViewMode, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:ChangeViewMode.Grid}}"/>
<Path Width="14" Height="14"
Data="{StaticResource Icons.Tree}"
IsVisible="{Binding ViewMode, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:ChangeViewMode.Tree}}"/>
IsVisible="{Binding #ThisControl.ViewMode, Converter={x:Static ObjectConverters.Equal}, ConverterParameter={x:Static m:ChangeViewMode.Tree}}"/>
</Grid>
</Button>
</UserControl>

View file

@ -1,5 +1,6 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace SourceGit.Views
{
@ -16,13 +17,25 @@ namespace SourceGit.Views
public ChangeViewModeSwitcher()
{
DataContext = this;
InitializeComponent();
}
public void SwitchMode(object param)
private void SwitchToList(object sender, RoutedEventArgs e)
{
ViewMode = (Models.ChangeViewMode)param;
ViewMode = Models.ChangeViewMode.List;
e.Handled = true;
}
private void SwitchToGrid(object sender, RoutedEventArgs e)
{
ViewMode = Models.ChangeViewMode.Grid;
e.Handled = true;
}
private void SwitchToTree(object sender, RoutedEventArgs e)
{
ViewMode = Models.ChangeViewMode.Tree;
e.Handled = true;
}
}
}

View file

@ -95,8 +95,8 @@
</StackPanel>
<!-- PARENTS -->
<TextBlock Grid.Row="1" Grid.Column="0" Classes="info_label" VerticalAlignment="Top" Margin="0,4,0,0" Text="{DynamicResource Text.CommitDetail.Info.Parents}" IsVisible="{Binding Parents.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"/>
<ItemsControl Grid.Row="1" Grid.Column="1" Height="24" Margin="12,0,0,0" ItemsSource="{Binding Parents}" IsVisible="{Binding Parents.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}">
<TextBlock Grid.Row="1" Grid.Column="0" Classes="info_label" VerticalAlignment="Top" Margin="0,4,0,0" Text="{DynamicResource Text.CommitDetail.Info.Parents}" IsVisible="{Binding Parents, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}"/>
<ItemsControl Grid.Row="1" Grid.Column="1" Height="24" Margin="12,0,0,0" ItemsSource="{Binding Parents}" IsVisible="{Binding Parents, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center"/>
@ -133,8 +133,8 @@
</ItemsControl>
<!-- CHILDREN -->
<TextBlock Grid.Row="2" Grid.Column="0" Classes="info_label" VerticalAlignment="Top" Margin="0,4,0,0" Text="{DynamicResource Text.CommitDetail.Info.Children}" IsVisible="{Binding #ThisControl.Children.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}"/>
<ItemsControl Grid.Row="2" Grid.Column="1" Margin="12,0,0,0" ItemsSource="{Binding #ThisControl.Children}" IsVisible="{Binding #ThisControl.Children.Count, Converter={x:Static c:IntConverters.IsGreaterThanZero}}">
<TextBlock Grid.Row="2" Grid.Column="0" Classes="info_label" VerticalAlignment="Top" Margin="0,4,0,0" Text="{DynamicResource Text.CommitDetail.Info.Children}" IsVisible="{Binding #ThisControl.Children, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}"/>
<ItemsControl Grid.Row="2" Grid.Column="1" Margin="12,0,0,0" ItemsSource="{Binding #ThisControl.Children}" IsVisible="{Binding #ThisControl.Children, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" VerticalAlignment="Center" ItemHeight="24"/>
@ -187,8 +187,7 @@
<v:CommitMessagePresenter Grid.Row="4" Grid.Column="1"
Margin="12,4,8,0"
Classes="primary"
Message="{Binding #ThisControl.Message}"
IssueTrackerRules="{Binding #ThisControl.IssueTrackerRules}"
FullMessage="{Binding #ThisControl.FullMessage}"
HorizontalAlignment="Stretch"
TextWrapping="Wrap">
<v:CommitMessagePresenter.DataTemplates>

View file

@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
@ -11,13 +11,13 @@ namespace SourceGit.Views
{
public partial class CommitBaseInfo : UserControl
{
public static readonly StyledProperty<string> MessageProperty =
AvaloniaProperty.Register<CommitBaseInfo, string>(nameof(Message), string.Empty);
public static readonly StyledProperty<Models.CommitFullMessage> FullMessageProperty =
AvaloniaProperty.Register<CommitBaseInfo, Models.CommitFullMessage>(nameof(FullMessage));
public string Message
public Models.CommitFullMessage FullMessage
{
get => GetValue(MessageProperty);
set => SetValue(MessageProperty, value);
get => GetValue(FullMessageProperty);
set => SetValue(FullMessageProperty, value);
}
public static readonly StyledProperty<Models.CommitSignInfo> SignInfoProperty =
@ -38,28 +38,19 @@ namespace SourceGit.Views
set => SetValue(SupportsContainsInProperty, value);
}
public static readonly StyledProperty<AvaloniaList<Models.CommitLink>> WebLinksProperty =
AvaloniaProperty.Register<CommitBaseInfo, AvaloniaList<Models.CommitLink>>(nameof(WebLinks));
public static readonly StyledProperty<List<Models.CommitLink>> WebLinksProperty =
AvaloniaProperty.Register<CommitBaseInfo, List<Models.CommitLink>>(nameof(WebLinks));
public AvaloniaList<Models.CommitLink> WebLinks
public List<Models.CommitLink> WebLinks
{
get => GetValue(WebLinksProperty);
set => SetValue(WebLinksProperty, value);
}
public static readonly StyledProperty<AvaloniaList<Models.IssueTrackerRule>> IssueTrackerRulesProperty =
AvaloniaProperty.Register<CommitBaseInfo, AvaloniaList<Models.IssueTrackerRule>>(nameof(IssueTrackerRules));
public static readonly StyledProperty<List<string>> ChildrenProperty =
AvaloniaProperty.Register<CommitBaseInfo, List<string>>(nameof(Children));
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{
get => GetValue(IssueTrackerRulesProperty);
set => SetValue(IssueTrackerRulesProperty, value);
}
public static readonly StyledProperty<AvaloniaList<string>> ChildrenProperty =
AvaloniaProperty.Register<CommitBaseInfo, AvaloniaList<string>>(nameof(Children));
public AvaloniaList<string> Children
public List<string> Children
{
get => GetValue(ChildrenProperty);
set => SetValue(ChildrenProperty, value);

View file

@ -20,12 +20,11 @@
<StackPanel Orientation="Vertical">
<!-- Base Information -->
<v:CommitBaseInfo Content="{Binding Commit}"
Message="{Binding FullMessage}"
FullMessage="{Binding FullMessage}"
SignInfo="{Binding SignInfo}"
SupportsContainsIn="True"
WebLinks="{Binding WebLinks}"
Children="{Binding Children}"
IssueTrackerRules="{Binding IssueTrackerRules}"/>
Children="{Binding Children}"/>
<!-- Line -->
<Rectangle Height=".65" Margin="8" Fill="{DynamicResource Brush.Border2}"/>

View file

@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Input;
@ -13,27 +11,15 @@ using Avalonia.VisualTree;
namespace SourceGit.Views
{
public partial class CommitMessagePresenter : SelectableTextBlock
public class CommitMessagePresenter : SelectableTextBlock
{
[GeneratedRegex(@"\b([0-9a-fA-F]{6,40})\b")]
private static partial Regex REG_SHA_FORMAT();
public static readonly StyledProperty<Models.CommitFullMessage> FullMessageProperty =
AvaloniaProperty.Register<CommitMessagePresenter, Models.CommitFullMessage>(nameof(FullMessage));
public static readonly StyledProperty<string> MessageProperty =
AvaloniaProperty.Register<CommitMessagePresenter, string>(nameof(Message));
public string Message
public Models.CommitFullMessage FullMessage
{
get => GetValue(MessageProperty);
set => SetValue(MessageProperty, value);
}
public static readonly StyledProperty<AvaloniaList<Models.IssueTrackerRule>> IssueTrackerRulesProperty =
AvaloniaProperty.Register<CommitMessagePresenter, AvaloniaList<Models.IssueTrackerRule>>(nameof(IssueTrackerRules));
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{
get => GetValue(IssueTrackerRulesProperty);
set => SetValue(IssueTrackerRulesProperty, value);
get => GetValue(FullMessageProperty);
set => SetValue(FullMessageProperty, value);
}
protected override Type StyleKeyOverride => typeof(SelectableTextBlock);
@ -42,69 +28,36 @@ namespace SourceGit.Views
{
base.OnPropertyChanged(change);
if (change.Property == MessageProperty || change.Property == IssueTrackerRulesProperty)
if (change.Property == FullMessageProperty)
{
Inlines!.Clear();
_inlineCommits.Clear();
_matches = null;
_lastHover = null;
ClearHoveredIssueLink();
var message = Message;
var message = FullMessage?.Message;
if (string.IsNullOrEmpty(message))
return;
var matches = new List<Models.Hyperlink>();
if (IssueTrackerRules is { Count: > 0 } rules)
{
foreach (var rule in rules)
rule.Matches(matches, message);
}
var shas = REG_SHA_FORMAT().Matches(message);
for (int i = 0; i < shas.Count; i++)
{
var sha = shas[i];
if (!sha.Success)
continue;
var start = sha.Index;
var len = sha.Length;
var intersect = false;
foreach (var match in matches)
{
if (match.Intersect(start, len))
{
intersect = true;
break;
}
}
if (!intersect)
matches.Add(new Models.Hyperlink(start, len, sha.Groups[1].Value, true));
}
if (matches.Count == 0)
var links = FullMessage?.Links;
if (links == null || links.Count == 0)
{
Inlines.Add(new Run(message));
return;
}
matches.Sort((l, r) => l.Start - r.Start);
_matches = matches;
var inlines = new List<Inline>();
var pos = 0;
foreach (var match in matches)
foreach (var link in links)
{
if (match.Start > pos)
inlines.Add(new Run(message.Substring(pos, match.Start - pos)));
if (link.Start > pos)
inlines.Add(new Run(message.Substring(pos, link.Start - pos)));
var link = new Run(message.Substring(match.Start, match.Length));
link.Classes.Add(match.IsCommitSHA ? "commit_link" : "issue_link");
inlines.Add(link);
var run = new Run(message.Substring(link.Start, link.Length));
run.Classes.Add(link.IsCommitSHA ? "commit_link" : "issue_link");
inlines.Add(run);
pos = match.Start + match.Length;
pos = link.Start + link.Length;
}
if (pos < message.Length)
@ -134,7 +87,7 @@ namespace SourceGit.Views
scrollViewer.LineDown();
}
}
else if (_matches != null)
else if (FullMessage is { Links: { Count: > 0 } links })
{
var point = e.GetPosition(this) - new Point(Padding.Left, Padding.Top);
var x = Math.Min(Math.Max(point.X, 0), Math.Max(TextLayout.WidthIncludingTrailingWhitespace, 0));
@ -142,25 +95,25 @@ namespace SourceGit.Views
point = new Point(x, y);
var pos = TextLayout.HitTestPoint(point).TextPosition;
foreach (var match in _matches)
foreach (var link in links)
{
if (!match.Intersect(pos, 1))
if (!link.Intersect(pos, 1))
continue;
if (match == _lastHover)
if (link == _lastHover)
return;
SetCurrentValue(CursorProperty, Cursor.Parse("Hand"));
_lastHover = match;
if (!match.IsCommitSHA)
_lastHover = link;
if (!link.IsCommitSHA)
{
ToolTip.SetTip(this, match.Link);
ToolTip.SetTip(this, link.Link);
ToolTip.SetIsOpen(this, true);
}
else
{
ProcessHoverCommitLink(match);
ProcessHoverCommitLink(link);
}
return;
@ -361,7 +314,6 @@ namespace SourceGit.Views
}
}
private List<Models.Hyperlink> _matches = null;
private Models.Hyperlink _lastHover = null;
private Dictionary<string, Models.Commit> _inlineCommits = new();
}

View file

@ -257,7 +257,7 @@
<ContentControl.DataTemplates>
<DataTemplate DataType="m:RevisionSubmodule">
<Border Margin="0,0,0,8" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}" Background="{DynamicResource Brush.Window}">
<v:CommitBaseInfo MaxHeight="256" Margin="0,0,0,4" Content="{Binding Commit}" Message="{Binding FullMessage}"/>
<v:CommitBaseInfo MaxHeight="256" Margin="0,0,0,4" Content="{Binding Commit}" FullMessage="{Binding FullMessage}"/>
</Border>
</DataTemplate>
</ContentControl.DataTemplates>
@ -271,7 +271,7 @@
<Path Width="16" Height="16" Data="{StaticResource Icons.DoubleDown}" HorizontalAlignment="Center" IsVisible="{Binding Old, Converter={x:Static ObjectConverters.IsNotNull}}"/>
<Border Margin="0,8,0,0" BorderThickness="1" BorderBrush="Green" Background="{DynamicResource Brush.Window}">
<v:CommitBaseInfo MaxHeight="256" Margin="0,0,0,4" Content="{Binding New.Commit}" Message="{Binding New.FullMessage}"/>
<v:CommitBaseInfo MaxHeight="256" Margin="0,0,0,4" Content="{Binding New.Commit}" FullMessage="{Binding New.FullMessage}"/>
</Border>
</StackPanel>
</ScrollViewer>

View file

@ -24,7 +24,7 @@
</v:LayoutableGrid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
<Grid RowDefinitions="24,*">
<Grid RowDefinitions="24,*" Grid.IsSharedSizeScope="True">
<!-- Headers -->
<Border Grid.Row="0"
Background="{DynamicResource Brush.Window}"
@ -35,8 +35,8 @@
<ColumnDefinition Width="*" MinWidth="100"/>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="{Binding #ThisControl.AuthorNameColumnWidth, Mode=TwoWay}" MinWidth="80"/>
<ColumnDefinition Width="100" MaxWidth="100" MinWidth="100"/>
<ColumnDefinition Width="170" MaxWidth="170" MinWidth="170"/>
<ColumnDefinition SharedSizeGroup="SHA"/>
<ColumnDefinition SharedSizeGroup="Time"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Classes="table_header" Text="{DynamicResource Text.Histories.Header.GraphAndSubject}" HorizontalAlignment="Center"/>
@ -121,8 +121,8 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="{Binding #ThisControl.AuthorNameColumnWidth, Mode=OneWay}"/>
<ColumnDefinition Width="100" MaxWidth="100" MinWidth="100"/>
<ColumnDefinition Width="170" MaxWidth="170" MinWidth="170"/>
<ColumnDefinition SharedSizeGroup="SHA" Width="Auto" MinWidth="100"/>
<ColumnDefinition SharedSizeGroup="Time" Width="Auto" MinWidth="160"/>
</Grid.ColumnDefinitions>
<!-- Subject & REFS -->

View file

@ -59,25 +59,39 @@
SelectionMode="Single"
SelectedItem="{Binding SelectedItem, Mode=OneWayToSource}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.Styles>
ScrollViewer.VerticalScrollBarVisibility="Auto"
Grid.IsSharedSizeScope="True">
<v:InteractiveRebaseListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Height" Value="28"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.Styles>
</v:InteractiveRebaseListBox.Styles>
<ListBox.ItemsPanel>
<v:InteractiveRebaseListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</v:InteractiveRebaseListBox.ItemsPanel>
<ListBox.ItemTemplate>
<v:InteractiveRebaseListBox.ItemTemplate>
<DataTemplate DataType="vm:InteractiveRebaseItem">
<Grid ColumnDefinitions="16,110,*,Auto" Margin="8,0" ClipToBounds="True">
<Grid Height="26" Margin="8,0" ClipToBounds="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="32"/>
<ColumnDefinition Width="108"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="CommitHashColumn"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="CommitTimeColumn"/>
<ColumnDefinition Width="32"/>
<ColumnDefinition Width="32"/>
</Grid.ColumnDefinitions>
<!-- Drag & Drop Anchor -->
<Border Grid.Column="0" Background="Transparent"
Loaded="OnSetupRowHeaderDragDrop"
@ -188,47 +202,45 @@
<TextBlock Grid.Column="1" Classes="primary" Margin="0,0,4,0" Text="{Binding Subject}"/>
</Grid>
<Grid Grid.Column="3" ColumnDefinitions="32,108,96,Auto,32,32" IsHitTestVisible="False" ClipToBounds="True">
<!-- Author Avatar -->
<v:Avatar Grid.Column="0"
Width="16" Height="16"
Margin="8,0,0,0"
VerticalAlignment="Center"
User="{Binding Commit.Author}"/>
<!-- Author Avatar -->
<v:Avatar Grid.Column="3"
Width="16" Height="16"
Margin="8,0,0,0"
VerticalAlignment="Center"
User="{Binding Commit.Author}"/>
<!-- Author Name -->
<TextBlock Grid.Column="1"
Classes="primary"
MaxWidth="90"
Margin="6,0,12,0"
Text="{Binding Commit.Author.Name}"
HorizontalAlignment="Left"/>
<!-- Author Name -->
<Border Grid.Column="4" ClipToBounds="True">
<TextBlock Classes="primary" Margin="6,0,12,0" Text="{Binding Commit.Author.Name}"/>
</Border>
<!-- Commit SHA -->
<Border Grid.Column="2" ClipToBounds="True">
<TextBlock Classes="primary"
Text="{Binding Commit.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}"
HorizontalAlignment="Center"/>
</Border>
<!-- Commit SHA -->
<Border Grid.Column="5" ClipToBounds="True">
<TextBlock Classes="primary"
Text="{Binding Commit.SHA, Converter={x:Static c:StringConverters.ToShortSHA}}"
HorizontalAlignment="Center"/>
</Border>
<!-- Commit Time -->
<Border Grid.Column="3" ClipToBounds="True">
<TextBlock Classes="primary" Text="{Binding Commit.CommitterTimeStr}" Margin="8,0"/>
</Border>
<!-- Commit Time -->
<Border Grid.Column="6">
<TextBlock Classes="primary"
Margin="16,0,8,0"
Text="{Binding Commit.CommitterTimeStr}"
HorizontalAlignment="Center"/>
</Border>
<!-- MoveUp Button -->
<Button Grid.Column="4" Classes="icon_button" Click="OnMoveItemUp" ToolTip.Tip="Alt+Up">
<Path Width="14" Height="14" Margin="0,4,0,0" Data="{StaticResource Icons.Up}"/>
</Button>
<!-- MoveUp Button -->
<Button Grid.Column="7" Classes="icon_button" Click="OnMoveItemUp" ToolTip.Tip="Alt+Up">
<Path Width="14" Height="14" Margin="0,4,0,0" Data="{StaticResource Icons.Up}"/>
</Button>
<!-- MoveDown Button -->
<Button Grid.Column="5" Classes="icon_button" Click="OnMoveItemDown" ToolTip.Tip="Alt+Down">
<Path Width="14" Height="14" Margin="0,4,0,0" Data="{StaticResource Icons.Down}"/>
</Button>
</Grid>
<!-- MoveDown Button -->
<Button Grid.Column="8" Classes="icon_button" Click="OnMoveItemDown" ToolTip.Tip="Alt+Down">
<Path Width="14" Height="14" Margin="0,4,0,0" Data="{StaticResource Icons.Down}"/>
</Button>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</v:InteractiveRebaseListBox.ItemTemplate>
</v:InteractiveRebaseListBox>
<v:LoadingIcon Grid.Row="0" Width="48" Height="48" HorizontalAlignment="Center" VerticalAlignment="Center" IsVisible="{Binding IsLoading}"/>

View file

@ -112,8 +112,19 @@ namespace SourceGit.Views
// We should clear all unhandled key modifiers.
_unhandledModifiers = KeyModifiers.None;
// Check for AltGr (which is detected as Ctrl+Alt)
bool isAltGr = e.KeyModifiers.HasFlag(KeyModifiers.Control) &&
e.KeyModifiers.HasFlag(KeyModifiers.Alt);
// Skip hotkey processing if AltGr is pressed
if (isAltGr)
{
base.OnKeyDown(e);
return;
}
// Ctrl+Shift+P opens preference dialog (macOS use hotkeys in system menu bar)
if (!OperatingSystem.IsMacOS() && e.KeyModifiers == (KeyModifiers.Control | KeyModifiers.Shift) && e.Key == Key.P)
if (!OperatingSystem.IsMacOS() && e is { KeyModifiers: (KeyModifiers.Control | KeyModifiers.Shift), Key: Key.P })
{
App.OpenDialog(new Preferences());
e.Handled = true;
@ -243,13 +254,13 @@ namespace SourceGit.Views
{
_unhandledModifiers = e.KeyModifiers;
if (!_unhandledModifiers.HasFlag(KeyModifiers.Alt) && (e.Key == Key.LeftAlt || e.Key == Key.RightAlt))
if (!_unhandledModifiers.HasFlag(KeyModifiers.Alt) && e.Key is Key.LeftAlt or Key.RightAlt)
_unhandledModifiers |= KeyModifiers.Alt;
if (!_unhandledModifiers.HasFlag(KeyModifiers.Control) && (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl))
if (!_unhandledModifiers.HasFlag(KeyModifiers.Control) && e.Key is Key.LeftCtrl or Key.RightCtrl)
_unhandledModifiers |= KeyModifiers.Control;
if (!_unhandledModifiers.HasFlag(KeyModifiers.Shift) && (e.Key == Key.LeftShift || e.Key == Key.RightShift))
if (!_unhandledModifiers.HasFlag(KeyModifiers.Shift) && e.Key is Key.LeftShift or Key.RightShift)
_unhandledModifiers |= KeyModifiers.Shift;
}
}

View file

@ -148,7 +148,7 @@
<TabItem.Header>
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Preferences.Appearance}"/>
</TabItem.Header>
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32,Auto" ColumnDefinitions="Auto,*">
<Grid Margin="8" RowDefinitions="32,32,32,32,32,32,32,32,Auto" ColumnDefinitions="Auto,*">
<TextBlock Grid.Row="0" Grid.Column="0"
Text="{DynamicResource Text.Preferences.Appearance.Theme}"
HorizontalAlignment="Right"
@ -190,12 +190,12 @@
Margin="0,0,16,0"/>
<Grid Grid.Row="3" Grid.Column="1" ColumnDefinitions="*,8,*">
<NumericUpDown Grid.Column="0"
Minimum="10" Maximum="18" Increment="0.5"
Height="28"
Padding="4"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}"
CornerRadius="3"
Value="{Binding DefaultFontSize, Mode=TwoWay}">
Minimum="10" Maximum="18" Increment="0.5"
Height="28"
Padding="4"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}"
CornerRadius="3"
Value="{Binding DefaultFontSize, Mode=TwoWay}">
<NumericUpDown.InnerLeftContent>
<Border BorderThickness="0,0,1,0" BorderBrush="{DynamicResource Brush.Border1}">
<TextBlock Margin="4,0" Text="{DynamicResource Text.Preferences.Appearance.FontSize.Default}"/>
@ -218,10 +218,23 @@
</Grid>
<TextBlock Grid.Row="4" Grid.Column="0"
Text="{DynamicResource Text.Preferences.Appearance.EditorTabWidth}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<Grid Grid.Row="4" Grid.Column="1">
<NumericUpDown Minimum="1" Maximum="16" Increment="1"
Height="28"
Padding="4"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}"
CornerRadius="3"
Value="{Binding EditorTabWidth, Mode=TwoWay}"/>
</Grid>
<TextBlock Grid.Row="5" Grid.Column="0"
Text="{DynamicResource Text.Preferences.Appearance.ThemeOverrides}"
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<TextBox Grid.Row="4" Grid.Column="1"
<TextBox Grid.Row="5" Grid.Column="1"
Height="28"
CornerRadius="3"
Text="{Binding ThemeOverrides, Mode=TwoWay}">
@ -232,16 +245,16 @@
</TextBox.InnerRightContent>
</TextBox>
<CheckBox Grid.Row="5" Grid.Column="1"
<CheckBox Grid.Row="6" Grid.Column="1"
Content="{DynamicResource Text.Preferences.Appearance.OnlyUseMonoFontInEditor}"
IsChecked="{Binding OnlyUseMonoFontInEditor, Mode=TwoWay}"/>
<CheckBox Grid.Row="6" Grid.Column="1"
<CheckBox Grid.Row="7" Grid.Column="1"
Height="32"
Content="{DynamicResource Text.Preferences.Appearance.UseFixedTabWidth}"
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseFixedTabWidth, Mode=TwoWay}"/>
<CheckBox Grid.Row="7" Grid.Column="1"
<CheckBox Grid.Row="8" Grid.Column="1"
Height="32"
Content="{DynamicResource Text.Preferences.Appearance.UseNativeWindowFrame}"
IsChecked="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSystemWindowFrame, Mode=OneTime}"

View file

@ -22,6 +22,7 @@
<DataTemplate DataType="m:RevisionTextFile">
<v:RevisionTextFileView FontFamily="{DynamicResource Fonts.Monospace}"
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}"
TabWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorTabWidth}"
Background="{DynamicResource Brush.Contents}"/>
</DataTemplate>
@ -63,7 +64,7 @@
<Grid RowDefinitions="Auto,*" Margin="8,0">
<TextBlock Grid.Row="0" Margin="0,8,0,0" Text="{DynamicResource Text.CommitDetail.Files.Submodule}" FontSize="18" FontWeight="Bold" HorizontalAlignment="Center" Foreground="{DynamicResource Brush.FG2}"/>
<ScrollViewer Grid.Row="1" Margin="0,16,0,0" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<v:CommitBaseInfo Content="{Binding Commit}" Message="{Binding FullMessage}"/>
<v:CommitBaseInfo Content="{Binding Commit}" FullMessage="{Binding FullMessage}"/>
</ScrollViewer>
</Grid>
</DataTemplate>

View file

@ -15,6 +15,15 @@ namespace SourceGit.Views
{
public class RevisionTextFileView : TextEditor
{
public static readonly StyledProperty<int> TabWidthProperty =
AvaloniaProperty.Register<RevisionTextFileView, int>(nameof(TabWidth), 4);
public int TabWidth
{
get => GetValue(TabWidthProperty);
set => SetValue(TabWidthProperty, value);
}
protected override Type StyleKeyOverride => typeof(TextEditor);
public RevisionTextFileView() : base(new TextArea(), new TextDocument())
@ -22,13 +31,16 @@ namespace SourceGit.Views
IsReadOnly = true;
ShowLineNumbers = true;
WordWrap = false;
Options.IndentationSize = TabWidth;
Options.EnableHyperlinks = false;
Options.EnableEmailHyperlinks = false;
HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
TextArea.LeftMargins[0].Margin = new Thickness(8, 0);
TextArea.TextView.Margin = new Thickness(4, 0);
TextArea.TextView.Options.EnableHyperlinks = false;
TextArea.TextView.Options.EnableEmailHyperlinks = false;
}
protected override void OnLoaded(RoutedEventArgs e)
@ -69,6 +81,16 @@ namespace SourceGit.Views
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == TabWidthProperty)
{
Options.IndentationSize = TabWidth;
}
}
private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e)
{
var selected = SelectedText;

View file

@ -44,7 +44,7 @@
<Popup PlacementTarget="{Binding #TxtSearchRevisionFiles}"
Placement="BottomEdgeAlignedLeft"
HorizontalOffset="-8" VerticalAlignment="-8"
IsOpen="{Binding IsRevisionFileSearchSuggestionOpen}">
IsOpen="{Binding RevisionFileSearchSuggestion, Converter={x:Static c:ListConverters.IsNotNullOrEmpty}}">
<Border Margin="8" VerticalAlignment="Top" Effect="drop-shadow(0 0 8 #80000000)">
<Border Background="{DynamicResource Brush.Popup}" CornerRadius="4" Padding="4" BorderThickness="0.65" BorderBrush="{DynamicResource Brush.Accent}">
<ListBox x:Name="SearchSuggestionBox"

View file

@ -23,7 +23,7 @@ namespace SourceGit.Views
}
else if (e.Key == Key.Down || e.Key == Key.Up)
{
if (vm.IsRevisionFileSearchSuggestionOpen)
if (vm.RevisionFileSearchSuggestion.Count > 0)
{
SearchSuggestionBox.Focus(NavigationMethod.Tab);
SearchSuggestionBox.SelectedIndex = 0;
@ -33,12 +33,7 @@ namespace SourceGit.Views
}
else if (e.Key == Key.Escape)
{
if (vm.IsRevisionFileSearchSuggestionOpen)
{
vm.RevisionFileSearchSuggestion.Clear();
vm.IsRevisionFileSearchSuggestionOpen = false;
}
vm.CancelRevisionFileSuggestions();
e.Handled = true;
}
}
@ -57,7 +52,7 @@ namespace SourceGit.Views
if (e.Key == Key.Escape)
{
vm.RevisionFileSearchSuggestion.Clear();
vm.CancelRevisionFileSuggestions();
e.Handled = true;
}
else if (e.Key == Key.Enter && SearchSuggestionBox.SelectedItem is string content)

View file

@ -9,18 +9,14 @@ namespace SourceGit.Views
{
public StandaloneCommitMessageEditor()
{
_file = string.Empty;
DataContext = this;
InitializeComponent();
}
public StandaloneCommitMessageEditor(string file)
public void SetFile(string file)
{
_file = file;
DataContext = this;
InitializeComponent();
var content = File.ReadAllText(file).ReplaceLineEndings("\n");
var content = File.ReadAllText(file).ReplaceLineEndings("\n").Trim();
var firstLineEnd = content.IndexOf('\n');
if (firstLineEnd == -1)
{
@ -29,7 +25,7 @@ namespace SourceGit.Views
else
{
Editor.SubjectEditor.Text = content.Substring(0, firstLineEnd);
Editor.DescriptionEditor.Text = content.Substring(firstLineEnd + 1);
Editor.DescriptionEditor.Text = content.Substring(firstLineEnd + 1).Trim();
}
}
@ -41,12 +37,16 @@ namespace SourceGit.Views
private void SaveAndClose(object _1, RoutedEventArgs _2)
{
File.WriteAllText(_file, Editor.Text);
_exitCode = 0;
if (!string.IsNullOrEmpty(_file))
{
File.WriteAllText(_file, Editor.Text);
_exitCode = 0;
}
Close();
}
private readonly string _file;
private string _file = string.Empty;
private int _exitCode = -1;
}
}

View file

@ -27,6 +27,7 @@
IndicatorForeground="{DynamicResource Brush.FG2}"
FontFamily="{DynamicResource Fonts.Monospace}"
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}"
TabWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorTabWidth}"
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSyntaxHighlighting}"
WordWrap="{Binding Source={x:Static vm:Preferences.Instance}, Path=EnableDiffViewWordWrap}"
ShowHiddenSymbols="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowHiddenSymbolsInDiffView}"
@ -59,6 +60,7 @@
IndicatorForeground="{DynamicResource Brush.FG2}"
FontFamily="{DynamicResource Fonts.Monospace}"
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}"
TabWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorTabWidth}"
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSyntaxHighlighting}"
WordWrap="False"
ShowHiddenSymbols="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowHiddenSymbolsInDiffView}"
@ -81,6 +83,7 @@
IndicatorForeground="{DynamicResource Brush.FG2}"
FontFamily="{DynamicResource Fonts.Monospace}"
FontSize="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorFontSize}"
TabWidth="{Binding Source={x:Static vm:Preferences.Instance}, Path=EditorTabWidth}"
UseSyntaxHighlighting="{Binding Source={x:Static vm:Preferences.Instance}, Path=UseSyntaxHighlighting}"
WordWrap="False"
ShowHiddenSymbols="{Binding Source={x:Static vm:Preferences.Instance}, Path=ShowHiddenSymbolsInDiffView}"

View file

@ -60,7 +60,7 @@ namespace SourceGit.Views
public class ThemedTextDiffPresenter : TextEditor
{
public class VerticalSeperatorMargin : AbstractMargin
public class VerticalSeparatorMargin : AbstractMargin
{
public override void Render(DrawingContext context)
{
@ -475,6 +475,15 @@ namespace SourceGit.Views
set => SetValue(ShowHiddenSymbolsProperty, value);
}
public static readonly StyledProperty<int> TabWidthProperty =
AvaloniaProperty.Register<ThemedTextDiffPresenter, int>(nameof(TabWidth), 4);
public int TabWidth
{
get => GetValue(TabWidthProperty);
set => SetValue(TabWidthProperty, value);
}
public static readonly StyledProperty<bool> EnableChunkSelectionProperty =
AvaloniaProperty.Register<ThemedTextDiffPresenter, bool>(nameof(EnableChunkSelection));
@ -519,12 +528,13 @@ namespace SourceGit.Views
ShowLineNumbers = false;
BorderThickness = new Thickness(0);
Options.IndentationSize = TabWidth;
Options.EnableHyperlinks = false;
Options.EnableEmailHyperlinks = false;
_lineStyleTransformer = new LineStyleTransformer(this);
TextArea.TextView.Margin = new Thickness(2, 0);
TextArea.TextView.Options.EnableHyperlinks = false;
TextArea.TextView.Options.EnableEmailHyperlinks = false;
TextArea.TextView.BackgroundRenderers.Add(new LineBackgroundRenderer(this));
TextArea.TextView.LineTransformers.Add(_lineStyleTransformer);
}
@ -734,10 +744,14 @@ namespace SourceGit.Views
}
else if (change.Property == ShowHiddenSymbolsProperty)
{
var val = change.NewValue is true;
var val = ShowHiddenSymbols;
Options.ShowTabs = val;
Options.ShowSpaces = val;
}
else if (change.Property == TabWidthProperty)
{
Options.IndentationSize = TabWidth;
}
else if (change.Property == FileNameProperty)
{
Models.TextMateHelper.SetGrammarByFileName(_textMate, FileName);
@ -1007,8 +1021,8 @@ namespace SourceGit.Views
if (startPosition.Location > endPosition.Location)
(startPosition, endPosition) = (endPosition, startPosition);
var startIdx = Math.Min(startPosition.Line - 1, lines.Count - 1);
var endIdx = Math.Min(endPosition.Line - 1, lines.Count - 1);
var startIdx = startPosition.Line - 1;
var endIdx = endPosition.Line - 1;
if (startIdx == endIdx)
{
@ -1025,25 +1039,35 @@ namespace SourceGit.Views
}
var builder = new StringBuilder();
for (var i = startIdx; i <= endIdx; i++)
for (var i = startIdx; i <= endIdx && i <= lines.Count - 1; i++)
{
var line = lines[i];
if (line.Type == Models.TextDiffLineType.Indicator ||
line.Type == Models.TextDiffLineType.None)
continue;
// The first selected line (partial selection)
if (i == startIdx && startPosition.Column > 1)
{
builder.AppendLine(line.Content.Substring(startPosition.Column - 1));
continue;
}
if (i == endIdx && endPosition.Column < line.Content.Length)
// The selection range is larger than original source.
if (i == lines.Count - 1 && i < endIdx)
{
builder.AppendLine(line.Content.Substring(0, endPosition.Column));
continue;
builder.Append(line.Content);
break;
}
// For the last line (selection range is within original source)
if (i == endIdx)
{
builder.Append(endPosition.Column - 1 < line.Content.Length ? line.Content.Substring(0, endPosition.Column - 1) : line.Content);
break;
}
// Other lines.
builder.AppendLine(line.Content);
}
@ -1061,9 +1085,9 @@ namespace SourceGit.Views
public CombinedTextDiffPresenter() : base(new TextArea(), new TextDocument())
{
TextArea.LeftMargins.Add(new LineNumberMargin(false, true));
TextArea.LeftMargins.Add(new VerticalSeperatorMargin());
TextArea.LeftMargins.Add(new VerticalSeparatorMargin());
TextArea.LeftMargins.Add(new LineNumberMargin(false, false));
TextArea.LeftMargins.Add(new VerticalSeperatorMargin());
TextArea.LeftMargins.Add(new VerticalSeparatorMargin());
TextArea.LeftMargins.Add(new LineModifyTypeMargin());
}
@ -1262,7 +1286,7 @@ namespace SourceGit.Views
public SingleSideTextDiffPresenter() : base(new TextArea(), new TextDocument())
{
TextArea.LeftMargins.Add(new LineNumberMargin(true, false));
TextArea.LeftMargins.Add(new VerticalSeperatorMargin());
TextArea.LeftMargins.Add(new VerticalSeparatorMargin());
TextArea.LeftMargins.Add(new LineModifyTypeMargin());
}

View file

@ -211,7 +211,12 @@
<StackPanel Orientation="Horizontal">
<Path Width="12" Height="12" Data="{StaticResource Icons.Branch}"/>
<TextBlock Margin="4,0,0,0" Text="{Binding FriendlyName}"/>
<TextBlock Margin="4,0,0,0" Text="{Binding Head, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/>
<TextBlock Margin="4,0,0,0"
Text="{Binding Head, Converter={x:Static c:StringConverters.ToShortSHA}}"
Foreground="DarkOrange"
TextDecorations="Underline"
Cursor="Hand"
PointerPressed="OnPressedSHA"/>
</StackPanel>
</DataTemplate>
@ -225,18 +230,15 @@
FontSize="11"
VerticalAlignment="Center"
UseGraphColor="False"/>
<TextBlock Margin="4,0,0,0" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/>
<TextBlock Margin="4,0,0,0"
Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}"
Foreground="DarkOrange"
TextDecorations="Underline"
Cursor="Hand"
PointerPressed="OnPressedSHA"/>
<TextBlock Margin="4,0,0,0" Text="{Binding Subject}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="m:Tag">
<StackPanel Orientation="Horizontal">
<Path Width="12" Height="12" Data="{StaticResource Icons.Tag}"/>
<TextBlock Margin="4,0,0,0" Text="{Binding Name}"/>
<TextBlock Margin="4,0,0,0" Text="{Binding SHA, Converter={x:Static c:StringConverters.ToShortSHA}}" Foreground="DarkOrange"/>
</StackPanel>
</DataTemplate>
</StackPanel.DataTemplates>
<Path Width="64" Height="64" Data="{StaticResource Icons.Conflict}" Fill="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
@ -397,7 +399,8 @@
Width="0" Height="0"
Background="Transparent"
Command="{Binding CommitWithAutoStage}"
HotKey="{OnPlatform Ctrl+Shift+Enter, macOS=⌘+Shift+Enter}"/>
HotKey="{OnPlatform Ctrl+Shift+Enter, macOS=⌘+Shift+Enter}"
IsEnabled="{Binding !IsCommitting}"/>
<Button Grid.Column="8"
Classes="flat"

View file

@ -1,6 +1,7 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace SourceGit.Views
{
@ -159,5 +160,14 @@ namespace SourceGit.Views
e.Handled = true;
}
private void OnPressedSHA(object sender, PointerPressedEventArgs e)
{
var repoView = this.FindAncestorOfType<Repository>();
if (repoView is { DataContext: ViewModels.Repository repo } && sender is TextBlock text)
repo.NavigateToCommit(text.Text);
e.Handled = true;
}
}
}