Reactos

[CONUTILS:PAGER][MORE] Implement text line caching + fix some bugs.

- Implement caching of individual (newline-separated) text lines; this
behaviour can be enabled with a flag (enabled by MORE):
CON_PAGER_CACHE_INCOMPLETE_LINE.
This feature is necessary when reading a text file, whose text lines
may span across two or more successive temporary read buffers, and is
required for correctly determining whether the lines being read are
blank and may be squeezed.

- When squeezing blank lines, the blank-line check must be done for each
line segment corresponding to the screen line (and following) that
need to be displayed. This matches the behaviour of MS MORE.COM.

- Fix the IsBlankLine() check to not consider FORM-FEEDs as being blank
characters: This is necessary for correctly handling FORM-FEED
expansion. Also note that MS MORE.COM only checks for spaces and TABs,
so we are slightly overdoing these checks (considering other types of
whitespace).

- Get rid of ConCallPagerLine() and the intermediate CON_PAGER_DONT_OUTPUT
state flag that were used repeatedly for each and every small line
chunks. Instead, call directly the user-specified 'PagerLine' callback
when we are about to start treating the next line segment to be
displayed (see comment above).

- Fix the exit return condition of ConPagerWorker(): it should return
TRUE whenever we displayed all the required lines, and FALSE otherwise.
Otherwise, the previous (buggy) condition on the data being read from
the text file, may lead to the prompt not showing when a screenful of
text has been displayed, if it happened that the current text buffer
becomes empty at the same time (even if, overall, the text file hasn't
been fully displayed).

- In MorePagerLine(), when we encounter for the first time a blank line
that will be squeezed with other successive ones, display a single
blank line. But for that, just display one space and a newline: this
single space is especially needed in order to force line wrapping when
the ENABLE_VIRTUAL_TERMINAL_PROCESSING or DISABLE_NEWLINE_AUTO_RETURN
console modes are enabled. Otherwise the cursor remains at the
previous line (without wrapping), and just outputting one newline will
not make it move past 2 lines as one would naively expect.

