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