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