+363 -109
+47 -20
base/applications/cmdutils/more/more.c
··· 80 80 WORD wType; 81 81 for (ich = 0; ich < cch; ++ich) 82 82 { 83 + /* 84 + * Explicitly exclude FORM-FEED from the check, 85 + * so that the pager can handle it. 86 + */ 87 + if (line[ich] == L'\f') 88 + return FALSE; 89 + 90 + /* 91 + * Otherwise do the extended blanks check. 92 + * Note that MS MORE.COM only checks for spaces (\x20) and TABs (\x09). 93 + * See http://archives.miloush.net/michkap/archive/2007/06/11/3230072.html 94 + * for more information. 95 + */ 83 96 wType = 0; 84 97 GetStringTypeW(CT_CTYPE1, &line[ich], 1, &wType); 85 98 if (!(wType & (C1_BLANK | C1_SPACE))) ··· 95 108 IN PCWCH line, 96 109 IN DWORD cch) 97 110 { 98 - DWORD ich; 99 - 100 111 if (s_dwFlags & FLAG_PLUSn) /* Skip lines */ 101 112 { 102 113 if (Pager->lineno < s_nNextLineNo) 103 114 { 104 - Pager->dwFlags |= CON_PAGER_DONT_OUTPUT; 105 115 s_bPrevLineIsBlank = FALSE; 106 - return TRUE; /* Don't output */ 116 + return TRUE; /* Handled */ 107 117 } 108 118 s_dwFlags &= ~FLAG_PLUSn; 109 119 } ··· 113 123 if (IsBlankLine(line, cch)) 114 124 { 115 125 if (s_bPrevLineIsBlank) 116 - { 117 - Pager->dwFlags |= CON_PAGER_DONT_OUTPUT; 118 - return TRUE; /* Don't output */ 119 - } 126 + return TRUE; /* Handled */ 120 127 121 - for (ich = 0; ich < cch; ++ich) 122 - { 123 - if (line[ich] == L'\n') 124 - { 125 - s_bPrevLineIsBlank = TRUE; 126 - break; 127 - } 128 - } 128 + /* 129 + * Display a single blank line, independently of the actual size 130 + * of the current line, by displaying just one space: this is 131 + * especially needed in order to force line wrapping when the 132 + * ENABLE_VIRTUAL_TERMINAL_PROCESSING or DISABLE_NEWLINE_AUTO_RETURN 133 + * console modes are enabled. 134 + * Then, reposition the cursor to the next line, first column. 135 + */ 136 + if (Pager->PageColumns > 0) 137 + ConStreamWrite(Pager->Screen->Stream, TEXT(" "), 1); 138 + ConStreamWrite(Pager->Screen->Stream, TEXT("\n"), 1); 139 + Pager->iLine++; 140 + Pager->iColumn = 0; 141 + 142 + s_bPrevLineIsBlank = TRUE; 143 + s_nNextLineNo = 0; 144 + 145 + return TRUE; /* Handled */ 129 146 } 130 147 else 131 148 { ··· 134 151 } 135 152 136 153 s_nNextLineNo = 0; 137 - return FALSE; /* Do output */ 154 + /* Not handled, let the pager do the default action */ 155 + return FALSE; 138 156 } 139 157 140 158 static BOOL ··· 998 1016 } 999 1017 1000 1018 Pager.PagerLine = MorePagerLine; 1001 - Pager.dwFlags |= CON_PAGER_EXPAND_TABS; 1019 + Pager.dwFlags |= CON_PAGER_EXPAND_TABS | CON_PAGER_CACHE_INCOMPLETE_LINE; 1002 1020 if (s_dwFlags & FLAG_P) 1003 1021 Pager.dwFlags |= CON_PAGER_EXPAND_FF; 1004 1022 Pager.nTabWidth = s_nTabWidth; ··· 1023 1041 Encoding = ENCODING_ANSI; // ENCODING_UTF8; 1024 1042 1025 1043 /* Start paging */ 1026 - bContinue = ConPutsPaging(&Pager, PagePrompt, TRUE, L""); 1044 + bContinue = ConWritePaging(&Pager, PagePrompt, TRUE, NULL, 0); 1027 1045 if (!bContinue) 1028 1046 goto Quit; 1029 1047 ··· 1050 1068 break; 1051 1069 } 1052 1070 while (bRet && dwReadBytes > 0); 1071 + 1072 + /* Flush any cached pager buffers */ 1073 + if (bContinue) 1074 + bContinue = ConWritePaging(&Pager, PagePrompt, FALSE, NULL, 0); 1075 + 1053 1076 goto Quit; 1054 1077 } 1055 1078 ··· 1100 1123 dwSumReadBytes = dwSumReadChars = 0; 1101 1124 1102 1125 /* Start paging */ 1103 - bContinue = ConPutsPaging(&Pager, PagePrompt, TRUE, L""); 1126 + bContinue = ConWritePaging(&Pager, PagePrompt, TRUE, NULL, 0); 1104 1127 if (!bContinue) 1105 1128 { 1106 1129 /* We stop displaying this file */ ··· 1151 1174 } 1152 1175 } 1153 1176 while (bRet && dwReadBytes > 0); 1177 + 1178 + /* Flush any cached pager buffers */ 1179 + if (bContinue) 1180 + bContinue = ConWritePaging(&Pager, PagePrompt, FALSE, NULL, 0); 1154 1181 1155 1182 CloseHandle(hFile); 1156 1183
+305 -82
sdk/lib/conutils/pager.c
··· 57 57 return ret; 58 58 } 59 59 60 - static VOID 61 - ConCallPagerLine( 60 + /** 61 + * @brief Retrieves a new text line, or continue fetching the current one. 62 + * 63 + * @remark Manages setting Pager's CurrentLine, ichCurr, iEndLine, and the 64 + * line cache (CachedLine, cchCachedLine). Other functions must not 65 + * modify these values. 66 + **/ 67 + static BOOL 68 + GetNextLine( 62 69 IN OUT PCON_PAGER Pager, 63 - IN PCTCH line, 64 - IN DWORD cch) 70 + IN PCTCH TextBuff, 71 + IN SIZE_T cch) 65 72 { 66 - Pager->dwFlags &= ~CON_PAGER_DONT_OUTPUT; /* Clear the flag */ 73 + SIZE_T ich = Pager->ich; 74 + SIZE_T ichStart; 75 + SIZE_T cchLine; 76 + BOOL bCacheLine; 77 + 78 + Pager->ichCurr = 0; 79 + Pager->iEndLine = 0; 80 + 81 + /* 82 + * If we already had an existing line, then we can safely start a new one 83 + * and getting rid of any current cached line. Otherwise, we don't have 84 + * a current line and we may be caching a new one, in which case, continue 85 + * caching it until it becomes complete. 86 + */ 87 + // INVESTIGATE: Do that only if (ichStart >= iEndLine) ?? 88 + if (Pager->CurrentLine) 89 + { 90 + // ASSERT(Pager->CurrentLine == Pager->CachedLine); 91 + if (Pager->CachedLine) 92 + { 93 + HeapFree(GetProcessHeap(), 0, (PVOID)Pager->CachedLine); 94 + Pager->CachedLine = NULL; 95 + Pager->cchCachedLine = 0; 96 + } 97 + 98 + Pager->CurrentLine = NULL; 99 + } 100 + 101 + /* Nothing else to read if we are past the end of the buffer */ 102 + if (ich >= cch) 103 + { 104 + /* If we have a pending cached line, terminate it now */ 105 + if (Pager->CachedLine) 106 + goto TerminateLine; 67 107 68 - if (!Pager->PagerLine || !Pager->PagerLine(Pager, line, cch)) 69 - CON_STREAM_WRITE(Pager->Screen->Stream, line, cch); 108 + /* Otherwise, bail out */ 109 + return FALSE; 110 + } 111 + 112 + /* Start a new line, or continue an existing one */ 113 + ichStart = ich; 114 + 115 + /* Find where this line ends, looking for a NEWLINE character. 116 + * (NOTE: We cannot use strchr because the buffer is not NULL-terminated) */ 117 + for (; ich < cch; ++ich) 118 + { 119 + if (TextBuff[ich] == TEXT('\n')) 120 + { 121 + ++ich; 122 + break; 123 + } 124 + } 125 + Pager->ich = ich; 126 + 127 + cchLine = (ich - ichStart); 128 + 129 + // 130 + // FIXME: Impose a maximum string limit when the line is cached, in order 131 + // not to potentially grow memory indefinitely. When the limit is reached, 132 + // terminate the line. 133 + // 134 + 135 + /* 136 + * If we have stopped because we have exhausted the text buffer 137 + * and we have not found an end-of-line character, this may mean 138 + * that the text line spans across different text buffers. If we 139 + * have been told so, cache this line: we will complete it during 140 + * the next call(s) and only then, display it. 141 + * Otherwise, consider the line to be terminated now. 142 + */ 143 + bCacheLine = ((Pager->dwFlags & CON_PAGER_CACHE_INCOMPLETE_LINE) && 144 + (ich >= cch) && (TextBuff[ich - 1] != TEXT('\n'))); 145 + 146 + /* Allocate, or re-allocate, the cached line buffer */ 147 + if (bCacheLine && !Pager->CachedLine) 148 + { 149 + /* We start caching, allocate the cached line buffer */ 150 + Pager->CachedLine = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 151 + cchLine * sizeof(TCHAR)); 152 + Pager->cchCachedLine = 0; 153 + 154 + if (!Pager->CachedLine) 155 + { 156 + SetLastError(ERROR_NOT_ENOUGH_MEMORY); 157 + return FALSE; 158 + } 159 + } 160 + else if (Pager->CachedLine) 161 + { 162 + /* We continue caching, re-allocate the cached line buffer */ 163 + PVOID ptr = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 164 + (PVOID)Pager->CachedLine, 165 + (Pager->cchCachedLine + cchLine) * sizeof(TCHAR)); 166 + if (!ptr) 167 + { 168 + HeapFree(GetProcessHeap(), 0, (PVOID)Pager->CachedLine); 169 + Pager->CachedLine = NULL; 170 + Pager->cchCachedLine = 0; 171 + 172 + SetLastError(ERROR_NOT_ENOUGH_MEMORY); 173 + return FALSE; 174 + } 175 + Pager->CachedLine = ptr; 176 + } 177 + if (Pager->CachedLine) 178 + { 179 + /* Copy/append the text to the cached line buffer */ 180 + RtlCopyMemory((PVOID)&Pager->CachedLine[Pager->cchCachedLine], 181 + &TextBuff[ichStart], 182 + cchLine * sizeof(TCHAR)); 183 + Pager->cchCachedLine += cchLine; 184 + } 185 + if (bCacheLine) 186 + { 187 + /* The line is currently incomplete, don't proceed further for now */ 188 + return FALSE; 189 + } 190 + 191 + TerminateLine: 192 + /* The line should be complete now. If we have an existing cached line, 193 + * it has been completed by appending the remaining text to it. */ 194 + 195 + /* We are starting a new line */ 196 + Pager->ichCurr = 0; 197 + if (Pager->CachedLine) 198 + { 199 + Pager->iEndLine = Pager->cchCachedLine; 200 + Pager->CurrentLine = Pager->CachedLine; 201 + } 202 + else 203 + { 204 + Pager->iEndLine = cchLine; 205 + Pager->CurrentLine = &TextBuff[ichStart]; 206 + } 207 + 208 + /* Increase only when we have got a NEWLINE */ 209 + if ((Pager->iEndLine > 0) && (Pager->CurrentLine[Pager->iEndLine - 1] == TEXT('\n'))) 210 + Pager->lineno++; 211 + 212 + return TRUE; 70 213 } 71 214 215 + /** 216 + * @brief Does the main paging work: fetching text lines and displaying them. 217 + **/ 72 218 static BOOL 73 - ConPagerWorker(IN PCON_PAGER Pager) 219 + ConPagerWorker( 220 + IN PCON_PAGER Pager, 221 + IN PCTCH TextBuff, 222 + IN DWORD cch) 74 223 { 75 224 const DWORD PageColumns = Pager->PageColumns; 76 225 const DWORD ScrollRows = Pager->ScrollRows; 77 - const PCTCH Line = Pager->TextBuff; 78 - const DWORD cch = Pager->cch; 79 226 80 227 BOOL bFinitePaging = ((PageColumns > 0) && (Pager->PageRows > 0)); 81 228 LONG nTabWidth = Pager->nTabWidth; 82 229 83 - DWORD ich = Pager->ich; 230 + PCTCH Line; 231 + SIZE_T ich; 232 + SIZE_T ichStart; 233 + SIZE_T iEndLine; 84 234 DWORD iColumn = Pager->iColumn; 85 - DWORD iLine = Pager->iLine; 86 - DWORD ichStart = ich; 87 235 88 - UINT nCodePage; 89 - BOOL IsCJK; 236 + UINT nCodePage = GetConsoleOutputCP(); 237 + BOOL IsCJK = IsCJKCodePage(nCodePage); 90 238 UINT nWidthOfChar = 1; 91 239 BOOL IsDoubleWidthCharTrailing = FALSE; 92 - 93 - if (ich >= cch) 94 - return FALSE; 95 - 96 - nCodePage = GetConsoleOutputCP(); 97 - IsCJK = IsCJKCodePage(nCodePage); 98 240 99 241 /* Normalize the tab width: if negative or too large, 100 242 * cap it to the number of columns. */ ··· 113 255 nTabWidth = 8; 114 256 } 115 257 116 - if (Pager->dwFlags & CON_PAGER_EXPAND_TABS) 258 + 259 + /* Continue displaying the previous line, if any, or start a new one */ 260 + Line = Pager->CurrentLine; 261 + ichStart = Pager->ichCurr; 262 + iEndLine = Pager->iEndLine; 263 + 264 + ProcessLine: 265 + 266 + /* Stop now if we have displayed more page lines than requested */ 267 + if (bFinitePaging && (Pager->iLine >= ScrollRows)) 268 + goto End; 269 + 270 + if (!Line || (ichStart >= iEndLine)) 271 + { 272 + /* Start a new line */ 273 + if (!GetNextLine(Pager, TextBuff, cch)) 274 + goto End; 275 + 276 + Line = Pager->CurrentLine; 277 + ichStart = Pager->ichCurr; 278 + iEndLine = Pager->iEndLine; 279 + } 280 + else 281 + { 282 + /* Continue displaying the current line */ 283 + } 284 + 285 + // ASSERT(Line && ((ichStart < iEndLine) || (ichStart == iEndLine && iEndLine == 0))); 286 + 287 + /* Determine whether this line segment (from the current position till the end) should be displayed */ 288 + Pager->iColumn = iColumn; 289 + if (Pager->PagerLine && Pager->PagerLine(Pager, &Line[ichStart], iEndLine - ichStart)) 290 + { 291 + iColumn = Pager->iColumn; 292 + 293 + /* Done with this line; start a new one */ 294 + Pager->nSpacePending = 0; // And reset any pending space. 295 + ichStart = iEndLine; 296 + goto ProcessLine; 297 + } 298 + // else: Continue displaying the line. 299 + 300 + 301 + /* Print out any pending TAB expansion */ 302 + if (Pager->nSpacePending > 0) 117 303 { 118 304 ExpandTab: 119 305 while (Pager->nSpacePending > 0) 120 306 { 121 - /* Stop now if we have displayed more screen lines than requested */ 122 - if (bFinitePaging && (iLine >= ScrollRows)) 123 - break; 124 - 125 - ConCallPagerLine(Pager, L" ", 1); 307 + /* Print filling spaces */ 308 + CON_STREAM_WRITE(Pager->Screen->Stream, TEXT(" "), 1); 126 309 --(Pager->nSpacePending); 127 310 ++iColumn; 311 + 312 + /* Check whether we are going across the column */ 128 313 if ((PageColumns > 0) && (iColumn % PageColumns == 0)) 129 314 { 130 - if (!(Pager->dwFlags & CON_PAGER_DONT_OUTPUT)) 131 - ++iLine; 315 + // Pager->nSpacePending = 0; // <-- This is the mode of most text editors... 316 + 317 + /* Reposition the cursor to the next line, first column */ 318 + if (!bFinitePaging || (PageColumns < Pager->Screen->csbi.dwSize.X)) 319 + CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1); 320 + 321 + Pager->iLine++; 322 + 323 + /* Restart at the character */ 324 + // ASSERT(ichStart == ich); 325 + goto ProcessLine; 132 326 } 133 327 } 134 328 } 135 329 136 - ProcessLine: 137 - /* Stop now if we have displayed more screen lines than requested */ 138 - if (bFinitePaging && (iLine >= ScrollRows)) 139 - goto End; 140 330 141 - /* Loop over each character in the buffer */ 142 - for (; ich < cch; ++ich) 331 + /* Find, within this line segment (starting from its 332 + * beginning), until where we can print to the page. */ 333 + for (ich = ichStart; ich < iEndLine; ++ich) 143 334 { 144 335 /* NEWLINE character */ 145 336 if (Line[ich] == TEXT('\n')) 146 337 { 147 338 /* We should stop now */ 339 + // ASSERT(ich == iEndLine - 1); 148 340 break; 149 341 } 150 342 ··· 194 386 } 195 387 } 196 388 197 - /* Output the pending text */ 198 - Pager->dwFlags &= ~CON_PAGER_DONT_OUTPUT; 389 + /* Output the pending line segment */ 199 390 if (ich - ichStart > 0) 200 - ConCallPagerLine(Pager, &Line[ichStart], ich - ichStart); 391 + CON_STREAM_WRITE(Pager->Screen->Stream, &Line[ichStart], ich - ichStart); 201 392 202 - /* Have we finished the buffer? */ 203 - if (ich >= cch) 204 - goto End; 393 + /* Have we finished the line segment? */ 394 + if (ich >= iEndLine) 395 + { 396 + /* Restart at the character */ 397 + ichStart = ich; 398 + goto ProcessLine; 399 + } 205 400 206 401 /* Handle special characters */ 207 402 208 403 /* NEWLINE character */ 209 404 if (Line[ich] == TEXT('\n')) 210 405 { 211 - /* Output the newline */ 212 - if (!(Pager->dwFlags & CON_PAGER_DONT_OUTPUT)) 213 - { 214 - // ConCallPagerLine(Pager, L"\n", 1); 215 - CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1); 216 - ++iLine; 217 - } 406 + // ASSERT(ich == iEndLine - 1); 407 + 408 + /* Reposition the cursor to the next line, first column */ 409 + CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1); 410 + 411 + Pager->iLine++; 218 412 iColumn = 0; 219 413 220 414 /* Done with this line; start a new one */ 221 415 Pager->nSpacePending = 0; // And reset any pending space. 222 - Pager->lineno++; 223 - ichStart = ++ich; 416 + ichStart = iEndLine; 224 417 goto ProcessLine; 225 418 } 226 419 ··· 245 438 if (Line[ich] == TEXT('\f') && 246 439 (Pager->dwFlags & CON_PAGER_EXPAND_FF)) 247 440 { 248 - // FIXME: Should we handle CON_PAGER_DONT_OUTPUT ? 249 441 if (bFinitePaging) 250 442 { 251 - /* Clear until the end of the screen */ 252 - while (iLine < ScrollRows) 443 + /* Clear until the end of the page */ 444 + while (Pager->iLine < ScrollRows) 253 445 { 254 - ConCallPagerLine(Pager, L"\n", 1); 255 - // CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1); 256 - // if (!(Pager->dwFlags & CON_PAGER_DONT_OUTPUT)) 257 - ++iLine; 446 + /* Call the user paging function in order to know 447 + * whether we need to output the blank lines. */ 448 + Pager->iColumn = iColumn; 449 + if (Pager->PagerLine && Pager->PagerLine(Pager, TEXT("\n"), 1)) 450 + { 451 + /* Only one blank line displayed, that counts in the line count */ 452 + Pager->iLine++; 453 + break; 454 + } 455 + else 456 + { 457 + CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1); 458 + Pager->iLine++; 459 + } 258 460 } 259 461 } 260 462 else 261 463 { 262 - /* Just output a FORM-FEED character */ 263 - ConCallPagerLine(Pager, L"\f", 1); 264 - // CON_STREAM_WRITE(Pager->Screen->Stream, L"\f", 1); 464 + /* Just output a FORM-FEED and a NEWLINE */ 465 + CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\f\n"), 2); 466 + Pager->iLine++; 265 467 } 266 468 267 469 iColumn = 0; 268 470 Pager->nSpacePending = 0; // And reset any pending space. 269 471 472 + /* Skip and restart past the character */ 270 473 ichStart = ++ich; 271 474 goto ProcessLine; 272 475 } ··· 276 479 if (IsDoubleWidthCharTrailing) 277 480 { 278 481 IsDoubleWidthCharTrailing = FALSE; // Reset the flag. 279 - 280 - if (!(Pager->dwFlags & CON_PAGER_DONT_OUTPUT)) 281 - CON_STREAM_WRITE(Pager->Screen->Stream, TEXT(" "), 1); 282 - // ++iLine; 283 - 482 + CON_STREAM_WRITE(Pager->Screen->Stream, TEXT(" "), 1); 284 483 /* Fall back below */ 285 484 } 286 485 287 486 /* Are we wrapping the line? */ 288 487 if ((PageColumns > 0) && (iColumn % PageColumns == 0)) 289 488 { 290 - if (!(Pager->dwFlags & CON_PAGER_DONT_OUTPUT)) 291 - ++iLine; 489 + /* Reposition the cursor to the next line, first column */ 490 + if (!bFinitePaging || (PageColumns < Pager->Screen->csbi.dwSize.X)) 491 + CON_STREAM_WRITE(Pager->Screen->Stream, TEXT("\n"), 1); 492 + 493 + Pager->iLine++; 292 494 } 293 495 294 496 /* Restart at the character */ ··· 297 499 298 500 299 501 End: 300 - if (iLine >= ScrollRows) 301 - iLine = 0; /* Reset the count of lines being printed */ 502 + /* 503 + * We are exiting, either because we displayed all the required lines 504 + * (iLine >= ScrollRows), or, because we don't have more data to display. 505 + */ 302 506 303 - Pager->ich = ich; 507 + Pager->ichCurr = ichStart; 304 508 Pager->iColumn = iColumn; 305 - Pager->iLine = iLine; 509 + // INVESTIGATE: Can we get rid of CurrentLine here? // if (ichStart >= iEndLine) ... 306 510 307 - return (ich < cch); 511 + /* Return TRUE if we displayed all the required lines; FALSE otherwise */ 512 + if (bFinitePaging && (Pager->iLine >= ScrollRows)) 513 + { 514 + Pager->iLine = 0; /* Reset the count of lines being printed */ 515 + return TRUE; 516 + } 517 + else 518 + { 519 + return FALSE; 520 + } 308 521 } 309 522 310 523 ··· 374 587 /* File output, or single line: all lines are displayed at once; reset to a default value */ 375 588 Pager->ScrollRows = 0; 376 589 } 377 - } 378 590 379 - if (StartPaging) 380 - { 591 + /* Reset the internal data buffer */ 592 + Pager->CachedLine = NULL; 593 + Pager->cchCachedLine = 0; 594 + 381 595 /* Reset the paging state */ 596 + Pager->CurrentLine = NULL; 597 + Pager->ichCurr = 0; 598 + Pager->iEndLine = 0; 382 599 Pager->nSpacePending = 0; 383 600 Pager->iColumn = 0; 384 601 Pager->iLine = 0; 385 - Pager->lineno = 1; 602 + Pager->lineno = 0; 386 603 } 387 604 388 - Pager->TextBuff = szStr; 389 - Pager->cch = len; 605 + /* Reset the reading index in the user-provided source buffer */ 390 606 Pager->ich = 0; 391 607 392 - if (len == 0 || szStr == NULL) 393 - return TRUE; 608 + /* Run the pager even when the user-provided source buffer is 609 + * empty, in case we need to flush any remaining cached line. */ 610 + if (!Pager->CachedLine) 611 + { 612 + /* No cached line, bail out now */ 613 + if (len == 0 || szStr == NULL) 614 + return TRUE; 615 + } 394 616 395 - while (ConPagerWorker(Pager)) 617 + while (ConPagerWorker(Pager, szStr, len)) 396 618 { 397 619 /* Prompt the user only when we display to a console and the screen 398 620 * is not too small: at least one line for the actual paged text and ··· 403 625 Pager->ScrollRows = Pager->PageRows - 1; 404 626 405 627 /* Prompt the user; give him some values for statistics */ 406 - if (!PagePrompt(Pager, Pager->ich, Pager->cch)) 628 + // FIXME: Doesn't reflect what's currently being displayed. 629 + if (!PagePrompt(Pager, Pager->ich, len)) 407 630 return FALSE; 408 631 } 409 632
+11 -7
sdk/lib/conutils/pager.h
··· 37 37 IN DWORD cch); 38 38 39 39 /* Flags for CON_PAGER */ 40 - #define CON_PAGER_DONT_OUTPUT (1 << 0) 41 - #define CON_PAGER_EXPAND_TABS (1 << 1) 42 - #define CON_PAGER_EXPAND_FF (1 << 2) 40 + #define CON_PAGER_EXPAND_TABS (1 << 0) 41 + #define CON_PAGER_EXPAND_FF (1 << 1) 42 + // Whether or not the pager will cache the line if it's incomplete (not NEWLINE-terminated). 43 + #define CON_PAGER_CACHE_INCOMPLETE_LINE (1 << 2) 43 44 44 45 typedef struct _CON_PAGER 45 46 { ··· 55 56 DWORD ScrollRows; 56 57 57 58 /* Data buffer */ 58 - PCTCH TextBuff; /* The text buffer */ 59 - DWORD cch; /* The total number of characters */ 59 + PCTCH CachedLine; /* Cached line, HeapAlloc'ated */ 60 + SIZE_T cchCachedLine; /* Its length (number of characters) */ 61 + SIZE_T ich; /* The current index of character in TextBuff (a user-provided source buffer) */ 60 62 61 63 /* Paging state */ 62 - DWORD ich; /* The current index of character */ 63 - DWORD nSpacePending; /* Pending spaces for TAB expansion */ 64 + PCTCH CurrentLine; /* Pointer to the current line (either within a user-provided source buffer, or to CachedLine) */ 65 + SIZE_T ichCurr; /* The current index of character in CurrentLine */ 66 + SIZE_T iEndLine; /* End (length) of CurrentLine */ 67 + DWORD nSpacePending; /* Pending spaces for TAB expansion */ 64 68 DWORD iColumn; /* The current index of column */ 65 69 DWORD iLine; /* The physical output line count of screen */ 66 70 DWORD lineno; /* The logical line number */