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