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