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