diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml new file mode 100644 index 00000000..9e465fe7 --- /dev/null +++ b/.github/workflows/publish-packages.yml @@ -0,0 +1,39 @@ +name: Publish to Buildkite +on: + workflow_call: + secrets: + BUILDKITE_TOKEN: + required: true +jobs: + publish: + name: Publish to Buildkite + runs-on: ubuntu-latest + strategy: + matrix: + runtime: [linux-x64, linux-arm64] + steps: + - name: Download package artifacts + uses: actions/download-artifact@v4 + with: + name: package.${{ matrix.runtime }} + path: packages + + - name: Publish DEB package + env: + BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }} + run: | + FILE=$(echo packages/*.deb) + curl -X POST https://api.buildkite.com/v2/packages/organizations/sourcegit/registries/sourcegit-deb/packages \ + -H "Authorization: Bearer $BUILDKITE_TOKEN" \ + -F "file=@$FILE" \ + --fail + + - name: Publish RPM package + env: + BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }} + run: | + FILE=$(echo packages/*.rpm) + curl -X POST https://api.buildkite.com/v2/packages/organizations/sourcegit/registries/sourcegit-rpm/packages \ + -H "Authorization: Bearer $BUILDKITE_TOKEN" \ + -F "file=@$FILE" \ + --fail diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c19103e3..6a38c174 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,11 @@ jobs: uses: ./.github/workflows/package.yml with: version: ${{ needs.version.outputs.version }} + publish-packages: + name: Publish Packages + uses: ./.github/workflows/publish-packages.yml + secrets: + BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }} release: needs: [package, version] name: Release diff --git a/README.md b/README.md index a1d19e1f..06842e37 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ ## Translation Status -[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.86%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-97.87%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-97.30%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-97.73%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-99.15%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-100.00%25-brightgreen)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md) +[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.58%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-99.86%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-97.03%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-97.45%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-98.87%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-100.00%25-brightgreen)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md) ## How to Use diff --git a/SourceGit.sln b/SourceGit.sln index 9c5fcdb1..3eeb8a54 100644 --- a/SourceGit.sln +++ b/SourceGit.sln @@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ .github\workflows\package.yml = .github\workflows\package.yml .github\workflows\release.yml = .github\workflows\release.yml .github\workflows\localization-check.yml = .github\workflows\localization-check.yml + .github\workflows\publish-packages.yml = .github\workflows\publish-packages.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{49A7C2D6-558C-4FAA-8F5D-EEE81497AED7}" diff --git a/TRANSLATION.md b/TRANSLATION.md index 82be4441..761edbf6 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -1,38 +1,26 @@ -### de_DE.axaml: 99.86% +### de_DE.axaml: 99.58%
Missing Keys +- Text.CommitDetail.Files.Search +- Text.Diff.UseBlockNavigation - Text.WorkingCopy.CommitToEdit
-### es_ES.axaml: 97.87% +### es_ES.axaml: 99.86%
Missing Keys -- Text.CommitDetail.Info.Children -- Text.Fetch.Force -- Text.Preference.Appearance.FontSize -- Text.Preference.Appearance.FontSize.Default -- Text.Preference.Appearance.FontSize.Editor -- Text.Preference.General.ShowChildren -- Text.Repository.FilterCommits -- Text.Repository.FilterCommits.Default -- Text.Repository.FilterCommits.Exclude -- Text.Repository.FilterCommits.Include -- Text.Repository.HistoriesOrder -- Text.Repository.HistoriesOrder.ByDate -- Text.Repository.HistoriesOrder.Topo -- Text.SHALinkCM.NavigateTo -- Text.WorkingCopy.CommitToEdit +- Text.Diff.UseBlockNavigation
-### fr_FR.axaml: 97.30% +### fr_FR.axaml: 97.03%
@@ -41,6 +29,8 @@ - Text.CherryPick.AppendSourceToMessage - Text.CherryPick.Mainline.Tips - Text.CommitCM.CherryPickMultiple +- Text.CommitDetail.Files.Search +- Text.Diff.UseBlockNavigation - Text.Fetch.Force - Text.Preference.Appearance.FontSize - Text.Preference.Appearance.FontSize.Default @@ -60,16 +50,18 @@
-### it_IT.axaml: 97.73% +### it_IT.axaml: 97.45%
Missing Keys +- Text.CommitDetail.Files.Search - Text.CommitDetail.Info.Children - Text.Configure.IssueTracker.AddSampleGitLabMergeRequest - Text.Configure.OpenAI.Preferred - Text.Configure.OpenAI.Preferred.Tip +- Text.Diff.UseBlockNavigation - Text.Fetch.Force - Text.Preference.General.ShowChildren - Text.Repository.FilterCommits @@ -85,13 +77,15 @@
-### pt_BR.axaml: 99.15% +### pt_BR.axaml: 98.87%
Missing Keys +- Text.CommitDetail.Files.Search - Text.CommitDetail.Info.Children +- Text.Diff.UseBlockNavigation - Text.Fetch.Force - Text.Preference.General.ShowChildren - Text.Repository.FilterCommits diff --git a/VERSION b/VERSION index cd658ce6..d72e21bd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.41 \ No newline at end of file +8.42 \ No newline at end of file diff --git a/build/resources/rpm/SPECS/build.spec b/build/resources/rpm/SPECS/build.spec index d31b6587..bc10ca48 100644 --- a/build/resources/rpm/SPECS/build.spec +++ b/build/resources/rpm/SPECS/build.spec @@ -7,6 +7,7 @@ URL: https://sourcegit-scm.github.io/ Source: https://github.com/sourcegit-scm/sourcegit/archive/refs/tags/v%_version.tar.gz Requires: libX11.so.6()(%{__isa_bits}bit) Requires: libSM.so.6()(%{__isa_bits}bit) +Requires: libicu %define _build_id_links none diff --git a/src/App.axaml.cs b/src/App.axaml.cs index a4e5bd5b..0615724a 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -164,7 +164,12 @@ namespace SourceGit var resDic = new ResourceDictionary(); var overrides = JsonSerializer.Deserialize(File.ReadAllText(themeOverridesFile), JsonCodeGen.Default.ThemeOverrides); foreach (var kv in overrides.BasicColors) - resDic[$"Color.{kv.Key}"] = kv.Value; + { + if (kv.Key.Equals("SystemAccentColor", StringComparison.Ordinal)) + resDic["SystemAccentColor"] = kv.Value; + else + resDic[$"Color.{kv.Key}"] = kv.Value; + } if (overrides.GraphColors.Count > 0) Models.CommitGraph.SetPens(overrides.GraphColors, overrides.GraphPenThickness); diff --git a/src/Commands/QueryCurrentRevisionFiles.cs b/src/Commands/QueryRevisionFileNames.cs similarity index 55% rename from src/Commands/QueryCurrentRevisionFiles.cs rename to src/Commands/QueryRevisionFileNames.cs index 217ea20e..d2d69614 100644 --- a/src/Commands/QueryCurrentRevisionFiles.cs +++ b/src/Commands/QueryRevisionFileNames.cs @@ -1,19 +1,19 @@ namespace SourceGit.Commands { - public class QueryCurrentRevisionFiles : Command + public class QueryRevisionFileNames : Command { - public QueryCurrentRevisionFiles(string repo) + public QueryRevisionFileNames(string repo, string revision) { WorkingDirectory = repo; Context = repo; - Args = "ls-tree -r --name-only HEAD"; + Args = $"ls-tree -r -z --name-only {revision}"; } public string[] Result() { var rs = ReadToEnd(); if (rs.IsSuccess) - return rs.StdOut.Split('\n', System.StringSplitOptions.RemoveEmptyEntries); + return rs.StdOut.Split('\0', System.StringSplitOptions.RemoveEmptyEntries); return []; } diff --git a/src/Commands/QueryRevisionObjects.cs b/src/Commands/QueryRevisionObjects.cs index bcad9129..de3406e8 100644 --- a/src/Commands/QueryRevisionObjects.cs +++ b/src/Commands/QueryRevisionObjects.cs @@ -12,7 +12,7 @@ namespace SourceGit.Commands { WorkingDirectory = repo; Context = repo; - Args = $"ls-tree {sha}"; + Args = $"ls-tree -z {sha}"; if (!string.IsNullOrEmpty(parentFolder)) Args += $" -- \"{parentFolder}\""; @@ -20,11 +20,27 @@ namespace SourceGit.Commands public List Result() { - Exec(); + var rs = ReadToEnd(); + if (rs.IsSuccess) + { + var start = 0; + var end = rs.StdOut.IndexOf('\0', start); + while (end > 0) + { + var line = rs.StdOut.Substring(start, end - start); + Parse(line); + start = end + 1; + end = rs.StdOut.IndexOf('\0', start); + } + + if (start < rs.StdOut.Length) + Parse(rs.StdOut.Substring(start)); + } + return _objects; } - protected override void OnReadline(string line) + private void Parse(string line) { var match = REG_FORMAT().Match(line); if (!match.Success) diff --git a/src/Models/Branch.cs b/src/Models/Branch.cs index ac6b8c67..0ba320c1 100644 --- a/src/Models/Branch.cs +++ b/src/Models/Branch.cs @@ -7,6 +7,8 @@ namespace SourceGit.Models public List Ahead { get; set; } = new List(); public List Behind { get; set; } = new List(); + public bool IsVisible => Ahead.Count > 0 || Behind.Count > 0; + public override string ToString() { if (Ahead.Count == 0 && Behind.Count == 0) diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 8d66a250..8fa23fb7 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -15,6 +15,7 @@ M797 829a49 49 0 1049 49 49 49 0 00-49-49zm147-114A49 49 0 10992 764a49 49 0 00-49-49zM928 861a49 49 0 1049 49A49 49 0 00928 861zm-5-586L992 205 851 64l-71 71a67 67 0 00-94 0l235 235a67 67 0 000-94zm-853 128a32 32 0 00-32 50 1291 1291 0 0075 112L288 552c20 0 25 21 8 37l-93 86a1282 1282 0 00120 114l100-32c19-6 28 15 14 34l-40 55c26 19 53 36 82 53a89 89 0 00115-20 1391 1391 0 00256-485l-188-188s-306 224-595 198z M1280 704c0 141-115 256-256 256H288C129 960 0 831 0 672c0-126 80-232 192-272A327 327 0 01192 384c0-177 143-320 320-320 119 0 222 64 277 160C820 204 857 192 896 192c106 0 192 86 192 192 0 24-5 48-13 69C1192 477 1280 580 1280 704zm-493-128H656V352c0-18-14-32-32-32h-96c-18 0-32 14-32 32v224h-131c-29 0-43 34-23 55l211 211c12 12 33 12 45 0l211-211c20-20 6-55-23-55z M853 102H171C133 102 102 133 102 171v683C102 891 133 922 171 922h683C891 922 922 891 922 853V171C922 133 891 102 853 102zM390 600l-48 48L205 512l137-137 48 48L301 512l88 88zM465 819l-66-18L559 205l66 18L465 819zm218-171L634 600 723 512l-88-88 48-48L819 512 683 649z + M320 171A21 21 0 00299 192v213c0 49-32 85-61 107 30 22 61 58 61 107V832A21 21 0 00320 853h85v85H320A107 107 0 01213 832V619c0-11-8-26-32-42a157 157 0 00-33-17c-11-4-18-5-20-5v-85c2 0 9-1 20-5a157 157 0 0033-17c24-16 32-32 32-42V192A107 107 0 01320 85h85v85H320zm384 0h-85V85H704A107 107 0 01811 192v213c0 11 8 26 32 42 11 7 22 13 33 17 11 4 18 5 20 5v85c-2 0-9 1-20 5a157 157 0 00-33 17c-24 16-32 31-32 42V832A107 107 0 01704 939h-85v-85H704A21 21 0 00725 832V619c0-49 32-85 61-107-30-22-61-58-61-107V192A21 21 0 00704 171z M128 854h768v86H128zM390 797c13 13 29 19 48 19s35-6 45-19l291-288c26-22 26-64 0-90L435 83l-61 61L426 192l-272 269c-22 22-22 64 0 90l237 246zm93-544 211 211-32 32H240l243-243zM707 694c0 48 38 86 86 86 48 0 86-38 86-86 0-22-10-45-26-61L794 576l-61 61c-13 13-26 35-26 58z M796 471A292 292 0 00512 256a293 293 0 00-284 215H0v144h228A293 293 0 00512 832a291 291 0 00284-217H1024V471h-228M512 688A146 146 0 01366 544A145 145 0 01512 400c80 0 146 63 146 144A146 146 0 01512 688 M796 561a5 5 0 014 7l-39 90a5 5 0 004 7h100a5 5 0 014 8l-178 247a5 5 0 01-9-4l32-148a5 5 0 00-5-6h-89a5 5 0 01-4-7l86-191a5 5 0 014-3h88zM731 122a73 73 0 0173 73v318a54 54 0 00-8-1H731V195H244v634h408l-16 73H244a73 73 0 01-73-73V195a73 73 0 0173-73h488zm-219 366v73h-195v-73h195zm146-146v73H317v-73h341z diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index d06635eb..89309bef 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -121,6 +121,7 @@ Search Changes... FILES LFS File + Search Files... Submodule INFORMATION AUTHOR @@ -246,6 +247,7 @@ Swap Syntax Highlighting Line Word Wrap + Enable Block-Navigation Open in Merge Tool Show All Lines Decrease Number of Visible Lines diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index 563419be..7c7f8d7c 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -126,10 +126,12 @@ Buscar Cambios... ARCHIVOS Archivo LFS + Buscar Archivos... Submódulo INFORMACIÓN AUTOR CAMBIADO + HIJOS COMMITTER Ver refs que contienen este commit COMMIT ESTÁ CONTENIDO EN @@ -272,6 +274,7 @@ Fast-Forward (sin checkout) Fetch Fetch todos los remotos + Utilizar opción '--force' Fetch sin etiquetas Remoto: Fetch Cambios Remotos @@ -437,6 +440,9 @@ Servidor APARIENCIA Fuente por defecto + Tamaño de fuente + Por defecto + Editor Fuente Monospace Usar solo fuente monospace en el editor de texto Tema @@ -452,6 +458,7 @@ Idioma Commits en el historial Mostrar hora del autor en lugar de la hora del commit en el gráfico + Mostrar hijos en los detalles de commit Longitud de la guía del asunto GIT Habilitar Auto CRLF @@ -541,6 +548,13 @@ Habilitar Opción '--reflog' Abrir en el Explorador Buscar Ramas/Etiquetas/Submódulos + Visibilidad en el Gráfico + Desestablecer + Ocultar en el Gráfico de Commits + Filtrar en el Gráfico de Commits + Cambiar Modo de Ordenación + Fecha de Commit (--date-order) + Topológicamente (--topo-order) RAMAS LOCALES Navegar a HEAD Habilitar Opción '--first-parent' @@ -593,6 +607,7 @@ Actualización de Software Actualmente no hay actualizaciones disponibles. Copiar SHA + Ir a Squash Commits En: Clave Privada SSH: @@ -670,6 +685,7 @@ COMMIT & PUSH Plantilla/Historias Activar evento de clic + Commit (Editar) Stagear todos los cambios y commit ¡Commit vacío detectado! ¿Quieres continuar (--allow-empty)? CONFLICTOS DETECTADOS diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index 84475a52..5b2cf129 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -124,6 +124,7 @@ Найти изменения.... ФАЙЛЫ Файл ХБФ + Поиск файлов... Подмодуль ИНФОРМАЦИЯ АВТОР @@ -250,6 +251,7 @@ Обмен Подсветка синтаксиса Перенос слов в строке + Разрешить навигацию по блокам Открыть в инструменте слияния Показывать все линии Уменьшить количество видимых линий diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 9cf0eedf..d6bbad3f 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -124,6 +124,7 @@ 查找变更... 文件列表 LFS文件 + 查找文件... 子模块 基本信息 修改者 @@ -249,6 +250,7 @@ 交换比对双方 语法高亮 自动换行 + 启用基于变更块的跳转 使用外部合并工具查看 显示完整文件 减少可见的行数 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 920d9114..0034c3f4 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -124,6 +124,7 @@ 搜尋變更... 檔案列表 LFS 檔案 + 搜尋檔案... 子模組 基本資訊 作者 @@ -249,6 +250,7 @@ 交換比對雙方 語法上色 自動換行 + 啟用基於變更區塊的導航 使用外部合併工具檢視 顯示檔案的全部內容 減少可見的行數 diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index a42b2cb8..2cc09e39 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -856,12 +856,7 @@ - + = Start && line <= End; + } + } + + public AvaloniaList Blocks + { + get; + } = []; + + public int Current + { + get => _current; + private set => SetProperty(ref _current, value); + } + + public string Indicator + { + get + { + if (Blocks.Count == 0) + return "-/-"; + + if (_current >= 0 && _current < Blocks.Count) + return $"{_current + 1}/{Blocks.Count}"; + + return $"-/{Blocks.Count}"; + } + } + + public BlockNavigation(object context) + { + Blocks.Clear(); + Current = -1; + + var lines = new List(); + if (context is Models.TextDiff combined) + lines = combined.Lines; + else if (context is TwoSideTextDiff twoSide) + lines = twoSide.Old; + + if (lines.Count == 0) + return; + + var lineIdx = 0; + var blockStartIdx = 0; + var isNewBlock = true; + var blocks = new List(); + + foreach (var line in lines) + { + lineIdx++; + if (line.Type == Models.TextDiffLineType.Added || + line.Type == Models.TextDiffLineType.Deleted || + line.Type == Models.TextDiffLineType.None) + { + if (isNewBlock) + { + isNewBlock = false; + blockStartIdx = lineIdx; + } + } + else + { + if (!isNewBlock) + { + blocks.Add(new Block(blockStartIdx, lineIdx - 1)); + isNewBlock = true; + } + } + } + + if (!isNewBlock) + blocks.Add(new Block(blockStartIdx, lines.Count - 1)); + + Blocks.AddRange(blocks); + } + + public Block GetCurrentBlock() + { + return (_current >= 0 && _current < Blocks.Count) ? Blocks[_current] : null; + } + + public Block GotoNext() + { + if (Blocks.Count == 0) + return null; + + Current = (_current + 1) % Blocks.Count; + return Blocks[_current]; + } + + public Block GotoPrev() + { + if (Blocks.Count == 0) + return null; + + Current = _current == -1 ? Blocks.Count - 1 : (_current - 1 + Blocks.Count) % Blocks.Count; + return Blocks[_current]; + } + + private int _current = -1; + } +} diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index f14b0359..bf9f57c9 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -82,7 +82,7 @@ namespace SourceGit.ViewModels { get; private set; - } = new AvaloniaList(); + } = []; public string SearchChangeFilter { @@ -106,13 +106,71 @@ namespace SourceGit.ViewModels { get; private set; - } = new AvaloniaList(); + } = []; public AvaloniaList 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(); + } + } + } + } + + public AvaloniaList RevisionFileSearchSuggestion + { + get; + private set; + } = []; + + public bool IsRevisionFileSearchSuggestionOpen + { + get => _isRevisionFileSearchSuggestionOpen; + set => SetProperty(ref _isRevisionFileSearchSuggestionOpen, value); + } + public CommitDetail(Repository repo) { _repo = repo; @@ -147,17 +205,23 @@ namespace SourceGit.ViewModels { _repo = null; _commit = null; + if (_changes != null) _changes.Clear(); if (_visibleChanges != null) _visibleChanges.Clear(); if (_selectedChanges != null) _selectedChanges.Clear(); + _signInfo = null; _searchChangeFilter = null; _diffContext = null; _viewRevisionFileContent = null; _cancelToken = null; + + WebLinks.Clear(); + _revisionFiles.Clear(); + RevisionFileSearchSuggestion.Clear(); } public void NavigateTo(string commitSHA) @@ -175,6 +239,11 @@ namespace SourceGit.ViewModels SearchChangeFilter = string.Empty; } + public void ClearRevisionFileSearchFilter() + { + RevisionFileSearchFilter = string.Empty; + } + public Models.Commit GetParent(string sha) { return new Commands.QuerySingleCommit(_repo.FullPath, sha).Result(); @@ -543,6 +612,8 @@ namespace SourceGit.ViewModels private void Refresh() { _changes = null; + _revisionFiles.Clear(); + FullMessage = string.Empty; SignInfo = null; Changes = []; @@ -550,6 +621,10 @@ namespace SourceGit.ViewModels SelectedChanges = null; ViewRevisionFileContent = null; Children.Clear(); + RevisionFileSearchFilter = string.Empty; + IsRevisionFileSearchSuggestionOpen = false; + + GC.Collect(); if (_commit == null) return; @@ -716,6 +791,24 @@ namespace SourceGit.ViewModels menu.Items.Add(new MenuItem() { Header = "-" }); } + private void UpdateRevisionFileSearchSuggestion() + { + var suggestion = new List(); + foreach (var file in _revisionFiles) + { + if (file.Contains(_revisionFileSearchFilter, StringComparison.OrdinalIgnoreCase) && + file.Length != _revisionFileSearchFilter.Length) + suggestion.Add(file); + + if (suggestion.Count >= 100) + break; + } + + RevisionFileSearchSuggestion.Clear(); + RevisionFileSearchSuggestion.AddRange(suggestion); + IsRevisionFileSearchSuggestionOpen = suggestion.Count > 0; + } + [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(); @@ -736,5 +829,8 @@ namespace SourceGit.ViewModels private DiffContext _diffContext = null; private object _viewRevisionFileContent = null; private Commands.Command.CancelToken _cancelToken = null; + private List _revisionFiles = []; + private string _revisionFileSearchFilter = string.Empty; + private bool _isRevisionFileSearchSuggestionOpen = false; } } diff --git a/src/ViewModels/Preference.cs b/src/ViewModels/Preference.cs index 1ccee4cd..a02df359 100644 --- a/src/ViewModels/Preference.cs +++ b/src/ViewModels/Preference.cs @@ -206,6 +206,12 @@ namespace SourceGit.ViewModels set => SetProperty(ref _useFullTextDiff, value); } + public bool UseBlockNavigationInDiffView + { + get => _useBlockNavigationInDiffView; + set => SetProperty(ref _useBlockNavigationInDiffView, value); + } + public Models.ChangeViewMode UnstagedChangeViewMode { get => _unstagedChangeViewMode; @@ -614,6 +620,7 @@ namespace SourceGit.ViewModels private bool _enableDiffViewWordWrap = false; private bool _showHiddenSymbolsInDiffView = false; private bool _useFullTextDiff = false; + private bool _useBlockNavigationInDiffView = false; private Models.ChangeViewMode _unstagedChangeViewMode = Models.ChangeViewMode.List; private Models.ChangeViewMode _stagedChangeViewMode = Models.ChangeViewMode.List; diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 92bfb288..38855fef 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -427,12 +427,12 @@ namespace SourceGit.ViewModels { // Ignore } - _settings = null; - _historiesFilterMode = Models.FilterMode.None; - _autoFetchTimer.Dispose(); _autoFetchTimer = null; + _settings = null; + _historiesFilterMode = Models.FilterMode.None; + _watcher?.Dispose(); _histories.Cleanup(); _workingCopy.Cleanup(); @@ -2147,7 +2147,7 @@ namespace SourceGit.ViewModels { Task.Run(() => { - var files = new Commands.QueryCurrentRevisionFiles(_fullpath).Result(); + var files = new Commands.QueryRevisionFileNames(_fullpath, "HEAD").Result(); Dispatcher.UIThread.Invoke(() => { if (_searchCommitFilterType != 3) diff --git a/src/Views/DeleteBranch.axaml b/src/Views/DeleteBranch.axaml index b2693bf0..05ec6e26 100644 --- a/src/Views/DeleteBranch.axaml +++ b/src/Views/DeleteBranch.axaml @@ -16,6 +16,18 @@ + + + diff --git a/src/Views/DeleteMultipleBranches.axaml b/src/Views/DeleteMultipleBranches.axaml index 2a888118..cf084e14 100644 --- a/src/Views/DeleteMultipleBranches.axaml +++ b/src/Views/DeleteMultipleBranches.axaml @@ -42,9 +42,22 @@ - + + + + diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index e0627ad8..6716ed3a 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -41,6 +41,17 @@ ToolTip.Tip="{DynamicResource Text.Diff.Prev}"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/src/Views/RevisionFiles.axaml.cs b/src/Views/RevisionFiles.axaml.cs index 53c36b1c..f748fb0d 100644 --- a/src/Views/RevisionFiles.axaml.cs +++ b/src/Views/RevisionFiles.axaml.cs @@ -3,6 +3,7 @@ using System; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; +using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media; @@ -118,5 +119,81 @@ namespace SourceGit.Views { InitializeComponent(); } + + private void OnSearchBoxKeyDown(object _, KeyEventArgs e) + { + var vm = DataContext as ViewModels.CommitDetail; + if (vm == null) + return; + + if (e.Key == Key.Enter) + { + FileTree.SetSearchResult(vm.RevisionFileSearchFilter); + e.Handled = true; + } + else if (e.Key == Key.Down || e.Key == Key.Up) + { + if (vm.IsRevisionFileSearchSuggestionOpen) + { + SearchSuggestionBox.Focus(NavigationMethod.Tab); + SearchSuggestionBox.SelectedIndex = 0; + } + + e.Handled = true; + } + else if (e.Key == Key.Escape) + { + if (vm.IsRevisionFileSearchSuggestionOpen) + { + vm.RevisionFileSearchSuggestion.Clear(); + vm.IsRevisionFileSearchSuggestionOpen = false; + } + + e.Handled = true; + } + } + + private void OnSearchBoxTextChanged(object _, TextChangedEventArgs e) + { + if (string.IsNullOrEmpty(TxtSearchRevisionFiles.Text)) + FileTree.SetSearchResult(null); + } + + private void OnSearchSuggestionBoxKeyDown(object _, KeyEventArgs e) + { + var vm = DataContext as ViewModels.CommitDetail; + if (vm == null) + return; + + if (e.Key == Key.Escape) + { + vm.RevisionFileSearchSuggestion.Clear(); + e.Handled = true; + } + else if (e.Key == Key.Enter && SearchSuggestionBox.SelectedItem is string content) + { + vm.RevisionFileSearchFilter = content; + TxtSearchRevisionFiles.CaretIndex = content.Length; + FileTree.SetSearchResult(vm.RevisionFileSearchFilter); + e.Handled = true; + } + } + + private void OnSearchSuggestionDoubleTapped(object sender, TappedEventArgs e) + { + var vm = DataContext as ViewModels.CommitDetail; + if (vm == null) + return; + + var content = (sender as StackPanel)?.DataContext as string; + if (!string.IsNullOrEmpty(content)) + { + vm.RevisionFileSearchFilter = content; + TxtSearchRevisionFiles.CaretIndex = content.Length; + FileTree.SetSearchResult(vm.RevisionFileSearchFilter); + } + + e.Handled = true; + } } } diff --git a/src/Views/TextDiffView.axaml b/src/Views/TextDiffView.axaml index d9d6dde3..1dc26bb3 100644 --- a/src/Views/TextDiffView.axaml +++ b/src/Views/TextDiffView.axaml @@ -31,7 +31,8 @@ WordWrap="{Binding Source={x:Static vm:Preference.Instance}, Path=EnableDiffViewWordWrap}" ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}" EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}" - SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/> + SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}" + BlockNavigation="{Binding #ThisControl.BlockNavigation, Mode=TwoWay}"/> @@ -62,7 +63,8 @@ WordWrap="False" ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}" EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}" - SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/> + SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}" + BlockNavigation="{Binding #ThisControl.BlockNavigation, Mode=TwoWay}"/> @@ -83,7 +85,8 @@ WordWrap="False" ShowHiddenSymbols="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowHiddenSymbolsInDiffView}" EnableChunkSelection="{Binding #ThisControl.EnableChunkSelection}" - SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}"/> + SelectedChunk="{Binding #ThisControl.SelectedChunk, Mode=TwoWay}" + BlockNavigation="{Binding #ThisControl.BlockNavigation, Mode=TwoWay}"/> diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index ea70ac93..a4c2bd0a 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Globalization; using System.IO; using System.Text; @@ -254,6 +255,10 @@ namespace SourceGit.Views if (_presenter.Document == null || !textView.VisualLinesValid) return; + var changeBlock = _presenter.BlockNavigation?.GetCurrentBlock(); + Brush changeBlockBG = new SolidColorBrush(Colors.Gray, 0.25); + Pen changeBlockFG = new Pen(Brushes.Gray); + var lines = _presenter.GetLines(); var width = textView.Bounds.Width; foreach (var line in textView.VisualLines) @@ -266,51 +271,62 @@ namespace SourceGit.Views break; var info = lines[index - 1]; - var bg = GetBrushByLineType(info.Type); - if (bg == null) - continue; var startY = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.LineTop) - textView.VerticalOffset; var endY = line.GetTextLineVisualYPosition(line.TextLines[^1], VisualYPosition.LineBottom) - textView.VerticalOffset; - drawingContext.DrawRectangle(bg, null, new Rect(0, startY, width, endY - startY)); - if (info.Highlights.Count > 0) + var bg = GetBrushByLineType(info.Type); + if (bg != null) { - var highlightBG = info.Type == Models.TextDiffLineType.Added ? _presenter.AddedHighlightBrush : _presenter.DeletedHighlightBrush; - var processingIdxStart = 0; - var processingIdxEnd = 0; - var nextHightlight = 0; + drawingContext.DrawRectangle(bg, null, new Rect(0, startY, width, endY - startY)); - foreach (var tl in line.TextLines) + if (info.Highlights.Count > 0) { - processingIdxEnd += tl.Length; + var highlightBG = info.Type == Models.TextDiffLineType.Added ? _presenter.AddedHighlightBrush : _presenter.DeletedHighlightBrush; + var processingIdxStart = 0; + var processingIdxEnd = 0; + var nextHighlight = 0; - var y = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineTop) - textView.VerticalOffset; - var h = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineBottom) - textView.VerticalOffset - y; - - while (nextHightlight < info.Highlights.Count) + foreach (var tl in line.TextLines) { - var highlight = info.Highlights[nextHightlight]; - if (highlight.Start >= processingIdxEnd) - break; + processingIdxEnd += tl.Length; - var start = line.GetVisualColumn(highlight.Start < processingIdxStart ? processingIdxStart : highlight.Start); - var end = line.GetVisualColumn(highlight.End >= processingIdxEnd ? processingIdxEnd : highlight.End + 1); + var y = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineTop) - textView.VerticalOffset; + var h = line.GetTextLineVisualYPosition(tl, VisualYPosition.LineBottom) - textView.VerticalOffset - y; - var x = line.GetTextLineVisualXPosition(tl, start) - textView.HorizontalOffset; - var w = line.GetTextLineVisualXPosition(tl, end) - textView.HorizontalOffset - x; - var rect = new Rect(x, y, w, h); - drawingContext.DrawRectangle(highlightBG, null, rect); + while (nextHighlight < info.Highlights.Count) + { + var highlight = info.Highlights[nextHighlight]; + if (highlight.Start >= processingIdxEnd) + break; - if (highlight.End >= processingIdxEnd) - break; + var start = line.GetVisualColumn(highlight.Start < processingIdxStart ? processingIdxStart : highlight.Start); + var end = line.GetVisualColumn(highlight.End >= processingIdxEnd ? processingIdxEnd : highlight.End + 1); - nextHightlight++; + var x = line.GetTextLineVisualXPosition(tl, start) - textView.HorizontalOffset; + var w = line.GetTextLineVisualXPosition(tl, end) - textView.HorizontalOffset - x; + var rect = new Rect(x, y, w, h); + drawingContext.DrawRectangle(highlightBG, null, rect); + + if (highlight.End >= processingIdxEnd) + break; + + nextHighlight++; + } + + processingIdxStart = processingIdxEnd; } - - processingIdxStart = processingIdxEnd; } } + + if (changeBlock != null && changeBlock.IsInRange(index)) + { + drawingContext.DrawRectangle(changeBlockBG, null, new Rect(0, startY, width, endY - startY)); + if (index == changeBlock.Start) + drawingContext.DrawLine(changeBlockFG, new Point(0, startY), new Point(width, startY)); + if (index == changeBlock.End) + drawingContext.DrawLine(changeBlockFG, new Point(0, endY), new Point(width, endY)); + } } } @@ -486,6 +502,15 @@ namespace SourceGit.Views set => SetValue(DisplayRangeProperty, value); } + public static readonly StyledProperty BlockNavigationProperty = + AvaloniaProperty.Register(nameof(BlockNavigation)); + + public ViewModels.BlockNavigation BlockNavigation + { + get => GetValue(BlockNavigationProperty); + set => SetValue(BlockNavigationProperty, value); + } + protected override Type StyleKeyOverride => typeof(TextEditor); public ThemedTextDiffPresenter(TextArea area, TextDocument doc) : base(area, doc) @@ -520,6 +545,19 @@ namespace SourceGit.Views public void GotoPrevChange() { + var blockNavigation = BlockNavigation; + if (blockNavigation != null) + { + var prev = blockNavigation.GotoPrev(); + if (prev != null) + { + TextArea.Caret.Line = prev.Start; + ScrollToLine(prev.Start); + } + + return; + } + var firstLineIdx = DisplayRange.StartIdx; if (firstLineIdx <= 1) return; @@ -563,6 +601,19 @@ namespace SourceGit.Views public void GotoNextChange() { + var blockNavigation = BlockNavigation; + if (blockNavigation != null) + { + var next = blockNavigation.GotoNext(); + if (next != null) + { + TextArea.Caret.Line = next.Start; + ScrollToLine(next.Start); + } + + return; + } + var lines = GetLines(); var lastLineIdx = DisplayRange.EndIdx; if (lastLineIdx >= lines.Count - 1) @@ -665,6 +716,22 @@ namespace SourceGit.Views { InvalidateVisual(); } + else if (change.Property == BlockNavigationProperty) + { + var oldValue = change.OldValue as ViewModels.BlockNavigation; + var newValue = change.NewValue as ViewModels.BlockNavigation; + if (oldValue != null) + oldValue.PropertyChanged -= OnBlockNavigationPropertyChanged; + if (newValue != null) + newValue.PropertyChanged += OnBlockNavigationPropertyChanged; + + InvalidateVisual(); + } + } + + private void OnBlockNavigationPropertyChanged(object _1, PropertyChangedEventArgs _2) + { + TextArea.TextView.Redraw(); } private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e) @@ -1089,6 +1156,8 @@ namespace SourceGit.Views public void ForceSyncScrollOffset() { + if (_scrollViewer == null) + return; if (DataContext is ViewModels.TwoSideTextDiff diff) diff.SyncScrollOffset = _scrollViewer?.Offset ?? Vector.Zero; } @@ -1444,15 +1513,6 @@ namespace SourceGit.Views set => SetValue(UseSideBySideDiffProperty, value); } - public static readonly StyledProperty UseFullTextDiffProperty = - AvaloniaProperty.Register(nameof(UseFullTextDiff)); - - public bool UseFullTextDiff - { - get => GetValue(UseFullTextDiffProperty); - set => SetValue(UseFullTextDiffProperty, value); - } - public static readonly StyledProperty SelectedChunkProperty = AvaloniaProperty.Register(nameof(SelectedChunk)); @@ -1480,6 +1540,33 @@ namespace SourceGit.Views set => SetValue(EnableChunkSelectionProperty, value); } + public static readonly StyledProperty UseBlockNavigationProperty = + AvaloniaProperty.Register(nameof(UseBlockNavigation)); + + public bool UseBlockNavigation + { + get => GetValue(UseBlockNavigationProperty); + set => SetValue(UseBlockNavigationProperty, value); + } + + public static readonly StyledProperty BlockNavigationProperty = + AvaloniaProperty.Register(nameof(BlockNavigation)); + + public ViewModels.BlockNavigation BlockNavigation + { + get => GetValue(BlockNavigationProperty); + set => SetValue(BlockNavigationProperty, value); + } + + public static readonly StyledProperty BlockNavigationIndicatorProperty = + AvaloniaProperty.Register(nameof(BlockNavigationIndicator)); + + public string BlockNavigationIndicator + { + get => GetValue(BlockNavigationIndicatorProperty); + set => SetValue(BlockNavigationIndicatorProperty, value); + } + static TextDiffView() { UseSideBySideDiffProperty.Changed.AddClassHandler((v, _) => @@ -1487,7 +1574,7 @@ namespace SourceGit.Views v.RefreshContent(v.DataContext as Models.TextDiff, false); }); - UseFullTextDiffProperty.Changed.AddClassHandler((v, _) => + UseBlockNavigationProperty.Changed.AddClassHandler((v, _) => { v.RefreshContent(v.DataContext as Models.TextDiff, false); }); @@ -1513,6 +1600,32 @@ namespace SourceGit.Views InitializeComponent(); } + public void GotoPrevChange() + { + var presenter = this.FindDescendantOfType(); + if (presenter == null) + return; + + presenter.GotoPrevChange(); + if (presenter is SingleSideTextDiffPresenter singleSide) + singleSide.ForceSyncScrollOffset(); + + BlockNavigationIndicator = BlockNavigation?.Indicator ?? string.Empty; + } + + public void GotoNextChange() + { + var presenter = this.FindDescendantOfType(); + if (presenter == null) + return; + + presenter.GotoNextChange(); + if (presenter is SingleSideTextDiffPresenter singleSide) + singleSide.ForceSyncScrollOffset(); + + BlockNavigationIndicator = BlockNavigation?.Indicator ?? string.Empty; + } + protected override void OnDataContextChanged(EventArgs e) { base.OnDataContextChanged(e); @@ -1551,6 +1664,17 @@ namespace SourceGit.Views Editor.Content = diff; } + if (UseBlockNavigation) + { + BlockNavigation = new ViewModels.BlockNavigation(Editor.Content); + BlockNavigationIndicator = BlockNavigation.Indicator; + } + else + { + BlockNavigation = null; + BlockNavigationIndicator = "-/-"; + } + IsUnstagedChange = diff.Option.IsUnstaged; EnableChunkSelection = diff.Option.WorkingCopyChange != null; } diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index e77547e7..594a72b9 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -239,7 +239,6 @@ Margin="8,0,0,0" HorizontalAlignment="Left" IsChecked="{Binding UseAmend, Mode=TwoWay}" - IsVisible="{Binding InProgressContext, Converter={x:Static ObjectConverters.IsNull}}" Content="{DynamicResource Text.WorkingCopy.Amend}"/>