Skip to content

Commit

Permalink
git-grep UI (#11350)
Browse files Browse the repository at this point in the history
Search commits (as well as worktree/index) without checking out those commits.

A "global search" popup with shortcut Ctrl-Shift-F as in many IDE.
It is also possible to add a searchbox to the Diff tab,
configurable in the search popup menu.

If there is no `-e ` in the search string, this is added to the search string.
Search options like `--and` may be added.

It is possible to add persistent search options in a separate box in the
popup (there are checkboxes for the most common case insensitive).
Search options like `--show-function` may be set as a preference for a user.
  • Loading branch information
gerhardol committed Mar 27, 2024
1 parent 38db5de commit f8c0353
Show file tree
Hide file tree
Showing 60 changed files with 1,346 additions and 66 deletions.
75 changes: 74 additions & 1 deletion GitCommands/Git/GitModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1440,7 +1440,7 @@ public bool ResetChanges(ObjectId? resetId, IReadOnlyList<GitItemStatus> selecte

static bool DeletableItem(GitItemStatus item) => item.IsNew || item.IsRenamed;

// For normal commits: 'git-checkout <id> --' must be used for Unmerged but will not work on e.g. SkipWorktree.
// For normal commits: 'git-checkout <objectId> --' must be used for Unmerged but will not work on e.g. SkipWorktree.
static bool UnmergedNotIndex(GitItemStatus item, Lazy<List<GitItemStatus>> status)
=> item.IsUnmerged && !status.Value.Any(i => !i.IsDeleted && !i.IsNew && i.Name == item.Name);

Expand Down Expand Up @@ -2297,6 +2297,79 @@ public string GetStatusText(bool untracked)
});
}

private ExecutionResult GetGrepFiles(ObjectId objectId, string grepString, CancellationToken cancellationToken = default)
{
bool noCache = objectId.IsArtificial;

return _gitExecutable.Execute(
new GitArgumentBuilder("grep")
{
"--files-with-matches",
"-z",
AppSettings.GitGrepUserArguments.Value,
{ AppSettings.GitGrepIgnoreCase.Value, "--ignore-case" },
{ AppSettings.GitGrepMatchWholeWord.Value, "--word-regexp" },
grepString,
!objectId.IsArtificial ? objectId.ToString() : objectId == ObjectId.IndexId ? "--cached" : "",
"--"
},
cache: noCache ? null : GitCommandCache,
throwOnErrorExit: false,
cancellationToken: cancellationToken);
}

public IReadOnlyList<GitItemStatus> GetGrepFilesStatus(ObjectId objectId, string grepString, CancellationToken cancellationToken = default)
{
List<GitItemStatus> result = [];
ExecutionResult exec = GetGrepFiles(objectId, grepString, cancellationToken);
if (!exec.ExitedSuccessfully)
{
// Cannot see difference from error and no matches
return [];
}

foreach (string file in exec.StandardOutput.LazySplit('\0', StringSplitOptions.RemoveEmptyEntries))
{
int startIndex = file.IndexOf(':') + 1;
result.Add(new GitItemStatus(file[startIndex..])
{
GrepString = grepString,

// Assume this file is handled by Git, may not be entirely correct for worktree
IsTracked = true
});
}

return result;
}

public async Task<ExecutionResult> GetGrepFileAsync(ObjectId objectId, string fileName, ArgumentString extraArgs, string grepString, bool useGitColoring, GitCommandConfiguration commandConfiguration, CancellationToken cancellationToken = default)
{
bool noCache = objectId.IsArtificial;

GitArgumentBuilder args = new("grep", commandConfiguration: commandConfiguration)
{
"--line-number",
{ !useGitColoring, "--column" },
{ useGitColoring, "--color=always" },
extraArgs,
AppSettings.GitGrepUserArguments.Value,
{ AppSettings.GitGrepIgnoreCase.Value, "--ignore-case" },
{ AppSettings.GitGrepMatchWholeWord.Value, "--word-regexp" },
grepString,
!objectId.IsArtificial ? objectId.ToString() : objectId == ObjectId.IndexId ? "--cached" : "",
"--",
fileName
};

return await _gitExecutable.ExecuteAsync(
args,
cache: noCache ? null : GitCommandCache,
throwOnErrorExit: false,
stripAnsiEscapeCodes: !useGitColoring,
cancellationToken: cancellationToken);
}

