This commit is contained in:
Laura Hausmann 2023-01-23 17:55:50 +01:00
parent 65d8141d18
commit f3372f39e4
Signed by: zotan
GPG key ID: D044E84C5BE01605
3 changed files with 52 additions and 57 deletions

View file

@ -10,45 +10,43 @@ public class PagedMessageInput {
[InlineData(218)] [InlineData(218)]
[InlineData(219)] [InlineData(219)]
[InlineData(289)] [InlineData(289)]
public void Test1(int offset) { public void TestGetPagedMessageInput(int offset) {
const string testMessage = 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."; "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; const int testBufferWidth = 80;
Assert.Equal(ReferenceMethods.GetViewIntoMessageBuffer(testMessage, offset, testBufferWidth), Assert.Equal(ReferenceMethods.GetPagedMessageInputLine(testMessage, offset, testBufferWidth), Util.GetPagedMessageInputLine(testMessage, offset, testBufferWidth));
Util.GetViewIntoMessageBuffer(testMessage, offset, testBufferWidth));
} }
private static class ReferenceMethods { private static class ReferenceMethods {
internal static (string messageBuffer, int relativeCursorPosition) internal static (string messageBuffer, int relCursorPos) GetPagedMessageInputLine(string message, int absCursorPos, int bufferWidth) {
GetViewIntoMessageBuffer(string message, int absoluteCursorPosition, 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 wraparoundOffsetPre = 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 wraparoundOffsetPost = 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 wrapOffsetPreI = wrapOffsetPre + 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 wrapOffsetPostI = wrapOffsetPost + 1; // offset + 1 (character on the edge), for easier calculations
if (absoluteCursorPosition > message.Length) if (absCursorPos > message.Length)
throw new ArgumentOutOfRangeException(); throw new ArgumentOutOfRangeException(nameof(absCursorPos), "Cursor position exceeds message length");
if (message.Length < bufferWidth) if (message.Length < bufferWidth)
return (message, absoluteCursorPosition); return (message, absCursorPos);
if (absoluteCursorPosition < bufferWidth - wraparoundOffsetPre - 1) if (absCursorPos < bufferWidth - wrapOffsetPre - 1)
return (Util.TruncateString(message, bufferWidth, $"{Util.Ansi.Inverse}>{Util.Ansi.InverseOff}"), absoluteCursorPosition); return (Util.TruncateString(message, bufferWidth, $"{Util.Ansi.Inverse}>{Util.Ansi.InverseOff}"), absCursorPos);
// now we can be sure the message needs at least one wrap // now we can be sure the message needs at least one wrap
// first wrap // first wrap
// get rid of the content shown on the zeroth wrap, which is buf width minus wraparoundPreW (respects > character on screen edge) // 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 finalMessage = message[(bufferWidth - wrapOffsetPreI - wrapOffsetPost)..];
var finalCursorPos = absoluteCursorPosition - bufferWidth + wraparoundOffsetPreW + wraparoundOffsetPostW; var finalCursorPos = absCursorPos - bufferWidth + wrapOffsetPreI + wrapOffsetPostI;
// successive wraps // successive wraps
// repeat above steps (but counting the new < character) until the string fits into the buffer // 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) // it fits into the buffer when cursorPos >= bufferwidth minus wraparound (this time respecting > character absent on first wrap)
while (finalCursorPos >= bufferWidth - wraparoundOffsetPreW) { while (finalCursorPos >= bufferWidth - wrapOffsetPreI) {
finalMessage = finalMessage[(bufferWidth - wraparoundOffsetPreW - wraparoundOffsetPostW)..]; finalMessage = finalMessage[(bufferWidth - wrapOffsetPreI - wrapOffsetPostI)..];
finalCursorPos = finalCursorPos - bufferWidth + wraparoundOffsetPreW + wraparoundOffsetPostW; finalCursorPos = finalCursorPos - bufferWidth + wrapOffsetPreI + wrapOffsetPostI;
} }
finalMessage = Util.TruncateString(finalMessage, bufferWidth - 1, $"{Util.Ansi.Inverse}>{Util.Ansi.InverseOff}"); finalMessage = Util.TruncateString(finalMessage, bufferWidth - 1, $"{Util.Ansi.Inverse}>{Util.Ansi.InverseOff}");

View file

@ -219,7 +219,7 @@ public static class Util {
} }
else { else {
if (key.Key == ConsoleKey.Backspace && pass.Length > 0) { if (key.Key == ConsoleKey.Backspace && pass.Length > 0) {
pass = pass.Substring(0, (pass.Length - 1)); pass = pass[..^1];
Console.Write("\b \b"); Console.Write("\b \b");
} }
else if (key.Key == ConsoleKey.Enter) { else if (key.Key == ConsoleKey.Enter) {
@ -238,9 +238,7 @@ public static class Util {
Emojis.ForEach(em => message = message.Replace(em.Item1, em.Item2)); Emojis.ForEach(em => message = message.Replace(em.Item1, em.Item2));
client.ExecuteAsync(new SendMessage { client.ExecuteAsync(new SendMessage {
ChatId = chatId, ChatId = chatId, InputMessageContent = new InputMessageContent.InputMessageText { Text = new FormattedText { Text = message } }, ReplyToMessageId = replyTo,
InputMessageContent = new InputMessageContent.InputMessageText { Text = new FormattedText { Text = message } },
ReplyToMessageId = replyTo,
}); });
currentUserRead = false; currentUserRead = false;
} }
@ -271,8 +269,7 @@ public static class Util {
var results = client.ExecuteAsync(new SearchChats { Query = query, Limit = 5 }).Result; var results = client.ExecuteAsync(new SearchChats { Query = query, Limit = 5 }).Result;
return query.StartsWith("@") return query.StartsWith("@")
? results.ChatIds.First(p => GetChat(p).Type is ChatType.ChatTypePrivate type ? results.ChatIds.First(p => GetChat(p).Type is ChatType.ChatTypePrivate type && GetUser(type.UserId).Usernames.ActiveUsernames.Contains(query[1..]))
&& GetUser(type.UserId).Usernames.ActiveUsernames.Contains(query[1..]))
: results.ChatIds.First(p => !(GetChat(p).Type is ChatType.ChatTypeSecret)); : results.ChatIds.First(p => !(GetChat(p).Type is ChatType.ChatTypeSecret));
} }
catch { catch {
@ -369,31 +366,31 @@ public static class Util {
public static string TruncateString(string input, int maxLen, string truncateMarker = "~") { public static string TruncateString(string input, int maxLen, string truncateMarker = "~") {
if (maxLen < 2) if (maxLen < 2)
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) { public static (string messageBuffer, int relCursorPos) GetPagedMessageInputLine(string message, int absCursorPos, 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 wrapdOffsetPre = 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 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 wrapOffsetPreI = wrapdOffsetPre + 1; // offset + 1 (indicator on the edge), for easier calculations
const int wraparoundOffsetPostW = wraparoundOffsetPost + 1; // offset + 1 (character on the edge), for easier calculations const int wrapOffsetPostI = wrapOffsetPost + 1; // offset + 1 (indicator on the edge), for easier calculations
if (absoluteCursorPosition > message.Length) if (absCursorPos > message.Length)
throw new ArgumentOutOfRangeException(nameof(absoluteCursorPosition), "Cursor position exceeds message length"); throw new ArgumentOutOfRangeException(nameof(absCursorPos), "Cursor position exceeds message length");
if (message.Length < bufferWidth) // entire message fits in buffer if (message.Length < bufferWidth) // entire message fits in buffer
return (message, absoluteCursorPosition); // return input as-is return (message, absCursorPos); // return input as-is
if (absoluteCursorPosition < bufferWidth - wraparoundOffsetPre - 1) // message is longer than buffer but we're on the first page 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}"), absoluteCursorPosition); // return input as-is but truncated and with a > indicator 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 wraps = (absCursorPos - wrapOffsetPostI) / (bufferWidth - wrapOffsetPreI - wrapOffsetPostI); // black magic
var finalCursorPos = absoluteCursorPosition - bufferWidth + wraparoundOffsetPreW + wraparoundOffsetPostW * wraparounds; var finalCursorPos = absCursorPos - bufferWidth + wrapOffsetPreI + wrapOffsetPostI * wraps; // respect the special case of the first page & add one post offset per wrap
finalCursorPos %= bufferWidth - wraparoundOffsetPreW; 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 messageOffset = (bufferWidth - wrapOffsetPreI - wrapOffsetPostI) * wraps + 1; // +1 to account for the first wrap not having a < indicator
var finalMessage = message[messageOffset..]; 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 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) { public static void InsertToInputLine(string strToInsert) {
var part1 = currentInputLine.Substring(0, currentInputPos); var part1 = currentInputLine[..currentInputPos];
var part2 = currentInputLine.Substring(currentInputPos); var part2 = currentInputLine[currentInputPos..];
currentInputLine = part1 + strToInsert + part2; currentInputLine = part1 + strToInsert + part2;
currentInputPos += strToInsert.Length; currentInputPos += strToInsert.Length;
} }
@ -425,14 +422,14 @@ public static class Util {
} }
public static void RemoveFromInputLine(bool word = false) { public static void RemoveFromInputLine(bool word = false) {
var part1 = currentInputLine.Substring(0, currentInputPos); var part1 = currentInputLine[..currentInputPos];
var oldlen = part1.Length; var oldlen = part1.Length;
var part2 = currentInputLine.Substring(currentInputPos); var part2 = currentInputLine[currentInputPos..];
if (word) { if (word) {
var lastIndex = part1.TrimEnd().LastIndexOf(" ", StringComparison.Ordinal); var lastIndex = part1.TrimEnd().LastIndexOf(" ", StringComparison.Ordinal);
if (lastIndex < 0) if (lastIndex < 0)
lastIndex = 0; lastIndex = 0;
part1 = part1.Substring(0, lastIndex); part1 = part1[..lastIndex];
if (lastIndex != 0) if (lastIndex != 0)
part1 += " "; part1 += " ";
//if (part1.EndsWith("⏎")) //if (part1.EndsWith("⏎"))
@ -445,18 +442,18 @@ public static class Util {
return; return;
} }
currentInputLine = part1.Substring(0, part1.Length - 1) + part2; currentInputLine = part1[..^1] + part2;
currentInputPos--; currentInputPos--;
} }
public static void RemoveFromInputLineForward(bool word = false) { public static void RemoveFromInputLineForward(bool word = false) {
var part1 = currentInputLine.Substring(0, currentInputPos); var part1 = currentInputLine[..currentInputPos];
var part2 = currentInputLine.Substring(currentInputPos).TrimStart(); var part2 = currentInputLine[currentInputPos..].TrimStart();
if (word) { if (word) {
var index = part2.IndexOf(" ", StringComparison.Ordinal); var index = part2.IndexOf(" ", StringComparison.Ordinal);
if (index < 0) if (index < 0)
index = part2.Length - 1; index = part2.Length - 1;
part2 = part2.Substring(index + 1); part2 = part2[(index + 1)..];
if (index != 0) if (index != 0)
part2 = " " + part2; part2 = " " + part2;
//if (part2.StartsWith("⏎")) //if (part2.StartsWith("⏎"))
@ -466,7 +463,7 @@ public static class Util {
return; return;
} }
currentInputLine = part1 + part2.Substring(1); currentInputLine = part1 + part2[1..];
} }
public static readonly List<ConsoleKey> SpecialKeys = new() { public static readonly List<ConsoleKey> SpecialKeys = new() {

View file

@ -190,8 +190,8 @@ public static class tgcli {
else else
output += "]"; output += "]";
output += " > "; output += " > ";
var prefixlen = GetActualStringWidth(output); var prefixlen = GetActualStringWidth(output);
var inputLine = GetViewIntoMessageBuffer(currentInputLine, currentInputPos, Console.LargestWindowWidth - prefixlen); var inputLine = GetPagedMessageInputLine(currentInputLine, currentInputPos, Console.LargestWindowWidth - prefixlen);
output += inputLine.messageBuffer; output += inputLine.messageBuffer;
ClearCurrentConsoleLine(); ClearCurrentConsoleLine();
@ -200,7 +200,7 @@ public static class tgcli {
Console.Write("\a"); //ring terminal bell Console.Write("\a"); //ring terminal bell
messageQueue.Clear(); messageQueue.Clear();
Console.Write(output); 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(); ScreenUpdate();
break; break;
case ConsoleKey.Enter when currentInputLine.StartsWith("/"): { case ConsoleKey.Enter when currentInputLine.StartsWith("/"): {
var command = currentInputLine.Substring(1); var command = currentInputLine[1..];
SetInputLine(""); SetInputLine("");
HandleCommand(command); HandleCommand(command);
ScreenUpdate(); ScreenUpdate();
@ -257,7 +257,7 @@ public static class tgcli {
if (currentInputPos == 0) if (currentInputPos == 0)
break; break;
var part1 = currentInputLine.Substring(0, currentInputPos); var part1 = currentInputLine[..currentInputPos];
var lastIndex = part1.TrimEnd().LastIndexOf(" ", StringComparison.Ordinal); var lastIndex = part1.TrimEnd().LastIndexOf(" ", StringComparison.Ordinal);
if (lastIndex < 0) if (lastIndex < 0)
lastIndex = 0; lastIndex = 0;