1 /* 2 * Copyright 2013, 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(0), 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 & A_WIDTH) != 0; 291 } 292 293 294 int 295 BasicTerminalBuffer::GetChar(int32 row, int32 column, UTF8Char& character, 296 uint32& 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 & A_WIDTH) != 0) 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 uint32& 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, uint32& 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 && IS_WIDTH(line->cells[x - 1].attributes)) 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 + (IS_WIDTH(line->cells[x].attributes) 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 += IS_WIDTH(line->cells[nextPos.x].attributes) 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 += IS_WIDTH(line->cells[pos.x].attributes) ? 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 int32 507 BasicTerminalBuffer::GetLineColor(int32 index) const 508 { 509 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 510 TerminalLine* line = _HistoryLineAt(index, lineBuffer); 511 return line != NULL ? line->attributes : 0; 512 } 513 514 515 bool 516 BasicTerminalBuffer::Find(const char* _pattern, const TermPos& start, 517 bool forward, bool caseSensitive, bool matchWord, TermPos& _matchStart, 518 TermPos& _matchEnd) const 519 { 520 //debug_printf("BasicTerminalBuffer::Find(\"%s\", (%ld, %ld), forward: %d, case: %d, " 521 //"word: %d)\n", _pattern, start.x, start.y, forward, caseSensitive, matchWord); 522 // normalize pos, so that _NextChar() and _PreviousChar() are happy 523 TermPos pos(start); 524 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 525 TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer); 526 if (line != NULL) { 527 if (forward) { 528 while (line != NULL && pos.x >= line->length && line->softBreak) { 529 pos.x = 0; 530 pos.y++; 531 line = _HistoryLineAt(pos.y, lineBuffer); 532 } 533 } else { 534 if (pos.x > line->length) 535 pos.x = line->length; 536 } 537 } 538 539 int32 patternByteLen = strlen(_pattern); 540 541 // convert pattern to UTF8Char array 542 BStackOrHeapArray<UTF8Char, 64> pattern(patternByteLen); 543 if (!pattern.IsValid()) 544 return false; 545 int32 patternLen = 0; 546 while (*_pattern != '\0') { 547 int32 charLen = UTF8Char::ByteCount(*_pattern); 548 if (charLen > 0) { 549 pattern[patternLen].SetTo(_pattern, charLen); 550 551 // if not case sensitive, convert to lower case 552 if (!caseSensitive && charLen == 1) 553 pattern[patternLen] = pattern[patternLen].ToLower(); 554 555 patternLen++; 556 _pattern += charLen; 557 } else 558 _pattern++; 559 } 560 //debug_printf(" pattern byte len: %ld, pattern len: %ld\n", patternByteLen, patternLen); 561 562 if (patternLen == 0) 563 return false; 564 565 // reverse pattern, if searching backward 566 if (!forward) { 567 for (int32 i = 0; i < patternLen / 2; i++) 568 std::swap(pattern[i], pattern[patternLen - i - 1]); 569 } 570 571 // search loop 572 int32 matchIndex = 0; 573 TermPos matchStart; 574 while (true) { 575 //debug_printf(" (%ld, %ld): matchIndex: %ld\n", pos.x, pos.y, matchIndex); 576 TermPos previousPos(pos); 577 UTF8Char c; 578 if (!(forward ? _NextChar(pos, c) : _PreviousChar(pos, c))) 579 return false; 580 581 if (caseSensitive ? (c == pattern[matchIndex]) 582 : (c.ToLower() == pattern[matchIndex])) { 583 if (matchIndex == 0) 584 matchStart = previousPos; 585 586 matchIndex++; 587 588 if (matchIndex == patternLen) { 589 //debug_printf(" match!\n"); 590 // compute the match range 591 TermPos matchEnd(pos); 592 if (!forward) 593 std::swap(matchStart, matchEnd); 594 595 // check word match 596 if (matchWord) { 597 TermPos tempPos(matchStart); 598 if ((_PreviousChar(tempPos, c) && !c.IsSpace()) 599 || (_NextChar(tempPos = matchEnd, c) && !c.IsSpace())) { 600 //debug_printf(" but no word match!\n"); 601 continue; 602 } 603 } 604 605 _matchStart = matchStart; 606 _matchEnd = matchEnd; 607 //debug_printf(" -> (%ld, %ld) - (%ld, %ld)\n", matchStart.x, matchStart.y, 608 //matchEnd.x, matchEnd.y); 609 return true; 610 } 611 } else if (matchIndex > 0) { 612 // continue after the position where we started matching 613 pos = matchStart; 614 if (forward) 615 _NextChar(pos, c); 616 else 617 _PreviousChar(pos, c); 618 matchIndex = 0; 619 } 620 } 621 } 622 623 624 void 625 BasicTerminalBuffer::InsertChar(UTF8Char c) 626 { 627 //debug_printf("BasicTerminalBuffer::InsertChar('%.*s' (%d), %#lx)\n", 628 //(int)c.ByteCount(), c.bytes, c.bytes[0], attributes); 629 fLast = c; 630 int32 width = c.IsFullWidth() ? FULL_WIDTH : HALF_WIDTH; 631 632 if (fSoftWrappedCursor || (fCursor.x + width) > fWidth) 633 _SoftBreakLine(); 634 else 635 _PadLineToCursor(); 636 637 fSoftWrappedCursor = false; 638 639 if (!fOverwriteMode) 640 _InsertGap(width); 641 642 TerminalLine* line = _LineAt(fCursor.y); 643 line->cells[fCursor.x].character = c; 644 line->cells[fCursor.x].attributes 645 = fAttributes | (width == FULL_WIDTH ? A_WIDTH : 0); 646 647 if (line->length < fCursor.x + width) 648 line->length = fCursor.x + width; 649 650 _Invalidate(fCursor.y, fCursor.y); 651 652 fCursor.x += width; 653 654 // TODO: Deal correctly with full-width chars! We must take care not to 655 // overwrite half of a full-width char. This holds also for other methods. 656 657 if (fCursor.x == fWidth) { 658 fCursor.x -= width; 659 fSoftWrappedCursor = true; 660 } 661 } 662 663 664 void 665 BasicTerminalBuffer::FillScreen(UTF8Char c, uint32 attributes) 666 { 667 uint32 width = HALF_WIDTH; 668 if (c.IsFullWidth()) { 669 attributes |= A_WIDTH; 670 width = FULL_WIDTH; 671 } 672 673 fSoftWrappedCursor = false; 674 675 for (int32 y = 0; y < fHeight; y++) { 676 TerminalLine *line = _LineAt(y); 677 for (int32 x = 0; x < fWidth / (int32)width; x++) { 678 line->cells[x].character = c; 679 line->cells[x].attributes = attributes; 680 } 681 line->length = fWidth / width; 682 } 683 684 _Invalidate(0, fHeight - 1); 685 } 686 687 688 void 689 BasicTerminalBuffer::InsertCR() 690 { 691 TerminalLine* line = _LineAt(fCursor.y); 692 693 line->attributes = fAttributes; 694 line->softBreak = false; 695 fSoftWrappedCursor = false; 696 fCursor.x = 0; 697 _Invalidate(fCursor.y, fCursor.y); 698 _CursorChanged(); 699 } 700 701 702 void 703 BasicTerminalBuffer::InsertLF() 704 { 705 fSoftWrappedCursor = false; 706 707 // If we're at the end of the scroll region, scroll. Otherwise just advance 708 // the cursor. 709 if (fCursor.y == fScrollBottom) { 710 _Scroll(fScrollTop, fScrollBottom, 1); 711 } else { 712 if (fCursor.y < fHeight - 1) 713 fCursor.y++; 714 _CursorChanged(); 715 } 716 } 717 718 719 void 720 BasicTerminalBuffer::InsertRI() 721 { 722 fSoftWrappedCursor = false; 723 724 // If we're at the beginning of the scroll region, scroll. Otherwise just 725 // reverse the cursor. 726 if (fCursor.y == fScrollTop) { 727 _Scroll(fScrollTop, fScrollBottom, -1); 728 } else { 729 if (fCursor.y > 0) 730 fCursor.y--; 731 _CursorChanged(); 732 } 733 } 734 735 736 void 737 BasicTerminalBuffer::InsertTab() 738 { 739 int32 x; 740 741 fSoftWrappedCursor = false; 742 743 // Find the next tab stop 744 for (x = fCursor.x + 1; x < fWidth && !fTabStops[x]; x++) 745 ; 746 // Ensure x stayx within the line bounds 747 x = restrict_value(x, 0, fWidth - 1); 748 749 if (x != fCursor.x) { 750 TerminalLine* line = _LineAt(fCursor.y); 751 for (int32 i = fCursor.x; i <= x; i++) { 752 if (line->length <= i) { 753 line->cells[i].character = ' '; 754 line->cells[i].attributes = fAttributes; 755 } 756 } 757 fCursor.x = x; 758 if (line->length < fCursor.x) 759 line->length = fCursor.x; 760 _CursorChanged(); 761 } 762 } 763 764 765 void 766 BasicTerminalBuffer::InsertCursorBackTab(int32 numTabs) 767 { 768 int32 x = fCursor.x - 1; 769 770 fSoftWrappedCursor = false; 771 772 // Find the next tab stop 773 while (numTabs-- > 0) 774 for (; x >=0 && !fTabStops[x]; x--) 775 ; 776 // Ensure x stays within the line bounds 777 x = restrict_value(x, 0, fWidth - 1); 778 779 if (x != fCursor.x) { 780 fCursor.x = x; 781 _CursorChanged(); 782 } 783 } 784 785 786 void 787 BasicTerminalBuffer::InsertLines(int32 numLines) 788 { 789 if (fCursor.y >= fScrollTop && fCursor.y < fScrollBottom) { 790 fSoftWrappedCursor = false; 791 _Scroll(fCursor.y, fScrollBottom, -numLines); 792 } 793 } 794 795 796 void 797 BasicTerminalBuffer::SetInsertMode(int flag) 798 { 799 fOverwriteMode = flag == MODE_OVER; 800 } 801 802 803 void 804 BasicTerminalBuffer::InsertSpace(int32 num) 805 { 806 // TODO: Deal with full-width chars! 807 if (fCursor.x + num > fWidth) 808 num = fWidth - fCursor.x; 809 810 if (num > 0) { 811 fSoftWrappedCursor = false; 812 _PadLineToCursor(); 813 _InsertGap(num); 814 815 TerminalLine* line = _LineAt(fCursor.y); 816 for (int32 i = fCursor.x; i < fCursor.x + num; i++) { 817 line->cells[i].character = kSpaceChar; 818 line->cells[i].attributes = line->cells[fCursor.x - 1].attributes; 819 } 820 line->attributes = fAttributes; 821 822 _Invalidate(fCursor.y, fCursor.y); 823 } 824 } 825 826 827 void 828 BasicTerminalBuffer::EraseCharsFrom(int32 first, int32 numChars) 829 { 830 TerminalLine* line = _LineAt(fCursor.y); 831 832 int32 end = min_c(first + numChars, fWidth); 833 for (int32 i = first; i < end; i++) 834 line->cells[i].attributes = fAttributes; 835 836 line->attributes = fAttributes; 837 838 fSoftWrappedCursor = false; 839 840 end = min_c(first + numChars, line->length); 841 if (first > 0 && IS_WIDTH(line->cells[first - 1].attributes)) 842 first--; 843 if (end > 0 && IS_WIDTH(line->cells[end - 1].attributes)) 844 end++; 845 846 for (int32 i = first; i < end; i++) { 847 line->cells[i].character = kSpaceChar; 848 line->cells[i].attributes = fAttributes; 849 } 850 851 _Invalidate(fCursor.y, fCursor.y); 852 } 853 854 855 void 856 BasicTerminalBuffer::EraseAbove() 857 { 858 // Clear the preceding lines. 859 if (fCursor.y > 0) 860 _ClearLines(0, fCursor.y - 1); 861 862 fSoftWrappedCursor = false; 863 864 // Delete the chars on the cursor line before (and including) the cursor. 865 TerminalLine* line = _LineAt(fCursor.y); 866 if (fCursor.x < line->length) { 867 int32 to = fCursor.x; 868 if (IS_WIDTH(line->cells[fCursor.x].attributes)) 869 to++; 870 for (int32 i = 0; i <= to; i++) { 871 line->cells[i].attributes = fAttributes; 872 line->cells[i].character = kSpaceChar; 873 } 874 } else 875 line->Clear(fAttributes, fWidth); 876 877 _Invalidate(fCursor.y, fCursor.y); 878 } 879 880 881 void 882 BasicTerminalBuffer::EraseBelow() 883 { 884 fSoftWrappedCursor = false; 885 886 // Clear the following lines. 887 if (fCursor.y < fHeight - 1) 888 _ClearLines(fCursor.y + 1, fHeight - 1); 889 890 // Delete the chars on the cursor line after (and including) the cursor. 891 DeleteColumns(); 892 } 893 894 895 void 896 BasicTerminalBuffer::EraseAll() 897 { 898 fSoftWrappedCursor = false; 899 _Scroll(0, fHeight - 1, fHeight); 900 } 901 902 903 void 904 BasicTerminalBuffer::DeleteChars(int32 numChars) 905 { 906 fSoftWrappedCursor = false; 907 908 TerminalLine* line = _LineAt(fCursor.y); 909 if (fCursor.x < line->length) { 910 if (fCursor.x + numChars < line->length) { 911 int32 left = line->length - fCursor.x - numChars; 912 memmove(line->cells + fCursor.x, line->cells + fCursor.x + numChars, 913 left * sizeof(TerminalCell)); 914 line->length = fCursor.x + left; 915 // process BCE on freed tail cells 916 for (int i = 0; i < numChars; i++) 917 line->cells[fCursor.x + left + i].attributes = fAttributes; 918 } else { 919 // process BCE on freed tail cells 920 for (int i = 0; i < line->length - fCursor.x; i++) 921 line->cells[fCursor.x + i].attributes = fAttributes; 922 // remove all remaining chars 923 line->length = fCursor.x; 924 } 925 926 _Invalidate(fCursor.y, fCursor.y); 927 } 928 } 929 930 931 void 932 BasicTerminalBuffer::DeleteColumnsFrom(int32 first) 933 { 934 fSoftWrappedCursor = false; 935 936 TerminalLine* line = _LineAt(fCursor.y); 937 938 for (int32 i = first; i < fWidth; i++) 939 line->cells[i].attributes = fAttributes; 940 941 if (first <= line->length) { 942 line->length = first; 943 line->attributes = fAttributes; 944 } 945 _Invalidate(fCursor.y, fCursor.y); 946 } 947 948 949 void 950 BasicTerminalBuffer::DeleteLines(int32 numLines) 951 { 952 if (fCursor.y >= fScrollTop && fCursor.y <= fScrollBottom) { 953 fSoftWrappedCursor = false; 954 _Scroll(fCursor.y, fScrollBottom, numLines); 955 } 956 } 957 958 959 void 960 BasicTerminalBuffer::SaveCursor() 961 { 962 fSavedCursors.push(fCursor); 963 } 964 965 966 void 967 BasicTerminalBuffer::RestoreCursor() 968 { 969 if (fSavedCursors.size() == 0) 970 return; 971 972 _SetCursor(fSavedCursors.top().x, fSavedCursors.top().y, true); 973 fSavedCursors.pop(); 974 } 975 976 977 void 978 BasicTerminalBuffer::SetScrollRegion(int32 top, int32 bottom) 979 { 980 fScrollTop = restrict_value(top, 0, fHeight - 1); 981 fScrollBottom = restrict_value(bottom, fScrollTop, fHeight - 1); 982 983 // also sets the cursor position 984 _SetCursor(0, 0, false); 985 } 986 987 988 void 989 BasicTerminalBuffer::SetOriginMode(bool enabled) 990 { 991 fOriginMode = enabled; 992 _SetCursor(0, 0, false); 993 } 994 995 996 void 997 BasicTerminalBuffer::SaveOriginMode() 998 { 999 fSavedOriginMode = fOriginMode; 1000 } 1001 1002 1003 void 1004 BasicTerminalBuffer::RestoreOriginMode() 1005 { 1006 fOriginMode = fSavedOriginMode; 1007 } 1008 1009 1010 void 1011 BasicTerminalBuffer::SetTabStop(int32 x) 1012 { 1013 x = restrict_value(x, 0, fWidth - 1); 1014 fTabStops[x] = true; 1015 } 1016 1017 1018 void 1019 BasicTerminalBuffer::ClearTabStop(int32 x) 1020 { 1021 x = restrict_value(x, 0, fWidth - 1); 1022 fTabStops[x] = false; 1023 } 1024 1025 1026 void 1027 BasicTerminalBuffer::ClearAllTabStops() 1028 { 1029 for (int32 i = 0; i < fWidth; i++) 1030 fTabStops[i] = false; 1031 } 1032 1033 1034 void 1035 BasicTerminalBuffer::NotifyListener() 1036 { 1037 // Implemented by derived classes. 1038 } 1039 1040 1041 // #pragma mark - private methods 1042 1043 1044 void 1045 BasicTerminalBuffer::_SetCursor(int32 x, int32 y, bool absolute) 1046 { 1047 //debug_printf("BasicTerminalBuffer::_SetCursor(%d, %d)\n", x, y); 1048 fSoftWrappedCursor = false; 1049 1050 x = restrict_value(x, 0, fWidth - 1); 1051 if (fOriginMode && !absolute) { 1052 y += fScrollTop; 1053 y = restrict_value(y, fScrollTop, fScrollBottom); 1054 } else { 1055 y = restrict_value(y, 0, fHeight - 1); 1056 } 1057 1058 if (x != fCursor.x || y != fCursor.y) { 1059 fCursor.x = x; 1060 fCursor.y = y; 1061 _CursorChanged(); 1062 } 1063 } 1064 1065 1066 void 1067 BasicTerminalBuffer::_InvalidateAll() 1068 { 1069 fDirtyInfo.invalidateAll = true; 1070 1071 if (!fDirtyInfo.messageSent) { 1072 NotifyListener(); 1073 fDirtyInfo.messageSent = true; 1074 } 1075 } 1076 1077 1078 /* static */ TerminalLine** 1079 BasicTerminalBuffer::_AllocateLines(int32 width, int32 count) 1080 { 1081 TerminalLine** lines = (TerminalLine**)malloc(sizeof(TerminalLine*) * count); 1082 if (lines == NULL) 1083 return NULL; 1084 1085 for (int32 i = 0; i < count; i++) { 1086 const int32 size = sizeof(TerminalLine) 1087 + sizeof(TerminalCell) * (width - 1); 1088 lines[i] = (TerminalLine*)malloc(size); 1089 if (lines[i] == NULL) { 1090 _FreeLines(lines, i); 1091 return NULL; 1092 } 1093 lines[i]->Clear(0, width); 1094 } 1095 1096 return lines; 1097 } 1098 1099 1100 /* static */ void 1101 BasicTerminalBuffer::_FreeLines(TerminalLine** lines, int32 count) 1102 { 1103 if (lines != NULL) { 1104 for (int32 i = 0; i < count; i++) 1105 free(lines[i]); 1106 1107 free(lines); 1108 } 1109 } 1110 1111 1112 void 1113 BasicTerminalBuffer::_ClearLines(int32 first, int32 last) 1114 { 1115 int32 firstCleared = -1; 1116 int32 lastCleared = -1; 1117 1118 for (int32 i = first; i <= last; i++) { 1119 TerminalLine* line = _LineAt(i); 1120 if (line->length > 0) { 1121 if (firstCleared == -1) 1122 firstCleared = i; 1123 lastCleared = i; 1124 } 1125 1126 line->Clear(fAttributes, fWidth); 1127 } 1128 1129 if (firstCleared >= 0) 1130 _Invalidate(firstCleared, lastCleared); 1131 } 1132 1133 1134 status_t 1135 BasicTerminalBuffer::_ResizeHistory(int32 width, int32 historyCapacity) 1136 { 1137 if (width == fWidth && historyCapacity == HistoryCapacity()) 1138 return B_OK; 1139 1140 if (historyCapacity <= 0) { 1141 // new history capacity is 0 -- delete the old history object 1142 delete fHistory; 1143 fHistory = NULL; 1144 1145 return B_OK; 1146 } 1147 1148 HistoryBuffer* history = new(std::nothrow) HistoryBuffer; 1149 if (history == NULL) 1150 return B_NO_MEMORY; 1151 1152 status_t error = history->Init(width, historyCapacity); 1153 if (error != B_OK) { 1154 delete history; 1155 return error; 1156 } 1157 1158 // Transfer the lines from the old history to the new one. 1159 if (fHistory != NULL) { 1160 int32 historySize = min_c(HistorySize(), historyCapacity); 1161 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1162 for (int32 i = historySize - 1; i >= 0; i--) { 1163 TerminalLine* line = fHistory->GetTerminalLineAt(i, lineBuffer); 1164 if (line->length > width) 1165 _TruncateLine(line, width); 1166 history->AddLine(line); 1167 } 1168 } 1169 1170 delete fHistory; 1171 fHistory = history; 1172 1173 return B_OK; 1174 } 1175 1176 1177 status_t 1178 BasicTerminalBuffer::_ResizeSimple(int32 width, int32 height, 1179 int32 historyCapacity) 1180 { 1181 //debug_printf("BasicTerminalBuffer::_ResizeSimple(): (%ld, %ld) -> " 1182 //"(%ld, %ld)\n", fWidth, fHeight, width, height); 1183 if (width == fWidth && height == fHeight) 1184 return B_OK; 1185 1186 if (width != fWidth || historyCapacity != HistoryCapacity()) { 1187 status_t error = _ResizeHistory(width, historyCapacity); 1188 if (error != B_OK) 1189 return error; 1190 } 1191 1192 TerminalLine** lines = _AllocateLines(width, height); 1193 if (lines == NULL) 1194 return B_NO_MEMORY; 1195 // NOTE: If width or history capacity changed, the object will be in 1196 // an invalid state, since the history will already use the new values. 1197 1198 int32 endLine = min_c(fHeight, height); 1199 int32 firstLine = 0; 1200 1201 if (height < fHeight) { 1202 if (endLine <= fCursor.y) { 1203 endLine = fCursor.y + 1; 1204 firstLine = endLine - height; 1205 } 1206 1207 // push the first lines to the history 1208 if (fHistory != NULL) { 1209 for (int32 i = 0; i < firstLine; i++) { 1210 TerminalLine* line = _LineAt(i); 1211 if (width < fWidth) 1212 _TruncateLine(line, width); 1213 fHistory->AddLine(line); 1214 } 1215 } 1216 } 1217 1218 // copy the lines we keep 1219 for (int32 i = firstLine; i < endLine; i++) { 1220 TerminalLine* sourceLine = _LineAt(i); 1221 TerminalLine* destLine = lines[i - firstLine]; 1222 if (width < fWidth) 1223 _TruncateLine(sourceLine, width); 1224 memcpy(destLine, sourceLine, (int32)sizeof(TerminalLine) 1225 + (sourceLine->length - 1) * (int32)sizeof(TerminalCell)); 1226 } 1227 1228 // clear the remaining lines 1229 for (int32 i = endLine - firstLine; i < height; i++) 1230 lines[i]->Clear(fAttributes, width); 1231 1232 _FreeLines(fScreen, fHeight); 1233 fScreen = lines; 1234 1235 if (fWidth != width) { 1236 status_t error = _ResetTabStops(width); 1237 if (error != B_OK) 1238 return error; 1239 } 1240 1241 fWidth = width; 1242 fHeight = height; 1243 1244 fScrollTop = 0; 1245 fScrollBottom = fHeight - 1; 1246 fOriginMode = fSavedOriginMode = false; 1247 1248 fScreenOffset = 0; 1249 1250 if (fCursor.x > width) 1251 fCursor.x = width; 1252 fCursor.y -= firstLine; 1253 fSoftWrappedCursor = false; 1254 1255 return B_OK; 1256 } 1257 1258 1259 status_t 1260 BasicTerminalBuffer::_ResizeRewrap(int32 width, int32 height, 1261 int32 historyCapacity) 1262 { 1263 //debug_printf("BasicTerminalBuffer::_ResizeRewrap(): (%ld, %ld, history: %ld) -> " 1264 //"(%ld, %ld, history: %ld)\n", fWidth, fHeight, HistoryCapacity(), width, height, 1265 //historyCapacity); 1266 1267 // The width stays the same. _ResizeSimple() does exactly what we need. 1268 if (width == fWidth) 1269 return _ResizeSimple(width, height, historyCapacity); 1270 1271 // The width changes. We have to allocate a new line array, a new history 1272 // and re-wrap all lines. 1273 1274 TerminalLine** screen = _AllocateLines(width, height); 1275 if (screen == NULL) 1276 return B_NO_MEMORY; 1277 1278 HistoryBuffer* history = NULL; 1279 1280 if (historyCapacity > 0) { 1281 history = new(std::nothrow) HistoryBuffer; 1282 if (history == NULL) { 1283 _FreeLines(screen, height); 1284 return B_NO_MEMORY; 1285 } 1286 1287 status_t error = history->Init(width, historyCapacity); 1288 if (error != B_OK) { 1289 _FreeLines(screen, height); 1290 delete history; 1291 return error; 1292 } 1293 } 1294 1295 int32 historySize = HistorySize(); 1296 int32 totalLines = historySize + fHeight; 1297 1298 // re-wrap 1299 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1300 TermPos cursor; 1301 int32 destIndex = 0; 1302 int32 sourceIndex = 0; 1303 int32 sourceX = 0; 1304 int32 destTotalLines = 0; 1305 int32 destScreenOffset = 0; 1306 int32 maxDestTotalLines = INT_MAX; 1307 bool newDestLine = true; 1308 bool cursorSeen = false; 1309 TerminalLine* sourceLine = _HistoryLineAt(-historySize, lineBuffer); 1310 1311 while (sourceIndex < totalLines) { 1312 TerminalLine* destLine = screen[destIndex]; 1313 1314 if (newDestLine) { 1315 // Clear a new dest line before using it. If we're about to 1316 // overwrite an previously written line, we push it to the 1317 // history first, though. 1318 if (history != NULL && destTotalLines >= height) 1319 history->AddLine(screen[destIndex]); 1320 destLine->Clear(fAttributes, width); 1321 newDestLine = false; 1322 } 1323 1324 int32 sourceLeft = sourceLine->length - sourceX; 1325 int32 destLeft = width - destLine->length; 1326 //debug_printf(" source: %ld, left: %ld, dest: %ld, left: %ld\n", 1327 //sourceIndex, sourceLeft, destIndex, destLeft); 1328 1329 if (sourceIndex == historySize && sourceX == 0) { 1330 destScreenOffset = destTotalLines; 1331 if (destLeft == 0 && sourceLeft > 0) 1332 destScreenOffset++; 1333 maxDestTotalLines = destScreenOffset + height; 1334 //debug_printf(" destScreenOffset: %ld\n", destScreenOffset); 1335 } 1336 1337 int32 toCopy = min_c(sourceLeft, destLeft); 1338 // If the last cell to copy is the first cell of a 1339 // full-width char, don't copy it yet. 1340 if (toCopy > 0 && IS_WIDTH( 1341 sourceLine->cells[sourceX + toCopy - 1].attributes)) { 1342 //debug_printf(" -> last char is full-width -- don't copy it\n"); 1343 toCopy--; 1344 } 1345 1346 // translate the cursor position 1347 if (fCursor.y + historySize == sourceIndex 1348 && fCursor.x >= sourceX 1349 && (fCursor.x < sourceX + toCopy 1350 || (destLeft >= sourceLeft 1351 && sourceX + sourceLeft <= fCursor.x))) { 1352 cursor.x = destLine->length + fCursor.x - sourceX; 1353 cursor.y = destTotalLines; 1354 1355 if (cursor.x >= width) { 1356 // The cursor was in free space after the official end 1357 // of line. 1358 cursor.x = width - 1; 1359 } 1360 //debug_printf(" cursor: (%ld, %ld)\n", cursor.x, cursor.y); 1361 1362 cursorSeen = true; 1363 } 1364 1365 if (toCopy > 0) { 1366 memcpy(destLine->cells + destLine->length, 1367 sourceLine->cells + sourceX, toCopy * sizeof(TerminalCell)); 1368 destLine->length += toCopy; 1369 } 1370 1371 destLine->attributes = sourceLine->attributes; 1372 1373 bool nextDestLine = false; 1374 if (toCopy == sourceLeft) { 1375 if (!sourceLine->softBreak) 1376 nextDestLine = true; 1377 sourceIndex++; 1378 sourceX = 0; 1379 sourceLine = _HistoryLineAt(sourceIndex - historySize, 1380 lineBuffer); 1381 } else { 1382 destLine->softBreak = true; 1383 nextDestLine = true; 1384 sourceX += toCopy; 1385 } 1386 1387 if (nextDestLine) { 1388 destIndex = (destIndex + 1) % height; 1389 destTotalLines++; 1390 newDestLine = true; 1391 if (cursorSeen && destTotalLines >= maxDestTotalLines) 1392 break; 1393 } 1394 } 1395 1396 // If the last source line had a soft break, the last dest line 1397 // won't have been counted yet. 1398 if (!newDestLine) { 1399 destIndex = (destIndex + 1) % height; 1400 destTotalLines++; 1401 } 1402 1403 //debug_printf(" total lines: %ld -> %ld\n", totalLines, destTotalLines); 1404 1405 if (destTotalLines - destScreenOffset > height) 1406 destScreenOffset = destTotalLines - height; 1407 1408 cursor.y -= destScreenOffset; 1409 1410 // When there are less lines (starting with the screen offset) than 1411 // there's room in the screen, clear the remaining screen lines. 1412 for (int32 i = destTotalLines; i < destScreenOffset + height; i++) { 1413 // Move the line we're going to clear to the history, if that's a 1414 // line we've written earlier. 1415 TerminalLine* line = screen[i % height]; 1416 if (history != NULL && i >= height) 1417 history->AddLine(line); 1418 line->Clear(fAttributes, width); 1419 } 1420 1421 // Update the values 1422 _FreeLines(fScreen, fHeight); 1423 delete fHistory; 1424 1425 fScreen = screen; 1426 fHistory = history; 1427 1428 if (fWidth != width) { 1429 status_t error = _ResetTabStops(width); 1430 if (error != B_OK) 1431 return error; 1432 } 1433 1434 //debug_printf(" cursor: (%ld, %ld) -> (%ld, %ld)\n", fCursor.x, fCursor.y, 1435 //cursor.x, cursor.y); 1436 fCursor.x = cursor.x; 1437 fCursor.y = cursor.y; 1438 fSoftWrappedCursor = false; 1439 //debug_printf(" screen offset: %ld -> %ld\n", fScreenOffset, destScreenOffset % height); 1440 fScreenOffset = destScreenOffset % height; 1441 //debug_printf(" height %ld -> %ld\n", fHeight, height); 1442 //debug_printf(" width %ld -> %ld\n", fWidth, width); 1443 fHeight = height; 1444 fWidth = width; 1445 1446 fScrollTop = 0; 1447 fScrollBottom = fHeight - 1; 1448 fOriginMode = fSavedOriginMode = false; 1449 1450 return B_OK; 1451 } 1452 1453 1454 status_t 1455 BasicTerminalBuffer::_ResetTabStops(int32 width) 1456 { 1457 if (fTabStops != NULL) 1458 delete[] fTabStops; 1459 1460 fTabStops = new(std::nothrow) bool[width]; 1461 if (fTabStops == NULL) 1462 return B_NO_MEMORY; 1463 1464 for (int32 i = 0; i < width; i++) 1465 fTabStops[i] = (i % TAB_WIDTH) == 0; 1466 return B_OK; 1467 } 1468 1469 1470 void 1471 BasicTerminalBuffer::_Scroll(int32 top, int32 bottom, int32 numLines) 1472 { 1473 if (numLines == 0) 1474 return; 1475 1476 if (numLines > 0) { 1477 // scroll text up 1478 if (top == 0) { 1479 // The lines scrolled out of the screen range are transferred to 1480 // the history. 1481 1482 // add the lines to the history 1483 if (fHistory != NULL) { 1484 int32 toHistory = min_c(numLines, bottom - top + 1); 1485 for (int32 i = 0; i < toHistory; i++) 1486 fHistory->AddLine(_LineAt(i)); 1487 1488 if (toHistory < numLines) 1489 fHistory->AddEmptyLines(numLines - toHistory); 1490 } 1491 1492 if (numLines >= bottom - top + 1) { 1493 // all lines are scrolled out of range -- just clear them 1494 _ClearLines(top, bottom); 1495 } else if (bottom == fHeight - 1) { 1496 // full screen scroll -- update the screen offset and clear new 1497 // lines 1498 fScreenOffset = (fScreenOffset + numLines) % fHeight; 1499 for (int32 i = bottom - numLines + 1; i <= bottom; i++) 1500 _LineAt(i)->Clear(fAttributes, fWidth); 1501 } else { 1502 // Partial screen scroll. We move the screen offset anyway, but 1503 // have to move the unscrolled lines to their new location. 1504 // TODO: It may be more efficient to actually move the scrolled 1505 // lines only (might depend on the number of scrolled/unscrolled 1506 // lines). 1507 for (int32 i = fHeight - 1; i > bottom; i--) { 1508 std::swap(fScreen[_LineIndex(i)], 1509 fScreen[_LineIndex(i + numLines)]); 1510 } 1511 1512 // update the screen offset and clear the new lines 1513 fScreenOffset = (fScreenOffset + numLines) % fHeight; 1514 for (int32 i = bottom - numLines + 1; i <= bottom; i++) 1515 _LineAt(i)->Clear(fAttributes, fWidth); 1516 } 1517 1518 // scroll/extend dirty range 1519 1520 if (fDirtyInfo.dirtyTop != INT_MAX) { 1521 // If the top or bottom of the dirty region are above the 1522 // bottom of the scroll region, we have to scroll them up. 1523 if (fDirtyInfo.dirtyTop <= bottom) { 1524 fDirtyInfo.dirtyTop -= numLines; 1525 if (fDirtyInfo.dirtyBottom <= bottom) 1526 fDirtyInfo.dirtyBottom -= numLines; 1527 } 1528 1529 // numLines above the bottom become dirty 1530 _Invalidate(bottom - numLines + 1, bottom); 1531 } 1532 1533 fDirtyInfo.linesScrolled += numLines; 1534 1535 // invalidate new empty lines 1536 _Invalidate(bottom + 1 - numLines, bottom); 1537 1538 // In case only part of the screen was scrolled, we invalidate also 1539 // the lines below the scroll region. Those remain unchanged, but 1540 // we can't convey that they have not been scrolled via 1541 // TerminalBufferDirtyInfo. So we need to force the view to sync 1542 // them again. 1543 if (bottom < fHeight - 1) 1544 _Invalidate(bottom + 1, fHeight - 1); 1545 } else if (numLines >= bottom - top + 1) { 1546 // all lines are completely scrolled out of range -- just clear 1547 // them 1548 _ClearLines(top, bottom); 1549 } else { 1550 // partial scroll -- clear the lines scrolled out of range and move 1551 // the other ones 1552 for (int32 i = top + numLines; i <= bottom; i++) { 1553 int32 lineToDrop = _LineIndex(i - numLines); 1554 int32 lineToKeep = _LineIndex(i); 1555 fScreen[lineToDrop]->Clear(fAttributes, fWidth); 1556 std::swap(fScreen[lineToDrop], fScreen[lineToKeep]); 1557 } 1558 // clear any lines between the two swapped ranges above 1559 for (int32 i = bottom - numLines + 1; i < top + numLines; i++) 1560 _LineAt(i)->Clear(fAttributes, fWidth); 1561 1562 _Invalidate(top, bottom); 1563 } 1564 } else { 1565 // scroll text down 1566 numLines = -numLines; 1567 1568 if (numLines >= bottom - top + 1) { 1569 // all lines are completely scrolled out of range -- just clear 1570 // them 1571 _ClearLines(top, bottom); 1572 } else { 1573 // partial scroll -- clear the lines scrolled out of range and move 1574 // the other ones 1575 // TODO: When scrolling the whole screen, we could just update fScreenOffset and 1576 // clear the respective lines. 1577 for (int32 i = bottom - numLines; i >= top; i--) { 1578 int32 lineToKeep = _LineIndex(i); 1579 int32 lineToDrop = _LineIndex(i + numLines); 1580 fScreen[lineToDrop]->Clear(fAttributes, fWidth); 1581 std::swap(fScreen[lineToDrop], fScreen[lineToKeep]); 1582 } 1583 // clear any lines between the two swapped ranges above 1584 for (int32 i = bottom - numLines + 1; i < top + numLines; i++) 1585 _LineAt(i)->Clear(fAttributes, fWidth); 1586 1587 _Invalidate(top, bottom); 1588 } 1589 } 1590 } 1591 1592 1593 void 1594 BasicTerminalBuffer::_SoftBreakLine() 1595 { 1596 TerminalLine* line = _LineAt(fCursor.y); 1597 line->softBreak = true; 1598 1599 fCursor.x = 0; 1600 if (fCursor.y == fScrollBottom) 1601 _Scroll(fScrollTop, fScrollBottom, 1); 1602 else 1603 fCursor.y++; 1604 } 1605 1606 1607 void 1608 BasicTerminalBuffer::_PadLineToCursor() 1609 { 1610 TerminalLine* line = _LineAt(fCursor.y); 1611 if (line->length < fCursor.x) 1612 for (int32 i = line->length; i < fCursor.x; i++) 1613 line->cells[i].character = kSpaceChar; 1614 } 1615 1616 1617 /*static*/ void 1618 BasicTerminalBuffer::_TruncateLine(TerminalLine* line, int32 length) 1619 { 1620 if (line->length <= length) 1621 return; 1622 1623 if (length > 0 && IS_WIDTH(line->cells[length - 1].attributes)) 1624 length--; 1625 1626 line->length = length; 1627 } 1628 1629 1630 void 1631 BasicTerminalBuffer::_InsertGap(int32 width) 1632 { 1633 // ASSERT(fCursor.x + width <= fWidth) 1634 TerminalLine* line = _LineAt(fCursor.y); 1635 1636 int32 toMove = min_c(line->length - fCursor.x, fWidth - fCursor.x - width); 1637 if (toMove > 0) { 1638 memmove(line->cells + fCursor.x + width, 1639 line->cells + fCursor.x, toMove * sizeof(TerminalCell)); 1640 } 1641 1642 line->length = min_c(line->length + width, fWidth); 1643 } 1644 1645 1646 /*! \a endColumn is not inclusive. 1647 */ 1648 TerminalLine* 1649 BasicTerminalBuffer::_GetPartialLineString(BString& string, int32 row, 1650 int32 startColumn, int32 endColumn) const 1651 { 1652 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1653 TerminalLine* line = _HistoryLineAt(row, lineBuffer); 1654 if (line == NULL) 1655 return NULL; 1656 1657 if (endColumn > line->length) 1658 endColumn = line->length; 1659 1660 for (int32 x = startColumn; x < endColumn; x++) { 1661 const TerminalCell& cell = line->cells[x]; 1662 string.Append(cell.character.bytes, cell.character.ByteCount()); 1663 1664 if (IS_WIDTH(cell.attributes)) 1665 x++; 1666 } 1667 1668 return line; 1669 } 1670 1671 1672 /*! Decrement \a pos and return the char at that location. 1673 */ 1674 bool 1675 BasicTerminalBuffer::_PreviousChar(TermPos& pos, UTF8Char& c) const 1676 { 1677 pos.x--; 1678 1679 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1680 TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer); 1681 1682 while (true) { 1683 if (pos.x < 0) { 1684 pos.y--; 1685 line = _HistoryLineAt(pos.y, lineBuffer); 1686 if (line == NULL) 1687 return false; 1688 1689 pos.x = line->length; 1690 if (line->softBreak) { 1691 pos.x--; 1692 } else { 1693 c = '\n'; 1694 return true; 1695 } 1696 } else { 1697 c = line->cells[pos.x].character; 1698 return true; 1699 } 1700 } 1701 } 1702 1703 1704 /*! Return the char at \a pos and increment it. 1705 */ 1706 bool 1707 BasicTerminalBuffer::_NextChar(TermPos& pos, UTF8Char& c) const 1708 { 1709 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1710 TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer); 1711 if (line == NULL) 1712 return false; 1713 1714 if (pos.x >= line->length) { 1715 c = '\n'; 1716 pos.x = 0; 1717 pos.y++; 1718 return true; 1719 } 1720 1721 c = line->cells[pos.x].character; 1722 1723 pos.x++; 1724 while (line != NULL && pos.x >= line->length && line->softBreak) { 1725 pos.x = 0; 1726 pos.y++; 1727 line = _HistoryLineAt(pos.y, lineBuffer); 1728 } 1729 1730 return true; 1731 } 1732 1733 1734 bool 1735 BasicTerminalBuffer::_PreviousLinePos(TerminalLine* lineBuffer, 1736 TerminalLine*& line, TermPos& pos) const 1737 { 1738 if (--pos.x < 0) { 1739 // Hit the beginning of the line -- continue at the end of the 1740 // previous line, if it soft-breaks. 1741 pos.y--; 1742 if ((line = _HistoryLineAt(pos.y, lineBuffer)) == NULL 1743 || !line->softBreak || line->length == 0) { 1744 return false; 1745 } 1746 pos.x = line->length - 1; 1747 } 1748 if (pos.x > 0 && IS_WIDTH(line->cells[pos.x - 1].attributes)) 1749 pos.x--; 1750 1751 return true; 1752 } 1753 1754 1755 bool 1756 BasicTerminalBuffer::_NormalizeLinePos(TerminalLine* lineBuffer, 1757 TerminalLine*& line, TermPos& pos) const 1758 { 1759 if (pos.x < line->length) 1760 return true; 1761 1762 // Hit the end of the line -- if it soft-breaks continue with the 1763 // next line. 1764 if (!line->softBreak) 1765 return false; 1766 1767 pos.y++; 1768 pos.x = 0; 1769 line = _HistoryLineAt(pos.y, lineBuffer); 1770 return line != NULL; 1771 } 1772 1773 1774 #ifdef USE_DEBUG_SNAPSHOTS 1775 1776 void 1777 BasicTerminalBuffer::MakeLinesSnapshots(time_t timeStamp, const char* fileName) 1778 { 1779 BString str("/var/log/"); 1780 struct tm* ts = gmtime(&timeStamp); 1781 str << ts->tm_hour << ts->tm_min << ts->tm_sec; 1782 str << fileName; 1783 FILE* fileOut = fopen(str.String(), "w"); 1784 1785 bool dumpHistory = false; 1786 do { 1787 if (dumpHistory && fHistory == NULL) { 1788 fprintf(fileOut, "> History is empty <\n"); 1789 break; 1790 } 1791 1792 int countLines = dumpHistory ? fHistory->Size() : fHeight; 1793 fprintf(fileOut, "> %s lines dump begin <\n", 1794 dumpHistory ? "History" : "Terminal"); 1795 1796 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth); 1797 for (int i = 0; i < countLines; i++) { 1798 TerminalLine* line = dumpHistory 1799 ? fHistory->GetTerminalLineAt(i, lineBuffer) 1800 : fScreen[_LineIndex(i)]; 1801 1802 if (line == NULL) { 1803 fprintf(fileOut, "line: %d is NULL!!!\n", i); 1804 continue; 1805 } 1806 1807 fprintf(fileOut, "%02" B_PRId16 ":%02" B_PRId16 ":%08" B_PRIx32 ":\n", 1808 i, line->length, line->attributes); 1809 for (int j = 0; j < line->length; j++) 1810 if (line->cells[j].character.bytes[0] != 0) 1811 fwrite(line->cells[j].character.bytes, 1, 1812 line->cells[j].character.ByteCount(), fileOut); 1813 1814 fprintf(fileOut, "\n"); 1815 for (int s = 28; s >= 0; s -= 4) { 1816 for (int j = 0; j < fWidth; j++) 1817 fprintf(fileOut, "%01" B_PRIx32, 1818 (line->cells[j].attributes >> s) & 0x0F); 1819 1820 fprintf(fileOut, "\n"); 1821 } 1822 1823 fprintf(fileOut, "\n"); 1824 } 1825 1826 fprintf(fileOut, "> %s lines dump finished <\n", 1827 dumpHistory ? "History" : "Terminal"); 1828 1829 dumpHistory = !dumpHistory; 1830 } while (dumpHistory); 1831 1832 fclose(fileOut); 1833 } 1834 1835 1836 void 1837 BasicTerminalBuffer::StartStopDebugCapture() 1838 { 1839 if (fCaptureFile >= 0) { 1840 close(fCaptureFile); 1841 fCaptureFile = -1; 1842 return; 1843 } 1844 1845 time_t timeStamp = time(NULL); 1846 BString str("/var/log/"); 1847 struct tm* ts = gmtime(&timeStamp); 1848 str << ts->tm_hour << ts->tm_min << ts->tm_sec; 1849 str << ".Capture.log"; 1850 fCaptureFile = open(str.String(), O_CREAT | O_WRONLY, 1851 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 1852 } 1853 1854 1855 void 1856 BasicTerminalBuffer::CaptureChar(char ch) 1857 { 1858 if (fCaptureFile >= 0) 1859 write(fCaptureFile, &ch, 1); 1860 } 1861 1862 1863 void 1864 BasicTerminalBuffer::InsertLastChar() 1865 { 1866 InsertChar(fLast); 1867 } 1868 1869 #endif 1870