From cb12a06a60add2569a8e4f7c4bec9b2bd7a45163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=98=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D1=87=D0=B5=D0=BD=D0=BA=D0=BE?= Date: Fri, 6 Nov 2026 10:52:49 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BD=D0=B0=D0=B6=D0=B0=D1=82=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BA=D0=BD=D0=BE=D0=BF=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- install/examples/ConsoleKeyRead.os | 22 +++ .../Text/ConsoleContext.cs | 58 ++++++++ .../ConsoleContextTests.cs | 126 ++++++++++++++++++ tests/console.os | 19 +++ 4 files changed, 225 insertions(+) create mode 100644 install/examples/ConsoleKeyRead.os create mode 100644 src/Tests/OneScript.StandardLibrary.Tests/ConsoleContextTests.cs diff --git a/install/examples/ConsoleKeyRead.os b/install/examples/ConsoleKeyRead.os new file mode 100644 index 000000000..918a43f9e --- /dev/null +++ b/install/examples/ConsoleKeyRead.os @@ -0,0 +1,22 @@ +Пока Истина Цикл + + Нажатие = Консоль.ПрочитатьНажатие(); + + Сообщить("Код кнопки: " + Нажатие.КодКнопки); + + Если Нажатие.Модификаторы.Control Тогда + Сообщить("Ctrl + " + Нажатие.Символ); + ИначеЕсли Нажатие.Модификаторы.Shift Тогда + Сообщить("Shift + " + Нажатие.Символ); + ИначеЕсли Нажатие.Модификаторы.Alt Тогда + Сообщить("Alt + " + Нажатие.Символ); + Иначе + Сообщить(Нажатие.Символ); + КонецЕсли; + + Если Нажатие.КодКнопки = 27 Тогда // Escape + Сообщить("Выход"); + Прервать; + КонецЕсли; + +КонецЦикла; \ No newline at end of file diff --git a/src/OneScript.StandardLibrary/Text/ConsoleContext.cs b/src/OneScript.StandardLibrary/Text/ConsoleContext.cs index 2ebe5f7c3..d3a25688f 100644 --- a/src/OneScript.StandardLibrary/Text/ConsoleContext.cs +++ b/src/OneScript.StandardLibrary/Text/ConsoleContext.cs @@ -11,6 +11,7 @@ This Source Code Form is subject to the terms of the using OneScript.Exceptions; using OneScript.Execution; using OneScript.StandardLibrary.Binary; +using OneScript.StandardLibrary.Collections; using OneScript.Values; using ScriptEngine.Machine; using ScriptEngine.Machine.Contexts; @@ -88,6 +89,63 @@ public int ReadKey() return (int)kki.Key; } + /// + /// Читает нажатие клавиши и возвращает подробное описание нажатия. + /// В отличие от , возвращает не только код клавиши, но и введённый символ + /// (в том числе кириллический), а также состояние модификаторов. + /// Символ не выводится в консоль (чтение выполняется без эха). + /// + /// + /// Структура с полями: + /// + /// КодКнопки — код клавиши (значение перечисления ), + /// совпадает с результатом ; + /// Символ — введённый символ; пустая строка для служебных клавиш + /// и управляющих сочетаний; + /// Модификаторы — структура с булевыми полями Shift, Alt, Control. + /// + /// + /// + /// Возможности платформы различаются: + /// + /// в Windows код клавиши и состояние модификаторов определяются для любых нажатий; + /// в Unix-подобных системах .NET распознаёт клавиши и модификаторы только для известных + /// символов и управляющих последовательностей. Для символов за пределами ASCII (в том числе кириллицы) + /// КодКнопки может быть равен 0, а Shift для заглавной кириллической буквы может не определиться + /// (при этом сам символ всегда возвращается в поле Символ). + /// + /// + [ContextMethod("ПрочитатьНажатие", "ReadKeyPress")] + public StructureImpl ReadKeyPress() + { + return CreateKeyPressInfo(Console.ReadKey(true)); + } + + /// + /// Преобразует нажатие, полученное от , + /// в структуру, доступную из BSL. + /// + internal static StructureImpl CreateKeyPressInfo(ConsoleKeyInfo keyInfo) + { + var modifiers = new StructureImpl( + "Shift, Alt, Control", + ValueFactory.Create((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0), + ValueFactory.Create((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0), + ValueFactory.Create((keyInfo.Modifiers & ConsoleModifiers.Control) != 0)); + + var result = new StructureImpl(); + result.Insert("КодКнопки", ValueFactory.Create((int)keyInfo.Key)); + result.Insert("Символ", ValueFactory.Create(GetPrintableSymbol(keyInfo.KeyChar))); + result.Insert("Модификаторы", modifiers); + + return result; + } + + private static string GetPrintableSymbol(char keyChar) + { + return char.IsControl(keyChar) ? string.Empty : keyChar.ToString(); + } + [ContextMethod("Очистить", "Clear")] public void Clear() { diff --git a/src/Tests/OneScript.StandardLibrary.Tests/ConsoleContextTests.cs b/src/Tests/OneScript.StandardLibrary.Tests/ConsoleContextTests.cs new file mode 100644 index 000000000..ce7164e0f --- /dev/null +++ b/src/Tests/OneScript.StandardLibrary.Tests/ConsoleContextTests.cs @@ -0,0 +1,126 @@ +/*---------------------------------------------------------- +This Source Code Form is subject to the terms of the +Mozilla Public License, v.2.0. If a copy of the MPL +was not distributed with this file, You can obtain one +at http://mozilla.org/MPL/2.0/. +----------------------------------------------------------*/ + +using System; +using System.Reflection; +using OneScript.Contexts; +using OneScript.StandardLibrary.Collections; +using OneScript.StandardLibrary.Text; +using OneScript.Values; +using ScriptEngine.Machine; +using Xunit; + +namespace OneScript.StandardLibrary.Tests +{ + public class ConsoleContextTests + { + private static IValue GetProperty(StructureImpl structure, string name) => + structure.GetPropValue(structure.GetPropertyNumber(name)); + + private static int GetNumber(StructureImpl structure, string name) => + (BslNumericValue)GetProperty(structure, name); + + private static string GetString(StructureImpl structure, string name) => + (BslStringValue)GetProperty(structure, name); + + private static bool GetBoolean(StructureImpl structure, string name) => + (bool)(BslBooleanValue)GetProperty(structure, name); + + private static StructureImpl GetNestedStructure(StructureImpl structure, string name) => + (StructureImpl)GetProperty(structure, name); + + [Fact] + public void ReadKeyPress_Is_Exposed_With_Bilingual_Name() + { + var method = typeof(ConsoleContext).GetMethod(nameof(ConsoleContext.ReadKeyPress)); + + Assert.NotNull(method); + + var attribute = method!.GetCustomAttribute(); + Assert.NotNull(attribute); + Assert.Equal("ПрочитатьНажатие", attribute!.Name); + Assert.Equal("ReadKeyPress", attribute.Alias); + } + + [Fact] + public void KeyPressInfo_Results_Have_Expected_Structure() + { + var keyPress = ConsoleContext.CreateKeyPressInfo( + new ConsoleKeyInfo('ф', (ConsoleKey)0, false, false, false)); + + Assert.Equal(3, keyPress.GetPropCount()); + Assert.Equal("КодКнопки", keyPress.GetPropName(0)); + Assert.Equal("Символ", keyPress.GetPropName(1)); + Assert.Equal("Модификаторы", keyPress.GetPropName(2)); + + var modifiers = GetNestedStructure(keyPress, "Модификаторы"); + Assert.Equal(3, modifiers.GetPropCount()); + Assert.Equal("Shift", modifiers.GetPropName(0)); + Assert.Equal("Alt", modifiers.GetPropName(1)); + Assert.Equal("Control", modifiers.GetPropName(2)); + } + + [Fact] + public void KeyPressInfo_Returns_Cyrillic_Symbol() + { + var keyPress = ConsoleContext.CreateKeyPressInfo( + new ConsoleKeyInfo('ф', (ConsoleKey)0, false, false, false)); + + Assert.Equal("ф", GetString(keyPress, "Символ")); + Assert.Equal(0, GetNumber(keyPress, "КодКнопки")); + } + + [Fact] + public void KeyPressInfo_Returns_Upper_Case_Cyrillic_Symbol_With_Shift() + { + var keyPress = ConsoleContext.CreateKeyPressInfo( + new ConsoleKeyInfo('Ф', (ConsoleKey)0, shift: true, alt: true, control: false)); + + Assert.Equal("Ф", GetString(keyPress, "Символ")); + + var modifiers = GetNestedStructure(keyPress, "Модификаторы"); + Assert.True(GetBoolean(modifiers, "Shift")); + Assert.True(GetBoolean(modifiers, "Alt")); + Assert.False(GetBoolean(modifiers, "Control")); + } + + [Fact] + public void KeyPressInfo_Returns_Empty_Symbol_For_Control_Key_Combination() + { + // Ctrl+A: .NET сообщает управляющий символ и код клавиши A + var keyPress = ConsoleContext.CreateKeyPressInfo( + new ConsoleKeyInfo('\u0001', ConsoleKey.A, shift: false, alt: false, control: true)); + + Assert.Equal(string.Empty, GetString(keyPress, "Символ")); + Assert.Equal((int)ConsoleKey.A, GetNumber(keyPress, "КодКнопки")); + + var modifiers = GetNestedStructure(keyPress, "Модификаторы"); + Assert.True(GetBoolean(modifiers, "Control")); + Assert.False(GetBoolean(modifiers, "Shift")); + Assert.False(GetBoolean(modifiers, "Alt")); + } + + [Fact] + public void KeyPressInfo_Returns_Empty_Symbol_For_Function_Key() + { + var keyPress = ConsoleContext.CreateKeyPressInfo( + new ConsoleKeyInfo('\0', ConsoleKey.F1, false, false, false)); + + Assert.Equal(string.Empty, GetString(keyPress, "Символ")); + Assert.Equal((int)ConsoleKey.F1, GetNumber(keyPress, "КодКнопки")); + } + + [Fact] + public void KeyPressInfo_Keeps_Space_As_Symbol() + { + var keyPress = ConsoleContext.CreateKeyPressInfo( + new ConsoleKeyInfo(' ', ConsoleKey.Spacebar, false, false, false)); + + Assert.Equal(" ", GetString(keyPress, "Символ")); + } + } +} diff --git a/tests/console.os b/tests/console.os index 2f6429031..02b4f81ed 100644 --- a/tests/console.os +++ b/tests/console.os @@ -13,6 +13,7 @@ #КонецЕсли ВсеТесты.Добавить("ТестДолжен_ПроверитьПеренаправлениеВывода"); ВсеТесты.Добавить("ТестДолжен_ПроверитЦветКонсоли"); + ВсеТесты.Добавить("ТестДолжен_ПроверитьНаличиеМетодаПрочитатьНажатие"); Возврат ВсеТесты; @@ -156,3 +157,21 @@ юТест.ПроверитьТип(ЦветФона, "ЦветКонсоли"); КонецПроцедуры + +Процедура ТестДолжен_ПроверитьНаличиеМетодаПрочитатьНажатие() Экспорт + + Рефлектор = Новый Рефлектор; + ТаблицаМетодов = Рефлектор.ПолучитьТаблицуМетодов(Консоль); + + Найден = Ложь; + Для Каждого СтрокаМетода Из ТаблицаМетодов Цикл + Если СтрокаМетода.Имя = "ПрочитатьНажатие" Тогда + Найден = Истина; + юТест.ПроверитьРавенство(0, СтрокаМетода.КоличествоПараметров, "Метод ПрочитатьНажатие не должен требовать параметров"); + юТест.ПроверитьИстину(СтрокаМетода.ЭтоФункция, "Метод ПрочитатьНажатие должен быть функцией"); + КонецЕсли; + КонецЦикла; + + юТест.ПроверитьИстину(Найден, "Метод Консоль.ПрочитатьНажатие должен быть доступен"); + +КонецПроцедуры