mirror of
https://github.com/reactos/reactos.git
synced 2025-08-05 07:02:56 +00:00
[MORE][CONUTILS] Implement missing features of the MORE command (#3658)
Implement missing features of the MORE command. Special thanks to @HBelusca. CORE-4019
This commit is contained in:
parent
afc27ab1aa
commit
b552901df5
26 changed files with 1423 additions and 267 deletions
|
@ -2,8 +2,8 @@
|
|||
* PROJECT: ReactOS Console Utilities Library
|
||||
* LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
|
||||
* PURPOSE: Console/terminal paging functionality.
|
||||
* COPYRIGHT: Copyright 2017-2018 ReactOS Team
|
||||
* Copyright 2017-2018 Hermes Belusca-Maito
|
||||
* COPYRIGHT: Copyright 2017-2021 Hermes Belusca-Maito
|
||||
* Copyright 2021 Katayama Hirofumi MZ
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -21,6 +21,7 @@
|
|||
#include <winbase.h>
|
||||
// #include <winnls.h>
|
||||
#include <wincon.h> // Console APIs (only if kernel32 support included)
|
||||
#include <winnls.h> // for WideCharToMultiByte
|
||||
#include <strsafe.h>
|
||||
|
||||
#include "conutils.h"
|
||||
|
@ -31,7 +32,240 @@
|
|||
// Temporary HACK
|
||||
#define CON_STREAM_WRITE ConStreamWrite
|
||||
|
||||
#define CP_SHIFTJIS 932 // Japanese Shift-JIS
|
||||
#define CP_HANGUL 949 // Korean Hangul/Wansung
|
||||
#define CP_JOHAB 1361 // Korean Johab
|
||||
#define CP_GB2312 936 // Chinese Simplified (GB2312)
|
||||
#define CP_BIG5 950 // Chinese Traditional (Big5)
|
||||
|
||||
/* IsFarEastCP(CodePage) */
|
||||
#define IsCJKCodePage(CodePage) \
|
||||
((CodePage) == CP_SHIFTJIS || (CodePage) == CP_HANGUL || \
|
||||
/* (CodePage) == CP_JOHAB || */ \
|
||||
(CodePage) == CP_BIG5 || (CodePage) == CP_GB2312)
|
||||
|
||||
static inline INT
|
||||
GetWidthOfCharCJK(
|
||||
IN UINT nCodePage,
|
||||
IN WCHAR ch)
|
||||
{
|
||||
INT ret = WideCharToMultiByte(nCodePage, 0, &ch, 1, NULL, 0, NULL, NULL);
|
||||
if (ret == 0)
|
||||
ret = 1;
|
||||
else if (ret > 2)
|
||||
ret = 2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static VOID
|
||||
ConCallPagerLine(
|
||||
IN OUT PCON_PAGER Pager,
|
||||
IN PCTCH line,
|
||||
IN DWORD cch)
|
||||
{
|
||||
Pager->dwFlags &= ~CON_PAGER_FLAG_DONT_OUTPUT; /* Clear the flag */
|
||||
|
||||
if (!Pager->PagerLine || !Pager->PagerLine(Pager, line, cch))
|
||||
CON_STREAM_WRITE(Pager->Screen->Stream, line, cch);
|
||||
}
|
||||
|
||||
static BOOL
|
||||
ConPagerWorker(IN PCON_PAGER Pager)
|
||||
{
|
||||
const DWORD ScreenColumns = Pager->ScreenColumns;
|
||||
const DWORD ScrollRows = Pager->ScrollRows;
|
||||
const PCTCH TextBuff = Pager->TextBuff;
|
||||
const DWORD cch = Pager->cch;
|
||||
|
||||
BOOL bFinitePaging = ((ScreenColumns > 0) && (Pager->ScreenRows > 0));
|
||||
LONG nTabWidth = Pager->nTabWidth;
|
||||
|
||||
DWORD ich = Pager->ich;
|
||||
DWORD iColumn = Pager->iColumn;
|
||||
DWORD iLine = Pager->iLine;
|
||||
DWORD lineno = Pager->lineno;
|
||||
|
||||
DWORD ichStart = ich;
|
||||
UINT nCodePage;
|
||||
BOOL IsCJK;
|
||||
UINT nWidthOfChar = 1;
|
||||
BOOL IsDoubleWidthCharTrailing = FALSE;
|
||||
|
||||
if (ich >= cch)
|
||||
return FALSE;
|
||||
|
||||
nCodePage = GetConsoleOutputCP();
|
||||
IsCJK = IsCJKCodePage(nCodePage);
|
||||
|
||||
/* Normalize the tab width: if negative or too large,
|
||||
* cap it to the number of columns. */
|
||||
if (ScreenColumns > 0) // if (bFinitePaging)
|
||||
{
|
||||
if (nTabWidth < 0)
|
||||
nTabWidth = ScreenColumns - 1;
|
||||
else
|
||||
nTabWidth = min(nTabWidth, ScreenColumns - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If no column width is known, default to 8 spaces if the
|
||||
* original value is negative; otherwise keep the current one. */
|
||||
if (nTabWidth < 0)
|
||||
nTabWidth = 8;
|
||||
}
|
||||
|
||||
if (Pager->dwFlags & CON_PAGER_FLAG_EXPAND_TABS)
|
||||
{
|
||||
ExpandTab:
|
||||
while (Pager->nSpacePending > 0)
|
||||
{
|
||||
/* Stop now if we have displayed more screen lines than requested */
|
||||
if (bFinitePaging && (iLine >= ScrollRows))
|
||||
break;
|
||||
|
||||
ConCallPagerLine(Pager, L" ", 1);
|
||||
--(Pager->nSpacePending);
|
||||
++iColumn;
|
||||
if ((ScreenColumns > 0) && (iColumn % ScreenColumns == 0))
|
||||
{
|
||||
if (!(Pager->dwFlags & CON_PAGER_FLAG_DONT_OUTPUT))
|
||||
++iLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop over each character in the buffer */
|
||||
for (; ich < cch; ++ich)
|
||||
{
|
||||
/* Stop now if we have displayed more screen lines than requested */
|
||||
if (bFinitePaging && (iLine >= ScrollRows))
|
||||
break;
|
||||
|
||||
Pager->lineno = lineno;
|
||||
|
||||
/* NEWLINE character */
|
||||
if (TextBuff[ich] == TEXT('\n'))
|
||||
{
|
||||
/* Output the pending text, including the newline */
|
||||
ConCallPagerLine(Pager, &TextBuff[ichStart], ich - ichStart + 1);
|
||||
ichStart = ich + 1;
|
||||
if (!(Pager->dwFlags & CON_PAGER_FLAG_DONT_OUTPUT))
|
||||
++iLine;
|
||||
iColumn = 0;
|
||||
|
||||
/* Done with this line; start a new one */
|
||||
++lineno;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* TAB character */
|
||||
if (TextBuff[ich] == TEXT('\t') &&
|
||||
(Pager->dwFlags & CON_PAGER_FLAG_EXPAND_TABS))
|
||||
{
|
||||
/* Output the pending text */
|
||||
ConCallPagerLine(Pager, &TextBuff[ichStart], ich - ichStart);
|
||||
|
||||
/* Perform tab expansion, unless the tab width is zero */
|
||||
if (nTabWidth == 0)
|
||||
{
|
||||
ichStart = ich + 1;
|
||||
continue;
|
||||
}
|
||||
ichStart = ++ich;
|
||||
Pager->nSpacePending += nTabWidth - (iColumn % nTabWidth);
|
||||
goto ExpandTab;
|
||||
}
|
||||
|
||||
/* FORM-FEED character */
|
||||
if (TextBuff[ich] == TEXT('\f') &&
|
||||
(Pager->dwFlags & CON_PAGER_FLAG_EXPAND_FF))
|
||||
{
|
||||
/* Output the pending text, skipping the form-feed */
|
||||
ConCallPagerLine(Pager, &TextBuff[ichStart], ich - ichStart);
|
||||
ichStart = ich + 1;
|
||||
// FIXME: Should we handle CON_PAGER_FLAG_DONT_OUTPUT ?
|
||||
|
||||
if (bFinitePaging)
|
||||
{
|
||||
/* Clear until the end of the screen */
|
||||
while (iLine < ScrollRows)
|
||||
{
|
||||
ConCallPagerLine(Pager, L"\n", 1);
|
||||
// CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1);
|
||||
// if (!(Pager->dwFlags & CON_PAGER_FLAG_DONT_OUTPUT))
|
||||
++iLine;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Just output a FORM-FEED character */
|
||||
ConCallPagerLine(Pager, L"\f", 1);
|
||||
// CON_STREAM_WRITE(Pager->Screen->Stream, L"\f", 1);
|
||||
}
|
||||
|
||||
iColumn = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Other character - Handle double-width for CJK */
|
||||
|
||||
if (IsCJK)
|
||||
{
|
||||
nWidthOfChar = GetWidthOfCharCJK(nCodePage, TextBuff[ich]);
|
||||
if (ScreenColumns > 0)
|
||||
{
|
||||
IsDoubleWidthCharTrailing = (nWidthOfChar == 2) &&
|
||||
((iColumn + 1) % ScreenColumns == 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Care about CJK character presentation only when outputting
|
||||
* to a device where the number of columns is known. */
|
||||
if (ScreenColumns > 0)
|
||||
{
|
||||
if ((iColumn + nWidthOfChar) % ScreenColumns == 0)
|
||||
{
|
||||
/* Output the pending text, including the last double-width character */
|
||||
ConCallPagerLine(Pager, &TextBuff[ichStart], ich - ichStart + 1);
|
||||
ichStart = ich + 1;
|
||||
if (!(Pager->dwFlags & CON_PAGER_FLAG_DONT_OUTPUT))
|
||||
++iLine;
|
||||
iColumn += nWidthOfChar;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsDoubleWidthCharTrailing)
|
||||
{
|
||||
/* Output the pending text, excluding the last double-width character */
|
||||
ConCallPagerLine(Pager, &TextBuff[ichStart], ich - ichStart);
|
||||
ichStart = ich;
|
||||
if (!(Pager->dwFlags & CON_PAGER_FLAG_DONT_OUTPUT))
|
||||
CON_STREAM_WRITE(Pager->Screen->Stream, TEXT(" "), 1);
|
||||
--ich;
|
||||
if (!(Pager->dwFlags & CON_PAGER_FLAG_DONT_OUTPUT))
|
||||
++iLine;
|
||||
++iColumn;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
iColumn += nWidthOfChar;
|
||||
}
|
||||
|
||||
/* Output the remaining text */
|
||||
if (ich - ichStart > 0)
|
||||
ConCallPagerLine(Pager, &TextBuff[ichStart], ich - ichStart);
|
||||
|
||||
if (iLine >= ScrollRows)
|
||||
iLine = 0; /* Reset the count of lines being printed */
|
||||
|
||||
Pager->ich = ich;
|
||||
Pager->iColumn = iColumn;
|
||||
Pager->iLine = iLine;
|
||||
Pager->lineno = lineno;
|
||||
|
||||
return (ich < cch);
|
||||
}
|
||||
|
||||
/* Returns TRUE when all the text is displayed, and FALSE if display is stopped */
|
||||
BOOL
|
||||
|
@ -43,75 +277,80 @@ ConWritePaging(
|
|||
IN DWORD len)
|
||||
{
|
||||
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
||||
|
||||
/* Used to see how big the screen is */
|
||||
DWORD ScreenLines = 0;
|
||||
|
||||
/* Chars since start of line */
|
||||
DWORD CharSL;
|
||||
|
||||
DWORD from = 0, i = 0;
|
||||
BOOL bIsConsole;
|
||||
|
||||
/* Parameters validation */
|
||||
if (!Pager)
|
||||
return FALSE;
|
||||
|
||||
/* Reset LineCount and return if no string has been given */
|
||||
if (StartPaging == TRUE)
|
||||
Pager->LineCount = 0;
|
||||
if (szStr == NULL)
|
||||
return TRUE;
|
||||
|
||||
/* Get the size of the visual screen that can be printed to */
|
||||
if (!ConGetScreenInfo(Pager->Screen, &csbi))
|
||||
bIsConsole = ConGetScreenInfo(Pager->Screen, &csbi);
|
||||
if (bIsConsole)
|
||||
{
|
||||
/* Calculate the console screen extent */
|
||||
Pager->ScreenColumns = csbi.srWindow.Right - csbi.srWindow.Left + 1;
|
||||
Pager->ScreenRows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* We assume it's a file handle */
|
||||
CON_STREAM_WRITE(Pager->Screen->Stream, szStr, len);
|
||||
return TRUE;
|
||||
Pager->ScreenColumns = 0;
|
||||
Pager->ScreenRows = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the number of lines currently displayed on screen, minus 1
|
||||
* to account for the "press any key..." prompt from PagePrompt().
|
||||
*/
|
||||
ScreenLines = (csbi.srWindow.Bottom - csbi.srWindow.Top);
|
||||
CharSL = csbi.dwCursorPosition.X;
|
||||
|
||||
/* Make sure the user doesn't have the screen too small */
|
||||
if (ScreenLines < 4)
|
||||
if (StartPaging)
|
||||
{
|
||||
CON_STREAM_WRITE(Pager->Screen->Stream, szStr, len);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
while (i < len)
|
||||
{
|
||||
/* Search until the end of a line is reached */
|
||||
if (szStr[i++] != TEXT('\n') && ++CharSL < csbi.dwSize.X)
|
||||
continue;
|
||||
|
||||
Pager->LineCount++;
|
||||
CharSL = 0;
|
||||
|
||||
if (Pager->LineCount >= ScreenLines)
|
||||
if (bIsConsole)
|
||||
{
|
||||
CON_STREAM_WRITE(Pager->Screen->Stream, &szStr[from], i-from);
|
||||
from = i;
|
||||
|
||||
/* Prompt the user; give him some values for statistics */
|
||||
// FIXME TODO: The prompt proc can also take ScreenLines ??
|
||||
if (!PagePrompt(Pager, from, len))
|
||||
return FALSE;
|
||||
|
||||
// TODO: Recalculate 'ScreenLines' in case the user redimensions
|
||||
// the window during the prompt.
|
||||
|
||||
/* Reset the number of lines being printed */
|
||||
Pager->LineCount = 0;
|
||||
/* Reset to display one page by default */
|
||||
Pager->ScrollRows = Pager->ScreenRows - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* File output: all lines are displayed at once; reset to a default value */
|
||||
Pager->ScrollRows = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (StartPaging)
|
||||
{
|
||||
/* Reset the output line count, the column index and the line number */
|
||||
Pager->iLine = 0;
|
||||
Pager->iColumn = 0;
|
||||
Pager->lineno = 1;
|
||||
Pager->nSpacePending = 0;
|
||||
}
|
||||
|
||||
Pager->TextBuff = szStr;
|
||||
Pager->cch = len;
|
||||
Pager->ich = 0;
|
||||
|
||||
if (len == 0 || szStr == NULL)
|
||||
return TRUE;
|
||||
|
||||
while (ConPagerWorker(Pager))
|
||||
{
|
||||
/* Prompt the user only when we display to a console and the screen
|
||||
* is not too small: at least one line for the actual paged text and
|
||||
* one line for the prompt. */
|
||||
if (bIsConsole && (Pager->ScreenRows >= 2))
|
||||
{
|
||||
/* Reset to display one page by default */
|
||||
Pager->ScrollRows = Pager->ScreenRows - 1;
|
||||
|
||||
/* Prompt the user; give him some values for statistics */
|
||||
if (!PagePrompt(Pager, Pager->ich, Pager->cch))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* If we display to a console, recalculate its screen extent
|
||||
* in case the user has redimensioned it during the prompt. */
|
||||
if (bIsConsole && ConGetScreenInfo(Pager->Screen, &csbi))
|
||||
{
|
||||
Pager->ScreenColumns = csbi.srWindow.Right - csbi.srWindow.Left + 1;
|
||||
Pager->ScreenRows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
|
||||
}
|
||||
}
|
||||
if (i > from)
|
||||
CON_STREAM_WRITE(Pager->Screen->Stream, &szStr[from], i-from);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue