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, СтрокаМетода.КоличествоПараметров, "Метод ПрочитатьНажатие не должен требовать параметров");
+ юТест.ПроверитьИстину(СтрокаМетода.ЭтоФункция, "Метод ПрочитатьНажатие должен быть функцией");
+ КонецЕсли;
+ КонецЦикла;
+
+ юТест.ПроверитьИстину(Найден, "Метод Консоль.ПрочитатьНажатие должен быть доступен");
+
+КонецПроцедуры