public ExecutionResult GetDiffFiles(string? firstRevision, string? secondRevision, bool noCache = false, bool nullSeparated = false, CancellationToken cancellationToken = default)
{
noCache = noCache || firstRevision.IsArtificial() || secondRevision.IsArtificial();
Expand Down
9 changes: 9 additions & 0 deletions GitCommands/Settings/AppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public static partial class AppSettings
private static readonly SettingsPath AppearanceSettingsPath = new AppSettingsPath("Appearance");
private static readonly SettingsPath ConfirmationsSettingsPath = new AppSettingsPath("Confirmations");
private static readonly SettingsPath DetailedSettingsPath = new AppSettingsPath("Detailed");
private static readonly SettingsPath DialogSettingsPath = new AppSettingsPath("Dialogs");
private static readonly SettingsPath ExperimentalSettingsPath = new AppSettingsPath(DetailedSettingsPath, "Experimental");
private static readonly SettingsPath RevisionGraphSettingsPath = new AppSettingsPath(AppearanceSettingsPath, "RevisionGraph");
private static readonly SettingsPath RootSettingsPath = new AppSettingsPath(pathName: "");
Expand Down Expand Up @@ -1342,6 +1343,8 @@ public static bool ShowDiffForAllParents
set => SetBool("showdiffforallparents", value);
}

public static ISetting<bool> ShowSearchCommit { get; } = Setting.Create(AppearanceSettingsPath, nameof(ShowSearchCommit), false);

public static bool ShowAvailableDiffTools
{
get => GetBool("difftools.showavailable", true);
Expand All @@ -1354,6 +1357,12 @@ public static int DiffVerticalRulerPosition
set => SetInt("diffverticalrulerposition", value);
}

public static ISetting<string> GitGrepUserArguments { get; } = Setting.Create(DialogSettingsPath, nameof(GitGrepUserArguments), "");

public static ISetting<bool> GitGrepIgnoreCase { get; } = Setting.Create(DialogSettingsPath, nameof(GitGrepIgnoreCase), false);

public static ISetting<bool> GitGrepMatchWholeWord { get; } = Setting.Create(DialogSettingsPath, nameof(GitGrepMatchWholeWord), false);

[MaybeNull]
public static string RecentWorkingDir
{
Expand Down
2 changes: 1 addition & 1 deletion GitExtUtils/GitCommandConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ static GitCommandConfiguration()

Default.Add(new GitConfigItem("log.showSignature", "false"), "log", "show", "whatchanged");

Default.Add(new GitConfigItem("color.ui", "never"), "diff", "range-diff");
Default.Add(new GitConfigItem("color.ui", "never"), "diff", "range-diff", "grep");
Default.Add(new GitConfigItem("diff.submodule", "short"), "diff");
Default.Add(new GitConfigItem("diff.noprefix", "false"), "diff");
Default.Add(new GitConfigItem("diff.mnemonicprefix", "false"), "diff");
Expand Down
13 changes: 12 additions & 1 deletion GitUI/CommandsDialogs/RevisionDiffControl.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 14 additions & 3 deletions GitUI/CommandsDialogs/RevisionDiffControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public RevisionDiffControl()
InitializeComponent();
InitializeComplete();
HotkeysEnabled = true;
DiffFiles.SearchEnabledForList = true;
_fullPathResolver = new FullPathResolver(() => Module.WorkingDir);
_revisionDiffController = new RevisionDiffController(() => Module, _fullPathResolver);
_findFilePredicateProvider = new FindFilePredicateProvider();
Expand Down Expand Up @@ -132,6 +133,7 @@ public enum Command
SelectFirstGroupChanges = 14,
FindFile = 15,
OpenWorkingDirectoryFileWith = 16,
SearchCommit = 17,
}

