diff --git a/base/applications/cmdutils/more/more.c b/base/applications/cmdutils/more/more.c index 5b868f0b5dc..afe20b9e803 100644 --- a/base/applications/cmdutils/more/more.c +++ b/base/applications/cmdutils/more/more.c @@ -80,6 +80,19 @@ static BOOL IsBlankLine(IN PCWCH line, IN DWORD cch) WORD wType; for (ich = 0; ich < cch; ++ich) { + /* + * Explicitly exclude FORM-FEED from the check, + * so that the pager can handle it. + */ + if (line[ich] == L'\f') + return FALSE; + + /* + * Otherwise do the extended blanks check. + * Note that MS MORE.COM only checks for spaces (\x20) and TABs (\x09). + * See http://archives.miloush.net/michkap/archive/2007/06/11/3230072.html + * for more information. + */ wType = 0; GetStringTypeW(CT_CTYPE1, &line[ich], 1, &wType); if (!(wType & (C1_BLANK | C1_SPACE))) @@ -95,15 +108,12 @@ MorePagerLine( IN PCWCH line, IN DWORD cch) { - DWORD ich; - if (s_dwFlags & FLAG_PLUSn) /* Skip lines */ { if (Pager->lineno < s_nNextLineNo) { - Pager->dwFlags |= CON_PAGER_DONT_OUTPUT; s_bPrevLineIsBlank = FALSE; - return TRUE; /* Don't output */ + return TRUE; /* Handled */ } s_dwFlags &= ~FLAG_PLUSn; } @@ -113,19 +123,26 @@ MorePagerLine( if (IsBlankLine(line, cch)) { if (s_bPrevLineIsBlank) - { - Pager->dwFlags |= CON_PAGER_DONT_OUTPUT; - return TRUE; /* Don't output */ - } + return TRUE; /* Handled */ - for (ich = 0; ich < cch; ++ich) - { - if (line[ich] == L'\n') - { - s_bPrevLineIsBlank = TRUE; - break; - } - } + /* + * Display a single blank line, independently of the actual size + * of the current line, by displaying just one space: this is + * especially needed in order to force line wrapping when the + * ENABLE_VIRTUAL_TERMINAL_PROCESSING or DISABLE_NEWLINE_AUTO_RETURN + * console modes are enabled. + * Then, reposition the cursor to the next line, first column. + */ + if (Pager->PageColumns > 0) + ConStreamWrite(Pager->Screen->Stream, TEXT(" "), 1); + ConStreamWrite(Pager->Screen->Stream, TEXT("\n"), 1); + Pager->iLine++; + Pager->iColumn = 0; + + s_bPrevLineIsBlank = TRUE; + s_nNextLineNo = 0; + + return TRUE; /* Handled */ } else { @@ -134,7 +151,8 @@ MorePagerLine( } s_nNextLineNo = 0; - return FALSE; /* Do output */ + /* Not handled, let the pager do the default action */ + return FALSE; } static BOOL @@ -998,7 +1016,7 @@ int wmain(int argc, WCHAR* argv[]) } Pager.PagerLine = MorePagerLine; - Pager.dwFlags |= CON_PAGER_EXPAND_TABS; + Pager.dwFlags |= CON_PAGER_EXPAND_TABS | CON_PAGER_CACHE_INCOMPLETE_LINE; if (s_dwFlags & FLAG_P) Pager.dwFlags |= CON_PAGER_EXPAND_FF; Pager.nTabWidth = s_nTabWidth; @@ -1023,7 +1041,7 @@ int wmain(int argc, WCHAR* argv[]) Encoding = ENCODING_ANSI; // ENCODING_UTF8; /* Start paging */ - bContinue = ConPutsPaging(&Pager, PagePrompt, TRUE, L""); + bContinue = ConWritePaging(&Pager, PagePrompt, TRUE, NULL, 0); if (!bContinue) goto Quit; @@ -1050,6 +1068,11 @@ int wmain(int argc, WCHAR* argv[]) break; } while (bRet && dwReadBytes > 0); + + /* Flush any cached pager buffers */ + if (bContinue) + bContinue = ConWritePaging(&Pager, PagePrompt, FALSE, NULL, 0); + goto Quit; } @@ -1100,7 +1123,7 @@ int wmain(int argc, WCHAR* argv[]) dwSumReadBytes = dwSumReadChars = 0; /* Start paging */ - bContinue = ConPutsPaging(&Pager, PagePrompt, TRUE, L""); + bContinue = ConWritePaging(&Pager, PagePrompt, TRUE, NULL, 0); if (!bContinue) { /* We stop displaying this file */ @@ -1152,6 +1175,10 @@ int wmain(int argc, WCHAR* argv[]) } while (bRet && dwReadBytes > 0); + /* Flush any cached pager buffers */ + if (bContinue) + bContinue = ConWritePaging(&Pager, PagePrompt, FALSE, NULL, 0); + CloseHandle(hFile); /* Check whether we should stop displaying this file */ diff --git a/sdk/lib/conutils/pager.c b/sdk/lib/conutils/pager.c index 4005e455410..56b0f3e95e3 100644 --- a/sdk/lib/conutils/pager.c +++ b/sdk/lib/conutils/pager.c @@ -57,45 +57,187 @@ GetWidthOfCharCJK( return ret; } -static VOID -ConCallPagerLine( +/** + * @brief Retrieves a new text line, or continue fetching the current one. + * + * @remark Manages setting Pager's CurrentLine, ichCurr, iEndLine, and the + * line cache (CachedLine, cchCachedLine). Other functions must not + * modify these values. + **/ +static BOOL +GetNextLine( IN OUT PCON_PAGER Pager, - IN PCTCH line, - IN DWORD cch) + IN PCTCH TextBuff, + IN SIZE_T cch) { - Pager->dwFlags &= ~CON_PAGER_DONT_OUTPUT; /* Clear the flag */ + SIZE_T ich = Pager->ich; + SIZE_T ichStart; + SIZE_T cchLine; + BOOL bCacheLine; - if (!Pager->PagerLine || !Pager->PagerLine(Pager, line, cch)) - CON_STREAM_WRITE(Pager->Screen->Stream, line, cch); + Pager->ichCurr = 0; + Pager->iEndLine = 0; + + /* + * If we already had an existing line, then we can safely start a new one + * and getting rid of any current cached line. Otherwise, we don't have + * a current line and we may be caching a new one, in which case, continue + * caching it until it becomes complete. + */ + // INVESTIGATE: Do that only if (ichStart >= iEndLine) ?? + if (Pager->CurrentLine) + { + // ASSERT(Pager->CurrentLine == Pager->CachedLine); + if (Pager->CachedLine) + { + HeapFree(GetProcessHeap(), 0, (PVOID)Pager->CachedLine); + Pager->CachedLine = NULL; + Pager->cchCachedLine = 0; + } + + Pager->CurrentLine = NULL; + } + + /* Nothing else to read if we are past the end of the buffer */ + if (ich >= cch) + { + /* If we have a pending cached line, terminate it now */ + if (Pager->CachedLine) + goto TerminateLine; + + /* Otherwise, bail out */ + return FALSE; + } + + /* Start a new line, or continue an existing one */ + ichStart = ich; + + /* Find where this line ends, looking for a NEWLINE character. + * (NOTE: We cannot use strchr because the buffer is not NULL-terminated) */ + for (; ich < cch; ++ich) + { + if (TextBuff[ich] == TEXT('\n')) + { + ++ich; + break; + } + } + Pager->ich = ich; + + cchLine = (ich - ichStart); + + // + // FIXME: Impose a maximum string limit when the line is cached, in order + // not to potentially grow memory indefinitely. When the limit is reached, + // terminate the line. + // + + /* + * If we have stopped because we have exhausted the text buffer + * and we have not found an end-of-line character, this may mean + * that the text line spans across different text buffers. If we + * have been told so, cache this line: we will complete it during + * the next call(s) and only then, display it. + * Otherwise, consider the line to be terminated now. + */ + bCacheLine = ((Pager->dwFlags & CON_PAGER_CACHE_INCOMPLETE_LINE) && + (ich >= cch) && (TextBuff[ich - 1] != TEXT('\n'))); + + /* Allocate, or re-allocate, the cached line buffer */ + if (bCacheLine && !Pager->CachedLine) + { + /* We start caching, allocate the cached line buffer */ + Pager->CachedLine = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + cchLine * sizeof(TCHAR)); + Pager->cchCachedLine = 0; + + if (!Pager->CachedLine) + { + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return FALSE; + } + } + else if (Pager->CachedLine) + { + /* We continue caching, re-allocate the cached line buffer */ + PVOID ptr = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + (PVOID)Pager->CachedLine, + (Pager->cchCachedLine + cchLine) * sizeof(TCHAR)); + if (!ptr) + { + HeapFree(GetProcessHeap(), 0, (PVOID)Pager->CachedLine); + Pager->CachedLine = NULL; + Pager->cchCachedLine = 0; + + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return FALSE; + } + Pager->CachedLine = ptr; + } + if (Pager->CachedLine) + { + /* Copy/append the text to the cached line buffer */ + RtlCopyMemory((PVOID)&Pager->CachedLine[Pager->cchCachedLine], + &TextBuff[ichStart], + cchLine * sizeof(TCHAR)); + Pager->cchCachedLine += cchLine; + } + if (bCacheLine) + { + /* The line is currently incomplete, don't proceed further for now */ + return FALSE; + } + +TerminateLine: + /* The line should be complete now. If we have an existing cached line, + * it has been completed by appending the remaining text to it. */ + + /* We are starting a new line */ + Pager->ichCurr = 0; + if (Pager->CachedLine) + { + Pager->iEndLine = Pager->cchCachedLine; + Pager->CurrentLine = Pager->CachedLine; + } + else + { + Pager->iEndLine = cchLine; + Pager->CurrentLine = &TextBuff[ichStart]; + } + + /* Increase only when we have got a NEWLINE */ + if ((Pager->iEndLine > 0) && (Pager->CurrentLine[Pager->iEndLine - 1] == TEXT('\n'))) + Pager->lineno++; + + return TRUE; } +/** + * @brief Does the main paging work: fetching text lines and displaying them. + **/ static BOOL -ConPagerWorker(IN PCON_PAGER Pager) +ConPagerWorker( + IN PCON_PAGER Pager, + IN PCTCH TextBuff, + IN DWORD cch) { const DWORD PageColumns = Pager->PageColumns; const DWORD ScrollRows = Pager->ScrollRows; - const PCTCH Line = Pager->TextBuff; - const DWORD cch = Pager->cch; BOOL bFinitePaging = ((PageColumns > 0) && (Pager->PageRows > 0)); LONG nTabWidth = Pager->nTabWidth; - DWORD ich = Pager->ich; + PCTCH Line; + SIZE_T ich; + SIZE_T ichStart; + SIZE_T iEndLine; DWORD iColumn = Pager->iColumn; - DWORD iLine = Pager->iLine; - DWORD ichStart = ich; - UINT nCodePage; - BOOL IsCJK; + UINT nCodePage = GetConsoleOutputCP(); + BOOL IsCJK = IsCJKCodePage(nCodePage); 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 (PageColumns > 0) // if (bFinitePaging) @@ -113,38 +255,88 @@ ConPagerWorker(IN PCON_PAGER Pager) nTabWidth = 8; } - if (Pager->dwFlags & CON_PAGER_EXPAND_TABS) + + /* Continue displaying the previous line, if any, or start a new one */ + Line = Pager->CurrentLine; + ichStart = Pager->ichCurr; + iEndLine = Pager->iEndLine; + +ProcessLine: + + /* Stop now if we have displayed more page lines than requested */ + if (bFinitePaging && (Pager->iLine >= ScrollRows)) + goto End; + + if (!Line || (ichStart >= iEndLine)) + { + /* Start a new line */ + if (!GetNextLine(Pager, TextBuff, cch)) + goto End; + + Line = Pager->CurrentLine; + ichStart = Pager->ichCurr; + iEndLine = Pager->iEndLine; + } + else + { + /* Continue displaying the current line */ + } + + // ASSERT(Line && ((ichStart < iEndLine) || (ichStart == iEndLine && iEndLine == 0))); + + /* Determine whether this line segment (from the current position till the end) should be displayed */ + Pager->iColumn = iColumn; + if (Pager->PagerLine && Pager->PagerLine(Pager, &Line[ichStart], iEndLine - ichStart)) + { + iColumn = Pager->iColumn; + + /* Done with this line; start a new one */ + Pager->nSpacePending = 0; // And reset any pending space. + ichStart = iEndLine; + goto ProcessLine; + } + // else: Continue displaying the line. + + + /* Print out any pending TAB expansion */ + if (Pager->nSpacePending > 0) { 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); + /* Print filling spaces */ + CON_STREAM_WRITE(Pager->Screen->Stream, TEXT(" "), 1); --(Pager->nSpacePending); ++iColumn; + + /* Check whether we are going across the column */ if ((PageColumns > 0) && (iColumn % PageColumns == 0)) { - if (!(Pager->dwFlags & CON_PAGER_DONT_OUTPUT)) - ++iLine; + // Pager->nSpacePending = 0; // <-- This is the mode of most text editors... + + /* Reposition the cursor to the next line, first column */ + if (!bFinitePaging || (PageColumns < Pager->Screen->csbi.dwSize.X)) + CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1); + + Pager->iLine++; + + /* Restart at the character */ + // ASSERT(ichStart == ich); + goto ProcessLine; } } } -ProcessLine: - /* Stop now if we have displayed more screen lines than requested */ - if (bFinitePaging && (iLine >= ScrollRows)) - goto End; - /* Loop over each character in the buffer */ - for (; ich < cch; ++ich) + /* Find, within this line segment (starting from its + * beginning), until where we can print to the page. */ + for (ich = ichStart; ich < iEndLine; ++ich) { /* NEWLINE character */ if (Line[ich] == TEXT('\n')) { /* We should stop now */ + // ASSERT(ich == iEndLine - 1); break; } @@ -194,33 +386,34 @@ ProcessLine: } } - /* Output the pending text */ - Pager->dwFlags &= ~CON_PAGER_DONT_OUTPUT; + /* Output the pending line segment */ if (ich - ichStart > 0) - ConCallPagerLine(Pager, &Line[ichStart], ich - ichStart); + CON_STREAM_WRITE(Pager->Screen->Stream, &Line[ichStart], ich - ichStart); - /* Have we finished the buffer? */ - if (ich >= cch) - goto End; + /* Have we finished the line segment? */ + if (ich >= iEndLine) + { + /* Restart at the character */ + ichStart = ich; + goto ProcessLine; + } /* Handle special characters */ /* NEWLINE character */ if (Line[ich] == TEXT('\n')) { - /* Output the newline */ - if (!(Pager->dwFlags & CON_PAGER_DONT_OUTPUT)) - { - // ConCallPagerLine(Pager, L"\n", 1); - CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1); - ++iLine; - } + // ASSERT(ich == iEndLine - 1); + + /* Reposition the cursor to the next line, first column */ + CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1); + + Pager->iLine++; iColumn = 0; /* Done with this line; start a new one */ Pager->nSpacePending = 0; // And reset any pending space. - Pager->lineno++; - ichStart = ++ich; + ichStart = iEndLine; goto ProcessLine; } @@ -245,28 +438,38 @@ ProcessLine: if (Line[ich] == TEXT('\f') && (Pager->dwFlags & CON_PAGER_EXPAND_FF)) { - // FIXME: Should we handle CON_PAGER_DONT_OUTPUT ? if (bFinitePaging) { - /* Clear until the end of the screen */ - while (iLine < ScrollRows) + /* Clear until the end of the page */ + while (Pager->iLine < ScrollRows) { - ConCallPagerLine(Pager, L"\n", 1); - // CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1); - // if (!(Pager->dwFlags & CON_PAGER_DONT_OUTPUT)) - ++iLine; + /* Call the user paging function in order to know + * whether we need to output the blank lines. */ + Pager->iColumn = iColumn; + if (Pager->PagerLine && Pager->PagerLine(Pager, TEXT("\n"), 1)) + { + /* Only one blank line displayed, that counts in the line count */ + Pager->iLine++; + break; + } + else + { + CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1); + Pager->iLine++; + } } } else { - /* Just output a FORM-FEED character */ - ConCallPagerLine(Pager, L"\f", 1); - // CON_STREAM_WRITE(Pager->Screen->Stream, L"\f", 1); + /* Just output a FORM-FEED and a NEWLINE */ + CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\f\n"), 2); + Pager->iLine++; } iColumn = 0; Pager->nSpacePending = 0; // And reset any pending space. + /* Skip and restart past the character */ ichStart = ++ich; goto ProcessLine; } @@ -276,19 +479,18 @@ ProcessLine: if (IsDoubleWidthCharTrailing) { IsDoubleWidthCharTrailing = FALSE; // Reset the flag. - - if (!(Pager->dwFlags & CON_PAGER_DONT_OUTPUT)) - CON_STREAM_WRITE(Pager->Screen->Stream, TEXT(" "), 1); - // ++iLine; - + CON_STREAM_WRITE(Pager->Screen->Stream, TEXT(" "), 1); /* Fall back below */ } /* Are we wrapping the line? */ if ((PageColumns > 0) && (iColumn % PageColumns == 0)) { - if (!(Pager->dwFlags & CON_PAGER_DONT_OUTPUT)) - ++iLine; + /* Reposition the cursor to the next line, first column */ + if (!bFinitePaging || (PageColumns < Pager->Screen->csbi.dwSize.X)) + CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1); + + Pager->iLine++; } /* Restart at the character */ @@ -297,14 +499,25 @@ ProcessLine: End: - if (iLine >= ScrollRows) - iLine = 0; /* Reset the count of lines being printed */ + /* + * We are exiting, either because we displayed all the required lines + * (iLine >= ScrollRows), or, because we don't have more data to display. + */ - Pager->ich = ich; + Pager->ichCurr = ichStart; Pager->iColumn = iColumn; - Pager->iLine = iLine; + // INVESTIGATE: Can we get rid of CurrentLine here? // if (ichStart >= iEndLine) ... - return (ich < cch); + /* Return TRUE if we displayed all the required lines; FALSE otherwise */ + if (bFinitePaging && (Pager->iLine >= ScrollRows)) + { + Pager->iLine = 0; /* Reset the count of lines being printed */ + return TRUE; + } + else + { + return FALSE; + } } @@ -374,25 +587,34 @@ ConWritePaging( /* File output, or single line: all lines are displayed at once; reset to a default value */ Pager->ScrollRows = 0; } - } - if (StartPaging) - { + /* Reset the internal data buffer */ + Pager->CachedLine = NULL; + Pager->cchCachedLine = 0; + /* Reset the paging state */ + Pager->CurrentLine = NULL; + Pager->ichCurr = 0; + Pager->iEndLine = 0; Pager->nSpacePending = 0; Pager->iColumn = 0; Pager->iLine = 0; - Pager->lineno = 1; + Pager->lineno = 0; } - Pager->TextBuff = szStr; - Pager->cch = len; + /* Reset the reading index in the user-provided source buffer */ Pager->ich = 0; - if (len == 0 || szStr == NULL) - return TRUE; + /* Run the pager even when the user-provided source buffer is + * empty, in case we need to flush any remaining cached line. */ + if (!Pager->CachedLine) + { + /* No cached line, bail out now */ + if (len == 0 || szStr == NULL) + return TRUE; + } - while (ConPagerWorker(Pager)) + while (ConPagerWorker(Pager, szStr, len)) { /* 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 @@ -403,7 +625,8 @@ ConWritePaging( Pager->ScrollRows = Pager->PageRows - 1; /* Prompt the user; give him some values for statistics */ - if (!PagePrompt(Pager, Pager->ich, Pager->cch)) + // FIXME: Doesn't reflect what's currently being displayed. + if (!PagePrompt(Pager, Pager->ich, len)) return FALSE; } diff --git a/sdk/lib/conutils/pager.h b/sdk/lib/conutils/pager.h index 8d38f07881f..1ddc5606970 100644 --- a/sdk/lib/conutils/pager.h +++ b/sdk/lib/conutils/pager.h @@ -37,9 +37,10 @@ typedef BOOL IN DWORD cch); /* Flags for CON_PAGER */ -#define CON_PAGER_DONT_OUTPUT (1 << 0) -#define CON_PAGER_EXPAND_TABS (1 << 1) -#define CON_PAGER_EXPAND_FF (1 << 2) +#define CON_PAGER_EXPAND_TABS (1 << 0) +#define CON_PAGER_EXPAND_FF (1 << 1) +// Whether or not the pager will cache the line if it's incomplete (not NEWLINE-terminated). +#define CON_PAGER_CACHE_INCOMPLETE_LINE (1 << 2) typedef struct _CON_PAGER { @@ -55,12 +56,15 @@ typedef struct _CON_PAGER DWORD ScrollRows; /* Data buffer */ - PCTCH TextBuff; /* The text buffer */ - DWORD cch; /* The total number of characters */ + PCTCH CachedLine; /* Cached line, HeapAlloc'ated */ + SIZE_T cchCachedLine; /* Its length (number of characters) */ + SIZE_T ich; /* The current index of character in TextBuff (a user-provided source buffer) */ /* Paging state */ - DWORD ich; /* The current index of character */ - DWORD nSpacePending; /* Pending spaces for TAB expansion */ + PCTCH CurrentLine; /* Pointer to the current line (either within a user-provided source buffer, or to CachedLine) */ + SIZE_T ichCurr; /* The current index of character in CurrentLine */ + SIZE_T iEndLine; /* End (length) of CurrentLine */ + DWORD nSpacePending; /* Pending spaces for TAB expansion */ DWORD iColumn; /* The current index of column */ DWORD iLine; /* The physical output line count of screen */ DWORD lineno; /* The logical line number */