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