diff --git a/GitCommands/Git/GitModule.cs b/GitCommands/Git/GitModule.cs index 00ac0b506ff..d02db43cc6a 100644 --- a/GitCommands/Git/GitModule.cs +++ b/GitCommands/Git/GitModule.cs @@ -2449,7 +2449,7 @@ public IReadOnlyList GetDiffFilesWithSubmodulesStatus(ObjectId fi /// to revision /// The parent for the second revision /// Git revisions are required to determine if allows stage/unstage. - private static StagedStatus GetStagedStatus([CanBeNull] ObjectId firstId, [CanBeNull] ObjectId secondId, [CanBeNull] ObjectId parentToSecond) + public static StagedStatus GetStagedStatus([CanBeNull] ObjectId firstId, [CanBeNull] ObjectId secondId, [CanBeNull] ObjectId parentToSecond) { StagedStatus staged; if (firstId == ObjectId.IndexId && secondId == ObjectId.WorkTreeId) diff --git a/GitUI/CommandsDialogs/FormCommit.Designer.cs b/GitUI/CommandsDialogs/FormCommit.Designer.cs index 42d3d50fa06..1285d549924 100644 --- a/GitUI/CommandsDialogs/FormCommit.Designer.cs +++ b/GitUI/CommandsDialogs/FormCommit.Designer.cs @@ -1083,8 +1083,8 @@ private void InitializeComponent() this.SelectedDiff.Size = new System.Drawing.Size(517, 426); this.SelectedDiff.TabIndex = 0; this.SelectedDiff.TabStop = false; - this.SelectedDiff.ContextMenuOpening += new System.ComponentModel.CancelEventHandler(this.SelectedDiff_ContextMenuOpening); - this.SelectedDiff.TextLoaded += new System.EventHandler(this.SelectedDiff_TextLoaded); + this.SelectedDiff.ExtraDiffArgumentsChanged += SelectedDiffExtraDiffArgumentsChanged; + this.SelectedDiff.PatchApplied += SelectedDiff_PatchApplied; // // tableLayoutPanel1 // diff --git a/GitUI/CommandsDialogs/FormCommit.cs b/GitUI/CommandsDialogs/FormCommit.cs index 2ac8c511ed0..8009673f9d6 100644 --- a/GitUI/CommandsDialogs/FormCommit.cs +++ b/GitUI/CommandsDialogs/FormCommit.cs @@ -16,8 +16,6 @@ using GitCommands.Config; using GitCommands.Git; using GitCommands.Git.Commands; -using GitCommands.Patches; -using GitCommands.Utils; using GitExtUtils; using GitExtUtils.GitUI; using GitExtUtils.GitUI.Theming; @@ -103,7 +101,6 @@ public sealed partial class FormCommit : GitModuleForm private readonly TranslationString _selectOnlyOneFile = new TranslationString("You must have only one file selected."); private readonly TranslationString _addSelectionToCommitMessage = new TranslationString("Add selection to commit message"); - private readonly TranslationString _formTitle = new TranslationString("Commit to {0} ({1})"); private readonly TranslationString _selectionFilterToolTip = new TranslationString("Enter a regular expression to select unstaged files."); @@ -151,8 +148,6 @@ public sealed partial class FormCommit : GitModuleForm private readonly ICommitTemplateManager _commitTemplateManager; [CanBeNull] private readonly GitRevision _editedCommit; private readonly ToolStripMenuItem _addSelectionToCommitMessageToolStripMenuItem; - private readonly ToolStripMenuItem _stageSelectedLinesToolStripMenuItem; - private readonly ToolStripMenuItem _resetSelectedLinesToolStripMenuItem; private readonly AsyncLoader _unstagedLoader = new AsyncLoader(); private readonly bool _useFormCommitMessage = AppSettings.UseFormCommitMessage; private readonly CancellationTokenSequence _interactiveAddSequence = new CancellationTokenSequence(); @@ -174,7 +169,6 @@ public sealed partial class FormCommit : GitModuleForm private bool _bypassActivatedEventHandler; private bool _loadUnstagedOutputFirstTime = true; private bool _initialized; - private bool _selectedDiffReloaded = true; [CanBeNull] private IReadOnlyList _currentSelection; private int _alreadyLoadedTemplatesCount = -1; private EventHandler _branchNameLabelOnClick; @@ -220,7 +214,6 @@ public FormCommit([NotNull] GitUICommands commands, CommitKind commitKind = Comm SolveMergeconflicts.Font = new Font(SolveMergeconflicts.Font, FontStyle.Bold); - SelectedDiff.ExtraDiffArgumentsChanged += SelectedDiffExtraDiffArgumentsChanged; StageInSuperproject.Visible = Module.SuperprojectModule is not null; StageInSuperproject.Checked = AppSettings.StageInSuperprojectAfterCommit; closeDialogAfterEachCommitToolStripMenuItem.Checked = AppSettings.CloseCommitDialogAfterCommit; @@ -251,15 +244,10 @@ public FormCommit([NotNull] GitUICommands commands, CommitKind commitKind = Comm stagedEditFileToolStripMenuItem11.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.EditFile); SelectedDiff.AddContextMenuSeparator(); - _stageSelectedLinesToolStripMenuItem = SelectedDiff.AddContextMenuEntry(Strings.StageSelectedLines, StageSelectedLinesToolStripMenuItemClick); - _stageSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.StageSelectedFile); - _resetSelectedLinesToolStripMenuItem = SelectedDiff.AddContextMenuEntry(Strings.ResetSelectedLines, ResetSelectedLinesToolStripMenuItemClick); - _resetSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.ResetSelectedFiles); - _resetSelectedLinesToolStripMenuItem.Image = Reset.Image; _addSelectionToCommitMessageToolStripMenuItem = SelectedDiff.AddContextMenuEntry(_addSelectionToCommitMessage.Text, (s, e) => AddSelectionToCommitMessage()); _addSelectionToCommitMessageToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.AddSelectionToCommitMessage); - resetChanges.ShortcutKeyDisplayString = _resetSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString; - stagedResetChanges.ShortcutKeyDisplayString = _resetSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString; + resetChanges.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.ResetSelectedFiles); + stagedResetChanges.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.ResetSelectedFiles); deleteFileToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.DeleteSelectedFiles); viewFileHistoryToolStripItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.ShowHistory); stagedFileHistoryToolStripMenuItem6.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.ShowHistory); @@ -310,6 +298,7 @@ public FormCommit([NotNull] GitUICommands commands, CommitKind commitKind = Comm SelectedDiff.EscapePressed += () => DialogResult = DialogResult.Cancel; SelectedDiff.TopScrollReached += FileViewer_TopScrollReached; SelectedDiff.BottomScrollReached += FileViewer_BottomScrollReached; + SelectedDiff.LinePatchingBlocksUntilReload = true; SolveMergeconflicts.BackColor = AppColor.Branch.GetThemeColor(); SolveMergeconflicts.SetForeColorForBackColor(); @@ -578,17 +567,6 @@ protected override bool ProcessCmdKey(ref Message msg, Keys keyData) return base.ProcessCmdKey(ref msg, keyData); } - private void SelectedDiff_ContextMenuOpening(object sender, System.ComponentModel.CancelEventArgs e) - { - _stageSelectedLinesToolStripMenuItem.Enabled = SelectedDiff.HasAnyPatches() || (_currentItem?.Item is not null && _currentItem.Item.IsNew); - _resetSelectedLinesToolStripMenuItem.Enabled = _stageSelectedLinesToolStripMenuItem.Enabled; - } - - private void SelectedDiff_TextLoaded(object sender, EventArgs e) - { - _selectedDiffReloaded = true; - } - private void FileViewer_TopScrollReached(object sender, EventArgs e) { var fileStatus = _currentItemStaged ? Staged : Unstaged; @@ -662,24 +640,24 @@ private bool AddSelectionToCommitMessage() private bool AddToGitIgnore() { - if (Unstaged.Focused) + if (!Unstaged.Focused) { - AddFileToGitIgnoreToolStripMenuItemClick(this, null); - return true; + return false; } - return false; + AddFileToGitIgnoreToolStripMenuItemClick(this, null); + return true; } private bool DeleteSelectedFiles() { - if (Unstaged.Focused) + if (!Unstaged.Focused) { - DeleteFileToolStripMenuItemClick(this, null); - return true; + return false; } - return false; + DeleteFileToolStripMenuItemClick(this, null); + return true; } private bool FocusStagedFiles() @@ -708,50 +686,35 @@ private bool FocusCommitMessage() private bool ResetSelectedFiles() { - if (Unstaged.Focused || Staged.Focused) + if (!Unstaged.Focused && !Staged.Focused) { - ResetSoftClick(this, null); - return true; - } - else if (SelectedDiff.ContainsFocus && _resetSelectedLinesToolStripMenuItem.Enabled) - { - ResetSelectedLinesToolStripMenuItemClick(this, null); - return true; + return false; } - return false; + ResetSoftClick(this, null); + return true; } private bool StageSelectedFile() { - if (Unstaged.Focused) + if (!Unstaged.Focused) { - StageClick(this, null); - return true; - } - else if (SelectedDiff.ContainsFocus && !_currentItemStaged && _stageSelectedLinesToolStripMenuItem.Enabled) - { - StageSelectedLinesToolStripMenuItemClick(this, null); - return true; + return false; } - return false; + StageClick(this, null); + return true; } private bool UnStageSelectedFile() { - if (Staged.Focused) - { - UnstageFilesClick(this, null); - return true; - } - else if (SelectedDiff.ContainsFocus && _currentItemStaged && _stageSelectedLinesToolStripMenuItem.Enabled) + if (!Staged.Focused) { - StageSelectedLinesToolStripMenuItemClick(this, null); - return true; + return false; } - return false; + UnstageFilesClick(this, null); + return true; } private bool StageAllFiles() @@ -760,29 +723,27 @@ private bool StageAllFiles() { return false; } - else - { - StageAllToolStripMenuItemClick(this, null); - return true; - } + + StageAllToolStripMenuItemClick(this, null); + return true; } private bool StartFileHistoryDialog() { - if (Staged.Focused || Unstaged.Focused) + if (!Unstaged.Focused && !Staged.Focused) + { + return false; + } + + if (_currentFilesList.SelectedItem?.Item is not null) { - if (_currentFilesList.SelectedItem?.Item is not null) + if ((!_currentFilesList.SelectedItem.Item.IsNew) && (!_currentFilesList.SelectedItem.Item.IsRenamed)) { - if ((!_currentFilesList.SelectedItem.Item.IsNew) && (!_currentFilesList.SelectedItem.Item.IsRenamed)) - { - UICommands.StartFileHistoryDialog(this, _currentFilesList.SelectedItem.Item.Name); - } + UICommands.StartFileHistoryDialog(this, _currentFilesList.SelectedItem.Item.Name); } - - return true; } - return false; + return true; } private bool ToggleSelectionFilter() @@ -877,133 +838,6 @@ public void ShowDialogWhenChanges(IWin32Window owner = null) }, false); } - private void StageSelectedLinesToolStripMenuItemClick(object sender, EventArgs e) - { - // to prevent multiple clicks - if (!_selectedDiffReloaded) - { - return; - } - - // File no longer selected - if (_currentItem?.Item is null) - { - return; - } - - byte[] patch; - if (_currentItem.Item.IsNew) - { - var treeGuid = _currentItemStaged ? _currentItem.Item.TreeGuid?.ToString() : null; - patch = PatchManager.GetSelectedLinesAsNewPatch(Module, _currentItem.Item.Name, - SelectedDiff.GetText(), SelectedDiff.GetSelectionPosition(), - SelectedDiff.GetSelectionLength(), SelectedDiff.Encoding, false, SelectedDiff.FilePreamble, treeGuid); - } - else - { - patch = PatchManager.GetSelectedLinesAsPatch( - SelectedDiff.GetText(), - SelectedDiff.GetSelectionPosition(), SelectedDiff.GetSelectionLength(), - _currentItemStaged, SelectedDiff.Encoding, _currentItem.Item.IsNew); - } - - if (patch is not null && patch.Length > 0) - { - var args = new GitArgumentBuilder("apply") - { - "--cached", - "--whitespace=nowarn", - { _currentItemStaged, "--reverse" } - }; - - string output = Module.GitExecutable.GetOutput(args, patch); - - ProcessApplyOutput(output, patch); - } - } - - private void ProcessApplyOutput(string output, byte[] patch) - { - if (!string.IsNullOrEmpty(output)) - { - MessageBox.Show(this, output + "\n\n" + SelectedDiff.Encoding.GetString(patch), Strings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); - } - - if (_currentItemStaged) - { - Staged.StoreNextIndexToSelect(); - } - else - { - Unstaged.StoreNextIndexToSelect(); - } - - _selectedDiffReloaded = false; - RescanChanges(); - } - - private void ResetSelectedLinesToolStripMenuItemClick(object sender, EventArgs e) - { - // to prevent multiple clicks - if (!_selectedDiffReloaded) - { - return; - } - - // File no longer selected - if (_currentItem?.Item is null) - { - return; - } - - if (MessageBox.Show(this, Strings.ResetSelectedLinesConfirmation, Strings.ResetChangesCaption, - MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No) - { - return; - } - - byte[] patch; - if (_currentItem.Item.IsNew) - { - var treeGuid = _currentItemStaged ? _currentItem.Item.TreeGuid?.ToString() : null; - patch = PatchManager.GetSelectedLinesAsNewPatch(Module, _currentItem.Item.Name, - SelectedDiff.GetText(), SelectedDiff.GetSelectionPosition(), SelectedDiff.GetSelectionLength(), - SelectedDiff.Encoding, !_currentItemStaged, SelectedDiff.FilePreamble, treeGuid); - } - else if (_currentItemStaged) - { - patch = PatchManager.GetSelectedLinesAsPatch( - SelectedDiff.GetText(), - SelectedDiff.GetSelectionPosition(), SelectedDiff.GetSelectionLength(), - _currentItemStaged, SelectedDiff.Encoding, _currentItem.Item.IsNew); - } - else - { - patch = PatchManager.GetResetWorkTreeLinesAsPatch(Module, SelectedDiff.GetText(), - SelectedDiff.GetSelectionPosition(), SelectedDiff.GetSelectionLength(), SelectedDiff.Encoding); - } - - if (patch is not null && patch.Length > 0) - { - var args = new GitArgumentBuilder("apply") - { - "--whitespace=nowarn", - { _currentItemStaged, "--reverse --index" } - }; - - string output = Module.GitExecutable.GetOutput(args, patch); - - if (EnvUtils.RunningOnWindows()) - { - // remove file mode warnings on windows - var regEx = new Regex("warning: .*has type .* expected .*", RegexOptions.Compiled); - output = output.RemoveLines(regEx.IsMatch); - } - - ProcessApplyOutput(output, patch); - } - } - private void EnableStageButtons(bool enable) { toolUnstageItem.Enabled = enable; @@ -1100,10 +934,8 @@ private void InitializedStaged() using (WaitCursorScope.Enter()) { SolveMergeconflicts.Visible = Module.InTheMiddleOfConflictedMerge(); - Staged.SetDiffs( - GetHeadRevision(), - new GitRevision(ObjectId.IndexId), - Module.GetIndexFilesWithSubmodulesStatus()); + var (headRev, indexRev, _) = GetHeadRevisions(); + Staged.SetDiffs(headRev, indexRev, Module.GetIndexFilesWithSubmodulesStatus()); } } @@ -1134,8 +966,9 @@ private void LoadUnstagedOutput(IReadOnlyList allChangedFiles) } } - Unstaged.SetDiffs(new GitRevision(ObjectId.IndexId), new GitRevision(ObjectId.WorkTreeId), unstagedFiles); - Staged.SetDiffs(GetHeadRevision(), new GitRevision(ObjectId.IndexId), stagedFiles); + var (headRev, indexRev, workTreeRev) = GetHeadRevisions(); + Unstaged.SetDiffs(indexRev, workTreeRev, unstagedFiles); + Staged.SetDiffs(headRev, indexRev, stagedFiles); var doChangesExist = Unstaged.AllItems.Any() || Staged.AllItems.Any(); @@ -1247,31 +1080,12 @@ private void ShowChanges(FileStatusItem item, bool staged) ThreadHelper.JoinableTaskFactory.RunAsync(async () => { - await SetSelectedDiffAsync(item, staged); - _selectedDiffReloaded = true; + await SelectedDiff.ViewChangesAsync(item, openWithDiffTool: () => OpenWithDiffTool()); }).FileAndForget(); - _stageSelectedLinesToolStripMenuItem.Text = staged ? Strings.UnstageSelectedLines : Strings.StageSelectedLines; - _stageSelectedLinesToolStripMenuItem.Image = staged ? toolUnstageItem.Image : toolStageItem.Image; - _stageSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString = - GetShortcutKeyDisplayString(staged ? Command.UnStageSelectedFile : Command.StageSelectedFile); - return; } - private async Task SetSelectedDiffAsync(FileStatusItem item, bool staged) - { - if (FileHelper.IsImage(item.Item.Name)) - { - var guid = staged ? ObjectId.IndexId : ObjectId.WorkTreeId; - await SelectedDiff.ViewGitItemRevisionAsync(item.Item, guid, () => OpenWithDiffTool()); - } - else - { - SelectedDiff.ViewCurrentChanges(item.Item, staged, () => OpenWithDiffTool()); - } - } - private void ClearDiffViewIfNoFilesLeft() { if ((Staged.IsEmpty && Unstaged.IsEmpty) || (!Unstaged.SelectedItems.Any() && !Staged.SelectedItems.Any())) @@ -1841,8 +1655,9 @@ private void Unstage(bool canUseUnstageAll = true) unstagedFiles.Add(item); } - Unstaged.SetDiffs(new GitRevision(ObjectId.IndexId), new GitRevision(ObjectId.WorkTreeId), unstagedFiles); - Staged.SetDiffs(GetHeadRevision(), new GitRevision(ObjectId.IndexId), stagedFiles); + var (headRev, indexRev, workTreeRev) = GetHeadRevisions(); + Unstaged.SetDiffs(indexRev, workTreeRev, unstagedFiles); + Staged.SetDiffs(headRev, indexRev, stagedFiles); _skipUpdate = false; Staged.SelectStoredNextIndex(); @@ -1876,16 +1691,24 @@ private void Unstage(bool canUseUnstageAll = true) } } - [CanBeNull] - private GitRevision GetHeadRevision() + private (GitRevision headRev, GitRevision indexRev, GitRevision workTreeRev) GetHeadRevisions() { + GitRevision headRev; + GitRevision indexRev; var headId = Module.RevParse("HEAD"); if (headId is not null) { - return new GitRevision(headId); + headRev = new GitRevision(headId); + indexRev = new GitRevision(ObjectId.IndexId) { ParentIds = new[] { headId } }; + } + else + { + headRev = null; + indexRev = new GitRevision(ObjectId.IndexId); } - return null; + var workTreeRev = new GitRevision(ObjectId.WorkTreeId) { ParentIds = new[] { ObjectId.IndexId } }; + return (headRev, indexRev, workTreeRev); } private void StageClick(object sender, EventArgs e) @@ -2045,7 +1868,8 @@ private void Stage(IReadOnlyList items) item.GetSubmoduleStatusAsync().CompletedResult().Status = SubmoduleStatus.Unknown; } - Unstaged.SetDiffs(new GitRevision(ObjectId.IndexId), new GitRevision(ObjectId.WorkTreeId), unstagedFiles); + var (_, indexRev, workTreeRev) = GetHeadRevisions(); + Unstaged.SetDiffs(indexRev, workTreeRev, unstagedFiles); Unstaged.ClearSelected(); _skipUpdate = false; Unstaged.SelectStoredNextIndex(); @@ -2554,6 +2378,20 @@ private void SelectedDiffExtraDiffArgumentsChanged(object sender, EventArgs e) ShowChanges(_currentItem, _currentItemStaged); } + private void SelectedDiff_PatchApplied(object sender, EventArgs e) + { + if (_currentItemStaged) + { + Staged.StoreNextIndexToSelect(); + } + else + { + Unstaged.StoreNextIndexToSelect(); + } + + RescanChanges(); + } + private void RescanChangesToolStripMenuItemClick(object sender, EventArgs e) { RescanChanges(); diff --git a/GitUI/CommandsDialogs/RevisionDiffControl.Designer.cs b/GitUI/CommandsDialogs/RevisionDiffControl.Designer.cs index 56b6f59f559..6aa2a3cf831 100644 --- a/GitUI/CommandsDialogs/RevisionDiffControl.Designer.cs +++ b/GitUI/CommandsDialogs/RevisionDiffControl.Designer.cs @@ -451,6 +451,7 @@ private void InitializeComponent() this.DiffText.Size = new System.Drawing.Size(423, 360); this.DiffText.TabIndex = 0; this.DiffText.ExtraDiffArgumentsChanged += new System.EventHandler(this.DiffText_ExtraDiffArgumentsChanged); + this.DiffText.PatchApplied += new System.EventHandler(this.DiffText_PatchApplied); // // saveToolStripMenuItem // diff --git a/GitUI/CommandsDialogs/RevisionDiffControl.cs b/GitUI/CommandsDialogs/RevisionDiffControl.cs index 8175b1b706d..ed074eeb2b9 100644 --- a/GitUI/CommandsDialogs/RevisionDiffControl.cs +++ b/GitUI/CommandsDialogs/RevisionDiffControl.cs @@ -59,6 +59,7 @@ public RevisionDiffControl() _revisionDiffContextMenuController = new FileStatusListContextMenuController(); DiffText.TopScrollReached += FileViewer_TopScrollReached; DiffText.BottomScrollReached += FileViewer_BottomScrollReached; + DiffText.LinePatchingBlocksUntilReload = true; } private void FileViewer_TopScrollReached(object sender, EventArgs e) @@ -335,7 +336,7 @@ private ContextMenuSelectionInfo GetSelectionInfo() bool isAnyTracked = selectedItems.Any(item => item.Item.IsTracked); bool isAnyIndex = selectedItems.Any(item => item.Item.Staged == StagedStatus.Index); bool isAnyWorkTree = selectedItems.Any(item => item.Item.Staged == StagedStatus.WorkTree); - bool supportPatches = selectedGitItemCount == 1 && DiffText.HasAnyPatches(); + bool supportPatches = selectedGitItemCount == 1 && DiffText.SupportLinePatching; bool isDeleted = selectedItems.Any(item => item.Item.IsDeleted); bool isAnySubmodule = selectedItems.Any(item => item.Item.IsSubmodule); (bool allFilesExist, bool allDirectoriesExist, bool allFilesOrUntrackedDirectoriesExist) = FileOrUntrackedDirExists(selectedItems, _fullPathResolver); @@ -479,6 +480,11 @@ private void DiffText_ExtraDiffArgumentsChanged(object sender, EventArgs e) }).FileAndForget(); } + private void DiffText_PatchApplied(object sender, EventArgs e) + { + RefreshArtificial(); + } + private void diffShowInFileTreeToolStripMenuItem_Click(object sender, EventArgs e) { // switch to view (and fills the first level of file tree data model if not already done) diff --git a/GitUI/CommandsDialogs/RevisionDiffController.cs b/GitUI/CommandsDialogs/RevisionDiffController.cs index f92a57d687e..841e277629f 100644 --- a/GitUI/CommandsDialogs/RevisionDiffController.cs +++ b/GitUI/CommandsDialogs/RevisionDiffController.cs @@ -117,6 +117,7 @@ public bool ShouldShowMenuCherryPick(ContextMenuSelectionInfo selectionInfo) && !selectionInfo.IsBareRepository && selectionInfo.AllFilesExist && selectionInfo.SupportPatches + && !(selectionInfo.IsAnyItemWorkTree || selectionInfo.IsAnyItemIndex) && !(selectionInfo.SelectedRevision?.IsArtificial ?? false); } diff --git a/GitUI/Editor/FileViewer.Designer.cs b/GitUI/Editor/FileViewer.Designer.cs index 2121531f3d4..4f872d496d6 100644 --- a/GitUI/Editor/FileViewer.Designer.cs +++ b/GitUI/Editor/FileViewer.Designer.cs @@ -1,4 +1,5 @@ -using System.Windows.Forms; +using System; +using System.Windows.Forms; namespace GitUI.Editor { @@ -20,7 +21,9 @@ private void InitializeComponent() this.components = new System.ComponentModel.Container(); this.contextMenu = new System.Windows.Forms.ContextMenuStrip(this.components); this.copyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.cherrypickSelectedLinesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.stageSelectedLinesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.unstageSelectedLinesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.resetSelectedLinesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.copyPatchToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.copyNewVersionToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.copyOldVersionToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -52,7 +55,6 @@ private void InitializeComponent() this.encodingToolStripComboBox = new System.Windows.Forms.ToolStripComboBox(); this.ignoreAllWhitespaces = new System.Windows.Forms.ToolStripButton(); this.PictureBox = new System.Windows.Forms.PictureBox(); - this.revertSelectedLinesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this._NO_TRANSLATE_lblShowPreview = new System.Windows.Forms.LinkLabel(); this.internalFileViewer = new GitUI.Editor.FileViewerInternal(); this.showSyntaxHighlighting = new System.Windows.Forms.ToolStripButton(); @@ -64,9 +66,10 @@ private void InitializeComponent() // contextMenu // this.contextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.stageSelectedLinesToolStripMenuItem, + this.unstageSelectedLinesToolStripMenuItem, + this.resetSelectedLinesToolStripMenuItem, this.copyToolStripMenuItem, - this.cherrypickSelectedLinesToolStripMenuItem, - this.revertSelectedLinesToolStripMenuItem, this.copyPatchToolStripMenuItem, this.copyNewVersionToolStripMenuItem, this.copyOldVersionToolStripMenuItem, @@ -86,6 +89,30 @@ private void InitializeComponent() this.contextMenu.Name = "ContextMenu"; this.contextMenu.Size = new System.Drawing.Size(244, 346); // + // stageSelectedLinesToolStripMenuItem + // + this.stageSelectedLinesToolStripMenuItem.Image = global::GitUI.Properties.Images.Stage; + this.stageSelectedLinesToolStripMenuItem.Name = "stageSelectedLinesToolStripMenuItem"; + this.stageSelectedLinesToolStripMenuItem.Size = new System.Drawing.Size(243, 22); + this.stageSelectedLinesToolStripMenuItem.Text = Strings.StageSelectedLines; + this.stageSelectedLinesToolStripMenuItem.Click += new System.EventHandler(this.stageSelectedLinesToolStripMenuItem_Click); + // + // unstageSelectedLinesToolStripMenuItem + // + this.unstageSelectedLinesToolStripMenuItem.Image = global::GitUI.Properties.Images.Unstage; + this.unstageSelectedLinesToolStripMenuItem.Name = "chunstageSelectedLinesToolStripMenuItemerrypickSelectedLinesToolStripMenuItem"; + this.unstageSelectedLinesToolStripMenuItem.Size = new System.Drawing.Size(243, 22); + this.unstageSelectedLinesToolStripMenuItem.Text = Strings.UnstageSelectedLines; + this.unstageSelectedLinesToolStripMenuItem.Click += new System.EventHandler(this.unstageSelectedLinesToolStripMenuItem_Click); + // + // resetSelectedLinesToolStripMenuItem + // + this.resetSelectedLinesToolStripMenuItem.Image = global::GitUI.Properties.Images.ResetWorkingDirChanges; + this.resetSelectedLinesToolStripMenuItem.Name = "resetSelectedLinesToolStripMenuItem"; + this.resetSelectedLinesToolStripMenuItem.Size = new System.Drawing.Size(243, 22); + this.resetSelectedLinesToolStripMenuItem.Text = Strings.ResetSelectedLines; + this.resetSelectedLinesToolStripMenuItem.Click += new System.EventHandler(this.resetSelectedLinesToolStripMenuItem_Click); + // // copyToolStripMenuItem // this.copyToolStripMenuItem.Name = "copyToolStripMenuItem"; @@ -94,14 +121,6 @@ private void InitializeComponent() this.copyToolStripMenuItem.Text = "Copy"; this.copyToolStripMenuItem.Click += new System.EventHandler(this.CopyToolStripMenuItemClick); // - // cherrypickSelectedLinesToolStripMenuItem - // - this.cherrypickSelectedLinesToolStripMenuItem.Image = global::GitUI.Properties.Images.CherryPick; - this.cherrypickSelectedLinesToolStripMenuItem.Name = "cherrypickSelectedLinesToolStripMenuItem"; - this.cherrypickSelectedLinesToolStripMenuItem.Size = new System.Drawing.Size(243, 22); - this.cherrypickSelectedLinesToolStripMenuItem.Text = Strings.StageSelectedLines; - this.cherrypickSelectedLinesToolStripMenuItem.Click += new System.EventHandler(this.cherrypickSelectedLinesToolStripMenuItem_Click); - // // copyPatchToolStripMenuItem // this.copyPatchToolStripMenuItem.Name = "copyPatchToolStripMenuItem"; @@ -373,14 +392,6 @@ private void InitializeComponent() this.PictureBox.TabIndex = 7; this.PictureBox.TabStop = false; this.PictureBox.Visible = false; - // - // resetSelectedLinesToolStripMenuItem - // - this.revertSelectedLinesToolStripMenuItem.Image = global::GitUI.Properties.Images.ResetFileTo; - this.revertSelectedLinesToolStripMenuItem.Name = "revertSelectedLinesToolStripMenuItem"; - this.revertSelectedLinesToolStripMenuItem.Size = new System.Drawing.Size(243, 22); - this.revertSelectedLinesToolStripMenuItem.Text = Strings.ResetSelectedLines; - this.revertSelectedLinesToolStripMenuItem.Click += new System.EventHandler(this.revertSelectedLinesToolStripMenuItem_Click); // // llShowPreview // @@ -474,8 +485,9 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem goToLineToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem copyNewVersionToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem copyOldVersionToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem cherrypickSelectedLinesToolStripMenuItem; - private System.Windows.Forms.ToolStripMenuItem revertSelectedLinesToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem stageSelectedLinesToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem unstageSelectedLinesToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem resetSelectedLinesToolStripMenuItem; private System.Windows.Forms.ToolStripButton ignoreAllWhitespaces; private System.Windows.Forms.ToolStripMenuItem ignoreAllWhitespaceChangesToolStripMenuItem; private LinkLabel _NO_TRANSLATE_lblShowPreview; diff --git a/GitUI/Editor/FileViewer.cs b/GitUI/Editor/FileViewer.cs index 0d15df5c1ce..d6edebbf9aa 100644 --- a/GitUI/Editor/FileViewer.cs +++ b/GitUI/Editor/FileViewer.cs @@ -5,11 +5,13 @@ using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using System.Windows.Forms; using GitCommands; using GitCommands.Patches; using GitCommands.Settings; +using GitCommands.Utils; using GitExtUtils; using GitExtUtils.GitUI; using GitExtUtils.GitUI.Theming; @@ -18,6 +20,7 @@ using GitUI.Editor.Diff; using GitUI.Hotkey; using GitUI.Properties; +using GitUI.UserControls; using GitUIPluginInterfaces; using JetBrains.Annotations; using ResourceManager; @@ -66,6 +69,7 @@ private enum ViewMode public event EventHandler TextLoaded; public event CancelEventHandler ContextMenuOpening; public event EventHandler ExtraDiffArgumentsChanged; + public event EventHandler PatchApplied; private readonly AsyncLoader _async; private readonly IFullPathResolver _fullPathResolver; @@ -73,6 +77,7 @@ private enum ViewMode private Encoding _encoding; private Func _deferShowFunc; private readonly ContinuousScrollEventManager _continuousScrollEventManager; + private FileStatusItem _viewItem; private static string[] _rangeDiffFullPrefixes = { " ", " ++", " + ", " +", " --", " - ", " -", " +-", " -+", " " }; private static string[] _combinedDiffFullPrefixes = { " ", "++", "+ ", " +", "--", "- ", " -" }; @@ -105,6 +110,7 @@ public FileViewer() PictureBox.MouseWheel += PictureBox_MouseWheel; internalFileViewer.SetContinuousScrollManager(_continuousScrollEventManager); + TextLoaded += (sender, args) => AllowLinePatching = SupportLinePatching; _async = new AsyncLoader(); _async.LoadingError += (_, e) => @@ -195,6 +201,7 @@ public FileViewer() }; _fullPathResolver = new FullPathResolver(() => Module.WorkingDir); + SupportLinePatching = false; } // Public properties @@ -321,6 +328,9 @@ public void SetGitBlameGutter(IEnumerable gitBlameEntries) public void ReloadHotkeys() { Hotkeys = HotkeySettingsManager.LoadHotkeys(HotkeySettingsName); + stageSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.StageLines); + unstageSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.UnstageLines); + resetSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.ResetLines); } public ToolStripSeparator AddContextMenuSeparator() @@ -395,73 +405,25 @@ public void ClearHighlighting() public string GetText() => internalFileViewer.GetText(); - public void ViewCurrentChanges(GitItemStatus item, bool isStaged, [CanBeNull] Action openWithDifftool) - { - ThreadHelper.JoinableTaskFactory.RunAsync( - async () => - { - if (item?.IsStatusOnly ?? false) - { - // Present error (e.g. parsing Git) - await ViewTextAsync(item.Name, item.ErrorMessage); - return; - } - - if (item.IsSubmodule) - { - var getStatusTask = item.GetSubmoduleStatusAsync(); - if (getStatusTask is not null) - { - var status = await getStatusTask; - if (status is null) - { - await ViewTextAsync(item.Name, $"Submodule \"{item.Name}\" has unresolved conflicts"); - return; - } - - await ViewTextAsync(item.Name, LocalizationHelpers.ProcessSubmoduleStatus(Module, status)); - return; - } - - var changes = await Module.GetCurrentChangesAsync(item.Name, item.OldName, isStaged, - GetExtraDiffArguments(), Encoding); - var text = LocalizationHelpers.ProcessSubmodulePatch(Module, item.Name, changes); - await ViewTextAsync(item.Name, text); - return; - } - - if (!item.IsTracked || item.IsNew) - { - var id = isStaged ? ObjectId.IndexId : ObjectId.WorkTreeId; - await ViewGitItemRevisionAsync(item, id, openWithDifftool); - } - else - { - var patch = await Module.GetCurrentChangesAsync( - item.Name, item.OldName, isStaged, GetExtraDiffArguments(), Encoding); - await ViewPatchAsync(item.Name, patch?.Text ?? "", openWithDifftool); - } - - SetVisibilityDiffContextMenuStaging(); - }); - } - /// /// Present the text as a patch in the file viewer /// - /// The fileName to present + /// The gitItem to present /// The patch text /// The action to open the difftool - public void ViewPatch([CanBeNull] string fileName, - [NotNull] string text, - [CanBeNull] Action openWithDifftool = null) + public async Task ViewPatchAsync(FileStatusItem item, string text, Action openWithDifftool) { - ThreadHelper.JoinableTaskFactory.Run( - () => ViewPatchAsync(fileName, text, openWithDifftool)); - } + await ShowOrDeferAsync( + text.Length, + () => + { + ResetView(ViewMode.Diff, item.Item.Name, item, text: text); + internalFileViewer.SetText(text, openWithDifftool, isDiff: IsDiffView(_viewMode), isRangeDiff: _viewMode == ViewMode.RangeDiff); - public async Task ViewPatchAsync(string fileName, string text, Action openWithDifftool) - => await ViewPrivateAsync(fileName, text, openWithDifftool, ViewMode.Diff); + TextLoaded?.Invoke(this, null); + return Task.CompletedTask; + }); + } /// /// Present the text as a patch in the file viewer, for GitHub @@ -690,10 +652,23 @@ public void Clear() ThreadHelper.JoinableTaskFactory.Run(() => ViewTextAsync("", "")); } - public bool HasAnyPatches() - { - return internalFileViewer.GetText() is not null && internalFileViewer.GetText().Contains("@@"); - } + /// + /// If the file viewer contents support line patches + /// + public bool SupportLinePatching { get; private set; } + + /// + /// Configuration to require that the form using the viewer reloads contents before allowing next line patch + /// by clearing + /// Used for index/worktree where line patches modifies the diff + /// + public bool LinePatchingBlocksUntilReload { private get; set; } + + /// + /// Current state for line patching allowed for worktree/index + /// Cleared when the file is reloaded + /// + private bool AllowLinePatching { get; set; } public void SetFileLoader(GetNextFileFnc fileLoader) { @@ -702,10 +677,7 @@ public void SetFileLoader(GetNextFileFnc fileLoader) public void CherryPickAllChanges() { - if (GetText().Length > 0) - { - applySelectedLines(0, GetText().Length, reverse: false); - } + ApplySelectedLines(allFile: true, reverse: false); } // Protected @@ -768,7 +740,9 @@ void DetectDefaultEncoding() private static bool IsDiffView(ViewMode viewMode) { - return viewMode == ViewMode.Diff || viewMode == ViewMode.FixedDiff || viewMode == ViewMode.RangeDiff; + return viewMode == ViewMode.Diff + || viewMode == ViewMode.FixedDiff + || viewMode == ViewMode.RangeDiff; } private async Task ViewPrivateAsync(string fileName, string text, Action openWithDifftool, ViewMode viewMode = ViewMode.Diff) @@ -777,7 +751,7 @@ private async Task ViewPrivateAsync(string fileName, string text, Action openWit text.Length, () => { - ResetView(viewMode, fileName); + ResetView(viewMode, fileName, text: text); internalFileViewer.SetText(text, openWithDifftool, isDiff: IsDiffView(_viewMode), isRangeDiff: _viewMode == ViewMode.RangeDiff); TextLoaded?.Invoke(this, null); @@ -824,14 +798,41 @@ private void CopyNotStartingWith(char startChar) ClipboardUtil.TrySetText(code.AdjustLineEndings(Module.EffectiveConfigFile.core.autocrlf.Value)); } - private void SetVisibilityDiffContextMenu(ViewMode viewMode, [CanBeNull] string fileName) + private StagedStatus ViewItemStagedStatus() { - bool changePhysicalFile = (viewMode == ViewMode.Diff || viewMode == ViewMode.FixedDiff) - && !Module.IsBareRepository() - && File.Exists(_fullPathResolver.Resolve(fileName)); + var stagedStatus = _viewItem?.Item?.Staged ?? StagedStatus.Unknown; + if (stagedStatus == StagedStatus.Unknown) + { + stagedStatus = GitModule.GetStagedStatus(_viewItem?.FirstRevision?.ObjectId, + _viewItem?.SecondRevision?.ObjectId, + _viewItem?.SecondRevision?.FirstParentId); + } + + return stagedStatus; + } - cherrypickSelectedLinesToolStripMenuItem.Visible = changePhysicalFile; - revertSelectedLinesToolStripMenuItem.Visible = changePhysicalFile; + private bool ViewItemIsWorkTree() + { + return _viewMode == ViewMode.Diff + && ViewItemStagedStatus() == StagedStatus.WorkTree + && SupportLinePatching; + } + + private bool ViewItemIsIndex() + { + return _viewMode == ViewMode.Diff + && ViewItemStagedStatus() == StagedStatus.Index + && SupportLinePatching; + } + + private void SetVisibilityDiffContextMenu(ViewMode viewMode) + { + // stage and reset has different implementation depending on the viewItem + // For the user it looks the same and they expect the same menu item (and hotkey) + var isIndex = ViewItemIsIndex(); + stageSelectedLinesToolStripMenuItem.Visible = SupportLinePatching && !isIndex; + unstageSelectedLinesToolStripMenuItem.Visible = isIndex; + resetSelectedLinesToolStripMenuItem.Visible = SupportLinePatching; // RangeDiff patch is undefined, could be new/old commit or to parents copyPatchToolStripMenuItem.Visible = viewMode == ViewMode.Diff || viewMode == ViewMode.FixedDiff; @@ -859,12 +860,6 @@ private void SetVisibilityDiffContextMenu(ViewMode viewMode, [CanBeNull] string ignoreAllWhitespaces.Visible = viewMode == ViewMode.Diff || viewMode == ViewMode.RangeDiff; } - private void SetVisibilityDiffContextMenuStaging() - { - cherrypickSelectedLinesToolStripMenuItem.Visible = false; - revertSelectedLinesToolStripMenuItem.Visible = false; - } - private void OnExtraDiffArgumentsChanged() { ExtraDiffArgumentsChanged?.Invoke(this, EventArgs.Empty); @@ -987,9 +982,10 @@ private void OnIgnoreWhitespaceChanged() AppSettings.IgnoreWhitespaceKind = IgnoreWhitespace; } - private void ResetView(ViewMode viewMode, [CanBeNull] string fileName) + private void ResetView(ViewMode viewMode, [CanBeNull] string fileName, [CanBeNull] FileStatusItem item = null, string text = null) { _viewMode = viewMode; + _viewItem = item; if (_viewMode == ViewMode.Text && !string.IsNullOrEmpty(fileName) && (fileName.EndsWith(".diff", StringComparison.OrdinalIgnoreCase) @@ -998,7 +994,12 @@ private void ResetView(ViewMode viewMode, [CanBeNull] string fileName) _viewMode = ViewMode.FixedDiff; } - SetVisibilityDiffContextMenu(_viewMode, fileName); + SupportLinePatching = IsDiffView(_viewMode) + && !Module.IsBareRepository() + && File.Exists(_fullPathResolver.Resolve(fileName)) + && (text?.Contains("@@") ?? false); + + SetVisibilityDiffContextMenu(_viewMode); ClearImage(); PictureBox.Visible = _viewMode == ViewMode.Image; internalFileViewer.Visible = _viewMode != ViewMode.Image; @@ -1315,6 +1316,286 @@ private void TreatAllFilesAsTextToolStripMenuItemClick(object sender, EventArgs OnExtraDiffArgumentsChanged(); } + private void settingsButton_Click(object sender, EventArgs e) + { + UICommands.StartSettingsDialog(ParentForm, DiffViewerSettingsPage.GetPageReference()); + } + + private void IgnoreAllWhitespaceChangesToolStripMenuItem_Click(object sender, EventArgs e) + { + if (IgnoreWhitespace == IgnoreWhitespaceKind.AllSpace) + { + IgnoreWhitespace = IgnoreWhitespaceKind.None; + } + else + { + IgnoreWhitespace = IgnoreWhitespaceKind.AllSpace; + } + + OnIgnoreWhitespaceChanged(); + OnExtraDiffArgumentsChanged(); + } + + private void stageSelectedLinesToolStripMenuItem_Click(object sender, EventArgs e) + { + StageSelectedLines(); + } + + private void unstageSelectedLinesToolStripMenuItem_Click(object sender, EventArgs e) + { + UnstageSelectedLines(); + } + + private void resetSelectedLinesToolStripMenuItem_Click(object sender, EventArgs e) + { + ResetSelectedLines(); + } + + /// + /// Use implementation matching the current viewItem + /// + private void StageSelectedLines() + { + if (!SupportLinePatching) + { + // Hotkey executed when menu is disabled + return; + } + + if (ViewItemIsWorkTree()) + { + StageSelectedLines(stage: true); + return; + } + + ApplySelectedLines(allFile: false, reverse: false); + } + + private void UnstageSelectedLines() + { + if (!ViewItemIsIndex()) + { + // Hotkey executed when menu is disabled + return; + } + + StageSelectedLines(stage: false); + } + + private void ResetSelectedLines() + { + if (!SupportLinePatching) + { + // Hotkey executed when menu is disabled + return; + } + + if (ViewItemIsWorkTree() || ViewItemIsIndex()) + { + ResetNoncommittedSelectedLines(); + return; + } + + ApplySelectedLines(allFile: false, reverse: true); + } + + /// + /// Stage lines in WorkTree or Unstage lines in Index + /// + public void StageSelectedLines(bool stage) + { + if (!AllowLinePatching || _viewItem is null) + { + // reload not completed + return; + } + + byte[] patch; + if (_viewItem.Item.IsNew) + { + var treeGuid = !stage ? _viewItem.Item.TreeGuid?.ToString() : null; + patch = PatchManager.GetSelectedLinesAsNewPatch( + Module, + _viewItem.Item.Name, + GetText(), + GetSelectionPosition(), + GetSelectionLength(), + Encoding, + false, + FilePreamble, + treeGuid); + } + else + { + patch = PatchManager.GetSelectedLinesAsPatch( + GetText(), + GetSelectionPosition(), + GetSelectionLength(), + !stage, + Encoding, + _viewItem.Item.IsNew); + } + + if (patch is null || patch.Length == 0) + { + return; + } + + var args = new GitArgumentBuilder("apply") + { + "--cached", + "--index", + "--whitespace=nowarn", + { !stage, "--reverse" } + }; + + string output = Module.GitExecutable.GetOutput(args, patch); + ProcessApplyOutput(output, patch, patchUpdateDiff: true); + } + + /// + /// Reset lines in Index or Worktree + /// + public void ResetNoncommittedSelectedLines() + { + if (!AllowLinePatching || _viewItem is null) + { + // reload not completed + return; + } + + if (MessageBox.Show(this, Strings.ResetSelectedLinesConfirmation, Strings.ResetChangesCaption, + MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No) + { + return; + } + + byte[] patch; + var currentItemStaged = _viewItem.SecondRevision.ObjectId == ObjectId.IndexId; + if (_viewItem.Item.IsNew) + { + var treeGuid = currentItemStaged ? _viewItem.Item.TreeGuid?.ToString() : null; + patch = PatchManager.GetSelectedLinesAsNewPatch( + Module, + _viewItem.Item.Name, + GetText(), + GetSelectionPosition(), + GetSelectionLength(), + Encoding, + true, + FilePreamble, + treeGuid); + } + else if (currentItemStaged) + { + patch = PatchManager.GetSelectedLinesAsPatch( + GetText(), + GetSelectionPosition(), + GetSelectionLength(), + currentItemStaged, + Encoding, + _viewItem.Item.IsNew); + } + else + { + patch = PatchManager.GetResetWorkTreeLinesAsPatch( + Module, + GetText(), + GetSelectionPosition(), + GetSelectionLength(), + Encoding); + } + + if (patch is null || patch.Length == 0) + { + return; + } + + var args = new GitArgumentBuilder("apply") + { + "--whitespace=nowarn", + { currentItemStaged, "--reverse --index" } + }; + + string output = Module.GitExecutable.GetOutput(args, patch); + if (EnvUtils.RunningOnWindows()) + { + // remove file mode warnings + var regEx = new Regex("warning: .*has type .* expected .*", RegexOptions.Compiled); + output = output.RemoveLines(regEx.IsMatch); + } + + ProcessApplyOutput(output, patch, patchUpdateDiff: true); + } + + /// + /// Cherry-pick/revert patches (not worktree or index) + /// + /// if patches is to be reversed; otherwise . + private void ApplySelectedLines(bool allFile, bool reverse) + { + int selectionStart = allFile ? 0 : GetSelectionPosition(); + int selectionLength = allFile ? GetText().Length : GetSelectionLength(); + if (allFile && selectionLength == 0) + { + return; + } + + byte[] patch; + if (reverse) + { + patch = PatchManager.GetResetWorkTreeLinesAsPatch( + Module, + GetText(), + selectionStart, + selectionLength, + Encoding); + } + else + { + patch = PatchManager.GetSelectedLinesAsPatch( + GetText(), + selectionStart, + selectionLength, + false, + Encoding, + false); + } + + if (patch is null || patch.Length == 0) + { + return; + } + + var args = new GitArgumentBuilder("apply") + { + "--3way", + "--index", + "--whitespace=nowarn" + }; + + string output = Module.GitExecutable.GetOutput(args, patch); + ProcessApplyOutput(output, patch); + } + + private void ProcessApplyOutput(string output, byte[] patch, bool patchUpdateDiff = false) + { + if (!string.IsNullOrEmpty(output)) + { + if (patchUpdateDiff || !MergeConflictHandler.HandleMergeConflicts(UICommands, this, false, false)) + { + MessageBox.Show(this, output + "\n\n" + Encoding.GetString(patch), Strings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } + + if (patchUpdateDiff && LinePatchingBlocksUntilReload) + { + AllowLinePatching = false; + } + + PatchApplied?.Invoke(this, EventArgs.Empty); + } + /// /// Copy selected text, excluding diff added/deleted information /// @@ -1398,6 +1679,16 @@ private void CopyPatchToolStripMenuItemClick(object sender, EventArgs e) } } + private void copyNewVersionToolStripMenuItem_Click(object sender, EventArgs e) + { + CopyNotStartingWith('-'); + } + + private void copyOldVersionToolStripMenuItem_Click(object sender, EventArgs e) + { + CopyNotStartingWith('+'); + } + /// /// Go to next change /// For normal diffs, this is the next diff @@ -1569,90 +1860,11 @@ private void goToLineToolStripMenuItem_Click(object sender, EventArgs e) } } - private void copyNewVersionToolStripMenuItem_Click(object sender, EventArgs e) - { - CopyNotStartingWith('-'); - } - - private void copyOldVersionToolStripMenuItem_Click(object sender, EventArgs e) - { - CopyNotStartingWith('+'); - } - - private void applySelectedLines(int selectionStart, int selectionLength, bool reverse) - { - // Prepare git command - var args = new GitArgumentBuilder("apply") - { - "--3way", - "--whitespace=nowarn" - }; - - byte[] patch; - - if (reverse) - { - patch = PatchManager.GetResetWorkTreeLinesAsPatch( - Module, GetText(), - selectionStart, selectionLength, Encoding); - } - else - { - patch = PatchManager.GetSelectedLinesAsPatch( - GetText(), - selectionStart, selectionLength, - false, Encoding, false); - } - - if (patch is not null && patch.Length > 0) - { - string output = Module.GitExecutable.GetOutput(args, patch); - - if (!string.IsNullOrEmpty(output)) - { - if (!MergeConflictHandler.HandleMergeConflicts(UICommands, this, false, false)) - { - MessageBox.Show(this, output + "\n\n" + Encoding.GetString(patch), Strings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - } - } - - private void cherrypickSelectedLinesToolStripMenuItem_Click(object sender, EventArgs e) - { - applySelectedLines(GetSelectionPosition(), GetSelectionLength(), reverse: false); - } - - private void settingsButton_Click(object sender, EventArgs e) - { - UICommands.StartSettingsDialog(ParentForm, DiffViewerSettingsPage.GetPageReference()); - } - - private void revertSelectedLinesToolStripMenuItem_Click(object sender, EventArgs e) - { - applySelectedLines(GetSelectionPosition(), GetSelectionLength(), reverse: true); - } - - private void IgnoreAllWhitespaceChangesToolStripMenuItem_Click(object sender, EventArgs e) - { - if (IgnoreWhitespace == IgnoreWhitespaceKind.AllSpace) - { - IgnoreWhitespace = IgnoreWhitespaceKind.None; - } - else - { - IgnoreWhitespace = IgnoreWhitespaceKind.AllSpace; - } - - OnIgnoreWhitespaceChanged(); - OnExtraDiffArgumentsChanged(); - } - #region Hotkey commands public static readonly string HotkeySettingsName = "FileViewer"; - internal enum Commands + internal enum Command { Find = 0, FindNextOrOpenWithDifftool = 8, @@ -1665,33 +1877,44 @@ internal enum Commands NextChange = 6, PreviousChange = 7, NextOccurrence = 10, - PreviousOccurrence = 11 + PreviousOccurrence = 11, + StageLines = 12, + UnstageLines = 13, + ResetLines = 14 } protected override CommandStatus ExecuteCommand(int cmd) { - var command = (Commands)cmd; + var command = (Command)cmd; switch (command) { - case Commands.Find: internalFileViewer.Find(); break; - case Commands.FindNextOrOpenWithDifftool: ThreadHelper.JoinableTaskFactory.RunAsync(() => internalFileViewer.FindNextAsync(searchForwardOrOpenWithDifftool: true)); break; - case Commands.FindPrevious: ThreadHelper.JoinableTaskFactory.RunAsync(() => internalFileViewer.FindNextAsync(searchForwardOrOpenWithDifftool: false)); break; - case Commands.GoToLine: goToLineToolStripMenuItem_Click(null, null); break; - case Commands.IncreaseNumberOfVisibleLines: IncreaseNumberOfLinesToolStripMenuItemClick(null, null); break; - case Commands.DecreaseNumberOfVisibleLines: DecreaseNumberOfLinesToolStripMenuItemClick(null, null); break; - case Commands.ShowEntireFile: ShowEntireFileToolStripMenuItemClick(null, null); break; - case Commands.TreatFileAsText: TreatAllFilesAsTextToolStripMenuItemClick(null, null); break; - case Commands.NextChange: NextChangeButtonClick(null, null); break; - case Commands.PreviousChange: PreviousChangeButtonClick(null, null); break; - case Commands.NextOccurrence: internalFileViewer.GoToNextOccurrence(); break; - case Commands.PreviousOccurrence: internalFileViewer.GoToPreviousOccurrence(); break; + case Command.Find: internalFileViewer.Find(); break; + case Command.FindNextOrOpenWithDifftool: ThreadHelper.JoinableTaskFactory.RunAsync(() => internalFileViewer.FindNextAsync(searchForwardOrOpenWithDifftool: true)); break; + case Command.FindPrevious: ThreadHelper.JoinableTaskFactory.RunAsync(() => internalFileViewer.FindNextAsync(searchForwardOrOpenWithDifftool: false)); break; + case Command.GoToLine: goToLineToolStripMenuItem_Click(null, null); break; + case Command.IncreaseNumberOfVisibleLines: IncreaseNumberOfLinesToolStripMenuItemClick(null, null); break; + case Command.DecreaseNumberOfVisibleLines: DecreaseNumberOfLinesToolStripMenuItemClick(null, null); break; + case Command.ShowEntireFile: ShowEntireFileToolStripMenuItemClick(null, null); break; + case Command.TreatFileAsText: TreatAllFilesAsTextToolStripMenuItemClick(null, null); break; + case Command.NextChange: NextChangeButtonClick(null, null); break; + case Command.PreviousChange: PreviousChangeButtonClick(null, null); break; + case Command.NextOccurrence: internalFileViewer.GoToNextOccurrence(); break; + case Command.PreviousOccurrence: internalFileViewer.GoToPreviousOccurrence(); break; + case Command.StageLines: StageSelectedLines(); break; + case Command.UnstageLines: UnstageSelectedLines(); break; + case Command.ResetLines: ResetSelectedLines(); break; default: return base.ExecuteCommand(cmd); } return true; } + private string GetShortcutKeyDisplayString(Command cmd) + { + return GetShortcutKeys((int)cmd).ToShortcutKeyDisplayString(); + } + #endregion internal TestAccessor GetTestAccessor() => new TestAccessor(this); @@ -1733,6 +1956,18 @@ public bool ShowSyntaxHighlightingInDiff internal void IgnoreWhitespaceAtEolToolStripMenuItem_Click(object sender, EventArgs e) => _fileViewer.IgnoreWhitespaceAtEolToolStripMenuItem_Click(sender, e); internal void IgnoreWhitespaceChangesToolStripMenuItemClick(object sender, EventArgs e) => _fileViewer.IgnoreWhitespaceChangesToolStripMenuItemClick(sender, e); internal void IgnoreAllWhitespaceChangesToolStripMenuItem_Click(object sender, EventArgs e) => _fileViewer.IgnoreAllWhitespaceChangesToolStripMenuItem_Click(sender, e); + + public void ViewPatch([CanBeNull] string fileName, + [NotNull] string text, + [CanBeNull] Action openWithDifftool = null) + { + FileStatusItem f = new FileStatusItem(new GitRevision(ObjectId.Random()), + new GitRevision(ObjectId.Random()), + new GitItemStatus { Name = fileName }); + var fileViewer = _fileViewer; + ThreadHelper.JoinableTaskFactory.Run( + () => fileViewer.ViewPatchAsync(f, text, openWithDifftool)); + } } } } diff --git a/GitUI/GitUIExtensions.cs b/GitUI/GitUIExtensions.cs index cc447dea9d0..d27b399162c 100644 --- a/GitUI/GitUIExtensions.cs +++ b/GitUI/GitUIExtensions.cs @@ -101,7 +101,7 @@ public static class GitUIExtensions return item.Item.IsSubmodule ? fileViewer.ViewTextAsync(item.Item.Name, text: selectedPatch, openWithDifftool: openWithDiffTool) - : fileViewer.ViewPatchAsync(item.Item.Name, text: selectedPatch, openWithDifftool: openWithDiffTool); + : fileViewer.ViewPatchAsync(item, text: selectedPatch, openWithDifftool: openWithDiffTool); void OpenWithDiffTool() { diff --git a/GitUI/Hotkey/HotkeySettingsManager.cs b/GitUI/Hotkey/HotkeySettingsManager.cs index 1ec85897445..a1e79f99809 100644 --- a/GitUI/Hotkey/HotkeySettingsManager.cs +++ b/GitUI/Hotkey/HotkeySettingsManager.cs @@ -335,18 +335,21 @@ public static HotkeySettings[] CreateDefaultSettings() Hk(RevisionGridControl.Command.ToggleShowTags, Keys.Control | Keys.Alt | Keys.T)), new HotkeySettings( FileViewer.HotkeySettingsName, - Hk(FileViewer.Commands.Find, Keys.Control | Keys.F), - Hk(FileViewer.Commands.FindNextOrOpenWithDifftool, OpenWithDifftoolHotkey), - Hk(FileViewer.Commands.FindPrevious, Keys.Shift | OpenWithDifftoolHotkey), - Hk(FileViewer.Commands.GoToLine, Keys.Control | Keys.G), - Hk(FileViewer.Commands.IncreaseNumberOfVisibleLines, Keys.None), - Hk(FileViewer.Commands.DecreaseNumberOfVisibleLines, Keys.None), - Hk(FileViewer.Commands.NextChange, Keys.Alt | Keys.Down), - Hk(FileViewer.Commands.PreviousChange, Keys.Alt | Keys.Up), - Hk(FileViewer.Commands.ShowEntireFile, Keys.None), - Hk(FileViewer.Commands.TreatFileAsText, Keys.None), - Hk(FileViewer.Commands.NextOccurrence, Keys.Alt | Keys.Right), - Hk(FileViewer.Commands.PreviousOccurrence, Keys.Alt | Keys.Left)), + Hk(FileViewer.Command.Find, Keys.Control | Keys.F), + Hk(FileViewer.Command.FindNextOrOpenWithDifftool, OpenWithDifftoolHotkey), + Hk(FileViewer.Command.FindPrevious, Keys.Shift | OpenWithDifftoolHotkey), + Hk(FileViewer.Command.GoToLine, Keys.Control | Keys.G), + Hk(FileViewer.Command.IncreaseNumberOfVisibleLines, Keys.None), + Hk(FileViewer.Command.DecreaseNumberOfVisibleLines, Keys.None), + Hk(FileViewer.Command.NextChange, Keys.Alt | Keys.Down), + Hk(FileViewer.Command.PreviousChange, Keys.Alt | Keys.Up), + Hk(FileViewer.Command.ShowEntireFile, Keys.None), + Hk(FileViewer.Command.TreatFileAsText, Keys.None), + Hk(FileViewer.Command.NextOccurrence, Keys.Alt | Keys.Right), + Hk(FileViewer.Command.PreviousOccurrence, Keys.Alt | Keys.Left), + Hk(FileViewer.Command.StageLines, Keys.S), + Hk(FileViewer.Command.UnstageLines, Keys.U), + Hk(FileViewer.Command.ResetLines, Keys.R)), new HotkeySettings( FormResolveConflicts.HotkeySettingsName, Hk(FormResolveConflicts.Commands.ChooseBase, Keys.B), diff --git a/GitUI/Translation/English.xlf b/GitUI/Translation/English.xlf index 92e43d5be05..88305eec75e 100644 --- a/GitUI/Translation/English.xlf +++ b/GitUI/Translation/English.xlf @@ -1336,10 +1336,6 @@ Please make sure git (Git for Windows or cygwin) is installed or set the correct Enable automatic continuous scroll (without ALT button) - - Stage selected line(s) - - Copy new version @@ -1412,7 +1408,7 @@ Please make sure git (Git for Windows or cygwin) is installed or set the correct Previous change - + Reset selected line(s) @@ -1440,10 +1436,18 @@ Please make sure git (Git for Windows or cygwin) is installed or set the correct Show syntax highlighting + + Stage selected line(s) + + Treat all files as text + + Unstage selected line(s) + + diff --git a/UnitTests/GitUI.Tests/Editor/FileViewerTextTests.cs b/UnitTests/GitUI.Tests/Editor/FileViewerTextTests.cs index 293d1d964f0..c80f224294d 100644 --- a/UnitTests/GitUI.Tests/Editor/FileViewerTextTests.cs +++ b/UnitTests/GitUI.Tests/Editor/FileViewerTextTests.cs @@ -100,7 +100,7 @@ public void FileViewer_ShowSyntaxHighlightingInDiff_enabled_use_highlighting() testAccessor.ShowSyntaxHighlightingInDiff = true; // act - _fileViewer.ViewPatch("FileViewerInternal.cs", sampleCsharpPatch); + testAccessor.ViewPatch("FileViewerInternal.cs", sampleCsharpPatch); // assert IHighlightingStrategy csharpHighlighting = HighlightingManager.Manager.FindHighlighterForFile("anycsharpfile.cs"); @@ -135,7 +135,7 @@ public void FileViewer_ShowSyntaxHighlightingInDiff_disabled_do_not_use_highligh testAccessor.ShowSyntaxHighlightingInDiff = false; // act - _fileViewer.ViewPatch("FileViewerInternal.cs", sampleCsharpPatch, null); + testAccessor.ViewPatch("FileViewerInternal.cs", sampleCsharpPatch, null); // assert _fileViewer.GetTestAccessor().FileViewerInternal.GetTestAccessor().TextEditor.Document.HighlightingStrategy.Should().Be(HighlightingManager.Manager.DefaultHighlighting); @@ -157,17 +157,19 @@ public void FileViewer_given_non_text_use_default_highlighting() _uiCommandsSource.UICommands.Returns(x => uiCommands); _fileViewer.UICommandsSource = _uiCommandsSource; + FileViewer.TestAccessor testAccessor = _fileViewer.GetTestAccessor(); + // act - _fileViewer.ViewPatch("binaryfile.bin", sampleBinaryPatch, null); + testAccessor.ViewPatch("binaryfile.bin", sampleBinaryPatch, null); // assert - _fileViewer.GetTestAccessor().FileViewerInternal.GetTestAccessor().TextEditor.Document.HighlightingStrategy.Should().Be(HighlightingManager.Manager.DefaultHighlighting); + testAccessor.FileViewerInternal.GetTestAccessor().TextEditor.Document.HighlightingStrategy.Should().Be(HighlightingManager.Manager.DefaultHighlighting); const string sampleRandomText = @"fldaksjflkdsjlfj"; // act - _fileViewer.ViewPatch(null, sampleRandomText, null); + testAccessor.ViewPatch(null, sampleRandomText, null); // assert _fileViewer.GetTestAccessor().FileViewerInternal.GetTestAccessor().TextEditor.Document.HighlightingStrategy.Should().Be(HighlightingManager.Manager.DefaultHighlighting);