mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* touching publish.yml * ColorScheme->Scheme * ColorScheme->Scheme 2 * Prototype of GetAttributeForRole * Badly broke CM * Further Badly broke CM * Refactored CM big-time. View still broken * All unit test pass again. Tons added. CM is still WIP, but Schemes is not mostly refactored and working. * Actually: All unit test pass again. Tons added. CM is still WIP, but Schemes is not mostly refactored and working. * Bug fixes. DeepMemberWiseClone cleanup * Further cleanup of Scope<T>, ConfigProperty, etc. * Made ConfigManager thread safe. * WIP: Broken * WIP: new deep clone impl * WIP: new deep clone impl is done. Now fixing CM * WIP: - config.md - Working on AOT clean up - Core CM is broken; but known. * WIP * Merged. Removed CM from Application.Init * WIP * More WIP; Less broke * All CM unit tests pass... Not sure if it actually works though * All unit tests pass... Themes are broken though in UI Cat * CM Ready for review? * Fixed failures due to TextStyles PR * Working on Scheme/Attribute * Working on Scheme/Attribute 2 * Working on Scheme/Attribute 3 * Working on Scheme/Attribute 4 * Working on Scheme/Attribute 5 * Working on Scheme/Attribute 6 * Added test to show how awful memory usage is * Improved schema. Updated config.json * Nade Scope<T> concurrentdictionary and added test to prove * Made Themes ConcrurrentDictionary. Added bunches of tests * Code cleanup * Code cleanup 2 * Code cleanup 3 * Tweaking Scheme * ClearJsonErrors * ClearJsonErrors2 * Updated Attribute API * It all (mostly) works! * Skip odd unit test * Messed with Themes * Theme tweaks * Code reorg. New .md stuff * Fixed Enabled. Added mock driver * Fixed a bunch of View.Enabled related issues * Scheme -> Get/SetScheme() * Cleanup * Cleanup2 * Broke something * Fixed everything * Made CM.Enable better * Text Style Scenario * Added comments * Fixed UI Catalog Theme Changing * Fixed more dynamic CM update stuff * Warning cleanup * New Default Theme * fixed unit test * Refactoring Scheme and Attribute to fix inheritance * more unit tests * ConfigProperty is not updating schemes correctly * All unit tests pass. Code cleanup * All unit tests pass. Code cleanup2 * Fixed unit tests * Upgraded TextField and TextView * Fixed TextView !Enabled bug * More updates to TextView. More unit tests for SchemeManager * Upgraded CharMap * API docs * Fixe HexView API * upgrade HexView * Fixed shortcut KeyView * Fixed more bugs. Added new themes * updated themes * upgraded Border * Fixed themes memory usage...mostly * Fixed themes memory usage...mostly2 * Fixed themes memory usage...2 * Fixed themes memory usage...3 * Added new colors * Fixed GetHardCodedConfig bug * Added Themes Scenario - WIP * Added Themes Scenario * Tweaked Themes Scenario * Code cleanup * Fixed json schmea * updated deepdives * updated deepdives * Tweaked Themes Scenario * Made Schemes a concurrent dict * Test cleanup * Thread safe ConfigProperty tests * trying to make things more thread safe * more trying to make things more thread safe * Fixing bugs in shadowview * Fixing bugs in shadowview 2 * Refactored GetViewsUnderMouse to GetViewsUnderLocation etc... * Fixed dupe unit tests? * Added better description of layout and coordiantes to deep dive * Added better description of layout and coordiantes to deep dive * Modified tests that call v2.AddTimeout; they were returning true which means restart the timer! This was causing mac/linux unit test failures. I think * Fixed auto scheme. Broke TextView/TextField selection * Realized Attribute.IsExplicitlySet is stupid; just use nullable * Fixed Attribute. Simplified. MOre theme testing * Updated themes again * GetViewsUnderMouse to GetViewsUnderLocation broke TransparentMouse. * Fixing mouseunder bugs * rewriting... * All working again. Shadows are now slick as snot. GetViewsUnderLocation is rewritten to actually work and be readable. Tons more low-level unit tests. Margin is now actually ViewportSettings.Transparent. * Code cleanup * Code cleanup * Code cleanup of color apis * Fixed Hover/Highlight * Update Examples/UICatalog/Scenarios/AllViewsTester.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update Examples/UICatalog/Scenarios/Clipping.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fixed race condition? * reverted * Simplified Attribute API by removing events from SetAttributeForRole * Removed recursion from GetViewsAtLocation * Removed unneeded code * Code clean up. Fixed Scheme bug. * reverted temporary disable * Adjusted scheme algo * Upgraded TextValidateField * Fixed TextValidate bugs * Tweaks * Frameview rounded border by default * API doc cleanup * Readme fix * Addressed tznind feeback * Fixed more unit test issues by protecting Application statics from being set if Application.Initialized is not true * Fixed more unit test issues by protecting Application statics from being set if Application.Initialized is not true 2 * cleanup --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
506 lines
14 KiB
C#
506 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.RegularExpressions;
|
|
using Terminal.Gui;
|
|
|
|
namespace UICatalog.Scenarios;
|
|
|
|
[ScenarioMetadata ("Syntax Highlighting", "Text editor with keyword highlighting using the TextView control.")]
|
|
[ScenarioCategory ("Text and Formatting")]
|
|
[ScenarioCategory ("Controls")]
|
|
[ScenarioCategory ("TextView")]
|
|
public class SyntaxHighlighting : Scenario
|
|
{
|
|
private readonly HashSet<string> _keywords = new (StringComparer.CurrentCultureIgnoreCase)
|
|
{
|
|
"select",
|
|
"distinct",
|
|
"top",
|
|
"from",
|
|
"create",
|
|
"CIPHER",
|
|
"CLASS_ORIGIN",
|
|
"CLIENT",
|
|
"CLOSE",
|
|
"COALESCE",
|
|
"CODE",
|
|
"COLUMNS",
|
|
"COLUMN_FORMAT",
|
|
"COLUMN_NAME",
|
|
"COMMENT",
|
|
"COMMIT",
|
|
"COMPACT",
|
|
"COMPLETION",
|
|
"COMPRESSED",
|
|
"COMPRESSION",
|
|
"CONCURRENT",
|
|
"CONNECT",
|
|
"CONNECTION",
|
|
"CONSISTENT",
|
|
"CONSTRAINT_CATALOG",
|
|
"CONSTRAINT_SCHEMA",
|
|
"CONSTRAINT_NAME",
|
|
"CONTAINS",
|
|
"CONTEXT",
|
|
"CONTRIBUTORS",
|
|
"COPY",
|
|
"CPU",
|
|
"CURSOR_NAME",
|
|
"primary",
|
|
"key",
|
|
"insert",
|
|
"alter",
|
|
"add",
|
|
"update",
|
|
"set",
|
|
"delete",
|
|
"truncate",
|
|
"as",
|
|
"order",
|
|
"by",
|
|
"asc",
|
|
"desc",
|
|
"between",
|
|
"where",
|
|
"and",
|
|
"or",
|
|
"not",
|
|
"limit",
|
|
"null",
|
|
"is",
|
|
"drop",
|
|
"database",
|
|
"table",
|
|
"having",
|
|
"in",
|
|
"join",
|
|
"on",
|
|
"union",
|
|
"exists"
|
|
};
|
|
|
|
private readonly string _path = "Cells.rce";
|
|
private Attribute _blue;
|
|
private Attribute _green;
|
|
private Attribute _magenta;
|
|
private MenuItem _miWrap;
|
|
private TextView _textView;
|
|
private Attribute _white;
|
|
|
|
/// <summary>
|
|
/// Reads an object instance from an Json file.
|
|
/// <para>Object type must have a parameterless constructor.</para>
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of object to read from the file.</typeparam>
|
|
/// <param name="filePath">The file path to read the object instance from.</param>
|
|
/// <returns>Returns a new instance of the object read from the Json file.</returns>
|
|
public static T ReadFromJsonFile<T> (string filePath) where T : new()
|
|
{
|
|
TextReader reader = null;
|
|
|
|
try
|
|
{
|
|
reader = new StreamReader (filePath);
|
|
string fileContents = reader.ReadToEnd ();
|
|
|
|
return (T)JsonSerializer.Deserialize (fileContents, typeof (T));
|
|
}
|
|
finally
|
|
{
|
|
if (reader != null)
|
|
{
|
|
reader.Close ();
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Main ()
|
|
{
|
|
// Init
|
|
Application.Init ();
|
|
|
|
// Setup - Create a top-level application window and configure it.
|
|
Toplevel appWindow = new ();
|
|
|
|
var menu = new MenuBar
|
|
{
|
|
Menus =
|
|
[
|
|
new (
|
|
"_TextView",
|
|
new []
|
|
{
|
|
_miWrap = new (
|
|
"_Word Wrap",
|
|
"",
|
|
() => WordWrap ()
|
|
)
|
|
{
|
|
CheckType = MenuItemCheckStyle
|
|
.Checked
|
|
},
|
|
null,
|
|
new (
|
|
"_Syntax Highlighting",
|
|
"",
|
|
() => ApplySyntaxHighlighting ()
|
|
),
|
|
null,
|
|
new (
|
|
"_Load Rune Cells",
|
|
"",
|
|
() => ApplyLoadCells ()
|
|
),
|
|
new (
|
|
"_Save Rune Cells",
|
|
"",
|
|
() => SaveCells ()
|
|
),
|
|
null,
|
|
new ("_Quit", "", () => Quit ())
|
|
}
|
|
)
|
|
]
|
|
};
|
|
appWindow.Add (menu);
|
|
|
|
_textView = new ()
|
|
{
|
|
Y = 1,
|
|
Width = Dim.Fill (),
|
|
Height = Dim.Fill (1)
|
|
};
|
|
|
|
ApplySyntaxHighlighting ();
|
|
|
|
appWindow.Add (_textView);
|
|
|
|
var statusBar = new StatusBar ([new (Application.QuitKey, "Quit", Quit)]);
|
|
|
|
appWindow.Add (statusBar);
|
|
|
|
// Run - Start the application.
|
|
Application.Run (appWindow);
|
|
appWindow.Dispose ();
|
|
|
|
// Shutdown - Calling Application.Shutdown is required.
|
|
Application.Shutdown ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the given object instance to a Json file.
|
|
/// <para>Object type must have a parameterless constructor.</para>
|
|
/// <para>
|
|
/// Only Public properties and variables will be written to the file. These can be any type though, even other
|
|
/// classes.
|
|
/// </para>
|
|
/// <para>
|
|
/// If there are public properties/variables that you do not want written to the file, decorate them with the
|
|
/// [JsonIgnore] attribute.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <typeparam name="T">The type of object being written to the file.</typeparam>
|
|
/// <param name="filePath">The file path to write the object instance to.</param>
|
|
/// <param name="objectToWrite">The object instance to write to the file.</param>
|
|
/// <param name="append">
|
|
/// If false the file will be overwritten if it already exists. If true the contents will be appended
|
|
/// to the file.
|
|
/// </param>
|
|
public static void WriteToJsonFile<T> (string filePath, T objectToWrite, bool append = false) where T : new()
|
|
{
|
|
TextWriter writer = null;
|
|
|
|
try
|
|
{
|
|
string contentsToWriteToFile = JsonSerializer.Serialize (objectToWrite);
|
|
writer = new StreamWriter (filePath, append);
|
|
writer.Write (contentsToWriteToFile);
|
|
}
|
|
finally
|
|
{
|
|
if (writer != null)
|
|
{
|
|
writer.Close ();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ApplyLoadCells ()
|
|
{
|
|
ClearAllEvents ();
|
|
|
|
List<Cell> cells = new ();
|
|
|
|
foreach (KeyValuePair<string, Scheme> color in SchemeManager.GetSchemesForCurrentTheme ())
|
|
{
|
|
string csName = color.Key;
|
|
|
|
foreach (Rune rune in csName.EnumerateRunes ())
|
|
{
|
|
cells.Add (new () { Rune = rune, Attribute = color.Value.Normal });
|
|
}
|
|
|
|
cells.Add (new () { Rune = (Rune)'\n', Attribute = color.Value.Focus });
|
|
}
|
|
|
|
if (File.Exists (_path))
|
|
{
|
|
//Reading the file
|
|
List<List<Cell>> fileCells = ReadFromJsonFile<List<List<Cell>>> (_path);
|
|
_textView.Load (fileCells);
|
|
}
|
|
else
|
|
{
|
|
_textView.Load (cells);
|
|
}
|
|
|
|
_textView.Autocomplete.SuggestionGenerator = new SingleWordSuggestionGenerator ();
|
|
}
|
|
|
|
private void ApplySyntaxHighlighting ()
|
|
{
|
|
ClearAllEvents ();
|
|
|
|
_green = new Attribute (Color.Green, Color.Black);
|
|
_blue = new Attribute (Color.Blue, Color.Black);
|
|
_magenta = new Attribute (Color.Magenta, Color.Black);
|
|
_white = new Attribute (Color.White, Color.Black);
|
|
_textView.SetScheme (new () { Focus = _white });
|
|
|
|
_textView.Text =
|
|
"/*Query to select:\nLots of data*/\nSELECT TOP 100 * \nfrom\n MyDb.dbo.Biochemistry where TestCode = 'blah';";
|
|
|
|
_textView.Autocomplete.SuggestionGenerator = new SingleWordSuggestionGenerator
|
|
{
|
|
AllSuggestions = _keywords.ToList ()
|
|
};
|
|
|
|
// DrawingText happens before DrawingContent so we use it to highlight
|
|
_textView.DrawingText += (s, e) => HighlightTextBasedOnKeywords ();
|
|
}
|
|
|
|
private void ClearAllEvents ()
|
|
{
|
|
_textView.ClearEventHandlers ("DrawingText");
|
|
_textView.InheritsPreviousAttribute = false;
|
|
}
|
|
|
|
private bool ContainsPosition (Match m, int pos) { return pos >= m.Index && pos < m.Index + m.Length; }
|
|
|
|
private void HighlightTextBasedOnKeywords ()
|
|
{
|
|
// Comment blocks, quote blocks etc
|
|
Dictionary<Rune, Scheme> blocks = new ();
|
|
|
|
var comments = new Regex (@"/\*.*?\*/", RegexOptions.Singleline);
|
|
MatchCollection commentMatches = comments.Matches (_textView.Text);
|
|
|
|
var singleQuote = new Regex (@"'.*?'", RegexOptions.Singleline);
|
|
MatchCollection singleQuoteMatches = singleQuote.Matches (_textView.Text);
|
|
|
|
// Find all keywords (ignoring for now if they are in comments, quotes etc)
|
|
Regex [] keywordRegexes =
|
|
_keywords.Select (k => new Regex ($@"\b{k}\b", RegexOptions.IgnoreCase)).ToArray ();
|
|
Match [] keywordMatches = keywordRegexes.SelectMany (r => r.Matches (_textView.Text)).ToArray ();
|
|
|
|
var pos = 0;
|
|
|
|
for (var y = 0; y < _textView.Lines; y++)
|
|
{
|
|
List<Cell> line = _textView.GetLine (y);
|
|
|
|
for (var x = 0; x < line.Count; x++)
|
|
{
|
|
Cell cell = line [x];
|
|
|
|
if (commentMatches.Any (m => ContainsPosition (m, pos)))
|
|
{
|
|
cell.Attribute = _green;
|
|
}
|
|
else if (singleQuoteMatches.Any (m => ContainsPosition (m, pos)))
|
|
{
|
|
cell.Attribute = _magenta;
|
|
}
|
|
else if (keywordMatches.Any (m => ContainsPosition (m, pos)))
|
|
{
|
|
cell.Attribute = _blue;
|
|
}
|
|
else
|
|
{
|
|
cell.Attribute = _white;
|
|
}
|
|
|
|
line [x] = cell;
|
|
pos++;
|
|
}
|
|
|
|
// for the \n or \r\n that exists in Text but not the returned lines
|
|
pos += Environment.NewLine.Length;
|
|
}
|
|
}
|
|
|
|
private string IdxToWord (List<Rune> line, int idx)
|
|
{
|
|
string [] words = Regex.Split (
|
|
new (line.Select (r => (char)r.Value).ToArray ()),
|
|
"\\b"
|
|
);
|
|
|
|
var count = 0;
|
|
string current = null;
|
|
|
|
foreach (string word in words)
|
|
{
|
|
current = word;
|
|
count += word.Length;
|
|
|
|
if (count > idx)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return current?.Trim ();
|
|
}
|
|
|
|
private bool IsKeyword (List<Rune> line, int idx)
|
|
{
|
|
string word = IdxToWord (line, idx);
|
|
|
|
if (string.IsNullOrWhiteSpace (word))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return _keywords.Contains (word, StringComparer.CurrentCultureIgnoreCase);
|
|
}
|
|
|
|
private void Quit () { Application.RequestStop (); }
|
|
|
|
private void SaveCells ()
|
|
{
|
|
//Writing to file
|
|
List<List<Cell>> cells = _textView.GetAllLines ();
|
|
WriteToJsonFile (_path, cells);
|
|
}
|
|
|
|
private void WordWrap ()
|
|
{
|
|
_miWrap.Checked = !_miWrap.Checked;
|
|
_textView.WordWrap = (bool)_miWrap.Checked;
|
|
}
|
|
}
|
|
|
|
public static class EventExtensions
|
|
{
|
|
public static void ClearEventHandlers (this object obj, string eventName)
|
|
{
|
|
if (obj == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Type objType = obj.GetType ();
|
|
EventInfo eventInfo = objType.GetEvent (eventName);
|
|
|
|
if (eventInfo == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var isEventProperty = false;
|
|
Type type = objType;
|
|
FieldInfo eventFieldInfo = null;
|
|
|
|
while (type != null)
|
|
{
|
|
/* Find events defined as field */
|
|
eventFieldInfo = type.GetField (
|
|
eventName,
|
|
BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic
|
|
);
|
|
|
|
if (eventFieldInfo != null
|
|
&& (eventFieldInfo.FieldType == typeof (MulticastDelegate)
|
|
|| eventFieldInfo.FieldType.IsSubclassOf (
|
|
typeof (MulticastDelegate)
|
|
)))
|
|
{
|
|
break;
|
|
}
|
|
|
|
/* Find events defined as property { add; remove; } */
|
|
eventFieldInfo = type.GetField (
|
|
"EVENT_" + eventName.ToUpper (),
|
|
BindingFlags.Static | BindingFlags.Instance | BindingFlags.NonPublic
|
|
);
|
|
|
|
if (eventFieldInfo != null)
|
|
{
|
|
isEventProperty = true;
|
|
|
|
break;
|
|
}
|
|
|
|
type = type.BaseType;
|
|
}
|
|
|
|
if (eventFieldInfo == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (isEventProperty)
|
|
{
|
|
// Default Events Collection Type
|
|
RemoveHandler<EventHandlerList> (obj, eventFieldInfo);
|
|
|
|
return;
|
|
}
|
|
|
|
if (!(eventFieldInfo.GetValue (obj) is Delegate eventDelegate))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Remove Field based event handlers
|
|
foreach (Delegate d in eventDelegate.GetInvocationList ())
|
|
{
|
|
eventInfo.RemoveEventHandler (obj, d);
|
|
}
|
|
}
|
|
|
|
private static void RemoveHandler<T> (object obj, FieldInfo eventFieldInfo)
|
|
{
|
|
Type objType = obj.GetType ();
|
|
object eventPropertyValue = eventFieldInfo.GetValue (obj);
|
|
|
|
if (eventPropertyValue == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
PropertyInfo propertyInfo = objType.GetProperties (BindingFlags.NonPublic | BindingFlags.Instance)
|
|
.FirstOrDefault (p => p.Name == "Events" && p.PropertyType == typeof (T));
|
|
|
|
if (propertyInfo == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
object eventList = propertyInfo?.GetValue (obj, null);
|
|
|
|
switch (eventList)
|
|
{
|
|
case null:
|
|
return;
|
|
}
|
|
}
|
|
}
|