@@ -304,16 +304,15 @@ static inline int fb_isdigit(const char c)
304304 return isdigit((int)(UCHAR)c);
305305}
306306
307+ typedef Array<char> CharBuffer;
308+ GlobalPtr<CharBuffer> charBuffer;
307309
308310#ifdef WIN_NT
309- // This function is highly based on code written by https://github.com/xenu
310- // He permitted our usage here: https://github.com/Perl/perl5/pull/18702#issuecomment-1156050577
311- static int win32ReadConsole(FILE* file, char* buffer, size_t bufferSize)
311+
312+ GlobalPtr<Array<WCHAR>> wideBuffer;
313+
314+ static int win32ReadConsole(FILE* file, CharBuffer& mbBuffer)
312315{
313- // This function is a workaround for a bug in Windows:
314- // https://github.com/microsoft/terminal/issues/4551
315- // tl;dr: ReadFile() and ReadConsoleA() return garbage when reading
316- // non-ASCII characters from the console with the 65001 codepage.
317316 auto handle = (HANDLE) _get_osfhandle(fileno(file));
318317
319318 if (handle == INVALID_HANDLE_VALUE)
@@ -322,94 +321,49 @@ static int win32ReadConsole(FILE* file, char* buffer, size_t bufferSize)
322321 return -1;
323322 }
324323
325- DWORD mode ;
326- if (! GetConsoleMode(handle, &mode ))
324+ DWORD oldMode, newMode ;
325+ if (GetConsoleMode(handle, &oldMode ))
327326 {
328- fb_assert(false);
329- return -1;
327+ newMode = oldMode | (ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
328+ if (newMode != oldMode)
329+ SetConsoleMode(handle, oldMode);
330330 }
331-
332- size_t leftToRead = bufferSize;
333-
334- while (leftToRead)
331+ else
335332 {
336- // The purpose of convertedBuf is to preserve partial UTF-8 (or of any
337- // other multibyte encoding) code points between read() calls. Since
338- // there's only one console, the buffer is global. It's needed because
339- // ReadConsoleW() returns a string of UTF-16 code units and its result,
340- // after conversion to the current console codepage, may not fit in the
341- // return buffer.
342- //
343- // The buffer's size is 8 because it will contain at most two UTF-8 code
344- // points.
345- static char convertedBuf[8];
346- static size_t convertedBufLen = 0;
347-
348- if (convertedBufLen)
349- {
350- bool newline = false;
351- const size_t toWrite = MIN(convertedBufLen, leftToRead);
352-
353- // Don't read anything if the *first* character is ^Z and
354- // ENABLE_PROCESSED_INPUT is enabled. On some versions of Windows,
355- // ReadFile() ignores ENABLE_PROCESSED_INPUT, but apparently it's a
356- // bug: https://github.com/microsoft/terminal/issues/4958
357- if (leftToRead == bufferSize && (mode & ENABLE_PROCESSED_INPUT) && convertedBuf[0] == 0x1A)
358- break;
359-
360- // Are we returning a newline?
361- if (memchr(convertedBuf, '\n', toWrite) != 0)
362- newline = true;
363-
364- memcpy(buffer, convertedBuf, toWrite);
365- buffer += toWrite;
366-
367- // If there's anything left in convertedBuf, move it to the beginning of the buffer.
368- convertedBufLen -= toWrite;
369-
370- if (convertedBufLen)
371- memmove(convertedBuf, convertedBuf + toWrite, convertedBufLen);
372-
373- leftToRead -= toWrite;
374-
375- // With ENABLE_LINE_INPUT enabled, we stop reading after the first
376- // newline, otherwise we stop reading after the first character.
377- if (!leftToRead || newline || (mode & ENABLE_LINE_INPUT) == 0)
378- break;
379- }
333+ fb_assert(false);
334+ newMode = oldMode = 0;
335+ }
380336
381- WCHAR wideBuf[2];
382- DWORD charsRead;
337+ Cleanup consoleMode([&] {
338+ if (oldMode != newMode)
339+ SetConsoleMode(handle, oldMode);
340+ });
383341
384- // Reading one code unit at a time is inefficient, but since this code
385- // is used only for the interactive console, that shouldn't matter.
386- if (!ReadConsoleW(handle, wideBuf, 1, &charsRead, 0))
387- return -1;
388-
389- if (!charsRead)
390- break;
342+ const size_t MAX_LINE_LENGTH = MAX_USHORT;
343+ WCHAR* wideBuf = wideBuffer->getBuffer(MAX_LINE_LENGTH, false);
391344
392- DWORD wideBufLen = 1;
345+ DWORD charsRead;
346+ if (!ReadConsoleW(handle, wideBuf, MAX_LINE_LENGTH, &charsRead, NULL))
347+ {
348+ fb_assert(false);
349+ return -1;
350+ }
393351
394- if (wideBuf[0] >= 0xD800 && wideBuf[0] <= 0xDBFF)
395- {
396- // High surrogate, read one more code unit.
397- if (!ReadConsoleW(handle, wideBuf + 1, 1, &charsRead, 0))
398- return -1;
352+ if (!charsRead)
353+ return 0;
399354
400- if (charsRead)
401- ++wideBufLen;
402- }
355+ int mbLength = WideCharToMultiByte(GetConsoleCP(), 0, wideBuf, charsRead,
356+ NULL, 0, NULL, NULL);
403357
404- convertedBufLen = WideCharToMultiByte(GetConsoleCP(), 0, wideBuf, wideBufLen,
405- convertedBuf, sizeof(convertedBuf), NULL, NULL) ;
358+ if (!mbLength)
359+ return -1 ;
406360
407- if (!convertedBufLen)
408- return -1;
409- }
361+ mbLength = WideCharToMultiByte(GetConsoleCP(), 0, wideBuf, charsRead,
362+ mbBuffer.getBuffer(mbLength, false), mbLength, NULL, NULL);
410363
411- return bufferSize - leftToRead ;
364+ return mbLength ;
412365}
366+
413367#endif
414368
415369
@@ -1095,16 +1049,20 @@ static void readNextInputLine(const char* prompt)
10951049 do
10961050 {
10971051 // Read the line
1098- char buffer[MAX_USHORT] ;
1052+ char* buffer;
10991053 int lineSize;
11001054
11011055#ifdef WIN_NT
11021056 if (!Input_file && isatty(fileno(Filelist->Ifp().indev_fpointer)))
1103- lineSize = win32ReadConsole(Filelist->Ifp().indev_fpointer, buffer, sizeof(buffer));
1057+ {
1058+ lineSize = win32ReadConsole(Filelist->Ifp().indev_fpointer, charBuffer);
1059+ buffer = charBuffer->begin();
1060+ }
11041061 else
11051062#endif
11061063 {
1107- if (fgets(buffer, sizeof(buffer), Filelist->Ifp().indev_fpointer) != NULL)
1064+ buffer = charBuffer->getBuffer(MAX_USHORT);
1065+ if (fgets(buffer, charBuffer->getCapacity(), Filelist->Ifp().indev_fpointer) != NULL)
11081066 lineSize = strlen(buffer);
11091067 else
11101068 lineSize = -1;
0 commit comments