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