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