public bool ExecuteCommand(Command cmd)
Expand All @@ -141,7 +143,7 @@ public bool ExecuteCommand(Command cmd)

protected override bool ExecuteCommand(int cmd)
{
if (DiffFiles.FilterFocused && IsTextEditKey(GetShortcutKeys(cmd)))
if ((DiffFiles.FilterFocused || DiffFiles.SearchFocused) && IsTextEditKey(GetShortcutKeys(cmd)))
{
return false;
}
Expand All @@ -167,6 +169,7 @@ protected override bool ExecuteCommand(int cmd)
case Command.FilterFileInGrid: diffFilterFileInGridToolStripMenuItem.PerformClick(); break;
case Command.SelectFirstGroupChanges: return SelectFirstGroupChangesIfFileNotFocused();
case Command.FindFile: findInDiffToolStripMenuItem.PerformClick(); break;
case Command.SearchCommit: showSearchCommitToolStripMenuItem.PerformClick(); break;
default: return base.ExecuteCommand(cmd);
}

Expand Down Expand Up @@ -204,6 +207,7 @@ public void ReloadHotkeys()
diffShowInFileTreeToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.ShowFileTree);
diffFilterFileInGridToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.FilterFileInGrid);
findInDiffToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.FindFile);
showSearchCommitToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeyDisplayString(Command.SearchCommit);

DiffText.ReloadHotkeys();
}
Expand Down Expand Up @@ -313,6 +317,8 @@ public void InitSplitterManager(SplitterManager splitterManager)
splitterManager.AddSplitter(DiffSplitContainer, "DiffSplitContainer");
}

public SplitContainer HorizontalSplitter => DiffSplitContainer;

