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