1 /* 2 * Copyright 2008-2010, Ingo Weinhold, ingo_weinhold@gmx.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 #include "BasicTerminalBuffer.h" 7 8 #include <alloca.h> 9 #include <stdlib.h> 10 #include <string.h> 11 12 #include <algorithm> 13 14 #include <String.h> 15 16 #include "TermConst.h" 17 #include "TerminalCharClassifier.h" 18 #include "TerminalLine.h" 19 20 21 static const UTF8Char kSpaceChar(' '); 22 23 // Soft size limits for the terminal buffer. The constants defined in 24 // TermConst.h are rather for the Terminal in general (i.e. the GUI). 25 static const int32 kMinRowCount = 2; 26 static const int32 kMaxRowCount = 1024; 27 static const int32 kMinColumnCount = 4; 28 static const int32 kMaxColumnCount = 1024; 29 30 31 #define ALLOC_LINE_ON_STACK(width) \ 32 ((TerminalLine*)alloca(sizeof(TerminalLine) \ 33 + sizeof(TerminalCell) * ((width) - 1))) 34 35 36 static inline int32 37 restrict_value(int32 value, int32 min, int32 max) 38 { 39 return value < min ? min : (value > max ? max : value); 40 } 41 42 43 // #pragma mark - private inline methods 44 45 46 inline int32 47 BasicTerminalBuffer::_LineIndex(int32 index) const 48 { 49 return (index + fScreenOffset) % fHeight; 50 } 51 52 53 inline TerminalLine* 54 BasicTerminalBuffer::_LineAt(int32 index) const 55 { 56 return fScreen[_LineIndex(index)]; 57 } 58 59 60 inline TerminalLine* 61 BasicTerminalBuffer::_HistoryLineAt(int32 index, TerminalLine* lineBuffer) const 62 { 63 if (index >= fHeight) 64 return NULL; 65 66 if (index < 0 && fHistory != NULL) 67 return fHistory->GetTerminalLineAt(-index - 1, lineBuffer); 68 69 return _LineAt(index + fHeight); 70 } 71 72 73 inline void 74 BasicTerminalBuffer::_Invalidate(int32 top, int32 bottom) 75 { 76 //debug_printf("%p->BasicTerminalBuffer::_Invalidate(%ld, %ld)\n", this, top, bottom); 77 fDirtyInfo.ExtendDirtyRegion(top, bottom); 78 79 if (!fDirtyInfo.messageSent) { 80 NotifyListener(); 81 fDirtyInfo.messageSent = true; 82 } 83 } 84 85 86 inline void 87 BasicTerminalBuffer::_CursorChanged() 88 { 89 if (!fDirtyInfo.messageSent) { 90 NotifyListener(); 91 fDirtyInfo.messageSent = true; 92 } 93 } 94 95 96 // #pragma mark - public methods 97 98 99 BasicTerminalBuffer::BasicTerminalBuffer() 100 : 101 fScreen(NULL), 102 fHistory(NULL), 103 fTabStops(NULL) 104 { 105 } 106 107 108 BasicTerminalBuffer::~BasicTerminalBuffer() 109 { 110 delete fHistory; 111 _FreeLines(fScreen, fHeight); 112 delete[] fTabStops; 113 } 114 115 116 status_t 117 BasicTerminalBuffer::Init(int32 width, int32 height, int32 historySize) 118 { 119 status_t error; 120 121 fWidth = width; 122 fHeight = height; 123 124 fScrollTop = 0; 125 fScrollBottom = fHeight - 1; 126 127 fCursor.x = 0; 128 fCursor.y = 0; 129 fSoftWrappedCursor = false; 130 131 fScreenOffset = 0; 132 133 fOverwriteMode = true; 134 fAlternateScreenActive = false; 135 fOriginMode = fSavedOriginMode = false; 136 137 fScreen = _AllocateLines(width, height); 138 if (fScreen == NULL) 139 return B_NO_MEMORY; 140 141 if (historySize > 0) { 142 fHistory = new(std::nothrow) HistoryBuffer; 143 if (fHistory == NULL) 144 return B_NO_MEMORY; 145 146 error = fHistory->Init(width, historySize); 147 if (error != B_OK) 148 return error; 149 } 150 151 error = _ResetTabStops(fWidth); 152 if (error != B_OK) 153 return error; 154 155 for (int32 i = 0; i < fHeight; i++) 156 fScreen[i]->Clear(); 157 158 fDirtyInfo.Reset(); 159 160 return B_OK; 161 } 162 163 164 status_t 165 BasicTerminalBuffer::ResizeTo(int32 width, int32 height) 166 { 167 return ResizeTo(width, height, fHistory != NULL ? fHistory->Capacity() : 0); 168 } 169 170 171 status_t 172 BasicTerminalBuffer::ResizeTo(int32 width, int32 height, int32 historyCapacity) 173 { 174 if (height < kMinRowCount || height > kMaxRowCount 175 || width < kMinColumnCount || width > kMaxColumnCount) { 176 return B_BAD_VALUE; 177 } 178 179 if (width == fWidth && height == fHeight) 180 return SetHistoryCapacity(historyCapacity); 181 182 if (fAlternateScreenActive) 183 return _ResizeSimple(width, height, historyCapacity); 184 185 return _ResizeRewrap(width, height, historyCapacity); 186 } 187 188 189 status_t 190 BasicTerminalBuffer::SetHistoryCapacity(int32 historyCapacity) 191 { 192 return _ResizeHistory(fWidth, historyCapacity); 193 } 194 195 196 void 197 BasicTerminalBuffer::Clear(bool resetCursor) 198 { 199 fSoftWrappedCursor = false; 200 fScreenOffset = 0; 201 _ClearLines(0, fHeight - 1); 202 203 if (resetCursor) 204 fCursor.SetTo(0, 0); 205 206 if (fHistory != NULL) 207 fHistory->Clear(); 208 209 fDirtyInfo.linesScrolled = 0; 210 _Invalidate(0, fHeight - 1); 211 } 212 213 214 void 215 BasicTerminalBuffer::SynchronizeWith(const BasicTerminalBuffer* other, 216 int32 offset, int32 dirtyTop, int32 dirtyBottom) 217 { 218 //debug_printf("BasicTerminalBuffer::SynchronizeWith(%p, %ld, %ld - %ld)\n", 219 //other, offset, dirtyTop, dirtyBottom); 220 221 // intersect the visible region with the dirty region 222 int32 first = 0; 223 int32 last = fHeight - 1; 224 dirtyTop -= offset; 225 dirtyBottom -= offset; 226 227 if (first > dirtyBottom || dirtyTop > last) 228 return; 229 230 if (first < dirtyTop) 231 first = dirtyTop; 232 if (last > dirtyBottom) 233 last = dirtyBottom; 234 235 // update the dirty lines 236 //debug_printf(" updating: %ld - %ld\n", first, last); 237 for (int32 i = first; i <= last; i++) { 238 TerminalLine* destLine = _LineAt(i); 239 TerminalLine* sourceLine = other->_HistoryLineAt(i + offset, destLine); 240 if (sourceLine != NULL) { 241 if (sourceLine != destLine) { 242 destLine->length = sourceLine->length; 243 destLine->softBreak = sourceLine->softBreak; 244 if (destLine->length > 0) { 245 memcpy(destLine->cells, sourceLine->cells, 246 destLine->length * sizeof(TerminalCell)); 247 } 248 } else { 249 // The source line was a history line and has been copied 250 // directly into destLine. 251 } 252 } else 253 destLine->Clear(); 254 } 255 } 256 257 258 bool 259 BasicTerminalBuffer::IsFullWidthChar(int32 row, int32 column) const 260 { 261 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 262 TerminalLine* line = _HistoryLineAt(row, lineBuffer); 263 return line != NULL && column > 0 && column < line->length 264 && (line->cells[column - 1].attributes & A_WIDTH) != 0; 265 } 266 267 268 int 269 BasicTerminalBuffer::GetChar(int32 row, int32 column, UTF8Char& character, 270 uint16& attributes) const 271 { 272 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 273 TerminalLine* line = _HistoryLineAt(row, lineBuffer); 274 if (line == NULL) 275 return NO_CHAR; 276 277 if (column < 0 || column >= line->length) 278 return NO_CHAR; 279 280 if (column > 0 && (line->cells[column - 1].attributes & A_WIDTH) != 0) 281 return IN_STRING; 282 283 TerminalCell& cell = line->cells[column]; 284 character = cell.character; 285 attributes = cell.attributes; 286 return A_CHAR; 287 } 288 289 290 int32 291 BasicTerminalBuffer::GetString(int32 row, int32 firstColumn, int32 lastColumn, 292 char* buffer, uint16& attributes) const 293 { 294 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 295 TerminalLine* line = _HistoryLineAt(row, lineBuffer); 296 if (line == NULL) 297 return 0; 298 299 if (lastColumn >= line->length) 300 lastColumn = line->length - 1; 301 302 int32 column = firstColumn; 303 if (column <= lastColumn) 304 attributes = line->cells[column].attributes; 305 306 for (; column <= lastColumn; column++) { 307 TerminalCell& cell = line->cells[column]; 308 if (cell.attributes != attributes) 309 break; 310 311 int32 bytes = cell.character.ByteCount(); 312 for (int32 i = 0; i < bytes; i++) 313 *buffer++ = cell.character.bytes[i]; 314 } 315 316 *buffer = '\0'; 317 318 return column - firstColumn; 319 } 320 321 322 void 323 BasicTerminalBuffer::GetStringFromRegion(BString& string, const TermPos& start, 324 const TermPos& end) const 325 { 326 //debug_printf("BasicTerminalBuffer::GetStringFromRegion((%ld, %ld), (%ld, %ld))\n", 327 //start.x, start.y, end.x, end.y); 328 if (start >= end) 329 return; 330 331 TermPos pos(start); 332 333 if (IsFullWidthChar(pos.y, pos.x)) 334 pos.x--; 335 336 // get all but the last line 337 while (pos.y < end.y) { 338 TerminalLine* line = _GetPartialLineString(string, pos.y, pos.x, 339 fWidth); 340 if (line != NULL && !line->softBreak) 341 string.Append('\n', 1); 342 pos.x = 0; 343 pos.y++; 344 } 345 346 // get the last line, if not empty 347 if (end.x > 0) 348 _GetPartialLineString(string, end.y, pos.x, end.x); 349 } 350 351 352 bool 353 BasicTerminalBuffer::FindWord(const TermPos& pos, 354 TerminalCharClassifier* classifier, bool findNonWords, TermPos& _start, 355 TermPos& _end) const 356 { 357 int32 x = pos.x; 358 int32 y = pos.y; 359 360 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 361 TerminalLine* line = _HistoryLineAt(y, lineBuffer); 362 if (line == NULL || x < 0 || x >= fWidth) 363 return false; 364 365 if (x >= line->length) { 366 // beyond the end of the line -- select all space 367 if (!findNonWords) 368 return false; 369 370 _start.SetTo(line->length, y); 371 _end.SetTo(fWidth, y); 372 return true; 373 } 374 375 if (x > 0 && IS_WIDTH(line->cells[x - 1].attributes)) 376 x--; 377 378 // get the char type at the given position 379 int type = classifier->Classify(line->cells[x].character.bytes); 380 381 // check whether we are supposed to find words only 382 if (type != CHAR_TYPE_WORD_CHAR && !findNonWords) 383 return false; 384 385 // find the beginning 386 TermPos start(x, y); 387 TermPos end(x + (IS_WIDTH(line->cells[x].attributes) ? 2 : 1), y); 388 while (true) { 389 if (--x < 0) { 390 // Hit the beginning of the line -- continue at the end of the 391 // previous line, if it soft-breaks. 392 y--; 393 if ((line = _HistoryLineAt(y, lineBuffer)) == NULL 394 || !line->softBreak || line->length == 0) { 395 break; 396 } 397 x = line->length - 1; 398 } 399 if (x > 0 && IS_WIDTH(line->cells[x - 1].attributes)) 400 x--; 401 402 if (classifier->Classify(line->cells[x].character.bytes) != type) 403 break; 404 405 start.SetTo(x, y); 406 } 407 408 // find the end 409 x = end.x; 410 y = end.y; 411 line = _HistoryLineAt(y, lineBuffer); 412 413 while (true) { 414 if (x >= line->length) { 415 // Hit the end of the line -- if it soft-breaks continue with the 416 // next line. 417 if (!line->softBreak) 418 break; 419 y++; 420 x = 0; 421 if ((line = _HistoryLineAt(y, lineBuffer)) == NULL) 422 break; 423 } 424 425 if (classifier->Classify(line->cells[x].character.bytes) != type) 426 break; 427 428 x += IS_WIDTH(line->cells[x].attributes) ? 2 : 1; 429 end.SetTo(x, y); 430 } 431 432 _start = start; 433 _end = end; 434 return true; 435 } 436 437 438 int32 439 BasicTerminalBuffer::LineLength(int32 index) const 440 { 441 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 442 TerminalLine* line = _HistoryLineAt(index, lineBuffer); 443 return line != NULL ? line->length : 0; 444 } 445 446 447 bool 448 BasicTerminalBuffer::Find(const char* _pattern, const TermPos& start, 449 bool forward, bool caseSensitive, bool matchWord, TermPos& _matchStart, 450 TermPos& _matchEnd) const 451 { 452 //debug_printf("BasicTerminalBuffer::Find(\"%s\", (%ld, %ld), forward: %d, case: %d, " 453 //"word: %d)\n", _pattern, start.x, start.y, forward, caseSensitive, matchWord); 454 // normalize pos, so that _NextChar() and _PreviousChar() are happy 455 TermPos pos(start); 456 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 457 TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer); 458 if (line != NULL) { 459 if (forward) { 460 while (line != NULL && pos.x >= line->length && line->softBreak) { 461 pos.x = 0; 462 pos.y++; 463 line = _HistoryLineAt(pos.y, lineBuffer); 464 } 465 } else { 466 if (pos.x > line->length) 467 pos.x = line->length; 468 } 469 } 470 471 int32 patternByteLen = strlen(_pattern); 472 473 // convert pattern to UTF8Char array 474 UTF8Char pattern[patternByteLen]; 475 int32 patternLen = 0; 476 while (*_pattern != '\0') { 477 int32 charLen = UTF8Char::ByteCount(*_pattern); 478 if (charLen > 0) { 479 pattern[patternLen].SetTo(_pattern, charLen); 480 481 // if not case sensitive, convert to lower case 482 if (!caseSensitive && charLen == 1) 483 pattern[patternLen] = pattern[patternLen].ToLower(); 484 485 patternLen++; 486 _pattern += charLen; 487 } else 488 _pattern++; 489 } 490 //debug_printf(" pattern byte len: %ld, pattern len: %ld\n", patternByteLen, patternLen); 491 492 if (patternLen == 0) 493 return false; 494 495 // reverse pattern, if searching backward 496 if (!forward) { 497 for (int32 i = 0; i < patternLen / 2; i++) 498 std::swap(pattern[i], pattern[patternLen - i - 1]); 499 } 500 501 // search loop 502 int32 matchIndex = 0; 503 TermPos matchStart; 504 while (true) { 505 //debug_printf(" (%ld, %ld): matchIndex: %ld\n", pos.x, pos.y, matchIndex); 506 TermPos previousPos(pos); 507 UTF8Char c; 508 if (!(forward ? _NextChar(pos, c) : _PreviousChar(pos, c))) 509 return false; 510 511 if (caseSensitive ? (c == pattern[matchIndex]) 512 : (c.ToLower() == pattern[matchIndex])) { 513 if (matchIndex == 0) 514 matchStart = previousPos; 515 516 matchIndex++; 517 518 if (matchIndex == patternLen) { 519 //debug_printf(" match!\n"); 520 // compute the match range 521 TermPos matchEnd(pos); 522 if (!forward) 523 std::swap(matchStart, matchEnd); 524 525 // check word match 526 if (matchWord) { 527 TermPos tempPos(matchStart); 528 if ((_PreviousChar(tempPos, c) && !c.IsSpace()) 529 || (_NextChar(tempPos = matchEnd, c) && !c.IsSpace())) { 530 //debug_printf(" but no word match!\n"); 531 continue; 532 } 533 } 534 535 _matchStart = matchStart; 536 _matchEnd = matchEnd; 537 //debug_printf(" -> (%ld, %ld) - (%ld, %ld)\n", matchStart.x, matchStart.y, 538 //matchEnd.x, matchEnd.y); 539 return true; 540 } 541 } else if (matchIndex > 0) { 542 // continue after the position where we started matching 543 pos = matchStart; 544 if (forward) 545 _NextChar(pos, c); 546 else 547 _PreviousChar(pos, c); 548 matchIndex = 0; 549 } 550 } 551 } 552 553 554 void 555 BasicTerminalBuffer::InsertChar(UTF8Char c, uint32 width, uint32 attributes) 556 { 557 //debug_printf("BasicTerminalBuffer::InsertChar('%.*s' (%d), %#lx)\n", 558 //(int)c.ByteCount(), c.bytes, c.bytes[0], attributes); 559 if ((int32)width == FULL_WIDTH) 560 attributes |= A_WIDTH; 561 562 if (fSoftWrappedCursor || fCursor.x + (int32)width > fWidth) 563 _SoftBreakLine(); 564 else 565 _PadLineToCursor(); 566 567 fSoftWrappedCursor = false; 568 569 if (!fOverwriteMode) 570 _InsertGap(width); 571 572 TerminalLine* line = _LineAt(fCursor.y); 573 line->cells[fCursor.x].character = c; 574 line->cells[fCursor.x].attributes = attributes; 575 576 if (line->length < fCursor.x + width) 577 line->length = fCursor.x + width; 578 579 _Invalidate(fCursor.y, fCursor.y); 580 581 fCursor.x += width; 582 583 // TODO: Deal correctly with full-width chars! We must take care not to 584 // overwrite half of a full-width char. This holds also for other methods. 585 586 if (fCursor.x == fWidth) { 587 fCursor.x -= width; 588 fSoftWrappedCursor = true; 589 } 590 } 591 592 593 void 594 BasicTerminalBuffer::FillScreen(UTF8Char c, uint32 width, uint32 attributes) 595 { 596 if ((int32)width == FULL_WIDTH) 597 attributes |= A_WIDTH; 598 599 fSoftWrappedCursor = false; 600 601 for (int32 y = 0; y < fHeight; y++) { 602 TerminalLine *line = _LineAt(y); 603 for (int32 x = 0; x < fWidth / (int32)width; x++) { 604 line->cells[x].character = c; 605 line->cells[x].attributes = attributes; 606 } 607 line->length = fWidth / width; 608 } 609 610 _Invalidate(0, fHeight - 1); 611 } 612 613 614 void 615 BasicTerminalBuffer::InsertCR() 616 { 617 _LineAt(fCursor.y)->softBreak = false; 618 fSoftWrappedCursor = false; 619 fCursor.x = 0; 620 _CursorChanged(); 621 } 622 623 624 void 625 BasicTerminalBuffer::InsertLF() 626 { 627 fSoftWrappedCursor = false; 628 629 // If we're at the end of the scroll region, scroll. Otherwise just advance 630 // the cursor. 631 if (fCursor.y == fScrollBottom) { 632 _Scroll(fScrollTop, fScrollBottom, 1); 633 } else { 634 if (fCursor.y < fHeight - 1) 635 fCursor.y++; 636 _CursorChanged(); 637 } 638 } 639 640 641 void 642 BasicTerminalBuffer::InsertRI() 643 { 644 fSoftWrappedCursor = false; 645 646 // If we're at the beginning of the scroll region, scroll. Otherwise just 647 // reverse the cursor. 648 if (fCursor.y == fScrollTop) { 649 _Scroll(fScrollTop, fScrollBottom, -1); 650 } else { 651 if (fCursor.y > 0) 652 fCursor.y--; 653 _CursorChanged(); 654 } 655 } 656 657 658 void 659 BasicTerminalBuffer::InsertTab() 660 { 661 int32 x; 662 663 fSoftWrappedCursor = false; 664 665 for (x = fCursor.x + 1; x < fWidth && !fTabStops[x]; x++) 666 ; // no body 667 x = restrict_value(x, 0, fWidth - 1); 668 669 if (x != fCursor.x) { 670 fCursor.x = x; 671 _CursorChanged(); 672 } 673 } 674 675 676 void 677 BasicTerminalBuffer::InsertLines(int32 numLines) 678 { 679 if (fCursor.y >= fScrollTop && fCursor.y < fScrollBottom) { 680 fSoftWrappedCursor = false; 681 _Scroll(fCursor.y, fScrollBottom, -numLines); 682 } 683 } 684 685 686 void 687 BasicTerminalBuffer::SetInsertMode(int flag) 688 { 689 fOverwriteMode = flag == MODE_OVER; 690 } 691 692 693 void 694 BasicTerminalBuffer::InsertSpace(int32 num) 695 { 696 // TODO: Deal with full-width chars! 697 if (fCursor.x + num > fWidth) 698 num = fWidth - fCursor.x; 699 700 if (num > 0) { 701 fSoftWrappedCursor = false; 702 _PadLineToCursor(); 703 _InsertGap(num); 704 705 TerminalLine* line = _LineAt(fCursor.y); 706 for (int32 i = fCursor.x; i < fCursor.x + num; i++) { 707 line->cells[i].character = kSpaceChar; 708 line->cells[i].attributes = 0; 709 } 710 711 _Invalidate(fCursor.y, fCursor.y); 712 } 713 } 714 715 716 void 717 BasicTerminalBuffer::EraseCharsFrom(int32 first, int32 numChars) 718 { 719 TerminalLine* line = _LineAt(fCursor.y); 720 if (fCursor.y >= line->length) 721 return; 722 723 fSoftWrappedCursor = false; 724 725 int32 end = min_c(first + numChars, line->length); 726 if (first > 0 && IS_WIDTH(line->cells[first - 1].attributes)) 727 first--; 728 if (end > 0 && IS_WIDTH(line->cells[end - 1].attributes)) 729 end++; 730 731 for (int32 i = first; i < end; i++) { 732 line->cells[i].character = kSpaceChar; 733 line->cells[i].attributes = 0; 734 } 735 736 _Invalidate(fCursor.y, fCursor.y); 737 } 738 739 740 void 741 BasicTerminalBuffer::EraseAbove() 742 { 743 // Clear the preceding lines. 744 if (fCursor.y > 0) 745 _ClearLines(0, fCursor.y - 1); 746 747 fSoftWrappedCursor = false; 748 749 // Delete the chars on the cursor line before (and including) the cursor. 750 TerminalLine* line = _LineAt(fCursor.y); 751 if (fCursor.x < line->length) { 752 int32 to = fCursor.x; 753 if (IS_WIDTH(line->cells[fCursor.x].attributes)) 754 to++; 755 for (int32 i = 0; i <= to; i++) { 756 line->cells[i].attributes = 0; 757 line->cells[i].character = kSpaceChar; 758 } 759 } else 760 line->Clear(); 761 762 _Invalidate(fCursor.y, fCursor.y); 763 } 764 765 766 void 767 BasicTerminalBuffer::EraseBelow() 768 { 769 fSoftWrappedCursor = false; 770 771 // Clear the following lines. 772 if (fCursor.y < fHeight - 1) 773 _ClearLines(fCursor.y + 1, fHeight - 1); 774 775 // Delete the chars on the cursor line after (and including) the cursor. 776 DeleteColumns(); 777 } 778 779 780 void 781 BasicTerminalBuffer::EraseAll() 782 { 783 fSoftWrappedCursor = false; 784 _Scroll(0, fHeight - 1, fHeight); 785 } 786 787 788 void 789 BasicTerminalBuffer::DeleteChars(int32 numChars) 790 { 791 fSoftWrappedCursor = false; 792 793 TerminalLine* line = _LineAt(fCursor.y); 794 if (fCursor.x < line->length) { 795 if (fCursor.x + numChars < line->length) { 796 int32 left = line->length - fCursor.x - numChars; 797 memmove(line->cells + fCursor.x, line->cells + fCursor.x + numChars, 798 left * sizeof(TerminalCell)); 799 line->length = fCursor.x + left; 800 } else { 801 // remove all remaining chars 802 line->length = fCursor.x; 803 } 804 805 _Invalidate(fCursor.y, fCursor.y); 806 } 807 } 808 809 810 void 811 BasicTerminalBuffer::DeleteColumnsFrom(int32 first) 812 { 813 fSoftWrappedCursor = false; 814 815 TerminalLine* line = _LineAt(fCursor.y); 816 if (first < line->length) { 817 line->length = first; 818 _Invalidate(fCursor.y, fCursor.y); 819 } 820 } 821 822 823 void 824 BasicTerminalBuffer::DeleteLines(int32 numLines) 825 { 826 if (fCursor.y >= fScrollTop && fCursor.y <= fScrollBottom) { 827 fSoftWrappedCursor = false; 828 _Scroll(fCursor.y, fScrollBottom, numLines); 829 } 830 } 831 832 833 void 834 BasicTerminalBuffer::SaveCursor() 835 { 836 fSavedCursor = fCursor; 837 } 838 839 840 void 841 BasicTerminalBuffer::RestoreCursor() 842 { 843 _SetCursor(fSavedCursor.x, fSavedCursor.y, true); 844 } 845 846 847 void 848 BasicTerminalBuffer::SetScrollRegion(int32 top, int32 bottom) 849 { 850 fScrollTop = restrict_value(top, 0, fHeight - 1); 851 fScrollBottom = restrict_value(bottom, fScrollTop, fHeight - 1); 852 853 // also sets the cursor position 854 _SetCursor(0, 0, false); 855 } 856 857 858 void 859 BasicTerminalBuffer::SetOriginMode(bool enabled) 860 { 861 fOriginMode = enabled; 862 _SetCursor(0, 0, false); 863 } 864 865 866 void 867 BasicTerminalBuffer::SaveOriginMode() 868 { 869 fSavedOriginMode = fOriginMode; 870 } 871 872 873 void 874 BasicTerminalBuffer::RestoreOriginMode() 875 { 876 fOriginMode = fSavedOriginMode; 877 } 878 879 880 void 881 BasicTerminalBuffer::SetTabStop(int32 x) 882 { 883 x = restrict_value(x, 0, fWidth - 1); 884 fTabStops[x] = true; 885 } 886 887 888 void 889 BasicTerminalBuffer::ClearTabStop(int32 x) 890 { 891 x = restrict_value(x, 0, fWidth - 1); 892 fTabStops[x] = false; 893 } 894 895 896 void 897 BasicTerminalBuffer::ClearAllTabStops() 898 { 899 for (int32 i = 0; i < fWidth; i++) 900 fTabStops[i] = false; 901 } 902 903 904 void 905 BasicTerminalBuffer::NotifyListener() 906 { 907 // Implemented by derived classes. 908 } 909 910 911 // #pragma mark - private methods 912 913 914 void 915 BasicTerminalBuffer::_SetCursor(int32 x, int32 y, bool absolute) 916 { 917 //debug_printf("BasicTerminalBuffer::_SetCursor(%d, %d)\n", x, y); 918 fSoftWrappedCursor = false; 919 920 x = restrict_value(x, 0, fWidth - 1); 921 if (fOriginMode && !absolute) { 922 y += fScrollTop; 923 y = restrict_value(y, fScrollTop, fScrollBottom); 924 } else { 925 y = restrict_value(y, 0, fHeight - 1); 926 } 927 928 if (x != fCursor.x || y != fCursor.y) { 929 fCursor.x = x; 930 fCursor.y = y; 931 _CursorChanged(); 932 } 933 } 934 935 936 void 937 BasicTerminalBuffer::_InvalidateAll() 938 { 939 fDirtyInfo.invalidateAll = true; 940 941 if (!fDirtyInfo.messageSent) { 942 NotifyListener(); 943 fDirtyInfo.messageSent = true; 944 } 945 } 946 947 948 /* static */ TerminalLine** 949 BasicTerminalBuffer::_AllocateLines(int32 width, int32 count) 950 { 951 TerminalLine** lines = (TerminalLine**)malloc(sizeof(TerminalLine*) * count); 952 if (lines == NULL) 953 return NULL; 954 955 for (int32 i = 0; i < count; i++) { 956 lines[i] = (TerminalLine*)malloc(sizeof(TerminalLine) 957 + sizeof(TerminalCell) * (width - 1)); 958 if (lines[i] == NULL) { 959 _FreeLines(lines, i); 960 return NULL; 961 } 962 } 963 964 return lines; 965 } 966 967 968 /* static */ void 969 BasicTerminalBuffer::_FreeLines(TerminalLine** lines, int32 count) 970 { 971 if (lines != NULL) { 972 for (int32 i = 0; i < count; i++) 973 free(lines[i]); 974 975 free(lines); 976 } 977 } 978 979 980 void 981 BasicTerminalBuffer::_ClearLines(int32 first, int32 last) 982 { 983 int32 firstCleared = -1; 984 int32 lastCleared = -1; 985 986 for (int32 i = first; i <= last; i++) { 987 TerminalLine* line = _LineAt(i); 988 if (line->length > 0) { 989 if (firstCleared == -1) 990 firstCleared = i; 991 lastCleared = i; 992 } 993 994 line->Clear(); 995 } 996 997 if (firstCleared >= 0) 998 _Invalidate(firstCleared, lastCleared); 999 } 1000 1001 1002 status_t 1003 BasicTerminalBuffer::_ResizeHistory(int32 width, int32 historyCapacity) 1004 { 1005 if (width == fWidth && historyCapacity == HistoryCapacity()) 1006 return B_OK; 1007 1008 if (historyCapacity <= 0) { 1009 // new history capacity is 0 -- delete the old history object 1010 delete fHistory; 1011 fHistory = NULL; 1012 1013 return B_OK; 1014 } 1015 1016 HistoryBuffer* history = new(std::nothrow) HistoryBuffer; 1017 if (history == NULL) 1018 return B_NO_MEMORY; 1019 1020 status_t error = history->Init(width, historyCapacity); 1021 if (error != B_OK) { 1022 delete history; 1023 return error; 1024 } 1025 1026 // Transfer the lines from the old history to the new one. 1027 if (fHistory != NULL) { 1028 int32 historySize = min_c(HistorySize(), historyCapacity); 1029 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1030 for (int32 i = historySize - 1; i >= 0; i--) { 1031 TerminalLine* line = fHistory->GetTerminalLineAt(i, lineBuffer); 1032 if (line->length > width) 1033 _TruncateLine(line, width); 1034 history->AddLine(line); 1035 } 1036 } 1037 1038 delete fHistory; 1039 fHistory = history; 1040 1041 return B_OK; 1042 } 1043 1044 1045 status_t 1046 BasicTerminalBuffer::_ResizeSimple(int32 width, int32 height, 1047 int32 historyCapacity) 1048 { 1049 //debug_printf("BasicTerminalBuffer::_ResizeSimple(): (%ld, %ld) -> " 1050 //"(%ld, %ld)\n", fWidth, fHeight, width, height); 1051 if (width == fWidth && height == fHeight) 1052 return B_OK; 1053 1054 if (width != fWidth || historyCapacity != HistoryCapacity()) { 1055 status_t error = _ResizeHistory(width, historyCapacity); 1056 if (error != B_OK) 1057 return error; 1058 } 1059 1060 TerminalLine** lines = _AllocateLines(width, height); 1061 if (lines == NULL) 1062 return B_NO_MEMORY; 1063 // NOTE: If width or history capacity changed, the object will be in 1064 // an invalid state, since the history will already use the new values. 1065 1066 int32 endLine = min_c(fHeight, height); 1067 int32 firstLine = 0; 1068 1069 if (height < fHeight) { 1070 if (endLine <= fCursor.y) { 1071 endLine = fCursor.y + 1; 1072 firstLine = endLine - height; 1073 } 1074 1075 // push the first lines to the history 1076 if (fHistory != NULL) { 1077 for (int32 i = 0; i < firstLine; i++) { 1078 TerminalLine* line = _LineAt(i); 1079 if (width < fWidth) 1080 _TruncateLine(line, width); 1081 fHistory->AddLine(line); 1082 } 1083 } 1084 } 1085 1086 // copy the lines we keep 1087 for (int32 i = firstLine; i < endLine; i++) { 1088 TerminalLine* sourceLine = _LineAt(i); 1089 TerminalLine* destLine = lines[i - firstLine]; 1090 if (width < fWidth) 1091 _TruncateLine(sourceLine, width); 1092 memcpy(destLine, sourceLine, (int32)sizeof(TerminalLine) 1093 + (sourceLine->length - 1) * (int32)sizeof(TerminalCell)); 1094 } 1095 1096 // clear the remaining lines 1097 for (int32 i = endLine - firstLine; i < height; i++) 1098 lines[i]->Clear(); 1099 1100 _FreeLines(fScreen, fHeight); 1101 fScreen = lines; 1102 1103 if (fWidth != width) { 1104 status_t error = _ResetTabStops(width); 1105 if (error != B_OK) 1106 return error; 1107 } 1108 1109 fWidth = width; 1110 fHeight = height; 1111 1112 fScrollTop = 0; 1113 fScrollBottom = fHeight - 1; 1114 fOriginMode = fSavedOriginMode = false; 1115 1116 fScreenOffset = 0; 1117 1118 if (fCursor.x > width) 1119 fCursor.x = width; 1120 fCursor.y -= firstLine; 1121 fSoftWrappedCursor = false; 1122 1123 return B_OK; 1124 } 1125 1126 1127 status_t 1128 BasicTerminalBuffer::_ResizeRewrap(int32 width, int32 height, 1129 int32 historyCapacity) 1130 { 1131 //debug_printf("BasicTerminalBuffer::_ResizeRewrap(): (%ld, %ld, history: %ld) -> " 1132 //"(%ld, %ld, history: %ld)\n", fWidth, fHeight, HistoryCapacity(), width, height, 1133 //historyCapacity); 1134 1135 // The width stays the same. _ResizeSimple() does exactly what we need. 1136 if (width == fWidth) 1137 return _ResizeSimple(width, height, historyCapacity); 1138 1139 // The width changes. We have to allocate a new line array, a new history 1140 // and re-wrap all lines. 1141 1142 TerminalLine** screen = _AllocateLines(width, height); 1143 if (screen == NULL) 1144 return B_NO_MEMORY; 1145 1146 HistoryBuffer* history = NULL; 1147 1148 if (historyCapacity > 0) { 1149 history = new(std::nothrow) HistoryBuffer; 1150 if (history == NULL) { 1151 _FreeLines(screen, height); 1152 return B_NO_MEMORY; 1153 } 1154 1155 status_t error = history->Init(width, historyCapacity); 1156 if (error != B_OK) { 1157 _FreeLines(screen, height); 1158 delete history; 1159 return error; 1160 } 1161 } 1162 1163 int32 historySize = HistorySize(); 1164 int32 totalLines = historySize + fHeight; 1165 1166 // re-wrap 1167 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1168 TermPos cursor; 1169 int32 destIndex = 0; 1170 int32 sourceIndex = 0; 1171 int32 sourceX = 0; 1172 int32 destTotalLines = 0; 1173 int32 destScreenOffset = 0; 1174 int32 maxDestTotalLines = INT_MAX; 1175 bool newDestLine = true; 1176 bool cursorSeen = false; 1177 TerminalLine* sourceLine = _HistoryLineAt(-historySize, lineBuffer); 1178 1179 while (sourceIndex < totalLines) { 1180 TerminalLine* destLine = screen[destIndex]; 1181 1182 if (newDestLine) { 1183 // Clear a new dest line before using it. If we're about to 1184 // overwrite an previously written line, we push it to the 1185 // history first, though. 1186 if (history != NULL && destTotalLines >= height) 1187 history->AddLine(screen[destIndex]); 1188 destLine->Clear(); 1189 newDestLine = false; 1190 } 1191 1192 int32 sourceLeft = sourceLine->length - sourceX; 1193 int32 destLeft = width - destLine->length; 1194 //debug_printf(" source: %ld, left: %ld, dest: %ld, left: %ld\n", 1195 //sourceIndex, sourceLeft, destIndex, destLeft); 1196 1197 if (sourceIndex == historySize && sourceX == 0) { 1198 destScreenOffset = destTotalLines; 1199 if (destLeft == 0 && sourceLeft > 0) 1200 destScreenOffset++; 1201 maxDestTotalLines = destScreenOffset + height; 1202 //debug_printf(" destScreenOffset: %ld\n", destScreenOffset); 1203 } 1204 1205 int32 toCopy = min_c(sourceLeft, destLeft); 1206 // If the last cell to copy is the first cell of a 1207 // full-width char, don't copy it yet. 1208 if (toCopy > 0 && IS_WIDTH( 1209 sourceLine->cells[sourceX + toCopy - 1].attributes)) { 1210 //debug_printf(" -> last char is full-width -- don't copy it\n"); 1211 toCopy--; 1212 } 1213 1214 // translate the cursor position 1215 if (fCursor.y + historySize == sourceIndex 1216 && fCursor.x >= sourceX 1217 && (fCursor.x < sourceX + toCopy 1218 || (destLeft >= sourceLeft 1219 && sourceX + sourceLeft <= fCursor.x))) { 1220 cursor.x = destLine->length + fCursor.x - sourceX; 1221 cursor.y = destTotalLines; 1222 1223 if (cursor.x >= width) { 1224 // The cursor was in free space after the official end 1225 // of line. 1226 cursor.x = width - 1; 1227 } 1228 //debug_printf(" cursor: (%ld, %ld)\n", cursor.x, cursor.y); 1229 1230 cursorSeen = true; 1231 } 1232 1233 if (toCopy > 0) { 1234 memcpy(destLine->cells + destLine->length, 1235 sourceLine->cells + sourceX, toCopy * sizeof(TerminalCell)); 1236 destLine->length += toCopy; 1237 } 1238 1239 bool nextDestLine = false; 1240 if (toCopy == sourceLeft) { 1241 if (!sourceLine->softBreak) 1242 nextDestLine = true; 1243 sourceIndex++; 1244 sourceX = 0; 1245 sourceLine = _HistoryLineAt(sourceIndex - historySize, 1246 lineBuffer); 1247 } else { 1248 destLine->softBreak = true; 1249 nextDestLine = true; 1250 sourceX += toCopy; 1251 } 1252 1253 if (nextDestLine) { 1254 destIndex = (destIndex + 1) % height; 1255 destTotalLines++; 1256 newDestLine = true; 1257 if (cursorSeen && destTotalLines >= maxDestTotalLines) 1258 break; 1259 } 1260 } 1261 1262 // If the last source line had a soft break, the last dest line 1263 // won't have been counted yet. 1264 if (!newDestLine) { 1265 destIndex = (destIndex + 1) % height; 1266 destTotalLines++; 1267 } 1268 1269 //debug_printf(" total lines: %ld -> %ld\n", totalLines, destTotalLines); 1270 1271 if (destTotalLines - destScreenOffset > height) 1272 destScreenOffset = destTotalLines - height; 1273 1274 cursor.y -= destScreenOffset; 1275 1276 // When there are less lines (starting with the screen offset) than 1277 // there's room in the screen, clear the remaining screen lines. 1278 for (int32 i = destTotalLines; i < destScreenOffset + height; i++) { 1279 // Move the line we're going to clear to the history, if that's a 1280 // line we've written earlier. 1281 TerminalLine* line = screen[i % height]; 1282 if (history != NULL && i >= height) 1283 history->AddLine(line); 1284 line->Clear(); 1285 } 1286 1287 // Update the values 1288 _FreeLines(fScreen, fHeight); 1289 delete fHistory; 1290 1291 fScreen = screen; 1292 fHistory = history; 1293 1294 if (fWidth != width) { 1295 status_t error = _ResetTabStops(width); 1296 if (error != B_OK) 1297 return error; 1298 } 1299 1300 //debug_printf(" cursor: (%ld, %ld) -> (%ld, %ld)\n", fCursor.x, fCursor.y, 1301 //cursor.x, cursor.y); 1302 fCursor.x = cursor.x; 1303 fCursor.y = cursor.y; 1304 fSoftWrappedCursor = false; 1305 //debug_printf(" screen offset: %ld -> %ld\n", fScreenOffset, destScreenOffset % height); 1306 fScreenOffset = destScreenOffset % height; 1307 //debug_printf(" height %ld -> %ld\n", fHeight, height); 1308 //debug_printf(" width %ld -> %ld\n", fWidth, width); 1309 fHeight = height; 1310 fWidth = width; 1311 1312 fScrollTop = 0; 1313 fScrollBottom = fHeight - 1; 1314 fOriginMode = fSavedOriginMode = false; 1315 1316 return B_OK; 1317 } 1318 1319 1320 status_t 1321 BasicTerminalBuffer::_ResetTabStops(int32 width) 1322 { 1323 if (fTabStops != NULL) 1324 delete[] fTabStops; 1325 1326 fTabStops = new(std::nothrow) bool[width]; 1327 if (fTabStops == NULL) 1328 return B_NO_MEMORY; 1329 1330 for (int32 i = 0; i < width; i++) 1331 fTabStops[i] = (i % TAB_WIDTH) == 0; 1332 return B_OK; 1333 } 1334 1335 1336 void 1337 BasicTerminalBuffer::_Scroll(int32 top, int32 bottom, int32 numLines) 1338 { 1339 if (numLines == 0) 1340 return; 1341 1342 if (numLines > 0) { 1343 // scroll text up 1344 if (top == 0) { 1345 // The lines scrolled out of the screen range are transferred to 1346 // the history. 1347 1348 // add the lines to the history 1349 if (fHistory != NULL) { 1350 int32 toHistory = min_c(numLines, bottom - top + 1); 1351 for (int32 i = 0; i < toHistory; i++) 1352 fHistory->AddLine(_LineAt(i)); 1353 1354 if (toHistory < numLines) 1355 fHistory->AddEmptyLines(numLines - toHistory); 1356 } 1357 1358 if (numLines >= bottom - top + 1) { 1359 // all lines are scrolled out of range -- just clear them 1360 _ClearLines(top, bottom); 1361 } else if (bottom == fHeight - 1) { 1362 // full screen scroll -- update the screen offset and clear new 1363 // lines 1364 fScreenOffset = (fScreenOffset + numLines) % fHeight; 1365 for (int32 i = bottom - numLines + 1; i <= bottom; i++) 1366 _LineAt(i)->Clear(); 1367 } else { 1368 // Partial screen scroll. We move the screen offset anyway, but 1369 // have to move the unscrolled lines to their new location. 1370 // TODO: It may be more efficient to actually move the scrolled 1371 // lines only (might depend on the number of scrolled/unscrolled 1372 // lines). 1373 for (int32 i = fHeight - 1; i > bottom; i--) { 1374 std::swap(fScreen[_LineIndex(i)], 1375 fScreen[_LineIndex(i + numLines)]); 1376 } 1377 1378 // update the screen offset and clear the new lines 1379 fScreenOffset = (fScreenOffset + numLines) % fHeight; 1380 for (int32 i = bottom - numLines + 1; i <= bottom; i++) 1381 _LineAt(i)->Clear(); 1382 } 1383 1384 // scroll/extend dirty range 1385 1386 if (fDirtyInfo.dirtyTop != INT_MAX) { 1387 // If the top or bottom of the dirty region are above the 1388 // bottom of the scroll region, we have to scroll them up. 1389 if (fDirtyInfo.dirtyTop <= bottom) { 1390 fDirtyInfo.dirtyTop -= numLines; 1391 if (fDirtyInfo.dirtyBottom <= bottom) 1392 fDirtyInfo.dirtyBottom -= numLines; 1393 } 1394 1395 // numLines above the bottom become dirty 1396 _Invalidate(bottom - numLines + 1, bottom); 1397 } 1398 1399 fDirtyInfo.linesScrolled += numLines; 1400 1401 // invalidate new empty lines 1402 _Invalidate(bottom + 1 - numLines, bottom); 1403 1404 // In case only part of the screen was scrolled, we invalidate also 1405 // the lines below the scroll region. Those remain unchanged, but 1406 // we can't convey that they have not been scrolled via 1407 // TerminalBufferDirtyInfo. So we need to force the view to sync 1408 // them again. 1409 if (bottom < fHeight - 1) 1410 _Invalidate(bottom + 1, fHeight - 1); 1411 } else if (numLines >= bottom - top + 1) { 1412 // all lines are completely scrolled out of range -- just clear 1413 // them 1414 _ClearLines(top, bottom); 1415 } else { 1416 // partial scroll -- clear the lines scrolled out of range and move 1417 // the other ones 1418 for (int32 i = top + numLines; i <= bottom; i++) { 1419 int32 lineToDrop = _LineIndex(i - numLines); 1420 int32 lineToKeep = _LineIndex(i); 1421 fScreen[lineToDrop]->Clear(); 1422 std::swap(fScreen[lineToDrop], fScreen[lineToKeep]); 1423 } 1424 // clear any lines between the two swapped ranges above 1425 for (int32 i = bottom - numLines + 1; i < top + numLines; i++) 1426 _LineAt(i)->Clear(); 1427 1428 _Invalidate(top, bottom); 1429 } 1430 } else { 1431 // scroll text down 1432 numLines = -numLines; 1433 1434 if (numLines >= bottom - top + 1) { 1435 // all lines are completely scrolled out of range -- just clear 1436 // them 1437 _ClearLines(top, bottom); 1438 } else { 1439 // partial scroll -- clear the lines scrolled out of range and move 1440 // the other ones 1441 // TODO: When scrolling the whole screen, we could just update fScreenOffset and 1442 // clear the respective lines. 1443 for (int32 i = bottom - numLines; i >= top; i--) { 1444 int32 lineToKeep = _LineIndex(i); 1445 int32 lineToDrop = _LineIndex(i + numLines); 1446 fScreen[lineToDrop]->Clear(); 1447 std::swap(fScreen[lineToDrop], fScreen[lineToKeep]); 1448 } 1449 // clear any lines between the two swapped ranges above 1450 for (int32 i = bottom - numLines + 1; i < top + numLines; i++) 1451 _LineAt(i)->Clear(); 1452 1453 _Invalidate(top, bottom); 1454 } 1455 } 1456 } 1457 1458 1459 void 1460 BasicTerminalBuffer::_SoftBreakLine() 1461 { 1462 TerminalLine* line = _LineAt(fCursor.y); 1463 line->softBreak = true; 1464 1465 fCursor.x = 0; 1466 if (fCursor.y == fScrollBottom) 1467 _Scroll(fScrollTop, fScrollBottom, 1); 1468 else 1469 fCursor.y++; 1470 } 1471 1472 1473 void 1474 BasicTerminalBuffer::_PadLineToCursor() 1475 { 1476 TerminalLine* line = _LineAt(fCursor.y); 1477 if (line->length < fCursor.x) { 1478 for (int32 i = line->length; i < fCursor.x; i++) { 1479 line->cells[i].character = kSpaceChar; 1480 line->cells[i].attributes = 0; 1481 // TODO: Other attributes? 1482 } 1483 } 1484 } 1485 1486 1487 /*static*/ void 1488 BasicTerminalBuffer::_TruncateLine(TerminalLine* line, int32 length) 1489 { 1490 if (line->length <= length) 1491 return; 1492 1493 if (length > 0 && IS_WIDTH(line->cells[length - 1].attributes)) 1494 length--; 1495 1496 line->length = length; 1497 } 1498 1499 1500 void 1501 BasicTerminalBuffer::_InsertGap(int32 width) 1502 { 1503 // ASSERT(fCursor.x + width <= fWidth) 1504 TerminalLine* line = _LineAt(fCursor.y); 1505 1506 int32 toMove = min_c(line->length - fCursor.x, fWidth - fCursor.x - width); 1507 if (toMove > 0) { 1508 memmove(line->cells + fCursor.x + width, 1509 line->cells + fCursor.x, toMove * sizeof(TerminalCell)); 1510 } 1511 1512 line->length = min_c(line->length + width, fWidth); 1513 } 1514 1515 1516 /*! \a endColumn is not inclusive. 1517 */ 1518 TerminalLine* 1519 BasicTerminalBuffer::_GetPartialLineString(BString& string, int32 row, 1520 int32 startColumn, int32 endColumn) const 1521 { 1522 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1523 TerminalLine* line = _HistoryLineAt(row, lineBuffer); 1524 if (line == NULL) 1525 return NULL; 1526 1527 if (endColumn > line->length) 1528 endColumn = line->length; 1529 1530 for (int32 x = startColumn; x < endColumn; x++) { 1531 const TerminalCell& cell = line->cells[x]; 1532 string.Append(cell.character.bytes, cell.character.ByteCount()); 1533 1534 if (IS_WIDTH(cell.attributes)) 1535 x++; 1536 } 1537 1538 return line; 1539 } 1540 1541 1542 /*! Decrement \a pos and return the char at that location. 1543 */ 1544 bool 1545 BasicTerminalBuffer::_PreviousChar(TermPos& pos, UTF8Char& c) const 1546 { 1547 pos.x--; 1548 1549 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1550 TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer); 1551 1552 while (true) { 1553 if (pos.x < 0) { 1554 pos.y--; 1555 line = _HistoryLineAt(pos.y, lineBuffer); 1556 if (line == NULL) 1557 return false; 1558 1559 pos.x = line->length; 1560 if (line->softBreak) { 1561 pos.x--; 1562 } else { 1563 c = '\n'; 1564 return true; 1565 } 1566 } else { 1567 c = line->cells[pos.x].character; 1568 return true; 1569 } 1570 } 1571 } 1572 1573 1574 /*! Return the char at \a pos and increment it. 1575 */ 1576 bool 1577 BasicTerminalBuffer::_NextChar(TermPos& pos, UTF8Char& c) const 1578 { 1579 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1580 TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer); 1581 if (line == NULL) 1582 return false; 1583 1584 if (pos.x >= line->length) { 1585 c = '\n'; 1586 pos.x = 0; 1587 pos.y++; 1588 return true; 1589 } 1590 1591 c = line->cells[pos.x].character; 1592 1593 pos.x++; 1594 while (line != NULL && pos.x >= line->length && line->softBreak) { 1595 pos.x = 0; 1596 pos.y++; 1597 line = _HistoryLineAt(pos.y, lineBuffer); 1598 } 1599 1600 return true; 1601 } 1602