protected override void OnRuntimeLoad()
{
base.OnRuntimeLoad();
Expand Down Expand Up @@ -556,7 +562,7 @@ private async Task ShowSelectedFileBlameAsync(bool ensureNoSwitchToFilter, int?
BlameControl.Visible = true;

// Avoid that focus is switched to the file filter after changing visibility
if (ensureNoSwitchToFilter && DiffFiles.FilterFocused)
if (ensureNoSwitchToFilter && (DiffFiles.FilterFocused || DiffFiles.SearchFocused))
{
BlameControl.Focus();
}
Expand All @@ -581,7 +587,7 @@ private async Task ShowSelectedFileDiffAsync(bool ensureNoSwitchToFilter, int? l
DiffText.Visible = true;

// Avoid that focus is switched to the file filter after changing visibility
if (ensureNoSwitchToFilter && DiffFiles.FilterFocused)
if (ensureNoSwitchToFilter && (DiffFiles.FilterFocused || DiffFiles.SearchFocused))
{
DiffText.Focus();
}
Expand Down Expand Up @@ -839,6 +845,11 @@ IEnumerable<GitItemStatus> FindDiffFilesMatches(string name)
}
}

private void showSearchCommitToolStripMenuItem_Click(object sender, EventArgs e)
{
DiffFiles.ShowSearchCommit_Click(DiffText.GetSelectedText());
}

private void fileHistoryDiffToolstripMenuItem_Click(object sender, EventArgs e)
{
FileStatusItem? item = DiffFiles.SelectedItem;
Expand Down
2 changes: 1 addition & 1 deletion GitUI/Editor/Diff/AnsiEscapeUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ static Color Get24StepGray(int level)
/// Add pre-parsed highlight info (parsed ANSI escape sequences) as markers in the document.
/// </summary>
/// <param name="hl">Info to set.</param>
private static bool TryGetTextMarker(HighlightInfo hl, out TextMarker? textMarker)
public static bool TryGetTextMarker(HighlightInfo hl, out TextMarker? textMarker)
{
if (hl.DocOffset < 0 || hl.Length < 0)
{
Expand Down
2 changes: 1 addition & 1 deletion GitUI/Editor/Diff/CombinedDiffHighlightService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public CombinedDiffHighlightService(ref string text, bool useGitColoring)
public override void SetLineControl(DiffViewerLineNumberControl lineNumbersControl, TextEditorControl textEditor)
{
DiffLinesInfo result = new DiffLineNumAnalyzer().Analyze(textEditor.Text, isCombinedDiff: true);
lineNumbersControl.DisplayLineNum(result);
lineNumbersControl.DisplayLineNum(result, showLeftColumn: true);
}

public static GitCommandConfiguration GetGitCommandConfiguration(IGitModule module, bool useGitColoring)
Expand Down
1 change: 1 addition & 0 deletions GitUI/Editor/Diff/DiffLineType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ public enum DiffLineType
Plus,
Minus,
Context,
Grep
}
14 changes: 9 additions & 5 deletions GitUI/Editor/Diff/DiffViewerLineNumberControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class DiffViewerLineNumberControl : AbstractMargin
private static readonly IReadOnlyDictionary<int, DiffLineInfo> Empty = new Dictionary<int, DiffLineInfo>();
private IReadOnlyDictionary<int, DiffLineInfo> _diffLines = Empty;
private bool _visible = true;
private bool _showLeftColumn = true;

public DiffViewerLineNumberControl(TextArea textArea)
: base(textArea)
Expand All @@ -28,8 +29,10 @@ public override int Width
{
if (_visible && _diffLines.Any())
{
// add a space behind each number
int maxDigits = MaxLineNumber > 0 ? ((int)Math.Log10(MaxLineNumber) + 1) : 0;
return TextHorizontalMargin + (textArea.TextView.WideSpaceWidth * ((2 * maxDigits) + /* a space behind each number */ 2));
int length = (_showLeftColumn ? 2 : 1) * (1 + maxDigits);
return TextHorizontalMargin + (textArea.TextView.WideSpaceWidth * length);
}

return 0;
Expand All @@ -42,15 +45,14 @@ public override int Width
/// <param name="caretLine">0-based (in contrast to the displayed line numbers which are 1-based).</param>
public DiffLineInfo GetLineInfo(int caretLine)
{
DiffLineInfo diffLine;
_diffLines.TryGetValue(caretLine + 1, out diffLine);
_diffLines.TryGetValue(caretLine + 1, out DiffLineInfo diffLine);
return diffLine;
}

public override void Paint(Graphics g, Rectangle rect)
{
int numbersWidth = Width - TextHorizontalMargin;
int leftWidth = TextHorizontalMargin + (numbersWidth / 2);
int leftWidth = _showLeftColumn ? TextHorizontalMargin + (numbersWidth / 2) : 0;
int rightWidth = rect.Width - leftWidth;

int fontHeight = textArea.TextView.FontHeight;
Expand Down Expand Up @@ -88,6 +90,7 @@ public override void Paint(Graphics g, Rectangle rect)
DiffLineType.Plus => new SolidBrush(AppColor.DiffAdded.GetThemeColor()),
DiffLineType.Minus => new SolidBrush(AppColor.DiffRemoved.GetThemeColor()),
DiffLineType.Header => new SolidBrush(AppColor.DiffSection.GetThemeColor()),
DiffLineType.Grep => new SolidBrush(AppColor.DiffRemoved.GetThemeColor()),
_ => default(Brush)
};

Expand All @@ -114,10 +117,11 @@ public override void Paint(Graphics g, Rectangle rect)
}
}

public void DisplayLineNum(DiffLinesInfo result)
public void DisplayLineNum(DiffLinesInfo result, bool showLeftColumn)
{
_diffLines = result.DiffLines;
MaxLineNumber = result.MaxLineNumber;
_showLeftColumn = showLeftColumn;
}

public void Clear(bool forDiff)
Expand Down
Loading

0 comments on commit f8c0353

Please sign in to comment.