/
yeremee
/
FormatConsole
Обзор
Документация
Войти
/
yeremee
/
FormatConsole
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
_old/FormatConsole.cs
664 строки
34 KB
Yεrεmεe
Репозиторий создан
01 мар 2026, 21:28
01 мар 2026, 21:28
706ce25
Код
Авторство
О чём код?
using ConsoleTools.Properties; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions; namespace ConsoleTools.Formatting._old { /// <summary> /// Представляет объект форматированного вывода на консоль. /// </summary> public class FormatConsole { #region Поля /// <!--NON-PUBLIC--> /// <summary> /// Хранит правила форматирования, установленные для экземпляра консоли /// </summary> private readonly FormatRules правилаФорматирования; /// <!--NON-PUBLIC--> /// <summary> /// Хранит символ, считающийся экранирующим при разборе форматной строки /// </summary> private readonly char экранирующийСимвол; /// <!--NON-PUBLIC--> /// <summary> /// Нужно ли неявно очищать экран консоли (при применении цветовых настроек, при выводе заголовка) /// </summary> private bool требуетсяОчистка = true; /// <!--NON-PUBLIC--> /// <summary> /// Хранит цвет переднего плана (текста), установленный по умолчанию для экземпляра консоли /// </summary> private readonly ConsoleColor цветТекста; /// <!--NON-PUBLIC--> /// <summary> /// Хранит цвет заднего плана (фона), установленный по умолчанию для экземпляра консоли /// </summary> private readonly ConsoleColor цветФона; ///// <!--NON-PUBLIC--> ///// <summary> ///// Хранит границы диапазона вывода для последней строки, выведенной методами <c>Write*/Rewrite*</c> ///// </summary> //private (int СтрокаНачало, int СтолбецНачало, int СтрокаКонец) диапазонВывода; /// <!--NON-PUBLIC--> /// <summary> /// Метка начала перезаписи последней выведенной строки /// </summary> private const string меткаПоУмолчанию = ""; /// <!--NON-PUBLIC--> /// <summary> /// Начальные границы диапазона вывода (пары строк и столбцов), связанные с метками /// </summary> private readonly Dictionary<string, (int столбец, int строка)> диапазонВыводаНачало = new(); /// <!--NON-PUBLIC--> /// <summary> /// Конечная граница диапазона вывода (только строка) /// </summary> private int диапазонВыводаКонец; /// <!--NON-PUBLIC--> /// <summary> /// Хранит форматированную строку заголовка, которая выводится после каждой очистки экрана консоли. /// </summary> private string заголовок = string.Empty; /// <!--NON-PUBLIC--> /// <summary> /// Количество пустых строк, оставляемых после вывода строки методом <see cref="WriteLine"/>. /// </summary> private ushort межстрочныйИнтервал = 0; #endregion #region Конструкторы /// <summary> /// Создаёт объект форматированного вывода на консоль прописными буквами для форматирующей разметки <c>`...`</c> /// и экранирующего символа <c>^</c>. /// </summary> public FormatConsole() : this( rules: new FormatRules { ("`", "`", null, null, true) }, escape: '^') { // Дополнительных действий нет. } /// <summary> /// Создаёт объект форматированного вывода на консоль с заданным цветом текста для форматирующей разметки <c>`...`</c> /// и экранирующего символа <c>^</c>. /// </summary> /// <param name="highlight">цветовой акцент</param> public FormatConsole(ConsoleColor highlight) : this( rules: new FormatRules { ("`", "`", highlight, null, false) }, escape: '^') { // Дополнительных действий нет. } /// <summary> /// Создаёт объект форматированного вывода на консоль с заданными цветами текста /// для форматирующей разметки <c>`...`</c> и <c>``...``</c> /// и экранирующего символа <c>^</c>. /// </summary> /// <param name="highlight1">первый цветовой акцент</param> /// <param name="highlight2">второй цветовой акцент</param> public FormatConsole(ConsoleColor highlight1, ConsoleColor highlight2) : this( rules: new FormatRules { ("`", "`", highlight1, null, false), ("``", "``", highlight2, null, false) }, escape: '^') { // Дополнительных действий нет. } /// <summary> /// Создаёт объект форматированного вывода на консоль с заданными цветами текста /// для форматирующей разметки <c>`...`</c>, <c>``...``</c>, и <c>```...```</c> /// и экранирующего символа <c>^</c>. /// </summary> /// <param name="highlight1">первый цветовой акцент</param> /// <param name="highlight2">второй цветовой акцент</param> /// <param name="highlight3">третий цветовой акцент</param> public FormatConsole(ConsoleColor highlight1, ConsoleColor highlight2, ConsoleColor highlight3) : this( rules: new FormatRules { ("`", "`", highlight1, null, false), ("``", "``", highlight2, null, false), ("```", "```", highlight3, null, false) }, escape: '^') { // Дополнительных действий нет. } /// <summary> /// Создаёт объект форматированного вывода на консоль с заданными правилами разметки и формата /// и экранирующим символом. /// </summary> /// <param name="rules">правила форматирования</param> /// <param name="escape">экранирующий символ</param> public FormatConsole(FormatRules rules, char escape) { правилаФорматирования = rules; экранирующийСимвол = escape; цветТекста = Console.ForegroundColor; цветФона = Console.BackgroundColor; } /// <summary> /// Создаёт объект форматированного вывода на консоль с заданными правилами разметки и формата /// и экранирующим символом, а также цветами текста и фона по умолчанию. /// </summary> /// <param name="rules">правила форматирования</param> /// <param name="escape">экранирующий символ</param> /// <param name="foregrd">цвет переднего плана (текста)</param> /// <param name="backgrd">цвет заднего плана (фона)</param> public FormatConsole(FormatRules rules, char escape, ConsoleColor foregrd, ConsoleColor backgrd) : this(rules, escape) { цветТекста = foregrd; цветФона = backgrd; } #endregion #region Основные методы /// <summary> /// Задаёт интервал между строками, используемый при вызове метода <see cref="WriteLine"/>. /// </summary> /// <param name="lines">количество пропускаемых строк</param> public void SetInterval(ushort lines) { межстрочныйИнтервал = lines; } /// <summary> /// Задаёт заголовок, выводимый после каждой очистки экрана консоли, /// или удаляет его (при передаче аргумента <see langword="null"/>). /// </summary> /// <param name="text">текст для вывода, содержащий форматирующую разметку</param> public void SetTitle(string text) { заголовок = text; требуетсяОчистка = true; } /// <summary> /// Выводит строку, отформатированную согласно установленным правилам консоли, /// с последующим переходом на новую строку через установленный интервал. /// </summary> /// <remarks> /// <para>Первый вызов этого метода (или метода <see cref="Write"/>) очищает консоль, /// применяет к ней цветовые настройки, и выводит заголовок (если он задан методом <see cref="SetTitle"/>).</para> /// <para>Межстрочный интервал задаётся методом <see cref="SetInterval"/>.</para> /// </remarks> /// <param name="text">текст для вывода, содержащий форматирующую разметку</param> /// <param name="label">метка начала перезаписи, связываемая с этой строкой (для методов <c>Rewrite*</c>)</param> /// <param name="needClear">нужно ли очищать экран консоли перед выводом строки</param> public void WriteLine(string text, string label = меткаПоУмолчанию, bool needClear = false) { Write(text, label, needClear); // Обеспечение межстрочного интервала Console.Write(Enumerable.Repeat("\n", межстрочныйИнтервал + 1).Aggregate((s1, s2) => s1 + s2)); диапазонВыводаКонец = Console.GetCursorPosition().Top; } /// <summary> /// Заменяет всю последнюю строку, выведенную методами <see cref="Write"/> или <see cref="WriteLine"/>, /// новой строкой. /// </summary> /// <param name="text">текст для вывода, содержащий форматирующую разметку</param> /// <param name="label">метка начала перезаписи (из методов <c>Write*</c>)</param> public void RewriteLine(string text, string label = меткаПоУмолчанию) { ОчиститьДиапазонВывода(label, true); WriteLine(text, меткаПоУмолчанию, false); } /// <summary> /// Выводит строку, отформатированную согласно установленным правилам консоли. /// </summary> /// <remarks> /// Первый вызов этого метода (или метода <see cref="WriteLine"/>) /// применяет цветовые настройки консоли и очищает её. /// </remarks> /// <param name="text">текст для вывода, содержащий форматирующую разметку</param> /// <param name="label">метка начала перезаписи, связываемая с этой строкой (для методов <c>Rewrite*</c>)</param> /// <param name="needClear">нужно ли очищать экран консоли перед выводом строки</param> public void Write(string text, string label = меткаПоУмолчанию, bool needClear = false) { #region Применение цветовых настроек консоли и вывод заголовка if (требуетсяОчистка || needClear) { требуетсяОчистка = false; Console.ForegroundColor = цветТекста; Console.BackgroundColor = цветФона; Console.Clear(); if (!string.IsNullOrEmpty(заголовок)) WriteLine(заголовок, меткаПоУмолчанию, false); } #endregion диапазонВыводаНачало[label] = Console.GetCursorPosition(); #region Инициализация // https://www.unicode.org/charts/PDF/UE0000.pdf // U+E00xx = U+DB40 & U+DCxx #if DEBUG const int минимКодМетки = 0xE00_20; const int максимКодМетки = 0xE00_7F; const int кодМеткиОтмены = 0xE00_7F; //const int кодМеткиЭкрана = 0xE00_01; string меткаЭкрана = "●"; string меткаОтмены = "■"; #else const int верхнийСуррогат = 0xDB40; const int минимНижнийСуррогат = 0xDC_20; const int максимНижнийСуррогат = 0xDC_7F; const int нижнийСуррогатОтмены = 0xDC_7F; const int нижнийСуррогатЭкрана = 0xDC_01; string меткаЭкрана = new(new[] { (char)верхнийСуррогат, (char)нижнийСуррогатЭкрана }); string меткаОтмены = new(new[] { (char)верхнийСуррогат, (char)нижнийСуррогатОтмены }); #endif StringBuilder рабочаяСтрока = new(text); //рабочаяСтрока.Replace("\r\n", null, 0, 2); // удаление переноса строки в начале int индексПоиска; // нереверсивный индекс начала поиска #endregion #region Обработка экранирующих символов индексПоиска = 0; while (true) { индексПоиска = рабочаяСтрока.ToString().IndexOf(экранирующийСимвол, индексПоиска); if (индексПоиска is -1) break; рабочаяСтрока.Remove(индексПоиска, 1); рабочаяСтрока.Insert(индексПоиска, меткаЭкрана); индексПоиска += меткаЭкрана.Length + 1; //индексПоиска++; } #endregion #region Обработка форматирующих символов foreach (var (кодМетки, инициаль, финаль, цветТекста, цветФона, вВерхнемРегистре) in правилаФорматирования.СоответствияМеткам) { #if DEBUG string текстМетки = $"⯇{кодМетки:X}⯈"; #else string текстМетки = char.ConvertFromUtf32(кодМетки); #endif #region Инициаль индексПоиска = 0; do { индексПоиска = рабочаяСтрока.ToString().IndexOf(инициаль, индексПоиска); if (индексПоиска is -1) goto КъСледующемуПравилу; if (индексПоиска >= 1 && рабочаяСтрока.ToString()[(индексПоиска - меткаЭкрана.Length)..индексПоиска] == меткаЭкрана) { //рабочаяСтрока.Remove(индексПоиска - меткаЭкрана.Length, меткаЭкрана.Length); //индексПоиска -= меткаЭкрана.Length - 1; индексПоиска++; continue; // к следующему индексу } string начальнаяМетка = текстМетки; рабочаяСтрока.Replace(инициаль, начальнаяМетка, индексПоиска, инициаль.Length); #region Финаль индексПоиска += начальнаяМетка.Length; do { индексПоиска = рабочаяСтрока.ToString().IndexOf(финаль, индексПоиска); if (индексПоиска is -1) { throw new FormatConsoleException(Resources.НедопустимаяРазметка); } if (рабочаяСтрока.ToString()[(индексПоиска - меткаЭкрана.Length)..индексПоиска] == меткаЭкрана) { //рабочаяСтрока.Remove(индексПоиска - меткаЭкрана.Length, меткаЭкрана.Length); //индексПоиска -= меткаЭкрана.Length - 1; индексПоиска++; continue; // к следующему индексу } string конечнаяМетка = меткаОтмены + текстМетки; рабочаяСтрока.Replace(финаль, конечнаяМетка, индексПоиска, финаль.Length); индексПоиска += конечнаяМетка.Length; goto КъСледующейИнициали; } while (true /*индексПоиска is not -1*/); #endregion КъСледующейИнициали: ; } while (true /*индексПоиска is not -1*/); #endregion КъСледующемуПравилу: ; } // удаление экранов рабочаяСтрока.Replace(меткаЭкрана, null); #endregion #region Выделение фрагментов текста и их вывод int индексНачалаФраг = 0; int индексКонцаФрагИскл = -1; Dictionary<int, (ConsoleColor?, ConsoleColor?, bool)> применяемыеФорматы = new(); индексПоиска = 0; do { // поиск метки для определения границы фрагмента int меткаСледФрагмента = 0; #if DEBUG var индТреуг = рабочаяСтрока.ToString().IndexOf("⯇", индексПоиска); var индКвадр = рабочаяСтрока.ToString().IndexOf("■", индексПоиска); if (индКвадр < индТреуг & индКвадр != -1) индексПоиска = индКвадр; else индексПоиска = индТреуг; #else индексПоиска = рабочаяСтрока.ToString().IndexOf((char)верхнийСуррогат, индексПоиска); #endif if (индексПоиска == -1) { индексКонцаФрагИскл = рабочаяСтрока.Length; goto ВыводТекста; } #if DEBUG int минимЗначение = минимКодМетки; int максимЗначение = максимКодМетки; int значениеОтмены = кодМеткиОтмены; #else int минимЗначение = минимНижнийСуррогат; int максимЗначение = максимНижнийСуррогат; int значениеОтмены = нижнийСуррогатОтмены; #endif for (int кодМетки = минимЗначение; кодМетки <= максимЗначение; кодМетки++) { #if DEBUG string текущаяМетка = (кодМетки != значениеОтмены) ? $"⯇{кодМетки:X}⯈" : "■"; #else string текущаяМетка = char.ConvertFromUtf32(char.ConvertToUtf32((char)верхнийСуррогат, (char)кодМетки)); #endif if (рабочаяСтрока.ToString().Substring(индексПоиска, текущаяМетка.Length) == текущаяМетка) { // метка найдена индексКонцаФрагИскл = индексПоиска; if (кодМетки == значениеОтмены) { #if DEBUG string следующаяМетка = рабочаяСтрока.ToString().Substring(индексПоиска + текущаяМетка.Length, 7); int _меткаСледФрагмента = int.Parse(следующаяМетка[1..^1], System.Globalization.NumberStyles.HexNumber); #else string следующаяМетка = рабочаяСтрока.ToString().Substring(индексПоиска + текущаяМетка.Length, 2); int _меткаСледФрагмента = char.ConvertToUtf32(следующаяМетка[0], следующаяМетка[1]); #endif меткаСледФрагмента = -1 * _меткаСледФрагмента; индексПоиска += текущаяМетка.Length + следующаяМетка.Length; } else { #if DEBUG меткаСледФрагмента = кодМетки; #else меткаСледФрагмента = char.ConvertToUtf32((char)верхнийСуррогат, (char)кодМетки); #endif индексПоиска += текущаяМетка.Length; } break; } } // форматирование и вывод текущего фрагмента ВыводТекста: string текущийФрагмент = рабочаяСтрока.ToString()[индексНачалаФраг..индексКонцаФрагИскл]; //Console.ResetColor(); Console.ForegroundColor = цветТекста; Console.BackgroundColor = цветФона; bool прописнымиБуквами = false; foreach (var формат in применяемыеФорматы) { var (цветТекста, цветФона, вВерхнемРегистре) = формат.Value; var исходныйЦвет = Console.ForegroundColor; Console.ForegroundColor = цветТекста ?? исходныйЦвет; исходныйЦвет = Console.BackgroundColor; Console.BackgroundColor = цветФона ?? исходныйЦвет; if (вВерхнемРегистре == true) прописнымиБуквами = true; } if (прописнымиБуквами) текущийФрагмент = текущийФрагмент.ToUpper(); Console.Write(текущийФрагмент); // установка формата для следующего фрагмента if (меткаСледФрагмента > 0) применяемыеФорматы.Add(меткаСледФрагмента, правилаФорматирования[меткаСледФрагмента]); else if (меткаСледФрагмента < 0) применяемыеФорматы.Remove(меткаСледФрагмента * -1); индексНачалаФраг = индексПоиска; } while (индексПоиска is not -1); #endregion диапазонВыводаКонец = Console.GetCursorPosition().Top; #region ========= выключенный код ============ /* var выделенныеФрагменты = Regex.Split(рабочаяСтрока.ToString(), @"(?x) ( ⯈ ( [A-Z0-9]{5} ) .+? \2 ⯇ )" ); //var выделенныеФрагменты = Regex.Split(промежуточнаяСтрока.ToString(), // @"(?x) ( ( (\uDB40\uDC7F)? \uDB40[\uDC20-\uDC7E] ) .+? \2 )" //); выделенныеФрагменты = выделенныеФрагменты //.Where(str => str != string.Empty) //.Where(str => !Regex.IsMatch(str, @"^[\uD800-\uDFFF]+$")) .ToArray(); // Вывод текста по фрагментам foreach (var фрагментТекста in выделенныеФрагменты) { // Перебрать все правила в поисках соответствия //foreach (var правило in правилаФорматирования.соответствияМетокФорматам) foreach (var (кодМетки, *//* ←→ *//* инициаль, финаль, цветТекста, цветФона, вВерхнемРегистре) in правилаФорматирования.СоответствияМеткам) { string суррогатМетки = ВСуррогатнуюПару(*//*кодСлужебнойМетки*//*0XE007F); var соответствиеФормату = Regex.Match(фрагментТекста, @"(?x) ^ ( (\uDB40\uDC7F)? \uDB40[\uDC20-\uDC7E] ) (.+?) (\1) \\\\\\\$" ); // Соответствие найдено if (соответствиеФормату.Success) { var форматируемыйТекст = соответствиеФормату.Groups[3].Value; //if (Regex.IsMatch(форматируемыйТекст, @"[\uD800-\uDFFF]")) //{ // throw new InvalidFormattingException(Resources.НедопустимаяРазметка); //} var исходныйЦвет = Console.ForegroundColor; Console.ForegroundColor = цветТекста ?? исходныйЦвет; исходныйЦвет = Console.BackgroundColor; Console.BackgroundColor = цветФона ?? исходныйЦвет; if (вВерхнемРегистре == true) форматируемыйТекст = форматируемыйТекст.ToUpper(); Console.Write(форматируемыйТекст); Console.ResetColor(); // Прекратить перебор правил goto кСледующемуФрагменту; } } // Соответствия правилам не найдено Console.Write(фрагментТекста); кСледующемуФрагменту: // Перейти к следующему фрагменту //continue; ; } */ #endregion } /// <summary> /// Заменяет последнюю строку, выведенную методом <see cref="Write"/>, новой строкой. /// </summary> /// <param name="text">текст для вывода, содержащий форматирующую разметку</param> /// <param name="label">метка начала перезаписи (из методов <c>Write*</c>)</param> public void Rewrite(string text, string label = меткаПоУмолчанию) { ОчиститьДиапазонВывода(label, false); Write(text, меткаПоУмолчанию, false); } #endregion #region Вспомогательные методы /// <!--NON-PUBLIC--> /// <summary> /// Преобразует код символа в пару суррогатных символов для использования в регулярных выражениях. /// </summary> /// <param name="кодСимвола">числовой код символа</param> /// <returns>строка вида "\uXXXX\uXXXX", где XXXX — hex-коды суррогатных символов</returns> private static string ВСуррогатнуюПару(int кодСимвола) { int верхнийСуррогат = (кодСимвола - 0x10000) / 0x400 + 0xD800; int нижнийСуррогат = (кодСимвола - 0x10000) % 0x400 + 0xDC00; return $"\\u{верхнийСуррогат:X4}\\u{нижнийСуррогат:X4}"; } /// <!--NON-PUBLIC--> /// <summary> /// Очищает диапазон вывода в границах, /// хранящихся в полях <see cref="диапазонВыводаНачало"/> и <see cref="диапазонВыводаКонец"/>. /// </summary> /// <param name="меткаНачала">метка, определяющая начальную границу диапазона вывода</param> /// <param name="съНачалаСтроки">смещать ли начальную границу диапазона вывода к началу строки</param> private void ОчиститьДиапазонВывода(string меткаНачала, bool съНачалаСтроки) { if (!диапазонВыводаНачало.ContainsKey(меткаНачала)) return; //диапазонВыводаНачало[меткаНачала] = диапазонВыводаНачало[меткаПоУмолчанию]; //меткаНачала = ""; for (int строка = диапазонВыводаНачало[меткаНачала].строка, столбец = съНачалаСтроки ? 0 : диапазонВыводаНачало[меткаНачала].столбец; строка <= диапазонВыводаКонец; строка++) { Console.SetCursorPosition(столбец, строка); //Console.ReadKey(); var затирающаяСтрока = string.Concat(Enumerable.Repeat(' ', Console.BufferWidth - столбец)); Console.Write(затирающаяСтрока); //Console.ReadKey(); столбец = 0; } Console.SetCursorPosition(съНачалаСтроки ? 0 : диапазонВыводаНачало[меткаНачала].столбец, диапазонВыводаНачало[меткаНачала].строка); //Console.ReadKey(); } #endregion } }