xref: /haiku/src/apps/terminal/BasicTerminalBuffer.cpp (revision e3dc9757d27727652a9862b2a71ab928d5af57e8)
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