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