BTRFS: Implement BTree::Path and change _Find.
[haiku.git] / src / apps / terminal / BasicTerminalBuffer.cpp
blob7d4dd2a8c2a7289aaf437b4b620b5a01f01bfe6d
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.
6 * Authors:
7 * Ingo Weinhold, ingo_weinhold@gmx.de
8 * Siarzhuk Zharski, zharik@gmx.li
9 */
11 #include "BasicTerminalBuffer.h"
13 #include <alloca.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <fcntl.h>
17 #include <string.h>
19 #include <algorithm>
21 #include <StackOrHeapArray.h>
22 #include <String.h>
24 #include "TermConst.h"
25 #include "TerminalCharClassifier.h"
26 #include "TerminalLine.h"
29 static const UTF8Char kSpaceChar(' ');
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;
39 #define ALLOC_LINE_ON_STACK(width) \
40 ((TerminalLine*)alloca(sizeof(TerminalLine) \
41 + sizeof(TerminalCell) * ((width) - 1)))
44 static inline int32
45 restrict_value(int32 value, int32 min, int32 max)
47 return value < min ? min : (value > max ? max : value);
51 // #pragma mark - private inline methods
54 inline int32
55 BasicTerminalBuffer::_LineIndex(int32 index) const
57 return (index + fScreenOffset) % fHeight;
61 inline TerminalLine*
62 BasicTerminalBuffer::_LineAt(int32 index) const
64 return fScreen[_LineIndex(index)];
68 inline TerminalLine*
69 BasicTerminalBuffer::_HistoryLineAt(int32 index, TerminalLine* lineBuffer) const
71 if (index >= fHeight)
72 return NULL;
74 if (index < 0 && fHistory != NULL)
75 return fHistory->GetTerminalLineAt(-index - 1, lineBuffer);
77 return _LineAt(index + fHeight);
81 inline void
82 BasicTerminalBuffer::_Invalidate(int32 top, int32 bottom)
84 //debug_printf("%p->BasicTerminalBuffer::_Invalidate(%ld, %ld)\n", this, top, bottom);
85 fDirtyInfo.ExtendDirtyRegion(top, bottom);
87 if (!fDirtyInfo.messageSent) {
88 NotifyListener();
89 fDirtyInfo.messageSent = true;
94 inline void
95 BasicTerminalBuffer::_CursorChanged()
97 if (!fDirtyInfo.messageSent) {
98 NotifyListener();
99 fDirtyInfo.messageSent = true;
104 // #pragma mark - public methods
107 BasicTerminalBuffer::BasicTerminalBuffer()
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)
129 BasicTerminalBuffer::~BasicTerminalBuffer()
131 delete fHistory;
132 _FreeLines(fScreen, fHeight);
133 delete[] fTabStops;
135 if (fCaptureFile >= 0)
136 close(fCaptureFile);
140 status_t
141 BasicTerminalBuffer::Init(int32 width, int32 height, int32 historySize)
143 status_t error;
145 fWidth = width;
146 fHeight = height;
148 fScrollTop = 0;
149 fScrollBottom = fHeight - 1;
151 fCursor.x = 0;
152 fCursor.y = 0;
153 fSoftWrappedCursor = false;
155 fScreenOffset = 0;
157 fOverwriteMode = true;
158 fAlternateScreenActive = false;
159 fOriginMode = fSavedOriginMode = false;
161 fScreen = _AllocateLines(width, height);
162 if (fScreen == NULL)
163 return B_NO_MEMORY;
165 if (historySize > 0) {
166 fHistory = new(std::nothrow) HistoryBuffer;
167 if (fHistory == NULL)
168 return B_NO_MEMORY;
170 error = fHistory->Init(width, historySize);
171 if (error != B_OK)
172 return error;
175 error = _ResetTabStops(fWidth);
176 if (error != B_OK)
177 return error;
179 for (int32 i = 0; i < fHeight; i++)
180 fScreen[i]->Clear();
182 fDirtyInfo.Reset();
184 return B_OK;
188 status_t
189 BasicTerminalBuffer::ResizeTo(int32 width, int32 height)
191 return ResizeTo(width, height, fHistory != NULL ? fHistory->Capacity() : 0);
195 status_t
196 BasicTerminalBuffer::ResizeTo(int32 width, int32 height, int32 historyCapacity)
198 if (height < kMinRowCount || height > kMaxRowCount
199 || width < kMinColumnCount || width > kMaxColumnCount) {
200 return B_BAD_VALUE;
203 if (width == fWidth && height == fHeight)
204 return SetHistoryCapacity(historyCapacity);
206 if (fAlternateScreenActive)
207 return _ResizeSimple(width, height, historyCapacity);
209 return _ResizeRewrap(width, height, historyCapacity);
213 status_t
214 BasicTerminalBuffer::SetHistoryCapacity(int32 historyCapacity)
216 return _ResizeHistory(fWidth, historyCapacity);
220 void
221 BasicTerminalBuffer::Clear(bool resetCursor)
223 fSoftWrappedCursor = false;
224 fScreenOffset = 0;
225 _ClearLines(0, fHeight - 1);
227 if (resetCursor)
228 fCursor.SetTo(0, 0);
230 if (fHistory != NULL)
231 fHistory->Clear();
233 fDirtyInfo.linesScrolled = 0;
234 _Invalidate(0, fHeight - 1);
238 void
239 BasicTerminalBuffer::SynchronizeWith(const BasicTerminalBuffer* other,
240 int32 offset, int32 dirtyTop, int32 dirtyBottom)
242 //debug_printf("BasicTerminalBuffer::SynchronizeWith(%p, %ld, %ld - %ld)\n",
243 //other, offset, dirtyTop, dirtyBottom);
245 // intersect the visible region with the dirty region
246 int32 first = 0;
247 int32 last = fHeight - 1;
248 dirtyTop -= offset;
249 dirtyBottom -= offset;
251 if (first > dirtyBottom || dirtyTop > last)
252 return;
254 if (first < dirtyTop)
255 first = dirtyTop;
256 if (last > dirtyBottom)
257 last = dirtyBottom;
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));
273 } else {
274 // The source line was a history line and has been copied
275 // directly into destLine.
277 } else
278 destLine->Clear(fAttributes, fWidth);
283 bool
284 BasicTerminalBuffer::IsFullWidthChar(int32 row, int32 column) const
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;
294 BasicTerminalBuffer::GetChar(int32 row, int32 column, UTF8Char& character,
295 uint32& attributes) const
297 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
298 TerminalLine* line = _HistoryLineAt(row, lineBuffer);
299 if (line == NULL)
300 return NO_CHAR;
302 if (column < 0 || column >= line->length)
303 return NO_CHAR;
305 if (column > 0 && (line->cells[column - 1].attributes & A_WIDTH) != 0)
306 return IN_STRING;
308 TerminalCell& cell = line->cells[column];
309 character = cell.character;
310 attributes = cell.attributes;
311 return A_CHAR;
315 void
316 BasicTerminalBuffer::GetCellAttributes(int32 row, int32 column,
317 uint32& attributes, uint32& count) const
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;
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;
332 count = c - column;
336 int32
337 BasicTerminalBuffer::GetString(int32 row, int32 firstColumn, int32 lastColumn,
338 char* buffer, uint32& attributes) const
340 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
341 TerminalLine* line = _HistoryLineAt(row, lineBuffer);
342 if (line == NULL)
343 return 0;
345 if (lastColumn >= line->length)
346 lastColumn = line->length - 1;
348 int32 column = firstColumn;
349 if (column <= lastColumn)
350 attributes = line->cells[column].attributes;
352 for (; column <= lastColumn; column++) {
353 TerminalCell& cell = line->cells[column];
354 if (cell.attributes != attributes)
355 break;
357 int32 bytes = cell.character.ByteCount();
358 for (int32 i = 0; i < bytes; i++)
359 *buffer++ = cell.character.bytes[i];
362 *buffer = '\0';
364 return column - firstColumn;
368 void
369 BasicTerminalBuffer::GetStringFromRegion(BString& string, const TermPos& start,
370 const TermPos& end) const
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;
377 TermPos pos(start);
379 if (IsFullWidthChar(pos.y, pos.x))
380 pos.x--;
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++;
392 // get the last line, if not empty
393 if (end.x > 0)
394 _GetPartialLineString(string, end.y, pos.x, end.x);
398 bool
399 BasicTerminalBuffer::FindWord(const TermPos& pos,
400 TerminalCharClassifier* classifier, bool findNonWords, TermPos& _start,
401 TermPos& _end) const
403 int32 x = pos.x;
404 int32 y = pos.y;
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;
411 if (x >= line->length) {
412 // beyond the end of the line -- select all space
413 if (!findNonWords)
414 return false;
416 _start.SetTo(line->length, y);
417 _end.SetTo(fWidth, y);
418 return true;
421 if (x > 0 && IS_WIDTH(line->cells[x - 1].attributes))
422 x--;
424 // get the char type at the given position
425 int type = classifier->Classify(line->cells[x].character);
427 // check whether we are supposed to find words only
428 if (type != CHAR_TYPE_WORD_CHAR && !findNonWords)
429 return false;
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;
443 start = previousPos;
446 // find the end
447 line = _HistoryLineAt(end.y, lineBuffer);
449 for (;;) {
450 TermPos nextPos = end;
451 if (!_NormalizeLinePos(lineBuffer, line, nextPos))
452 break;
454 if (classifier->Classify(line->cells[nextPos.x].character) != type)
455 break;
457 nextPos.x += IS_WIDTH(line->cells[nextPos.x].attributes)
458 ? FULL_WIDTH : HALF_WIDTH;
459 end = nextPos;
462 _start = start;
463 _end = end;
464 return true;
468 bool
469 BasicTerminalBuffer::PreviousLinePos(TermPos& pos) const
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;
476 return _PreviousLinePos(lineBuffer, line, pos);
480 bool
481 BasicTerminalBuffer::NextLinePos(TermPos& pos, bool normalize) const
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;
488 if (!_NormalizeLinePos(lineBuffer, line, pos))
489 return false;
491 pos.x += IS_WIDTH(line->cells[pos.x].attributes) ? FULL_WIDTH : HALF_WIDTH;
492 return !normalize || _NormalizeLinePos(lineBuffer, line, pos);
496 int32
497 BasicTerminalBuffer::LineLength(int32 index) const
499 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
500 TerminalLine* line = _HistoryLineAt(index, lineBuffer);
501 return line != NULL ? line->length : 0;
505 int32
506 BasicTerminalBuffer::GetLineColor(int32 index) const
508 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
509 TerminalLine* line = _HistoryLineAt(index, lineBuffer);
510 return line != NULL ? line->attributes : 0;
514 bool
515 BasicTerminalBuffer::Find(const char* _pattern, const TermPos& start,
516 bool forward, bool caseSensitive, bool matchWord, TermPos& _matchStart,
517 TermPos& _matchEnd) const
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);
532 } else {
533 if (pos.x > line->length)
534 pos.x = line->length;
538 int32 patternByteLen = strlen(_pattern);
540 // convert pattern to UTF8Char array
541 BStackOrHeapArray<UTF8Char, 64> pattern(patternByteLen);
542 int32 patternLen = 0;
543 while (*_pattern != '\0') {
544 int32 charLen = UTF8Char::ByteCount(*_pattern);
545 if (charLen > 0) {
546 pattern[patternLen].SetTo(_pattern, charLen);
548 // if not case sensitive, convert to lower case
549 if (!caseSensitive && charLen == 1)
550 pattern[patternLen] = pattern[patternLen].ToLower();
552 patternLen++;
553 _pattern += charLen;
554 } else
555 _pattern++;
557 //debug_printf(" pattern byte len: %ld, pattern len: %ld\n", patternByteLen, patternLen);
559 if (patternLen == 0)
560 return false;
562 // reverse pattern, if searching backward
563 if (!forward) {
564 for (int32 i = 0; i < patternLen / 2; i++)
565 std::swap(pattern[i], pattern[patternLen - i - 1]);
568 // search loop
569 int32 matchIndex = 0;
570 TermPos matchStart;
571 while (true) {
572 //debug_printf(" (%ld, %ld): matchIndex: %ld\n", pos.x, pos.y, matchIndex);
573 TermPos previousPos(pos);
574 UTF8Char c;
575 if (!(forward ? _NextChar(pos, c) : _PreviousChar(pos, c)))
576 return false;
578 if (caseSensitive ? (c == pattern[matchIndex])
579 : (c.ToLower() == pattern[matchIndex])) {
580 if (matchIndex == 0)
581 matchStart = previousPos;
583 matchIndex++;
585 if (matchIndex == patternLen) {
586 //debug_printf(" match!\n");
587 // compute the match range
588 TermPos matchEnd(pos);
589 if (!forward)
590 std::swap(matchStart, matchEnd);
592 // check word match
593 if (matchWord) {
594 TermPos tempPos(matchStart);
595 if ((_PreviousChar(tempPos, c) && !c.IsSpace())
596 || (_NextChar(tempPos = matchEnd, c) && !c.IsSpace())) {
597 //debug_printf(" but no word match!\n");
598 continue;
602 _matchStart = matchStart;
603 _matchEnd = matchEnd;
604 //debug_printf(" -> (%ld, %ld) - (%ld, %ld)\n", matchStart.x, matchStart.y,
605 //matchEnd.x, matchEnd.y);
606 return true;
608 } else if (matchIndex > 0) {
609 // continue after the position where we started matching
610 pos = matchStart;
611 if (forward)
612 _NextChar(pos, c);
613 else
614 _PreviousChar(pos, c);
615 matchIndex = 0;
621 void
622 BasicTerminalBuffer::InsertChar(UTF8Char c)
624 //debug_printf("BasicTerminalBuffer::InsertChar('%.*s' (%d), %#lx)\n",
625 //(int)c.ByteCount(), c.bytes, c.bytes[0], attributes);
626 int32 width = c.IsFullWidth() ? FULL_WIDTH : HALF_WIDTH;
628 if (fSoftWrappedCursor || (fCursor.x + width) > fWidth)
629 _SoftBreakLine();
630 else
631 _PadLineToCursor();
633 fSoftWrappedCursor = false;
635 if (!fOverwriteMode)
636 _InsertGap(width);
638 TerminalLine* line = _LineAt(fCursor.y);
639 line->cells[fCursor.x].character = c;
640 line->cells[fCursor.x].attributes
641 = fAttributes | (width == FULL_WIDTH ? A_WIDTH : 0);
643 if (line->length < fCursor.x + width)
644 line->length = fCursor.x + width;
646 _Invalidate(fCursor.y, fCursor.y);
648 fCursor.x += width;
650 // TODO: Deal correctly with full-width chars! We must take care not to
651 // overwrite half of a full-width char. This holds also for other methods.
653 if (fCursor.x == fWidth) {
654 fCursor.x -= width;
655 fSoftWrappedCursor = true;
660 void
661 BasicTerminalBuffer::FillScreen(UTF8Char c, uint32 attributes)
663 uint32 width = HALF_WIDTH;
664 if (c.IsFullWidth()) {
665 attributes |= A_WIDTH;
666 width = FULL_WIDTH;
669 fSoftWrappedCursor = false;
671 for (int32 y = 0; y < fHeight; y++) {
672 TerminalLine *line = _LineAt(y);
673 for (int32 x = 0; x < fWidth / (int32)width; x++) {
674 line->cells[x].character = c;
675 line->cells[x].attributes = attributes;
677 line->length = fWidth / width;
680 _Invalidate(0, fHeight - 1);
684 void
685 BasicTerminalBuffer::InsertCR()
687 TerminalLine* line = _LineAt(fCursor.y);
689 line->attributes = fAttributes;
690 line->softBreak = false;
691 fSoftWrappedCursor = false;
692 fCursor.x = 0;
693 _Invalidate(fCursor.y, fCursor.y);
694 _CursorChanged();
698 void
699 BasicTerminalBuffer::InsertLF()
701 fSoftWrappedCursor = false;
703 // If we're at the end of the scroll region, scroll. Otherwise just advance
704 // the cursor.
705 if (fCursor.y == fScrollBottom) {
706 _Scroll(fScrollTop, fScrollBottom, 1);
707 } else {
708 if (fCursor.y < fHeight - 1)
709 fCursor.y++;
710 _CursorChanged();
715 void
716 BasicTerminalBuffer::InsertRI()
718 fSoftWrappedCursor = false;
720 // If we're at the beginning of the scroll region, scroll. Otherwise just
721 // reverse the cursor.
722 if (fCursor.y == fScrollTop) {
723 _Scroll(fScrollTop, fScrollBottom, -1);
724 } else {
725 if (fCursor.y > 0)
726 fCursor.y--;
727 _CursorChanged();
732 void
733 BasicTerminalBuffer::InsertTab()
735 int32 x;
737 fSoftWrappedCursor = false;
739 // Find the next tab stop
740 for (x = fCursor.x + 1; x < fWidth && !fTabStops[x]; x++)
742 // Ensure x stayx within the line bounds
743 x = restrict_value(x, 0, fWidth - 1);
745 if (x != fCursor.x) {
746 TerminalLine* line = _LineAt(fCursor.y);
747 for (int32 i = fCursor.x; i <= x; i++) {
748 if (line->length <= i) {
749 line->cells[i].character = ' ';
750 line->cells[i].attributes = fAttributes;
753 fCursor.x = x;
754 if (line->length < fCursor.x)
755 line->length = fCursor.x;
756 _CursorChanged();
761 void
762 BasicTerminalBuffer::InsertCursorBackTab(int32 numTabs)
764 int32 x = fCursor.x - 1;
766 fSoftWrappedCursor = false;
768 // Find the next tab stop
769 while (numTabs-- > 0)
770 for (; x >=0 && !fTabStops[x]; x--)
772 // Ensure x stays within the line bounds
773 x = restrict_value(x, 0, fWidth - 1);
775 if (x != fCursor.x) {
776 fCursor.x = x;
777 _CursorChanged();
782 void
783 BasicTerminalBuffer::InsertLines(int32 numLines)
785 if (fCursor.y >= fScrollTop && fCursor.y < fScrollBottom) {
786 fSoftWrappedCursor = false;
787 _Scroll(fCursor.y, fScrollBottom, -numLines);
792 void
793 BasicTerminalBuffer::SetInsertMode(int flag)
795 fOverwriteMode = flag == MODE_OVER;
799 void
800 BasicTerminalBuffer::InsertSpace(int32 num)
802 // TODO: Deal with full-width chars!
803 if (fCursor.x + num > fWidth)
804 num = fWidth - fCursor.x;
806 if (num > 0) {
807 fSoftWrappedCursor = false;
808 _PadLineToCursor();
809 _InsertGap(num);
811 TerminalLine* line = _LineAt(fCursor.y);
812 for (int32 i = fCursor.x; i < fCursor.x + num; i++) {
813 line->cells[i].character = kSpaceChar;
814 line->cells[i].attributes = line->cells[fCursor.x - 1].attributes;
816 line->attributes = fAttributes;
818 _Invalidate(fCursor.y, fCursor.y);
823 void
824 BasicTerminalBuffer::EraseCharsFrom(int32 first, int32 numChars)
826 TerminalLine* line = _LineAt(fCursor.y);
828 int32 end = min_c(first + numChars, fWidth);
829 for (int32 i = first; i < end; i++)
830 line->cells[i].attributes = fAttributes;
832 line->attributes = fAttributes;
834 fSoftWrappedCursor = false;
836 end = min_c(first + numChars, line->length);
837 if (first > 0 && IS_WIDTH(line->cells[first - 1].attributes))
838 first--;
839 if (end > 0 && IS_WIDTH(line->cells[end - 1].attributes))
840 end++;
842 for (int32 i = first; i < end; i++) {
843 line->cells[i].character = kSpaceChar;
844 line->cells[i].attributes = fAttributes;
847 _Invalidate(fCursor.y, fCursor.y);
851 void
852 BasicTerminalBuffer::EraseAbove()
854 // Clear the preceding lines.
855 if (fCursor.y > 0)
856 _ClearLines(0, fCursor.y - 1);
858 fSoftWrappedCursor = false;
860 // Delete the chars on the cursor line before (and including) the cursor.
861 TerminalLine* line = _LineAt(fCursor.y);
862 if (fCursor.x < line->length) {
863 int32 to = fCursor.x;
864 if (IS_WIDTH(line->cells[fCursor.x].attributes))
865 to++;
866 for (int32 i = 0; i <= to; i++) {
867 line->cells[i].attributes = fAttributes;
868 line->cells[i].character = kSpaceChar;
870 } else
871 line->Clear(fAttributes, fWidth);
873 _Invalidate(fCursor.y, fCursor.y);
877 void
878 BasicTerminalBuffer::EraseBelow()
880 fSoftWrappedCursor = false;
882 // Clear the following lines.
883 if (fCursor.y < fHeight - 1)
884 _ClearLines(fCursor.y + 1, fHeight - 1);
886 // Delete the chars on the cursor line after (and including) the cursor.
887 DeleteColumns();
891 void
892 BasicTerminalBuffer::EraseAll()
894 fSoftWrappedCursor = false;
895 _Scroll(0, fHeight - 1, fHeight);
899 void
900 BasicTerminalBuffer::DeleteChars(int32 numChars)
902 fSoftWrappedCursor = false;
904 TerminalLine* line = _LineAt(fCursor.y);
905 if (fCursor.x < line->length) {
906 if (fCursor.x + numChars < line->length) {
907 int32 left = line->length - fCursor.x - numChars;
908 memmove(line->cells + fCursor.x, line->cells + fCursor.x + numChars,
909 left * sizeof(TerminalCell));
910 line->length = fCursor.x + left;
911 // process BCE on freed tail cells
912 for (int i = 0; i < numChars; i++)
913 line->cells[fCursor.x + left + i].attributes = fAttributes;
914 } else {
915 // process BCE on freed tail cells
916 for (int i = 0; i < line->length - fCursor.x; i++)
917 line->cells[fCursor.x + i].attributes = fAttributes;
918 // remove all remaining chars
919 line->length = fCursor.x;
922 _Invalidate(fCursor.y, fCursor.y);
927 void
928 BasicTerminalBuffer::DeleteColumnsFrom(int32 first)
930 fSoftWrappedCursor = false;
932 TerminalLine* line = _LineAt(fCursor.y);
934 for (int32 i = first; i < fWidth; i++)
935 line->cells[i].attributes = fAttributes;
937 if (first <= line->length) {
938 line->length = first;
939 line->attributes = fAttributes;
941 _Invalidate(fCursor.y, fCursor.y);
945 void
946 BasicTerminalBuffer::DeleteLines(int32 numLines)
948 if (fCursor.y >= fScrollTop && fCursor.y <= fScrollBottom) {
949 fSoftWrappedCursor = false;
950 _Scroll(fCursor.y, fScrollBottom, numLines);
955 void
956 BasicTerminalBuffer::SaveCursor()
958 fSavedCursors.push(fCursor);
962 void
963 BasicTerminalBuffer::RestoreCursor()
965 if (fSavedCursors.size() == 0)
966 return;
968 _SetCursor(fSavedCursors.top().x, fSavedCursors.top().y, true);
969 fSavedCursors.pop();
973 void
974 BasicTerminalBuffer::SetScrollRegion(int32 top, int32 bottom)
976 fScrollTop = restrict_value(top, 0, fHeight - 1);
977 fScrollBottom = restrict_value(bottom, fScrollTop, fHeight - 1);
979 // also sets the cursor position
980 _SetCursor(0, 0, false);
984 void
985 BasicTerminalBuffer::SetOriginMode(bool enabled)
987 fOriginMode = enabled;
988 _SetCursor(0, 0, false);
992 void
993 BasicTerminalBuffer::SaveOriginMode()
995 fSavedOriginMode = fOriginMode;
999 void
1000 BasicTerminalBuffer::RestoreOriginMode()
1002 fOriginMode = fSavedOriginMode;
1006 void
1007 BasicTerminalBuffer::SetTabStop(int32 x)
1009 x = restrict_value(x, 0, fWidth - 1);
1010 fTabStops[x] = true;
1014 void
1015 BasicTerminalBuffer::ClearTabStop(int32 x)
1017 x = restrict_value(x, 0, fWidth - 1);
1018 fTabStops[x] = false;
1022 void
1023 BasicTerminalBuffer::ClearAllTabStops()
1025 for (int32 i = 0; i < fWidth; i++)
1026 fTabStops[i] = false;
1030 void
1031 BasicTerminalBuffer::NotifyListener()
1033 // Implemented by derived classes.
1037 // #pragma mark - private methods
1040 void
1041 BasicTerminalBuffer::_SetCursor(int32 x, int32 y, bool absolute)
1043 //debug_printf("BasicTerminalBuffer::_SetCursor(%d, %d)\n", x, y);
1044 fSoftWrappedCursor = false;
1046 x = restrict_value(x, 0, fWidth - 1);
1047 if (fOriginMode && !absolute) {
1048 y += fScrollTop;
1049 y = restrict_value(y, fScrollTop, fScrollBottom);
1050 } else {
1051 y = restrict_value(y, 0, fHeight - 1);
1054 if (x != fCursor.x || y != fCursor.y) {
1055 fCursor.x = x;
1056 fCursor.y = y;
1057 _CursorChanged();
1062 void
1063 BasicTerminalBuffer::_InvalidateAll()
1065 fDirtyInfo.invalidateAll = true;
1067 if (!fDirtyInfo.messageSent) {
1068 NotifyListener();
1069 fDirtyInfo.messageSent = true;
1074 /* static */ TerminalLine**
1075 BasicTerminalBuffer::_AllocateLines(int32 width, int32 count)
1077 TerminalLine** lines = (TerminalLine**)malloc(sizeof(TerminalLine*) * count);
1078 if (lines == NULL)
1079 return NULL;
1081 for (int32 i = 0; i < count; i++) {
1082 const int32 size = sizeof(TerminalLine)
1083 + sizeof(TerminalCell) * (width - 1);
1084 lines[i] = (TerminalLine*)malloc(size);
1085 if (lines[i] == NULL) {
1086 _FreeLines(lines, i);
1087 return NULL;
1089 memset(lines[i], 0, size);
1092 return lines;
1096 /* static */ void
1097 BasicTerminalBuffer::_FreeLines(TerminalLine** lines, int32 count)
1099 if (lines != NULL) {
1100 for (int32 i = 0; i < count; i++)
1101 free(lines[i]);
1103 free(lines);
1108 void
1109 BasicTerminalBuffer::_ClearLines(int32 first, int32 last)
1111 int32 firstCleared = -1;
1112 int32 lastCleared = -1;
1114 for (int32 i = first; i <= last; i++) {
1115 TerminalLine* line = _LineAt(i);
1116 if (line->length > 0) {
1117 if (firstCleared == -1)
1118 firstCleared = i;
1119 lastCleared = i;
1122 line->Clear(fAttributes, fWidth);
1125 if (firstCleared >= 0)
1126 _Invalidate(firstCleared, lastCleared);
1130 status_t
1131 BasicTerminalBuffer::_ResizeHistory(int32 width, int32 historyCapacity)
1133 if (width == fWidth && historyCapacity == HistoryCapacity())
1134 return B_OK;
1136 if (historyCapacity <= 0) {
1137 // new history capacity is 0 -- delete the old history object
1138 delete fHistory;
1139 fHistory = NULL;
1141 return B_OK;
1144 HistoryBuffer* history = new(std::nothrow) HistoryBuffer;
1145 if (history == NULL)
1146 return B_NO_MEMORY;
1148 status_t error = history->Init(width, historyCapacity);
1149 if (error != B_OK) {
1150 delete history;
1151 return error;
1154 // Transfer the lines from the old history to the new one.
1155 if (fHistory != NULL) {
1156 int32 historySize = min_c(HistorySize(), historyCapacity);
1157 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
1158 for (int32 i = historySize - 1; i >= 0; i--) {
1159 TerminalLine* line = fHistory->GetTerminalLineAt(i, lineBuffer);
1160 if (line->length > width)
1161 _TruncateLine(line, width);
1162 history->AddLine(line);
1166 delete fHistory;
1167 fHistory = history;
1169 return B_OK;
1173 status_t
1174 BasicTerminalBuffer::_ResizeSimple(int32 width, int32 height,
1175 int32 historyCapacity)
1177 //debug_printf("BasicTerminalBuffer::_ResizeSimple(): (%ld, %ld) -> "
1178 //"(%ld, %ld)\n", fWidth, fHeight, width, height);
1179 if (width == fWidth && height == fHeight)
1180 return B_OK;
1182 if (width != fWidth || historyCapacity != HistoryCapacity()) {
1183 status_t error = _ResizeHistory(width, historyCapacity);
1184 if (error != B_OK)
1185 return error;
1188 TerminalLine** lines = _AllocateLines(width, height);
1189 if (lines == NULL)
1190 return B_NO_MEMORY;
1191 // NOTE: If width or history capacity changed, the object will be in
1192 // an invalid state, since the history will already use the new values.
1194 int32 endLine = min_c(fHeight, height);
1195 int32 firstLine = 0;
1197 if (height < fHeight) {
1198 if (endLine <= fCursor.y) {
1199 endLine = fCursor.y + 1;
1200 firstLine = endLine - height;
1203 // push the first lines to the history
1204 if (fHistory != NULL) {
1205 for (int32 i = 0; i < firstLine; i++) {
1206 TerminalLine* line = _LineAt(i);
1207 if (width < fWidth)
1208 _TruncateLine(line, width);
1209 fHistory->AddLine(line);
1214 // copy the lines we keep
1215 for (int32 i = firstLine; i < endLine; i++) {
1216 TerminalLine* sourceLine = _LineAt(i);
1217 TerminalLine* destLine = lines[i - firstLine];
1218 if (width < fWidth)
1219 _TruncateLine(sourceLine, width);
1220 memcpy(destLine, sourceLine, (int32)sizeof(TerminalLine)
1221 + (sourceLine->length - 1) * (int32)sizeof(TerminalCell));
1224 // clear the remaining lines
1225 for (int32 i = endLine - firstLine; i < height; i++)
1226 lines[i]->Clear(fAttributes, width);
1228 _FreeLines(fScreen, fHeight);
1229 fScreen = lines;
1231 if (fWidth != width) {
1232 status_t error = _ResetTabStops(width);
1233 if (error != B_OK)
1234 return error;
1237 fWidth = width;
1238 fHeight = height;
1240 fScrollTop = 0;
1241 fScrollBottom = fHeight - 1;
1242 fOriginMode = fSavedOriginMode = false;
1244 fScreenOffset = 0;
1246 if (fCursor.x > width)
1247 fCursor.x = width;
1248 fCursor.y -= firstLine;
1249 fSoftWrappedCursor = false;
1251 return B_OK;
1255 status_t
1256 BasicTerminalBuffer::_ResizeRewrap(int32 width, int32 height,
1257 int32 historyCapacity)
1259 //debug_printf("BasicTerminalBuffer::_ResizeRewrap(): (%ld, %ld, history: %ld) -> "
1260 //"(%ld, %ld, history: %ld)\n", fWidth, fHeight, HistoryCapacity(), width, height,
1261 //historyCapacity);
1263 // The width stays the same. _ResizeSimple() does exactly what we need.
1264 if (width == fWidth)
1265 return _ResizeSimple(width, height, historyCapacity);
1267 // The width changes. We have to allocate a new line array, a new history
1268 // and re-wrap all lines.
1270 TerminalLine** screen = _AllocateLines(width, height);
1271 if (screen == NULL)
1272 return B_NO_MEMORY;
1274 HistoryBuffer* history = NULL;
1276 if (historyCapacity > 0) {
1277 history = new(std::nothrow) HistoryBuffer;
1278 if (history == NULL) {
1279 _FreeLines(screen, height);
1280 return B_NO_MEMORY;
1283 status_t error = history->Init(width, historyCapacity);
1284 if (error != B_OK) {
1285 _FreeLines(screen, height);
1286 delete history;
1287 return error;
1291 int32 historySize = HistorySize();
1292 int32 totalLines = historySize + fHeight;
1294 // re-wrap
1295 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
1296 TermPos cursor;
1297 int32 destIndex = 0;
1298 int32 sourceIndex = 0;
1299 int32 sourceX = 0;
1300 int32 destTotalLines = 0;
1301 int32 destScreenOffset = 0;
1302 int32 maxDestTotalLines = INT_MAX;
1303 bool newDestLine = true;
1304 bool cursorSeen = false;
1305 TerminalLine* sourceLine = _HistoryLineAt(-historySize, lineBuffer);
1307 while (sourceIndex < totalLines) {
1308 TerminalLine* destLine = screen[destIndex];
1310 if (newDestLine) {
1311 // Clear a new dest line before using it. If we're about to
1312 // overwrite an previously written line, we push it to the
1313 // history first, though.
1314 if (history != NULL && destTotalLines >= height)
1315 history->AddLine(screen[destIndex]);
1316 destLine->Clear(fAttributes, width);
1317 newDestLine = false;
1320 int32 sourceLeft = sourceLine->length - sourceX;
1321 int32 destLeft = width - destLine->length;
1322 //debug_printf(" source: %ld, left: %ld, dest: %ld, left: %ld\n",
1323 //sourceIndex, sourceLeft, destIndex, destLeft);
1325 if (sourceIndex == historySize && sourceX == 0) {
1326 destScreenOffset = destTotalLines;
1327 if (destLeft == 0 && sourceLeft > 0)
1328 destScreenOffset++;
1329 maxDestTotalLines = destScreenOffset + height;
1330 //debug_printf(" destScreenOffset: %ld\n", destScreenOffset);
1333 int32 toCopy = min_c(sourceLeft, destLeft);
1334 // If the last cell to copy is the first cell of a
1335 // full-width char, don't copy it yet.
1336 if (toCopy > 0 && IS_WIDTH(
1337 sourceLine->cells[sourceX + toCopy - 1].attributes)) {
1338 //debug_printf(" -> last char is full-width -- don't copy it\n");
1339 toCopy--;
1342 // translate the cursor position
1343 if (fCursor.y + historySize == sourceIndex
1344 && fCursor.x >= sourceX
1345 && (fCursor.x < sourceX + toCopy
1346 || (destLeft >= sourceLeft
1347 && sourceX + sourceLeft <= fCursor.x))) {
1348 cursor.x = destLine->length + fCursor.x - sourceX;
1349 cursor.y = destTotalLines;
1351 if (cursor.x >= width) {
1352 // The cursor was in free space after the official end
1353 // of line.
1354 cursor.x = width - 1;
1356 //debug_printf(" cursor: (%ld, %ld)\n", cursor.x, cursor.y);
1358 cursorSeen = true;
1361 if (toCopy > 0) {
1362 memcpy(destLine->cells + destLine->length,
1363 sourceLine->cells + sourceX, toCopy * sizeof(TerminalCell));
1364 destLine->length += toCopy;
1367 destLine->attributes = sourceLine->attributes;
1369 bool nextDestLine = false;
1370 if (toCopy == sourceLeft) {
1371 if (!sourceLine->softBreak)
1372 nextDestLine = true;
1373 sourceIndex++;
1374 sourceX = 0;
1375 sourceLine = _HistoryLineAt(sourceIndex - historySize,
1376 lineBuffer);
1377 } else {
1378 destLine->softBreak = true;
1379 nextDestLine = true;
1380 sourceX += toCopy;
1383 if (nextDestLine) {
1384 destIndex = (destIndex + 1) % height;
1385 destTotalLines++;
1386 newDestLine = true;
1387 if (cursorSeen && destTotalLines >= maxDestTotalLines)
1388 break;
1392 // If the last source line had a soft break, the last dest line
1393 // won't have been counted yet.
1394 if (!newDestLine) {
1395 destIndex = (destIndex + 1) % height;
1396 destTotalLines++;
1399 //debug_printf(" total lines: %ld -> %ld\n", totalLines, destTotalLines);
1401 if (destTotalLines - destScreenOffset > height)
1402 destScreenOffset = destTotalLines - height;
1404 cursor.y -= destScreenOffset;
1406 // When there are less lines (starting with the screen offset) than
1407 // there's room in the screen, clear the remaining screen lines.
1408 for (int32 i = destTotalLines; i < destScreenOffset + height; i++) {
1409 // Move the line we're going to clear to the history, if that's a
1410 // line we've written earlier.
1411 TerminalLine* line = screen[i % height];
1412 if (history != NULL && i >= height)
1413 history->AddLine(line);
1414 line->Clear(fAttributes, width);
1417 // Update the values
1418 _FreeLines(fScreen, fHeight);
1419 delete fHistory;
1421 fScreen = screen;
1422 fHistory = history;
1424 if (fWidth != width) {
1425 status_t error = _ResetTabStops(width);
1426 if (error != B_OK)
1427 return error;
1430 //debug_printf(" cursor: (%ld, %ld) -> (%ld, %ld)\n", fCursor.x, fCursor.y,
1431 //cursor.x, cursor.y);
1432 fCursor.x = cursor.x;
1433 fCursor.y = cursor.y;
1434 fSoftWrappedCursor = false;
1435 //debug_printf(" screen offset: %ld -> %ld\n", fScreenOffset, destScreenOffset % height);
1436 fScreenOffset = destScreenOffset % height;
1437 //debug_printf(" height %ld -> %ld\n", fHeight, height);
1438 //debug_printf(" width %ld -> %ld\n", fWidth, width);
1439 fHeight = height;
1440 fWidth = width;
1442 fScrollTop = 0;
1443 fScrollBottom = fHeight - 1;
1444 fOriginMode = fSavedOriginMode = false;
1446 return B_OK;
1450 status_t
1451 BasicTerminalBuffer::_ResetTabStops(int32 width)
1453 if (fTabStops != NULL)
1454 delete[] fTabStops;
1456 fTabStops = new(std::nothrow) bool[width];
1457 if (fTabStops == NULL)
1458 return B_NO_MEMORY;
1460 for (int32 i = 0; i < width; i++)
1461 fTabStops[i] = (i % TAB_WIDTH) == 0;
1462 return B_OK;
1466 void
1467 BasicTerminalBuffer::_Scroll(int32 top, int32 bottom, int32 numLines)
1469 if (numLines == 0)
1470 return;
1472 if (numLines > 0) {
1473 // scroll text up
1474 if (top == 0) {
1475 // The lines scrolled out of the screen range are transferred to
1476 // the history.
1478 // add the lines to the history
1479 if (fHistory != NULL) {
1480 int32 toHistory = min_c(numLines, bottom - top + 1);
1481 for (int32 i = 0; i < toHistory; i++)
1482 fHistory->AddLine(_LineAt(i));
1484 if (toHistory < numLines)
1485 fHistory->AddEmptyLines(numLines - toHistory);
1488 if (numLines >= bottom - top + 1) {
1489 // all lines are scrolled out of range -- just clear them
1490 _ClearLines(top, bottom);
1491 } else if (bottom == fHeight - 1) {
1492 // full screen scroll -- update the screen offset and clear new
1493 // lines
1494 fScreenOffset = (fScreenOffset + numLines) % fHeight;
1495 for (int32 i = bottom - numLines + 1; i <= bottom; i++)
1496 _LineAt(i)->Clear(fAttributes, fWidth);
1497 } else {
1498 // Partial screen scroll. We move the screen offset anyway, but
1499 // have to move the unscrolled lines to their new location.
1500 // TODO: It may be more efficient to actually move the scrolled
1501 // lines only (might depend on the number of scrolled/unscrolled
1502 // lines).
1503 for (int32 i = fHeight - 1; i > bottom; i--) {
1504 std::swap(fScreen[_LineIndex(i)],
1505 fScreen[_LineIndex(i + numLines)]);
1508 // update the screen offset and clear the new lines
1509 fScreenOffset = (fScreenOffset + numLines) % fHeight;
1510 for (int32 i = bottom - numLines + 1; i <= bottom; i++)
1511 _LineAt(i)->Clear(fAttributes, fWidth);
1514 // scroll/extend dirty range
1516 if (fDirtyInfo.dirtyTop != INT_MAX) {
1517 // If the top or bottom of the dirty region are above the
1518 // bottom of the scroll region, we have to scroll them up.
1519 if (fDirtyInfo.dirtyTop <= bottom) {
1520 fDirtyInfo.dirtyTop -= numLines;
1521 if (fDirtyInfo.dirtyBottom <= bottom)
1522 fDirtyInfo.dirtyBottom -= numLines;
1525 // numLines above the bottom become dirty
1526 _Invalidate(bottom - numLines + 1, bottom);
1529 fDirtyInfo.linesScrolled += numLines;
1531 // invalidate new empty lines
1532 _Invalidate(bottom + 1 - numLines, bottom);
1534 // In case only part of the screen was scrolled, we invalidate also
1535 // the lines below the scroll region. Those remain unchanged, but
1536 // we can't convey that they have not been scrolled via
1537 // TerminalBufferDirtyInfo. So we need to force the view to sync
1538 // them again.
1539 if (bottom < fHeight - 1)
1540 _Invalidate(bottom + 1, fHeight - 1);
1541 } else if (numLines >= bottom - top + 1) {
1542 // all lines are completely scrolled out of range -- just clear
1543 // them
1544 _ClearLines(top, bottom);
1545 } else {
1546 // partial scroll -- clear the lines scrolled out of range and move
1547 // the other ones
1548 for (int32 i = top + numLines; i <= bottom; i++) {
1549 int32 lineToDrop = _LineIndex(i - numLines);
1550 int32 lineToKeep = _LineIndex(i);
1551 fScreen[lineToDrop]->Clear(fAttributes, fWidth);
1552 std::swap(fScreen[lineToDrop], fScreen[lineToKeep]);
1554 // clear any lines between the two swapped ranges above
1555 for (int32 i = bottom - numLines + 1; i < top + numLines; i++)
1556 _LineAt(i)->Clear(fAttributes, fWidth);
1558 _Invalidate(top, bottom);
1560 } else {
1561 // scroll text down
1562 numLines = -numLines;
1564 if (numLines >= bottom - top + 1) {
1565 // all lines are completely scrolled out of range -- just clear
1566 // them
1567 _ClearLines(top, bottom);
1568 } else {
1569 // partial scroll -- clear the lines scrolled out of range and move
1570 // the other ones
1571 // TODO: When scrolling the whole screen, we could just update fScreenOffset and
1572 // clear the respective lines.
1573 for (int32 i = bottom - numLines; i >= top; i--) {
1574 int32 lineToKeep = _LineIndex(i);
1575 int32 lineToDrop = _LineIndex(i + numLines);
1576 fScreen[lineToDrop]->Clear(fAttributes, fWidth);
1577 std::swap(fScreen[lineToDrop], fScreen[lineToKeep]);
1579 // clear any lines between the two swapped ranges above
1580 for (int32 i = bottom - numLines + 1; i < top + numLines; i++)
1581 _LineAt(i)->Clear(fAttributes, fWidth);
1583 _Invalidate(top, bottom);
1589 void
1590 BasicTerminalBuffer::_SoftBreakLine()
1592 TerminalLine* line = _LineAt(fCursor.y);
1593 line->softBreak = true;
1595 fCursor.x = 0;
1596 if (fCursor.y == fScrollBottom)
1597 _Scroll(fScrollTop, fScrollBottom, 1);
1598 else
1599 fCursor.y++;
1603 void
1604 BasicTerminalBuffer::_PadLineToCursor()
1606 TerminalLine* line = _LineAt(fCursor.y);
1607 if (line->length < fCursor.x)
1608 for (int32 i = line->length; i < fCursor.x; i++)
1609 line->cells[i].character = kSpaceChar;
1613 /*static*/ void
1614 BasicTerminalBuffer::_TruncateLine(TerminalLine* line, int32 length)
1616 if (line->length <= length)
1617 return;
1619 if (length > 0 && IS_WIDTH(line->cells[length - 1].attributes))
1620 length--;
1622 line->length = length;
1626 void
1627 BasicTerminalBuffer::_InsertGap(int32 width)
1629 // ASSERT(fCursor.x + width <= fWidth)
1630 TerminalLine* line = _LineAt(fCursor.y);
1632 int32 toMove = min_c(line->length - fCursor.x, fWidth - fCursor.x - width);
1633 if (toMove > 0) {
1634 memmove(line->cells + fCursor.x + width,
1635 line->cells + fCursor.x, toMove * sizeof(TerminalCell));
1638 line->length = min_c(line->length + width, fWidth);
1642 /*! \a endColumn is not inclusive.
1644 TerminalLine*
1645 BasicTerminalBuffer::_GetPartialLineString(BString& string, int32 row,
1646 int32 startColumn, int32 endColumn) const
1648 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
1649 TerminalLine* line = _HistoryLineAt(row, lineBuffer);
1650 if (line == NULL)
1651 return NULL;
1653 if (endColumn > line->length)
1654 endColumn = line->length;
1656 for (int32 x = startColumn; x < endColumn; x++) {
1657 const TerminalCell& cell = line->cells[x];
1658 string.Append(cell.character.bytes, cell.character.ByteCount());
1660 if (IS_WIDTH(cell.attributes))
1661 x++;
1664 return line;
1668 /*! Decrement \a pos and return the char at that location.
1670 bool
1671 BasicTerminalBuffer::_PreviousChar(TermPos& pos, UTF8Char& c) const
1673 pos.x--;
1675 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
1676 TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer);
1678 while (true) {
1679 if (pos.x < 0) {
1680 pos.y--;
1681 line = _HistoryLineAt(pos.y, lineBuffer);
1682 if (line == NULL)
1683 return false;
1685 pos.x = line->length;
1686 if (line->softBreak) {
1687 pos.x--;
1688 } else {
1689 c = '\n';
1690 return true;
1692 } else {
1693 c = line->cells[pos.x].character;
1694 return true;
1700 /*! Return the char at \a pos and increment it.
1702 bool
1703 BasicTerminalBuffer::_NextChar(TermPos& pos, UTF8Char& c) const
1705 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
1706 TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer);
1707 if (line == NULL)
1708 return false;
1710 if (pos.x >= line->length) {
1711 c = '\n';
1712 pos.x = 0;
1713 pos.y++;
1714 return true;
1717 c = line->cells[pos.x].character;
1719 pos.x++;
1720 while (line != NULL && pos.x >= line->length && line->softBreak) {
1721 pos.x = 0;
1722 pos.y++;
1723 line = _HistoryLineAt(pos.y, lineBuffer);
1726 return true;
1730 bool
1731 BasicTerminalBuffer::_PreviousLinePos(TerminalLine* lineBuffer,
1732 TerminalLine*& line, TermPos& pos) const
1734 if (--pos.x < 0) {
1735 // Hit the beginning of the line -- continue at the end of the
1736 // previous line, if it soft-breaks.
1737 pos.y--;
1738 if ((line = _HistoryLineAt(pos.y, lineBuffer)) == NULL
1739 || !line->softBreak || line->length == 0) {
1740 return false;
1742 pos.x = line->length - 1;
1744 if (pos.x > 0 && IS_WIDTH(line->cells[pos.x - 1].attributes))
1745 pos.x--;
1747 return true;
1751 bool
1752 BasicTerminalBuffer::_NormalizeLinePos(TerminalLine* lineBuffer,
1753 TerminalLine*& line, TermPos& pos) const
1755 if (pos.x < line->length)
1756 return true;
1758 // Hit the end of the line -- if it soft-breaks continue with the
1759 // next line.
1760 if (!line->softBreak)
1761 return false;
1763 pos.y++;
1764 pos.x = 0;
1765 line = _HistoryLineAt(pos.y, lineBuffer);
1766 return line != NULL;
1770 #ifdef USE_DEBUG_SNAPSHOTS
1772 void
1773 BasicTerminalBuffer::MakeLinesSnapshots(time_t timeStamp, const char* fileName)
1775 BString str("/var/log/");
1776 struct tm* ts = gmtime(&timeStamp);
1777 str << ts->tm_hour << ts->tm_min << ts->tm_sec;
1778 str << fileName;
1779 FILE* fileOut = fopen(str.String(), "w");
1781 bool dumpHistory = false;
1782 do {
1783 if (dumpHistory && fHistory == NULL) {
1784 fprintf(fileOut, "> History is empty <\n");
1785 break;
1788 int countLines = dumpHistory ? fHistory->Size() : fHeight;
1789 fprintf(fileOut, "> %s lines dump begin <\n",
1790 dumpHistory ? "History" : "Terminal");
1792 TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
1793 for (int i = 0; i < countLines; i++) {
1794 TerminalLine* line = dumpHistory
1795 ? fHistory->GetTerminalLineAt(i, lineBuffer)
1796 : fScreen[_LineIndex(i)];
1798 if (line == NULL) {
1799 fprintf(fileOut, "line: %d is NULL!!!\n", i);
1800 continue;
1803 fprintf(fileOut, "%02" B_PRId16 ":%02" B_PRId16 ":%08" B_PRIx32 ":\n",
1804 i, line->length, line->attributes);
1805 for (int j = 0; j < line->length; j++)
1806 if (line->cells[j].character.bytes[0] != 0)
1807 fwrite(line->cells[j].character.bytes, 1,
1808 line->cells[j].character.ByteCount(), fileOut);
1810 fprintf(fileOut, "\n");
1811 for (int s = 28; s >= 0; s -= 4) {
1812 for (int j = 0; j < fWidth; j++)
1813 fprintf(fileOut, "%01" B_PRIx32,
1814 (line->cells[j].attributes >> s) & 0x0F);
1816 fprintf(fileOut, "\n");
1819 fprintf(fileOut, "\n");
1822 fprintf(fileOut, "> %s lines dump finished <\n",
1823 dumpHistory ? "History" : "Terminal");
1825 dumpHistory = !dumpHistory;
1826 } while (dumpHistory);
1828 fclose(fileOut);
1832 void
1833 BasicTerminalBuffer::StartStopDebugCapture()
1835 if (fCaptureFile >= 0) {
1836 close(fCaptureFile);
1837 fCaptureFile = -1;
1838 return;
1841 time_t timeStamp = time(NULL);
1842 BString str("/var/log/");
1843 struct tm* ts = gmtime(&timeStamp);
1844 str << ts->tm_hour << ts->tm_min << ts->tm_sec;
1845 str << ".Capture.log";
1846 fCaptureFile = open(str.String(), O_CREAT | O_WRONLY,
1847 S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
1851 void
1852 BasicTerminalBuffer::CaptureChar(char ch)
1854 if (fCaptureFile >= 0)
1855 write(fCaptureFile, &ch, 1);
1858 #endif