Editor/HotCodeEditor/CodeView/FindBar.cs
using Sandbox;
using Editor;
using System;
/// <summary>
/// The find / replace strip shown above a <see cref="CodeView"/> (Ctrl+F / Ctrl+H).
/// Enter = next, Shift+Enter = previous, Esc = close.
/// </summary>
public class FindBar : Widget
{
const string OptionsCookie = "hotcode.find.";
private readonly CodeView _view;
private readonly FindLineEdit _query;
private readonly FindLineEdit _replace;
private readonly Widget _replaceRow;
private readonly IconButton _expand;
private readonly IconButton _matchCase;
private readonly IconButton _wholeWord;
private readonly IconButton _regex;
private readonly Label _status;
public FindBar( CodeView view ) : base( view )
{
_view = view;
Layout = Layout.Column();
Layout.Margin = 4;
Layout.Spacing = 4;
SetStyles( $"background-color: {Theme.WidgetBackground.Hex};" );
// Row 1: [>] [find........] [Aa] [ab] [.*] 3 of 12 [↑] [↓] [×]
var findRow = Layout.AddRow();
findRow.Spacing = 4;
_expand = findRow.Add( new IconButton( "chevron_right", () => ShowReplace = !ShowReplace ) { ToolTip = "Toggle Replace (Ctrl+H)" } );
_query = findRow.Add( new FindLineEdit( this ) { PlaceholderText = "Find" }, 1 );
_query.TextEdited += _ => _view.RunSearch( keepPosition: false );
_query.KeyHandler = e => HandleKey( e, isReplace: false );
_matchCase = findRow.Add( MakeToggle( "match_case", "Match Case", "matchcase" ) );
_wholeWord = findRow.Add( MakeToggle( "match_word", "Match Whole Word", "wholeword" ) );
_regex = findRow.Add( MakeToggle( "regular_expression", "Use Regular Expression", "regex" ) );
_status = findRow.Add( new Label( "", this ) { MinimumWidth = 80 } );
_status.Alignment = TextFlag.Center;
findRow.Add( new IconButton( "arrow_upward", () => _view.FindNext( backwards: true ) ) { ToolTip = "Previous Match (Shift+Enter)" } );
findRow.Add( new IconButton( "arrow_downward", () => _view.FindNext( backwards: false ) ) { ToolTip = "Next Match (Enter)" } );
findRow.Add( new IconButton( "close", _view.CloseFind ) { ToolTip = "Close (Escape)" } );
// Row 2: [replace.....] [Replace] [Replace All]
_replaceRow = Layout.Add( new Widget( this ) );
_replaceRow.Layout = Layout.Row();
_replaceRow.Layout.Spacing = 4;
_replaceRow.Layout.AddSpacingCell( 26 );
_replace = _replaceRow.Layout.Add( new FindLineEdit( _replaceRow ) { PlaceholderText = "Replace" }, 1 );
_replace.KeyHandler = e => HandleKey( e, isReplace: true );
_replaceRow.Layout.Add( new Button( "Replace", "find_replace", _replaceRow ) { Clicked = _view.ReplaceCurrent, ToolTip = "Replace (Enter in the replace box)" } );
_replaceRow.Layout.Add( new Button( "Replace All", "done_all", _replaceRow ) { Clicked = _view.ReplaceAll, ToolTip = "Replace All (Ctrl+Alt+Enter)" } );
_replaceRow.Visible = false;
}
private IconButton MakeToggle( string icon, string tooltip, string cookie )
{
var button = new IconButton( icon ) { ToolTip = tooltip, IsToggle = true };
button.IsActive = EditorCookie.Get( OptionsCookie + cookie, false );
button.OnToggled = active =>
{
EditorCookie.Set( OptionsCookie + cookie, active );
_view.RunSearch( keepPosition: false );
};
return button;
}
public SearchOptions Options => new( _query.Text, _matchCase.IsActive, _wholeWord.IsActive, _regex.IsActive );
public string Query
{
get => _query.Text;
set => _query.Text = value;
}
public string Replacement => _replace.Text;
public bool ShowReplace
{
get => _replaceRow.Visible;
set
{
_replaceRow.Visible = value;
_expand.Icon = value ? "expand_more" : "chevron_right";
}
}
public void FocusQuery()
{
_query.Focus( false );
_query.SelectAll();
}
public void SetStatus( int count, int current, string error )
{
if ( error is not null )
{
_status.Text = "Invalid";
_status.ToolTip = error;
_status.SetStyles( $"color: {Theme.Red.Hex};" );
return;
}
_status.ToolTip = null;
_status.SetStyles( count == 0 && !string.IsNullOrEmpty( _query.Text ) ? $"color: {Theme.Red.Hex};" : "" );
_status.Text = string.IsNullOrEmpty( _query.Text ) ? ""
: count == 0 ? "No results"
: count >= TextSearch.MaxMatches ? $"{current + 1} of {count}+"
: $"{current + 1} of {count}";
}
private bool HandleKey( KeyEvent e, bool isReplace )
{
switch ( e.Key )
{
case KeyCode.Escape:
_view.CloseFind();
return true;
case KeyCode.Return or KeyCode.Enter when isReplace && e.HasCtrl && e.HasAlt:
_view.ReplaceAll();
return true;
case KeyCode.Return or KeyCode.Enter when isReplace:
_view.ReplaceCurrent();
return true;
case KeyCode.Return or KeyCode.Enter:
case KeyCode.F3:
_view.FindNext( backwards: e.HasShift );
return true;
case KeyCode.H when e.HasCtrl:
ShowReplace = true;
_replace.Focus( false );
return true;
}
return false;
}
}
/// <summary>
/// A LineEdit that lets its owner handle keys like Enter and Escape first.
/// </summary>
public class FindLineEdit : LineEdit
{
public Func<KeyEvent, bool> KeyHandler;
public FindLineEdit( Widget parent ) : base( parent )
{
}
protected override void OnKeyPress( KeyEvent e )
{
if ( KeyHandler?.Invoke( e ) == true )
{
e.Accepted = true;
return;
}
base.OnKeyPress( e );
}
}