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