diff --git a/tgcli.Tests/PagedMessageInput.cs b/tgcli.Tests/PagedMessageInput.cs index f420297..5b440f8 100644 --- a/tgcli.Tests/PagedMessageInput.cs +++ b/tgcli.Tests/PagedMessageInput.cs @@ -10,45 +10,43 @@ public class PagedMessageInput { [InlineData(218)] [InlineData(219)] [InlineData(289)] - public void Test1(int offset) { + public void TestGetPagedMessageInput(int offset) { const string testMessage = "this is a test string please ignore 1, this is a test string please ignore 2, this is a test string please ignore 3, this is a test string please ignore 4, this is a test string please ignore 5, this is a test string please ignore 6, this is a test string please ignore 7, this is a test str."; const int testBufferWidth = 80; - Assert.Equal(ReferenceMethods.GetViewIntoMessageBuffer(testMessage, offset, testBufferWidth), - Util.GetViewIntoMessageBuffer(testMessage, offset, testBufferWidth)); + Assert.Equal(ReferenceMethods.GetPagedMessageInputLine(testMessage, offset, testBufferWidth), Util.GetPagedMessageInputLine(testMessage, offset, testBufferWidth)); } private static class ReferenceMethods { - internal static (string messageBuffer, int relativeCursorPosition) - GetViewIntoMessageBuffer(string message, int absoluteCursorPosition, int bufferWidth) { - const int wraparoundOffsetPre = 2; // number of "untouchable" characters moving the cursor onto will cause a wrap on the right screen edge - const int wraparoundOffsetPost = 5; // number of "untouchable" characters moving the cursor onto will cause a wrap on the left screen edge + internal static (string messageBuffer, int relCursorPos) GetPagedMessageInputLine(string message, int absCursorPos, int bufferWidth) { + const int wrapOffsetPre = 2; // number of "untouchable" characters moving the cursor onto will cause a wrap on the right screen edge + const int wrapOffsetPost = 5; // number of "untouchable" characters moving the cursor onto will cause a wrap on the left screen edge - const int wraparoundOffsetPreW = wraparoundOffsetPre + 1; // offset + 1 (character on the edge), for easier calculations - const int wraparoundOffsetPostW = wraparoundOffsetPost + 1; // offset + 1 (character on the edge), for easier calculations + const int wrapOffsetPreI = wrapOffsetPre + 1; // offset + 1 (character on the edge), for easier calculations + const int wrapOffsetPostI = wrapOffsetPost + 1; // offset + 1 (character on the edge), for easier calculations - if (absoluteCursorPosition > message.Length) - throw new ArgumentOutOfRangeException(); + if (absCursorPos > message.Length) + throw new ArgumentOutOfRangeException(nameof(absCursorPos), "Cursor position exceeds message length"); if (message.Length < bufferWidth) - return (message, absoluteCursorPosition); + return (message, absCursorPos); - if (absoluteCursorPosition < bufferWidth - wraparoundOffsetPre - 1) - return (Util.TruncateString(message, bufferWidth, $"{Util.Ansi.Inverse}>{Util.Ansi.InverseOff}"), absoluteCursorPosition); + if (absCursorPos < bufferWidth - wrapOffsetPre - 1) + return (Util.TruncateString(message, bufferWidth, $"{Util.Ansi.Inverse}>{Util.Ansi.InverseOff}"), absCursorPos); // now we can be sure the message needs at least one wrap // first wrap // get rid of the content shown on the zeroth wrap, which is buf width minus wraparoundPreW (respects > character on screen edge) - var finalMessage = message[(bufferWidth - wraparoundOffsetPreW - wraparoundOffsetPost)..]; - var finalCursorPos = absoluteCursorPosition - bufferWidth + wraparoundOffsetPreW + wraparoundOffsetPostW; + var finalMessage = message[(bufferWidth - wrapOffsetPreI - wrapOffsetPost)..]; + var finalCursorPos = absCursorPos - bufferWidth + wrapOffsetPreI + wrapOffsetPostI; // successive wraps // repeat above steps (but counting the new < character) until the string fits into the buffer // it fits into the buffer when cursorPos >= bufferwidth minus wraparound (this time respecting > character absent on first wrap) - while (finalCursorPos >= bufferWidth - wraparoundOffsetPreW) { - finalMessage = finalMessage[(bufferWidth - wraparoundOffsetPreW - wraparoundOffsetPostW)..]; - finalCursorPos = finalCursorPos - bufferWidth + wraparoundOffsetPreW + wraparoundOffsetPostW; + while (finalCursorPos >= bufferWidth - wrapOffsetPreI) { + finalMessage = finalMessage[(bufferWidth - wrapOffsetPreI - wrapOffsetPostI)..]; + finalCursorPos = finalCursorPos - bufferWidth + wrapOffsetPreI + wrapOffsetPostI; } finalMessage = Util.TruncateString(finalMessage, bufferWidth - 1, $"{Util.Ansi.Inverse}>{Util.Ansi.InverseOff}"); diff --git a/tgcli/Util.cs b/tgcli/Util.cs index 1fb1165..ca86d14 100644 --- a/tgcli/Util.cs +++ b/tgcli/Util.cs @@ -219,7 +219,7 @@ public static class Util { } else { if (key.Key == ConsoleKey.Backspace && pass.Length > 0) { - pass = pass.Substring(0, (pass.Length - 1)); + pass = pass[..^1]; Console.Write("\b \b"); } else if (key.Key == ConsoleKey.Enter) { @@ -238,9 +238,7 @@ public static class Util { Emojis.ForEach(em => message = message.Replace(em.Item1, em.Item2)); client.ExecuteAsync(new SendMessage { - ChatId = chatId, - InputMessageContent = new InputMessageContent.InputMessageText { Text = new FormattedText { Text = message } }, - ReplyToMessageId = replyTo, + ChatId = chatId, InputMessageContent = new InputMessageContent.InputMessageText { Text = new FormattedText { Text = message } }, ReplyToMessageId = replyTo, }); currentUserRead = false; } @@ -271,8 +269,7 @@ public static class Util { var results = client.ExecuteAsync(new SearchChats { Query = query, Limit = 5 }).Result; return query.StartsWith("@") - ? results.ChatIds.First(p => GetChat(p).Type is ChatType.ChatTypePrivate type - && GetUser(type.UserId).Usernames.ActiveUsernames.Contains(query[1..])) + ? results.ChatIds.First(p => GetChat(p).Type is ChatType.ChatTypePrivate type && GetUser(type.UserId).Usernames.ActiveUsernames.Contains(query[1..])) : results.ChatIds.First(p => !(GetChat(p).Type is ChatType.ChatTypeSecret)); } catch { @@ -369,31 +366,31 @@ public static class Util { public static string TruncateString(string input, int maxLen, string truncateMarker = "~") { if (maxLen < 2) maxLen = 2; - return input.Length <= maxLen ? input : input.Substring(0, maxLen - 1) + truncateMarker; + return input.Length <= maxLen ? input : input[..(maxLen - 1)] + truncateMarker; } - public static (string messageBuffer, int relativeCursorPosition) GetViewIntoMessageBuffer(string message, int absoluteCursorPosition, int bufferWidth) { - const int wraparoundOffsetPre = 2; // number of "untouchable" characters moving the cursor onto will cause a wrap on the right screen edge - const int wraparoundOffsetPost = 5; // number of "untouchable" characters moving the cursor onto will cause a wrap on the left screen edge + public static (string messageBuffer, int relCursorPos) GetPagedMessageInputLine(string message, int absCursorPos, int bufferWidth) { + const int wrapdOffsetPre = 2; // number of "untouchable" characters moving the cursor onto will cause a wrap on the right screen edge + const int wrapOffsetPost = 5; // number of "untouchable" characters moving the cursor onto will cause a wrap on the left screen edge - const int wraparoundOffsetPreW = wraparoundOffsetPre + 1; // offset + 1 (character on the edge), for easier calculations - const int wraparoundOffsetPostW = wraparoundOffsetPost + 1; // offset + 1 (character on the edge), for easier calculations + const int wrapOffsetPreI = wrapdOffsetPre + 1; // offset + 1 (indicator on the edge), for easier calculations + const int wrapOffsetPostI = wrapOffsetPost + 1; // offset + 1 (indicator on the edge), for easier calculations - if (absoluteCursorPosition > message.Length) - throw new ArgumentOutOfRangeException(nameof(absoluteCursorPosition), "Cursor position exceeds message length"); + if (absCursorPos > message.Length) + throw new ArgumentOutOfRangeException(nameof(absCursorPos), "Cursor position exceeds message length"); - if (message.Length < bufferWidth) // entire message fits in buffer - return (message, absoluteCursorPosition); // return input as-is + if (message.Length < bufferWidth) // entire message fits in buffer + return (message, absCursorPos); // return input as-is - if (absoluteCursorPosition < bufferWidth - wraparoundOffsetPre - 1) // message is longer than buffer but we're on the first page - return (TruncateString(message, bufferWidth, $"{Ansi.Inverse}>{Ansi.InverseOff}"), absoluteCursorPosition); // return input as-is but truncated and with a > indicator + if (absCursorPos < bufferWidth - wrapdOffsetPre - 1) // message is longer than buffer but we're on the first page + return (TruncateString(message, bufferWidth, $"{Ansi.Inverse}>{Ansi.InverseOff}"), absCursorPos); // return input as-is but truncated and with a > indicator - var wraparounds = (absoluteCursorPosition - wraparoundOffsetPostW) / (bufferWidth - wraparoundOffsetPreW - wraparoundOffsetPostW); - var finalCursorPos = absoluteCursorPosition - bufferWidth + wraparoundOffsetPreW + wraparoundOffsetPostW * wraparounds; - finalCursorPos %= bufferWidth - wraparoundOffsetPreW; + var wraps = (absCursorPos - wrapOffsetPostI) / (bufferWidth - wrapOffsetPreI - wrapOffsetPostI); // black magic + var finalCursorPos = absCursorPos - bufferWidth + wrapOffsetPreI + wrapOffsetPostI * wraps; // respect the special case of the first page & add one post offset per wrap + finalCursorPos %= bufferWidth - wrapOffsetPreI; // make sure the final cursor position is within the acceptable range (between zero and bufWidth - wrapOffsetPreI) - var messageOffset = (bufferWidth - wraparoundOffsetPreW - wraparoundOffsetPostW) * wraparounds + 1; // +1 to account for the first wrap not having a < indicator - var finalMessage = message[messageOffset..]; + var messageOffset = (bufferWidth - wrapOffsetPreI - wrapOffsetPostI) * wraps + 1; // +1 to account for the first wrap not having a < indicator + var finalMessage = message[messageOffset..]; // we only care about the message starting from the current page finalMessage = TruncateString(finalMessage, bufferWidth - 1, $"{Ansi.Inverse}>{Ansi.InverseOff}"); // replace the last character with a > indicator if required @@ -413,8 +410,8 @@ public static class Util { }; public static void InsertToInputLine(string strToInsert) { - var part1 = currentInputLine.Substring(0, currentInputPos); - var part2 = currentInputLine.Substring(currentInputPos); + var part1 = currentInputLine[..currentInputPos]; + var part2 = currentInputLine[currentInputPos..]; currentInputLine = part1 + strToInsert + part2; currentInputPos += strToInsert.Length; } @@ -425,14 +422,14 @@ public static class Util { } public static void RemoveFromInputLine(bool word = false) { - var part1 = currentInputLine.Substring(0, currentInputPos); + var part1 = currentInputLine[..currentInputPos]; var oldlen = part1.Length; - var part2 = currentInputLine.Substring(currentInputPos); + var part2 = currentInputLine[currentInputPos..]; if (word) { var lastIndex = part1.TrimEnd().LastIndexOf(" ", StringComparison.Ordinal); if (lastIndex < 0) lastIndex = 0; - part1 = part1.Substring(0, lastIndex); + part1 = part1[..lastIndex]; if (lastIndex != 0) part1 += " "; //if (part1.EndsWith("⏎")) @@ -445,18 +442,18 @@ public static class Util { return; } - currentInputLine = part1.Substring(0, part1.Length - 1) + part2; + currentInputLine = part1[..^1] + part2; currentInputPos--; } public static void RemoveFromInputLineForward(bool word = false) { - var part1 = currentInputLine.Substring(0, currentInputPos); - var part2 = currentInputLine.Substring(currentInputPos).TrimStart(); + var part1 = currentInputLine[..currentInputPos]; + var part2 = currentInputLine[currentInputPos..].TrimStart(); if (word) { var index = part2.IndexOf(" ", StringComparison.Ordinal); if (index < 0) index = part2.Length - 1; - part2 = part2.Substring(index + 1); + part2 = part2[(index + 1)..]; if (index != 0) part2 = " " + part2; //if (part2.StartsWith("⏎")) @@ -466,7 +463,7 @@ public static class Util { return; } - currentInputLine = part1 + part2.Substring(1); + currentInputLine = part1 + part2[1..]; } public static readonly List SpecialKeys = new() { diff --git a/tgcli/tgcli.cs b/tgcli/tgcli.cs index 8c755fc..619b588 100644 --- a/tgcli/tgcli.cs +++ b/tgcli/tgcli.cs @@ -190,8 +190,8 @@ public static class tgcli { else output += "]"; output += " > "; - var prefixlen = GetActualStringWidth(output); - var inputLine = GetViewIntoMessageBuffer(currentInputLine, currentInputPos, Console.LargestWindowWidth - prefixlen); + var prefixlen = GetActualStringWidth(output); + var inputLine = GetPagedMessageInputLine(currentInputLine, currentInputPos, Console.LargestWindowWidth - prefixlen); output += inputLine.messageBuffer; ClearCurrentConsoleLine(); @@ -200,7 +200,7 @@ public static class tgcli { Console.Write("\a"); //ring terminal bell messageQueue.Clear(); Console.Write(output); - Console.Write($"\u001b[{inputLine.relativeCursorPosition + prefixlen + 1}G"); + Console.Write($"\u001b[{inputLine.relCursorPos + prefixlen + 1}G"); } } @@ -212,7 +212,7 @@ public static class tgcli { ScreenUpdate(); break; case ConsoleKey.Enter when currentInputLine.StartsWith("/"): { - var command = currentInputLine.Substring(1); + var command = currentInputLine[1..]; SetInputLine(""); HandleCommand(command); ScreenUpdate(); @@ -257,7 +257,7 @@ public static class tgcli { if (currentInputPos == 0) break; - var part1 = currentInputLine.Substring(0, currentInputPos); + var part1 = currentInputLine[..currentInputPos]; var lastIndex = part1.TrimEnd().LastIndexOf(" ", StringComparison.Ordinal); if (lastIndex < 0) lastIndex = 0;