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::DeleteChars(int32 numChars) 714 { 715 fSoftWrappedCursor = false; 716 717 TerminalLine* line = _LineAt(fCursor.y); 718 if (fCursor.x < line->length) { 719 if (fCursor.x + numChars < line->length) { 720 int32 left = line->length - fCursor.x - numChars; 721 memmove(line->cells + fCursor.x, line->cells + fCursor.x + numChars, 722 left * sizeof(TerminalCell)); 723 line->length = fCursor.x + left; 724 } else { 725 // remove all remaining chars 726 line->length = fCursor.x; 727 } 728 729 _Invalidate(fCursor.y, fCursor.y); 730 } 731 } 732 733 734 void 735 BasicTerminalBuffer::DeleteColumns() 736 { 737 fSoftWrappedCursor = false; 738 739 TerminalLine* line = _LineAt(fCursor.y); 740 if (fCursor.x < line->length) { 741 line->length = fCursor.x; 742 _Invalidate(fCursor.y, fCursor.y); 743 } 744 } 745 746 747 void 748 BasicTerminalBuffer::DeleteLines(int32 numLines) 749 { 750 if (fCursor.y >= fScrollTop && fCursor.y <= fScrollBottom) { 751 fSoftWrappedCursor = false; 752 _Scroll(fCursor.y, fScrollBottom, numLines); 753 } 754 } 755 756 757 void 758 BasicTerminalBuffer::SetCursor(int32 x, int32 y) 759 { 760 //debug_printf("BasicTerminalBuffer::SetCursor(%d, %d)\n", x, y); 761 fSoftWrappedCursor = false; 762 x = restrict_value(x, 0, fWidth - 1); 763 y = restrict_value(y, 0, fHeight - 1); 764 if (x != fCursor.x || y != fCursor.y) { 765 fCursor.x = x; 766 fCursor.y = y; 767 _CursorChanged(); 768 } 769 } 770 771 772 void 773 BasicTerminalBuffer::SaveCursor() 774 { 775 fSavedCursor = fCursor; 776 } 777 778 779 void 780 BasicTerminalBuffer::RestoreCursor() 781 { 782 SetCursor(fSavedCursor.x, fSavedCursor.y); 783 } 784 785 786 void 787 BasicTerminalBuffer::SetScrollRegion(int32 top, int32 bottom) 788 { 789 fScrollTop = restrict_value(top, 0, fHeight - 1); 790 fScrollBottom = restrict_value(bottom, fScrollTop, fHeight - 1); 791 792 // also sets the cursor position 793 SetCursor(0, 0); 794 } 795 796 797 void 798 BasicTerminalBuffer::NotifyListener() 799 { 800 // Implemented by derived classes. 801 } 802 803 804 // #pragma mark - private methods 805 806 807 void 808 BasicTerminalBuffer::_InvalidateAll() 809 { 810 fDirtyInfo.invalidateAll = true; 811 812 if (!fDirtyInfo.messageSent) { 813 NotifyListener(); 814 fDirtyInfo.messageSent = true; 815 } 816 } 817 818 819 /* static */ TerminalLine** 820 BasicTerminalBuffer::_AllocateLines(int32 width, int32 count) 821 { 822 TerminalLine** lines = (TerminalLine**)malloc(sizeof(TerminalLine*) * count); 823 if (lines == NULL) 824 return NULL; 825 826 for (int32 i = 0; i < count; i++) { 827 lines[i] = (TerminalLine*)malloc(sizeof(TerminalLine) 828 + sizeof(TerminalCell) * (width - 1)); 829 if (lines[i] == NULL) { 830 _FreeLines(lines, i); 831 return NULL; 832 } 833 } 834 835 return lines; 836 } 837 838 839 /* static */ void 840 BasicTerminalBuffer::_FreeLines(TerminalLine** lines, int32 count) 841 { 842 if (lines != NULL) { 843 for (int32 i = 0; i < count; i++) 844 free(lines[i]); 845 846 free(lines); 847 } 848 } 849 850 851 void 852 BasicTerminalBuffer::_ClearLines(int32 first, int32 last) 853 { 854 int32 firstCleared = -1; 855 int32 lastCleared = -1; 856 857 for (int32 i = first; i <= last; i++) { 858 TerminalLine* line = _LineAt(i); 859 if (line->length > 0) { 860 if (firstCleared == -1) 861 firstCleared = i; 862 lastCleared = i; 863 } 864 865 line->Clear(); 866 } 867 868 if (firstCleared >= 0) 869 _Invalidate(firstCleared, lastCleared); 870 } 871 872 873 status_t 874 BasicTerminalBuffer::_ResizeHistory(int32 width, int32 historyCapacity) 875 { 876 if (width == fWidth && historyCapacity == HistoryCapacity()) 877 return B_OK; 878 879 if (historyCapacity <= 0) { 880 // new history capacity is 0 -- delete the old history object 881 delete fHistory; 882 fHistory = NULL; 883 884 return B_OK; 885 } 886 887 HistoryBuffer* history = new(std::nothrow) HistoryBuffer; 888 if (history == NULL) 889 return B_NO_MEMORY; 890 891 status_t error = history->Init(width, historyCapacity); 892 if (error != B_OK) { 893 delete history; 894 return error; 895 } 896 897 // Transfer the lines from the old history to the new one. 898 if (fHistory != NULL) { 899 int32 historySize = min_c(HistorySize(), historyCapacity); 900 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 901 for (int32 i = historySize - 1; i >= 0; i--) { 902 TerminalLine* line = fHistory->GetTerminalLineAt(i, lineBuffer); 903 if (line->length > width) 904 _TruncateLine(line, width); 905 history->AddLine(line); 906 } 907 } 908 909 delete fHistory; 910 fHistory = history; 911 912 return B_OK; 913 } 914 915 916 status_t 917 BasicTerminalBuffer::_ResizeSimple(int32 width, int32 height, 918 int32 historyCapacity) 919 { 920 //debug_printf("BasicTerminalBuffer::_ResizeSimple(): (%ld, %ld) -> " 921 //"(%ld, %ld)\n", fWidth, fHeight, width, height); 922 if (width == fWidth && height == fHeight) 923 return B_OK; 924 925 if (width != fWidth || historyCapacity != HistoryCapacity()) { 926 status_t error = _ResizeHistory(width, historyCapacity); 927 if (error != B_OK) 928 return error; 929 } 930 931 TerminalLine** lines = _AllocateLines(width, height); 932 if (lines == NULL) 933 return B_NO_MEMORY; 934 // NOTE: If width or history capacity changed, the object will be in 935 // an invalid state, since the history will already use the new values. 936 937 int32 endLine = min_c(fHeight, height); 938 int32 firstLine = 0; 939 940 if (height < fHeight) { 941 if (endLine <= fCursor.y) { 942 endLine = fCursor.y + 1; 943 firstLine = endLine - height; 944 } 945 946 // push the first lines to the history 947 if (fHistory != NULL) { 948 for (int32 i = 0; i < firstLine; i++) { 949 TerminalLine* line = _LineAt(i); 950 if (width < fWidth) 951 _TruncateLine(line, width); 952 fHistory->AddLine(line); 953 } 954 } 955 } 956 957 // copy the lines we keep 958 for (int32 i = firstLine; i < endLine; i++) { 959 TerminalLine* sourceLine = _LineAt(i); 960 TerminalLine* destLine = lines[i - firstLine]; 961 if (width < fWidth) 962 _TruncateLine(sourceLine, width); 963 memcpy(destLine, sourceLine, (int32)sizeof(TerminalLine) 964 + (sourceLine->length - 1) * (int32)sizeof(TerminalCell)); 965 } 966 967 // clear the remaining lines 968 for (int32 i = endLine - firstLine; i < height; i++) 969 lines[i]->Clear(); 970 971 _FreeLines(fScreen, fHeight); 972 fScreen = lines; 973 974 fWidth = width; 975 fHeight = height; 976 977 fScrollTop = 0; 978 fScrollBottom = fHeight - 1; 979 980 fScreenOffset = 0; 981 982 if (fCursor.x > width) 983 fCursor.x = width; 984 fCursor.y -= firstLine; 985 fSoftWrappedCursor = false; 986 987 return B_OK; 988 } 989 990 991 status_t 992 BasicTerminalBuffer::_ResizeRewrap(int32 width, int32 height, 993 int32 historyCapacity) 994 { 995 //debug_printf("BasicTerminalBuffer::_ResizeRewrap(): (%ld, %ld, history: %ld) -> " 996 //"(%ld, %ld, history: %ld)\n", fWidth, fHeight, HistoryCapacity(), width, height, 997 //historyCapacity); 998 999 // The width stays the same. _ResizeSimple() does exactly what we need. 1000 if (width == fWidth) 1001 return _ResizeSimple(width, height, historyCapacity); 1002 1003 // The width changes. We have to allocate a new line array, a new history 1004 // and re-wrap all lines. 1005 1006 TerminalLine** screen = _AllocateLines(width, height); 1007 if (screen == NULL) 1008 return B_NO_MEMORY; 1009 1010 HistoryBuffer* history = NULL; 1011 1012 if (historyCapacity > 0) { 1013 history = new(std::nothrow) HistoryBuffer; 1014 if (history == NULL) { 1015 _FreeLines(screen, height); 1016 return B_NO_MEMORY; 1017 } 1018 1019 status_t error = history->Init(width, historyCapacity); 1020 if (error != B_OK) { 1021 _FreeLines(screen, height); 1022 delete history; 1023 return error; 1024 } 1025 } 1026 1027 int32 historySize = HistorySize(); 1028 int32 totalLines = historySize + fHeight; 1029 1030 // re-wrap 1031 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1032 TermPos cursor; 1033 int32 destIndex = 0; 1034 int32 sourceIndex = 0; 1035 int32 sourceX = 0; 1036 int32 destTotalLines = 0; 1037 int32 destScreenOffset = 0; 1038 int32 maxDestTotalLines = INT_MAX; 1039 bool newDestLine = true; 1040 bool cursorSeen = false; 1041 TerminalLine* sourceLine = _HistoryLineAt(-historySize, lineBuffer); 1042 1043 while (sourceIndex < totalLines) { 1044 TerminalLine* destLine = screen[destIndex]; 1045 1046 if (newDestLine) { 1047 // Clear a new dest line before using it. If we're about to 1048 // overwrite an previously written line, we push it to the 1049 // history first, though. 1050 if (history != NULL && destTotalLines >= height) 1051 history->AddLine(screen[destIndex]); 1052 destLine->Clear(); 1053 newDestLine = false; 1054 } 1055 1056 int32 sourceLeft = sourceLine->length - sourceX; 1057 int32 destLeft = width - destLine->length; 1058 //debug_printf(" source: %ld, left: %ld, dest: %ld, left: %ld\n", 1059 //sourceIndex, sourceLeft, destIndex, destLeft); 1060 1061 if (sourceIndex == historySize && sourceX == 0) { 1062 destScreenOffset = destTotalLines; 1063 if (destLeft == 0 && sourceLeft > 0) 1064 destScreenOffset++; 1065 maxDestTotalLines = destScreenOffset + height; 1066 //debug_printf(" destScreenOffset: %ld\n", destScreenOffset); 1067 } 1068 1069 int32 toCopy = min_c(sourceLeft, destLeft); 1070 // If the last cell to copy is the first cell of a 1071 // full-width char, don't copy it yet. 1072 if (toCopy > 0 && IS_WIDTH( 1073 sourceLine->cells[sourceX + toCopy - 1].attributes)) { 1074 //debug_printf(" -> last char is full-width -- don't copy it\n"); 1075 toCopy--; 1076 } 1077 1078 // translate the cursor position 1079 if (fCursor.y + historySize == sourceIndex 1080 && fCursor.x >= sourceX 1081 && (fCursor.x < sourceX + toCopy 1082 || destLeft >= sourceLeft 1083 && sourceX + sourceLeft <= fCursor.x)) { 1084 cursor.x = destLine->length + fCursor.x - sourceX; 1085 cursor.y = destTotalLines; 1086 1087 if (cursor.x >= width) { 1088 // The cursor was in free space after the official end 1089 // of line. 1090 cursor.x = width - 1; 1091 } 1092 //debug_printf(" cursor: (%ld, %ld)\n", cursor.x, cursor.y); 1093 1094 cursorSeen = true; 1095 } 1096 1097 if (toCopy > 0) { 1098 memcpy(destLine->cells + destLine->length, 1099 sourceLine->cells + sourceX, toCopy * sizeof(TerminalCell)); 1100 destLine->length += toCopy; 1101 } 1102 1103 bool nextDestLine = false; 1104 if (toCopy == sourceLeft) { 1105 if (!sourceLine->softBreak) 1106 nextDestLine = true; 1107 sourceIndex++; 1108 sourceX = 0; 1109 sourceLine = _HistoryLineAt(sourceIndex - historySize, 1110 lineBuffer); 1111 } else { 1112 destLine->softBreak = true; 1113 nextDestLine = true; 1114 sourceX += toCopy; 1115 } 1116 1117 if (nextDestLine) { 1118 destIndex = (destIndex + 1) % height; 1119 destTotalLines++; 1120 newDestLine = true; 1121 if (cursorSeen && destTotalLines >= maxDestTotalLines) 1122 break; 1123 } 1124 } 1125 1126 // If the last source line had a soft break, the last dest line 1127 // won't have been counted yet. 1128 if (!newDestLine) { 1129 destIndex = (destIndex + 1) % height; 1130 destTotalLines++; 1131 } 1132 1133 //debug_printf(" total lines: %ld -> %ld\n", totalLines, destTotalLines); 1134 1135 if (destTotalLines - destScreenOffset > height) 1136 destScreenOffset = destTotalLines - height; 1137 1138 cursor.y -= destScreenOffset; 1139 1140 // When there are less lines (starting with the screen offset) than 1141 // there's room in the screen, clear the remaining screen lines. 1142 for (int32 i = destTotalLines; i < destScreenOffset + height; i++) { 1143 // Move the line we're going to clear to the history, if that's a 1144 // line we've written earlier. 1145 TerminalLine* line = screen[i % height]; 1146 if (history != NULL && i >= height) 1147 history->AddLine(line); 1148 line->Clear(); 1149 } 1150 1151 // Update the values 1152 _FreeLines(fScreen, fHeight); 1153 delete fHistory; 1154 1155 fScreen = screen; 1156 fHistory = history; 1157 1158 //debug_printf(" cursor: (%ld, %ld) -> (%ld, %ld)\n", fCursor.x, fCursor.y, 1159 //cursor.x, cursor.y); 1160 fCursor.x = cursor.x; 1161 fCursor.y = cursor.y; 1162 fSoftWrappedCursor = false; 1163 //debug_printf(" screen offset: %ld -> %ld\n", fScreenOffset, destScreenOffset % height); 1164 fScreenOffset = destScreenOffset % height; 1165 //debug_printf(" height %ld -> %ld\n", fHeight, height); 1166 //debug_printf(" width %ld -> %ld\n", fWidth, width); 1167 fHeight = height; 1168 fWidth = width; 1169 1170 fScrollTop = 0; 1171 fScrollBottom = fHeight - 1; 1172 1173 return B_OK; 1174 } 1175 1176 1177 void 1178 BasicTerminalBuffer::_Scroll(int32 top, int32 bottom, int32 numLines) 1179 { 1180 if (numLines == 0) 1181 return; 1182 1183 if (numLines > 0) { 1184 // scroll text up 1185 if (top == 0) { 1186 // The lines scrolled out of the screen range are transferred to 1187 // the history. 1188 1189 // add the lines to the history 1190 if (fHistory != NULL) { 1191 int32 toHistory = min_c(numLines, bottom - top + 1); 1192 for (int32 i = 0; i < toHistory; i++) 1193 fHistory->AddLine(_LineAt(i)); 1194 1195 if (toHistory < numLines) 1196 fHistory->AddEmptyLines(numLines - toHistory); 1197 } 1198 1199 if (numLines >= bottom - top + 1) { 1200 // all lines are scrolled out of range -- just clear them 1201 _ClearLines(top, bottom); 1202 } else if (bottom == fHeight - 1) { 1203 // full screen scroll -- update the screen offset and clear new 1204 // lines 1205 fScreenOffset = (fScreenOffset + numLines) % fHeight; 1206 for (int32 i = bottom - numLines + 1; i <= bottom; i++) 1207 _LineAt(i)->Clear(); 1208 } else { 1209 // Partial screen scroll. We move the screen offset anyway, but 1210 // have to move the unscrolled lines to their new location. 1211 // TODO: It may be more efficient to actually move the scrolled 1212 // lines only (might depend on the number of scrolled/unscrolled 1213 // lines). 1214 for (int32 i = bottom + 1; i < fHeight; i++) { 1215 std::swap(fScreen[_LineIndex(i)], 1216 fScreen[_LineIndex(i + numLines)]); 1217 } 1218 1219 // update the screen offset and clear the new lines 1220 fScreenOffset = (fScreenOffset + numLines) % fHeight; 1221 for (int32 i = bottom - numLines + 1; i <= bottom; i++) 1222 _LineAt(i)->Clear(); 1223 } 1224 1225 // scroll/extend dirty range 1226 1227 if (fDirtyInfo.dirtyTop != INT_MAX) { 1228 // If the top or bottom of the dirty region are above the 1229 // bottom of the scroll region, we have to scroll them up. 1230 if (fDirtyInfo.dirtyTop <= bottom) { 1231 fDirtyInfo.dirtyTop -= numLines; 1232 if (fDirtyInfo.dirtyBottom <= bottom) 1233 fDirtyInfo.dirtyBottom -= numLines; 1234 } 1235 1236 // numLines above the bottom become dirty 1237 _Invalidate(bottom - numLines + 1, bottom); 1238 } 1239 1240 fDirtyInfo.linesScrolled += numLines; 1241 1242 // invalidate new empty lines 1243 _Invalidate(bottom + 1 - numLines, bottom); 1244 1245 // In case only part of the screen was scrolled, we invalidate also 1246 // the lines below the scroll region. Those remain unchanged, but 1247 // we can't convey that they have not been scrolled via 1248 // TerminalBufferDirtyInfo. So we need to force the view to sync 1249 // them again. 1250 if (bottom < fHeight - 1) 1251 _Invalidate(bottom + 1, fHeight - 1); 1252 } else if (numLines >= bottom - top + 1) { 1253 // all lines are completely scrolled out of range -- just clear 1254 // them 1255 _ClearLines(top, bottom); 1256 } else { 1257 // partial scroll -- clear the lines scrolled out of range and move 1258 // the other ones 1259 for (int32 i = top + numLines; i <= bottom; i++) { 1260 int32 lineToDrop = _LineIndex(i - numLines); 1261 int32 lineToKeep = _LineIndex(i); 1262 fScreen[lineToDrop]->Clear(); 1263 std::swap(fScreen[lineToDrop], fScreen[lineToKeep]); 1264 } 1265 1266 _Invalidate(top, bottom); 1267 } 1268 } else { 1269 // scroll text down 1270 numLines = -numLines; 1271 1272 if (numLines >= bottom - top + 1) { 1273 // all lines are completely scrolled out of range -- just clear 1274 // them 1275 _ClearLines(top, bottom); 1276 } else { 1277 // partial scroll -- clear the lines scrolled out of range and move 1278 // the other ones 1279 // TODO: When scrolling the whole screen, we could just update fScreenOffset and 1280 // clear the respective lines. 1281 for (int32 i = bottom - numLines; i >= top; i--) { 1282 int32 lineToKeep = _LineIndex(i); 1283 int32 lineToDrop = _LineIndex(i + numLines); 1284 fScreen[lineToDrop]->Clear(); 1285 std::swap(fScreen[lineToDrop], fScreen[lineToKeep]); 1286 } 1287 1288 _Invalidate(top, bottom); 1289 } 1290 } 1291 } 1292 1293 1294 void 1295 BasicTerminalBuffer::_SoftBreakLine() 1296 { 1297 TerminalLine* line = _LineAt(fCursor.y); 1298 line->softBreak = true; 1299 1300 fCursor.x = 0; 1301 if (fCursor.y == fScrollBottom) 1302 _Scroll(fScrollTop, fScrollBottom, 1); 1303 else 1304 fCursor.y++; 1305 } 1306 1307 1308 void 1309 BasicTerminalBuffer::_PadLineToCursor() 1310 { 1311 TerminalLine* line = _LineAt(fCursor.y); 1312 if (line->length < fCursor.x) { 1313 for (int32 i = line->length; i < fCursor.x; i++) { 1314 line->cells[i].character = kSpaceChar; 1315 line->cells[i].attributes = 0; 1316 // TODO: Other attributes? 1317 } 1318 } 1319 } 1320 1321 1322 /*static*/ void 1323 BasicTerminalBuffer::_TruncateLine(TerminalLine* line, int32 length) 1324 { 1325 if (line->length <= length) 1326 return; 1327 1328 if (length > 0 && IS_WIDTH(line->cells[length - 1].attributes)) 1329 length--; 1330 1331 line->length = length; 1332 } 1333 1334 1335 void 1336 BasicTerminalBuffer::_InsertGap(int32 width) 1337 { 1338 // ASSERT(fCursor.x + width <= fWidth) 1339 TerminalLine* line = _LineAt(fCursor.y); 1340 1341 int32 toMove = min_c(line->length - fCursor.x, fWidth - fCursor.x - width); 1342 if (toMove > 0) { 1343 memmove(line->cells + fCursor.x + width, 1344 line->cells + fCursor.x, toMove * sizeof(TerminalCell)); 1345 } 1346 1347 line->length += width; 1348 } 1349 1350 1351 /*! \a endColumn is not inclusive. 1352 */ 1353 TerminalLine* 1354 BasicTerminalBuffer::_GetPartialLineString(BString& string, int32 row, 1355 int32 startColumn, int32 endColumn) const 1356 { 1357 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1358 TerminalLine* line = _HistoryLineAt(row, lineBuffer); 1359 if (line == NULL) 1360 return NULL; 1361 1362 if (endColumn > line->length) 1363 endColumn = line->length; 1364 1365 for (int32 x = startColumn; x < endColumn; x++) { 1366 const TerminalCell& cell = line->cells[x]; 1367 string.Append(cell.character.bytes, cell.character.ByteCount()); 1368 1369 if (IS_WIDTH(cell.attributes)) 1370 x++; 1371 } 1372 1373 return line; 1374 } 1375 1376 1377 /*! Decrement \a pos and return the char at that location. 1378 */ 1379 bool 1380 BasicTerminalBuffer::_PreviousChar(TermPos& pos, UTF8Char& c) const 1381 { 1382 pos.x--; 1383 1384 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1385 TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer); 1386 1387 while (true) { 1388 if (pos.x < 0) { 1389 pos.y--; 1390 line = _HistoryLineAt(pos.y, lineBuffer); 1391 if (line == NULL) 1392 return false; 1393 1394 pos.x = line->length; 1395 if (line->softBreak) { 1396 pos.x--; 1397 } else { 1398 c = '\n'; 1399 return true; 1400 } 1401 } else { 1402 c = line->cells[pos.x].character; 1403 return true; 1404 } 1405 } 1406 } 1407 1408 1409 /*! Return the char at \a pos and increment it. 1410 */ 1411 bool 1412 BasicTerminalBuffer::_NextChar(TermPos& pos, UTF8Char& c) const 1413 { 1414 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1415 TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer); 1416 if (line == NULL) 1417 return false; 1418 1419 if (pos.x >= line->length) { 1420 c = '\n'; 1421 pos.x = 0; 1422 pos.y++; 1423 return true; 1424 } 1425 1426 c = line->cells[pos.x].character; 1427 1428 pos.x++; 1429 while (line != NULL && pos.x >= line->length && line->softBreak) { 1430 pos.x = 0; 1431 pos.y++; 1432 line = _HistoryLineAt(pos.y, lineBuffer); 1433 } 1434 1435 return true; 1436 } 1437