Editor/HotCodeEditor/Output/ConsoleInput.cs
using Sandbox;
using Editor;
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// The console command box under the Output log.
/// <list type="bullet">
/// <item>Enter runs the command</item>
/// <item>Up / Down walk through previous commands (kept between sessions)</item>
/// <item>Suggestions pop up as you type; Tab completes the first one</item>
/// </list>
/// </summary>
public class ConsoleInput : LineEdit
{
const string HistoryCookieName = "hotcode.console.history";
const int MaxHistory = 100;
const int MaxSuggestions = 15;
public Action<string> CommandEntered;
private readonly List<string> _history;
/// <summary>
/// Position while walking history with Up/Down; equal to the count when not browsing.
/// </summary>
private int _historyIndex;
/// <summary>
/// What was typed before browsing history started, restored when Down goes past the newest entry.
/// </summary>
private string _draft = "";
public ConsoleInput( Widget parent ) : base( parent )
{
PlaceholderText = "Enter a console command...";
SetStyles( $"font-family: '{Theme.MonospaceFont}';" );
_history = EditorCookie.Get<List<string>>( HistoryCookieName, null ) ?? new();
_historyIndex = _history.Count;
SetAutoComplete( BuildSuggestions );
}
private void BuildSuggestions( Menu menu, string text )
{
if ( string.IsNullOrWhiteSpace( text ) ) return;
foreach ( var result in Suggest( text ) )
{
var command = result.Command;
var label = string.IsNullOrEmpty( result.Description ) ? command : $"{command} - {result.Description}";
menu.AddOption( label, null, () =>
{
Text = command + " ";
Focus( false );
} );
}
}
private static ConCmdAttribute.AutoCompleteResult[] Suggest( string text )
{
try
{
return EditorUtility.AutoComplete( text.Trim(), MaxSuggestions ) ?? Array.Empty<ConCmdAttribute.AutoCompleteResult>();
}
catch ( Exception e )
{
Log.Warning( $"[HotCode] Console autocomplete failed: {e.Message}" );
return Array.Empty<ConCmdAttribute.AutoCompleteResult>();
}
}
protected override void OnKeyPress( KeyEvent e )
{
switch ( e.Key )
{
case KeyCode.Return:
case KeyCode.Enter:
Submit();
e.Accepted = true;
return;
case KeyCode.Up:
BrowseHistory( -1 );
e.Accepted = true;
return;
case KeyCode.Down:
BrowseHistory( 1 );
e.Accepted = true;
return;
case KeyCode.Tab when !string.IsNullOrWhiteSpace( Text ):
var first = Suggest( Text ).FirstOrDefault();
if ( !string.IsNullOrEmpty( first.Command ) ) Text = first.Command + " ";
e.Accepted = true;
return;
case KeyCode.Escape:
Text = "";
_historyIndex = _history.Count;
e.Accepted = true;
return;
}
base.OnKeyPress( e );
}
private void Submit()
{
var command = Text.Trim();
if ( command.Length == 0 ) return;
// Most recent last, no repeats
_history.Remove( command );
_history.Add( command );
if ( _history.Count > MaxHistory ) _history.RemoveRange( 0, _history.Count - MaxHistory );
EditorCookie.Set( HistoryCookieName, _history );
_historyIndex = _history.Count;
_draft = "";
Text = "";
CommandEntered?.Invoke( command );
}
private void BrowseHistory( int direction )
{
if ( _history.Count == 0 ) return;
if ( _historyIndex == _history.Count ) _draft = Text;
_historyIndex = Math.Clamp( _historyIndex + direction, 0, _history.Count );
Text = _historyIndex == _history.Count ? _draft : _history[_historyIndex];
CursorPosition = Text.Length;
}
}