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