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