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