Editor/HotCodeEditor/Settings/HotCodeSettings.cs
using Sandbox;
using Editor;
using System;
public enum AutoSaveMode
{
[Title( "Off" )] Off,
[Title( "After a short pause" )] AfterDelay,
[Title( "When switching tabs" )] WhenSwitchingTabs
}
public enum CodeTheme
{
[Title( "Dark+ (VS Code)" )] DarkPlus,
[Title( "Monokai" )] Monokai,
[Title( "One Dark" )] OneDark
}
/// <summary>
/// HotCode's settings. Static properties so s&box's ControlSheet can edit them directly, the same
/// way its own Editor Settings pages work. Saved as one editor cookie; <see cref="Version"/> goes up on
/// every change so open editors know to re-measure and re-layout.
/// </summary>
public static class HotCodeSettings
{
const string CookieKey = "hotcode.settings";
/// <summary>
/// The stored values. A plain class so it round-trips through JSON.
/// </summary>
public class Data
{
public float FontSize { get; set; } = 10;
public string FontFamily { get; set; } = "";
public float LineSpacing { get; set; } = 1.35f;
public int TabSize { get; set; } = 4;
public bool IndentWithSpaces { get; set; }
public bool WordWrap { get; set; }
public bool ShowLineNumbers { get; set; } = true;
public bool HighlightCurrentLine { get; set; } = true;
public bool CursorBlink { get; set; } = true;
public CodeTheme Theme { get; set; } = CodeTheme.DarkPlus;
public bool AutoCloseBrackets { get; set; } = true;
public bool SuggestWhileTyping { get; set; } = true;
public bool ParameterHints { get; set; } = true;
public bool UnderlineErrors { get; set; } = true;
public AutoSaveMode AutoSave { get; set; } = AutoSaveMode.Off;
public float AutoSaveDelay { get; set; } = 1.5f;
public bool TrimTrailingWhitespace { get; set; }
public bool InsertFinalNewline { get; set; }
public bool ReloadChangedFiles { get; set; } = true;
}
private static Data _data;
private static Data Current
{
get
{
if ( _data is not null ) return _data;
_data = EditorCookie.Get<Data>( CookieKey, null );
if ( _data is null )
{
// First run: carry over the settings that used to be stored on their own
_data = new Data
{
FontSize = EditorCookie.Get( "hotcode.fontsize", 10f ),
WordWrap = EditorCookie.Get( "hotcode.wordwrap", false )
};
}
Apply();
return _data;
}
}
/// <summary>
/// Goes up every time any setting changes.
/// </summary>
public static int Version { get; private set; }
private static void Set( Action<Data> change )
{
change( Current );
EditorCookie.Set( CookieKey, Current );
Apply();
Version++;
}
/// <summary>
/// Pushes settings into the places that read them as plain values.
/// </summary>
private static void Apply()
{
TextLayout.TabSize = Math.Clamp( _data.TabSize, 1, 16 );
SyntaxColors.Use( _data.Theme );
}
public static void ResetToDefaults() => Set( d =>
{
var defaults = new Data();
foreach ( var property in typeof( Data ).GetProperties() )
property.SetValue( d, property.GetValue( defaults ) );
} );
// --- EDITOR ---
[Title( "Font Size" ), Description( "Code font size in points. Ctrl+= / Ctrl+- or Ctrl+mouse wheel change it too." ), Range( 6, 28 ), Step( 1 )]
public static float FontSize { get => Current.FontSize; set => Set( d => d.FontSize = Math.Clamp( value, 6, 28 ) ); }
[Title( "Font" ), Description( "Code font family. Leave empty to use s&box's monospace font. Use a monospaced font so columns line up." )]
public static string FontFamily { get => Current.FontFamily; set => Set( d => d.FontFamily = value?.Trim() ?? "" ); }
[Title( "Line Spacing" ), Description( "Line height as a multiple of the font height." ), Range( 1, 2 ), Step( 0.05f )]
public static float LineSpacing { get => Current.LineSpacing; set => Set( d => d.LineSpacing = Math.Clamp( value, 1, 2 ) ); }
[Title( "Tab Size" ), Description( "How many columns a tab takes up." ), Range( 1, 8 ), Step( 1 )]
public static int TabSize { get => Current.TabSize; set => Set( d => d.TabSize = Math.Clamp( value, 1, 8 ) ); }
[Title( "Indent With Spaces" ), Description( "For files whose indentation can't be detected. Existing files keep whatever they already use." )]
public static bool IndentWithSpaces { get => Current.IndentWithSpaces; set => Set( d => d.IndentWithSpaces = value ); }
[Title( "Word Wrap" ), Description( "Wrap long lines to the width of the editor (Alt+Z)." )]
public static bool WordWrap { get => Current.WordWrap; set => Set( d => d.WordWrap = value ); }
[Title( "Line Numbers" )]
public static bool ShowLineNumbers { get => Current.ShowLineNumbers; set => Set( d => d.ShowLineNumbers = value ); }
[Title( "Highlight Current Line" )]
public static bool HighlightCurrentLine { get => Current.HighlightCurrentLine; set => Set( d => d.HighlightCurrentLine = value ); }
[Title( "Blinking Cursor" )]
public static bool CursorBlink { get => Current.CursorBlink; set => Set( d => d.CursorBlink = value ); }
[Title( "Code Colours" ), Description( "Syntax colours for the code area. The rest of HotCode follows s&box's theme." )]
public static CodeTheme CodeColors { get => Current.Theme; set => Set( d => d.Theme = value ); }
// --- CODE HELP ---
[Title( "Auto-Close Brackets and Quotes" ), Description( "Typing ( [ { \" or ' adds the closing one." )]
public static bool AutoCloseBrackets { get => Current.AutoCloseBrackets; set => Set( d => d.AutoCloseBrackets = value ); }
[Title( "Suggestions While Typing" ), Description( "Show autocomplete as you type. Ctrl+Space always works." )]
public static bool SuggestWhileTyping { get => Current.SuggestWhileTyping; set => Set( d => d.SuggestWhileTyping = value ); }
[Title( "Parameter Hints" ), Description( "Show a method's parameters when typing ( or , in a call. Ctrl+Shift+Space always works." )]
public static bool ParameterHints { get => Current.ParameterHints; set => Set( d => d.ParameterHints = value ); }
[Title( "Underline Compile Errors" ), Description( "Underline errors and warnings from s&box's last compile in the code." )]
public static bool UnderlineErrors { get => Current.UnderlineErrors; set => Set( d => d.UnderlineErrors = value ); }
// --- FILES ---
[Title( "Auto Save" ), Description( "Saving triggers s&box's hotload, so auto save means changes show up in the game as you type." )]
public static AutoSaveMode AutoSave { get => Current.AutoSave; set => Set( d => d.AutoSave = value ); }
[Title( "Auto Save Delay" ), Description( "Seconds after you stop typing, when Auto Save is \"After a short pause\"." ), Range( 0.5f, 10 ), Step( 0.5f )]
public static float AutoSaveDelay { get => Current.AutoSaveDelay; set => Set( d => d.AutoSaveDelay = Math.Clamp( value, 0.5f, 10 ) ); }
[Title( "Trim Trailing Whitespace" ), Description( "Remove spaces and tabs at the ends of lines when saving." )]
public static bool TrimTrailingWhitespace { get => Current.TrimTrailingWhitespace; set => Set( d => d.TrimTrailingWhitespace = value ); }
[Title( "Insert Final Newline" ), Description( "Make sure files end with a newline when saving." )]
public static bool InsertFinalNewline { get => Current.InsertFinalNewline; set => Set( d => d.InsertFinalNewline = value ); }
[Title( "Reload Files Changed Elsewhere" ), Description( "When another program changes an open file that has no unsaved edits here, reload it." )]
public static bool ReloadChangedFiles { get => Current.ReloadChangedFiles; set => Set( d => d.ReloadChangedFiles = value ); }
/// <summary>
/// The code font to use: the chosen family, or s&box's monospace font.
/// </summary>
public static string CodeFont => string.IsNullOrWhiteSpace( FontFamily ) ? Editor.Theme.MonospaceFont : FontFamily;
}