@@ -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,107 +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))
326+ {
327+ newMode = oldMode | (ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
328+ if (newMode != oldMode)
329+ SetConsoleMode(handle, oldMode);
330+ }
331+ else
327332 {
328333 fb_assert(false);
329- return -1 ;
334+ newMode = oldMode = 0 ;
330335 }
331336
332- const size_t MAX_LINE_LENGTH = 32765;
333- static WCHAR wideBuf[MAX_LINE_LENGTH +1];
337+ Cleanup consoleMode([&] {
338+ if (oldMode != newMode)
339+ SetConsoleMode(handle, oldMode);
340+ });
334341
335- WCHAR* pWide;
336- DWORD charsRead = 0;
337- size_t leftToRead = bufferSize;
342+ const size_t MAX_LINE_LENGTH = MAX_USHORT;
343+ WCHAR* wideBuf = wideBuffer->getBuffer(MAX_LINE_LENGTH, false);
338344
339- while (leftToRead)
345+ DWORD charsRead;
346+ if (!ReadConsoleW(handle, wideBuf, MAX_LINE_LENGTH, &charsRead, NULL))
340347 {
341- // The purpose of convertedBuf is to preserve partial UTF-8 (or of any
342- // other multibyte encoding) code points between read() calls. Since
343- // there's only one console, the buffer is global. It's needed because
344- // ReadConsoleW() returns a string of UTF-16 code units and its result,
345- // after conversion to the current console codepage, may not fit in the
346- // return buffer.
347- //
348- // The buffer's size is 8 because it will contain at most two UTF-8 code
349- // points.
350- static char convertedBuf[8];
351- static size_t convertedBufLen = 0;
352-
353- if (convertedBufLen)
354- {
355- bool newline = false;
356- const size_t toWrite = MIN(convertedBufLen, leftToRead);
357-
358- // Don't read anything if the *first* character is ^Z and
359- // ENABLE_PROCESSED_INPUT is enabled. On some versions of Windows,
360- // ReadFile() ignores ENABLE_PROCESSED_INPUT, but apparently it's a
361- // bug: https://github.com/microsoft/terminal/issues/4958
362- if (leftToRead == bufferSize && (mode & ENABLE_PROCESSED_INPUT) && convertedBuf[0] == 0x1A)
363- break;
364-
365- // Are we returning a newline?
366- if (memchr(convertedBuf, '\n', toWrite) != 0)
367- newline = true;
368-
369- memcpy(buffer, convertedBuf, toWrite);
370- buffer += toWrite;
371-
372- // If there's anything left in convertedBuf, move it to the beginning of the buffer.
373- convertedBufLen -= toWrite;
374-
375- if (convertedBufLen)
376- memmove(convertedBuf, convertedBuf + toWrite, convertedBufLen);
377-
378- leftToRead -= toWrite;
379-
380- // With ENABLE_LINE_INPUT enabled, we stop reading after the first
381- // newline, otherwise we stop reading after the first character.
382- if (!leftToRead || newline || (mode & ENABLE_LINE_INPUT) == 0)
383- break;
384- }
385-
386- if (!charsRead)
387- {
388- if (!ReadConsoleW(handle, wideBuf, MAX_LINE_LENGTH, &charsRead, NULL))
389- return -1;
390-
391- if (!charsRead)
392- break;
393-
394- pWide = wideBuf;
395- }
396-
397- DWORD wideBufLen = 1;
398- charsRead--;
399-
400- if (IS_HIGH_SURROGATE(*pWide))
401- {
402- // High surrogate, read one more code unit.
403- if (charsRead)
404- charsRead--;
405- else
406- {
407- DWORD read2;
408- if (!ReadConsoleW(handle, pWide + 1, 1, &read2, NULL))
409- return -1;
410- }
348+ fb_assert(false);
349+ return -1;
350+ }
411351
412- ++wideBufLen;
413- }
352+ if (!charsRead)
353+ return 0;
414354
415- convertedBufLen = WideCharToMultiByte(GetConsoleCP(), 0, pWide, wideBufLen ,
416- convertedBuf, sizeof(convertedBuf) , NULL, NULL);
355+ int mbLength = WideCharToMultiByte(GetConsoleCP(), 0, wideBuf, charsRead ,
356+ NULL, 0 , NULL, NULL);
417357
418- if (!convertedBufLen )
419- return -1;
358+ if (!mbLength )
359+ return -1;
420360
421- pWide += wideBufLen;
422- }
361+ mbLength = WideCharToMultiByte(GetConsoleCP(), 0, wideBuf, charsRead,
362+ mbBuffer.getBuffer(mbLength, false), mbLength, NULL, NULL);
423363
424- return bufferSize - leftToRead ;
364+ return mbLength ;
425365}
366+
426367#endif
427368
428369
@@ -1108,16 +1049,20 @@ static void readNextInputLine(const char* prompt)
11081049 do
11091050 {
11101051 // Read the line
1111- char buffer[MAX_USHORT] ;
1052+ char* buffer;
11121053 int lineSize;
11131054
11141055#ifdef WIN_NT
11151056 if (!Input_file && isatty(fileno(Filelist->Ifp().indev_fpointer)))
1116- lineSize = win32ReadConsole(Filelist->Ifp().indev_fpointer, buffer, sizeof(buffer));
1057+ {
1058+ lineSize = win32ReadConsole(Filelist->Ifp().indev_fpointer, charBuffer);
1059+ buffer = charBuffer->begin();
1060+ }
11171061 else
11181062#endif
11191063 {
1120- 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)
11211066 lineSize = strlen(buffer);
11221067 else
11231068 lineSize = -1;
0 commit comments