Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use span-based dictionary lookup in System.Console #103742

Merged
merged 2 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/libraries/System.Console/src/System/IO/KeyParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,15 @@ private static bool TryParseTerminalInputSequence(char[] buffer, TerminalFormatS
return false;
}

Dictionary<ReadOnlyMemory<char>, ConsoleKeyInfo> terminfoDb = terminalFormatStrings.KeyFormatToConsoleKey; // the most important source of truth
Dictionary<string, ConsoleKeyInfo>.AlternateLookup<ReadOnlySpan<char>> terminfoDb = // the most important source of truth
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we be caching the alternate lookup struct somewhere? This is incurring a runtime type test against a generic interface which presumably would add up assuming this is a performance sensitive application.

Copy link
Member Author

@stephentoub stephentoub Jun 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't seem worth trying to avoid the overhead of a cast as part of the handling of a user's key strokes?

terminalFormatStrings.KeyFormatToConsoleKey.GetAlternateLookup<string, ConsoleKeyInfo, ReadOnlySpan<char>>();
ConsoleModifiers modifiers = ConsoleModifiers.None;
ConsoleKey key;

// Is it a three character sequence? (examples: '^[[H' (Home), '^[OP' (F1))
if (input[1] == 'O' || char.IsAsciiLetter(input[2]) || input.Length == MinimalSequenceLength)
{
if (!terminfoDb.TryGetValue(buffer.AsMemory(startIndex, MinimalSequenceLength), out parsed))
if (!terminfoDb.TryGetValue(buffer.AsSpan(startIndex, MinimalSequenceLength), out parsed))
{
// All terminals which use "^[O{letter}" escape sequences don't define conflicting mappings.
// Example: ^[OH either means Home or simply is not used by given terminal.
Expand Down Expand Up @@ -103,7 +104,7 @@ private static bool TryParseTerminalInputSequence(char[] buffer, TerminalFormatS
// Is it a four character sequence used by Linux Console or PuTTy configured to emulate it? (examples: '^[[[A' (F1), '^[[[B' (F2))
if (input[1] == '[' && input[2] == '[' && char.IsBetween(input[3], 'A', 'E'))
{
if (!terminfoDb.TryGetValue(buffer.AsMemory(startIndex, 4), out parsed))
if (!terminfoDb.TryGetValue(buffer.AsSpan(startIndex, 4), out parsed))
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
{
parsed = new ConsoleKeyInfo(default, ConsoleKey.F1 + input[3] - 'A', false, false, false);
}
Expand All @@ -128,7 +129,7 @@ private static bool TryParseTerminalInputSequence(char[] buffer, TerminalFormatS
{
// it's a VT Sequence like ^[[11~ or rxvt like ^[[11^
int sequenceLength = SequencePrefixLength + digitCount + 1;
if (!terminfoDb.TryGetValue(buffer.AsMemory(startIndex, sequenceLength), out parsed))
if (!terminfoDb.TryGetValue(buffer.AsSpan(startIndex, sequenceLength), out parsed))
{
key = MapEscapeSequenceNumber(byte.Parse(input.Slice(SequencePrefixLength, digitCount)));
if (key == default)
Expand Down
22 changes: 6 additions & 16 deletions src/libraries/System.Console/src/System/TerminalFormatStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ internal sealed class TerminalFormatStrings
/// The dictionary of keystring to ConsoleKeyInfo.
/// Only some members of the ConsoleKeyInfo are used; in particular, the actual char is ignored.
/// </summary>
public readonly Dictionary<ReadOnlyMemory<char>, ConsoleKeyInfo> KeyFormatToConsoleKey =
new Dictionary<ReadOnlyMemory<char>, ConsoleKeyInfo>(new ReadOnlyMemoryContentComparer());
public readonly Dictionary<string, ConsoleKeyInfo> KeyFormatToConsoleKey = new();
stephentoub marked this conversation as resolved.
Show resolved Hide resolved

/// <summary> Max key length </summary>
public readonly int MaxKeyFormatLength;
Expand Down Expand Up @@ -164,7 +163,7 @@ public TerminalFormatStrings(TermInfo.Database? db)
MaxKeyFormatLength = int.MinValue;
MinKeyFormatLength = int.MaxValue;

foreach (KeyValuePair<ReadOnlyMemory<char>, ConsoleKeyInfo> entry in KeyFormatToConsoleKey)
foreach (KeyValuePair<string, ConsoleKeyInfo> entry in KeyFormatToConsoleKey)
{
if (entry.Key.Length > MaxKeyFormatLength)
{
Expand Down Expand Up @@ -229,8 +228,8 @@ private void AddKey(TermInfo.Database db, TermInfo.WellKnownStrings keyId, Conso

private void AddKey(TermInfo.Database db, TermInfo.WellKnownStrings keyId, ConsoleKey key, bool shift, bool alt, bool control)
{
ReadOnlyMemory<char> keyFormat = db.GetString(keyId).AsMemory();
if (!keyFormat.IsEmpty)
string? keyFormat = db.GetString(keyId);
if (!string.IsNullOrEmpty(keyFormat))
KeyFormatToConsoleKey[keyFormat] = new ConsoleKeyInfo(key == ConsoleKey.Enter ? '\r' : '\0', key, shift, alt, control);
}

Expand All @@ -248,17 +247,8 @@ private void AddPrefixKey(TermInfo.Database db, string extendedNamePrefix, Conso

private void AddKey(TermInfo.Database db, string extendedName, ConsoleKey key, bool shift, bool alt, bool control)
{
ReadOnlyMemory<char> keyFormat = db.GetExtendedString(extendedName).AsMemory();
if (!keyFormat.IsEmpty)
string? keyFormat = db.GetExtendedString(extendedName);
if (!string.IsNullOrEmpty(keyFormat))
KeyFormatToConsoleKey[keyFormat] = new ConsoleKeyInfo('\0', key, shift, alt, control);
}

private sealed class ReadOnlyMemoryContentComparer : IEqualityComparer<ReadOnlyMemory<char>>
{
public bool Equals(ReadOnlyMemory<char> x, ReadOnlyMemory<char> y) =>
x.Span.SequenceEqual(y.Span);

public int GetHashCode(ReadOnlyMemory<char> obj) =>
string.GetHashCode(obj.Span);
}
}
Loading