* Copyright 2013-2024, Haiku, Inc. All rights reserved.
* Copyright 2008-2010, Ingo Weinhold, ingo_weinhold@gmx.de.
* Distributed under the terms of the MIT License.
*
* Authors:
* Ingo Weinhold, ingo_weinhold@gmx.de
* Siarzhuk Zharski, zharik@gmx.li
*/
#include "BasicTerminalBuffer.h"
#include <alloca.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <algorithm>
#include <StackOrHeapArray.h>
#include <String.h>
#include "TermConst.h"
#include "TerminalCharClassifier.h"
#include "TerminalLine.h"
static const UTF8Char kSpaceChar(' ');
static const int32 kMinRowCount = 2;
static const int32 kMaxRowCount = 1024;
static const int32 kMinColumnCount = 4;
static const int32 kMaxColumnCount = 1024;
#define ALLOC_LINE_ON_STACK(width) \
((TerminalLine*)alloca(sizeof(TerminalLine) \
+ sizeof(TerminalCell) * ((width) - 1)))
static inline int32
restrict_value(int32 value, int32 min, int32 max)
{
return value < min ? min : (value > max ? max : value);
}
inline int32
BasicTerminalBuffer::_LineIndex(int32 index) const
{
return (index + fScreenOffset) % fHeight;
}
inline TerminalLine*
BasicTerminalBuffer::_LineAt(int32 index) const
{
return fScreen[_LineIndex(index)];
}
inline TerminalLine*
BasicTerminalBuffer::_HistoryLineAt(int32 index, TerminalLine* lineBuffer) const
{
if (index >= fHeight)
return NULL;
if (index < 0 && fHistory != NULL)
return fHistory->GetTerminalLineAt(-index - 1, lineBuffer);
return _LineAt(index + fHeight);
}
inline void
BasicTerminalBuffer::_Invalidate(int32 top, int32 bottom)
{
fDirtyInfo.ExtendDirtyRegion(top, bottom);
if (!fDirtyInfo.messageSent) {
NotifyListener();
fDirtyInfo.messageSent = true;
}
}
inline void
BasicTerminalBuffer::_CursorChanged()
{
if (!fDirtyInfo.messageSent) {
NotifyListener();
fDirtyInfo.messageSent = true;
}
}
BasicTerminalBuffer::BasicTerminalBuffer()
:
fWidth(0),
fHeight(0),
fScrollTop(0),
fScrollBottom(0),
fScreen(NULL),
fScreenOffset(0),
fHistory(NULL),
fAttributes(),
fSoftWrappedCursor(false),
fOverwriteMode(false),
fAlternateScreenActive(false),
fOriginMode(false),
fSavedOriginMode(false),
fTabStops(NULL),
fEncoding(M_UTF8),
fCaptureFile(-1),
fLast()
{
}
BasicTerminalBuffer::~BasicTerminalBuffer()
{
delete fHistory;
_FreeLines(fScreen, fHeight);
delete[] fTabStops;
if (fCaptureFile >= 0)
close(fCaptureFile);
}
status_t
BasicTerminalBuffer::Init(int32 width, int32 height, int32 historySize)
{
status_t error;
fWidth = width;
fHeight = height;
fScrollTop = 0;
fScrollBottom = fHeight - 1;
fCursor.x = 0;
fCursor.y = 0;
fSoftWrappedCursor = false;
fScreenOffset = 0;
fOverwriteMode = true;
fAlternateScreenActive = false;
fOriginMode = fSavedOriginMode = false;
fScreen = _AllocateLines(width, height);
if (fScreen == NULL)
return B_NO_MEMORY;
if (historySize > 0) {
fHistory = new(std::nothrow) HistoryBuffer;
if (fHistory == NULL)
return B_NO_MEMORY;
error = fHistory->Init(width, historySize);
if (error != B_OK)
return error;
}
error = _ResetTabStops(fWidth);
if (error != B_OK)
return error;
for (int32 i = 0; i < fHeight; i++)
fScreen[i]->Clear();
fDirtyInfo.Reset();
return B_OK;
}
status_t
BasicTerminalBuffer::ResizeTo(int32 width, int32 height)
{
return ResizeTo(width, height, fHistory != NULL ? fHistory->Capacity() : 0);
}
status_t
BasicTerminalBuffer::ResizeTo(int32 width, int32 height, int32 historyCapacity)
{
if (height < kMinRowCount || height > kMaxRowCount
|| width < kMinColumnCount || width > kMaxColumnCount) {
return B_BAD_VALUE;
}
if (width == fWidth && height == fHeight)
return SetHistoryCapacity(historyCapacity);
if (fAlternateScreenActive)
return _ResizeSimple(width, height, historyCapacity);
return _ResizeRewrap(width, height, historyCapacity);
}
status_t
BasicTerminalBuffer::SetHistoryCapacity(int32 historyCapacity)
{
return _ResizeHistory(fWidth, historyCapacity);
}
void
BasicTerminalBuffer::Clear(bool resetCursor)
{
fSoftWrappedCursor = false;
fScreenOffset = 0;
_ClearLines(0, fHeight - 1);
if (resetCursor)
fCursor.SetTo(0, 0);
if (fHistory != NULL)
fHistory->Clear();
fDirtyInfo.linesScrolled = 0;
_Invalidate(0, fHeight - 1);
}
void
BasicTerminalBuffer::SynchronizeWith(const BasicTerminalBuffer* other,
int32 offset, int32 dirtyTop, int32 dirtyBottom)
{
int32 first = 0;
int32 last = fHeight - 1;
dirtyTop -= offset;
dirtyBottom -= offset;
if (first > dirtyBottom || dirtyTop > last)
return;
if (first < dirtyTop)
first = dirtyTop;
if (last > dirtyBottom)
last = dirtyBottom;
for (int32 i = first; i <= last; i++) {
TerminalLine* destLine = _LineAt(i);
TerminalLine* sourceLine = other->_HistoryLineAt(i + offset, destLine);
if (sourceLine != NULL) {
if (sourceLine != destLine) {
destLine->length = sourceLine->length;
destLine->attributes = sourceLine->attributes;
destLine->softBreak = sourceLine->softBreak;
if (destLine->length > 0) {
memcpy(destLine->cells, sourceLine->cells,
fWidth * sizeof(TerminalCell));
}
} else {
}
} else
destLine->Clear(fAttributes, fWidth);
}
}
bool
BasicTerminalBuffer::IsFullWidthChar(int32 row, int32 column) const
{
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
TerminalLine* line = _HistoryLineAt(row, lineBuffer);
return line != NULL && column > 0 && column < line->length
&& line->cells[column - 1].attributes.IsWidth();
}
int
BasicTerminalBuffer::GetChar(int32 row, int32 column, UTF8Char& character,
Attributes& attributes) const
{
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
TerminalLine* line = _HistoryLineAt(row, lineBuffer);
if (line == NULL)
return NO_CHAR;
if (column < 0 || column >= line->length)
return NO_CHAR;
if (column > 0 && line->cells[column - 1].attributes.IsWidth())
return IN_STRING;
TerminalCell& cell = line->cells[column];
character = cell.character;
attributes = cell.attributes;
return A_CHAR;
}
void
BasicTerminalBuffer::GetCellAttributes(int32 row, int32 column,
Attributes& attributes, uint32& count) const
{
count = 0;
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
TerminalLine* line = _HistoryLineAt(row, lineBuffer);
if (line == NULL || column < 0)
return;
int32 c = column;
for (; c < fWidth; c++) {
TerminalCell& cell = line->cells[c];
if (c > column && attributes != cell.attributes)
break;
attributes = cell.attributes;
}
count = c - column;
}
int32
BasicTerminalBuffer::GetString(int32 row, int32 firstColumn, int32 lastColumn,
char* buffer, Attributes& attributes) const
{
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
TerminalLine* line = _HistoryLineAt(row, lineBuffer);
if (line == NULL)
return 0;
if (lastColumn >= line->length)
lastColumn = line->length - 1;
int32 column = firstColumn;
if (column <= lastColumn)
attributes = line->cells[column].attributes;
for (; column <= lastColumn; column++) {
TerminalCell& cell = line->cells[column];
if (cell.attributes != attributes)
break;
int32 bytes = cell.character.ByteCount();
for (int32 i = 0; i < bytes; i++)
*buffer++ = cell.character.bytes[i];
}
*buffer = '\0';
return column - firstColumn;
}
void
BasicTerminalBuffer::GetStringFromRegion(BString& string, const TermPos& start,
const TermPos& end) const
{
if (start >= end)
return;
TermPos pos(start);
if (IsFullWidthChar(pos.y, pos.x))
pos.x--;
while (pos.y < end.y) {
TerminalLine* line = _GetPartialLineString(string, pos.y, pos.x,
fWidth);
if (line != NULL && !line->softBreak)
string.Append('\n', 1);
pos.x = 0;
pos.y++;
}
if (end.x > 0)
_GetPartialLineString(string, end.y, pos.x, end.x);
}
bool
BasicTerminalBuffer::FindWord(const TermPos& pos,
TerminalCharClassifier* classifier, bool findNonWords, TermPos& _start,
TermPos& _end) const
{
int32 x = pos.x;
int32 y = pos.y;
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
TerminalLine* line = _HistoryLineAt(y, lineBuffer);
if (line == NULL || x < 0 || x >= fWidth)
return false;
if (x >= line->length) {
if (!findNonWords)
return false;
_start.SetTo(line->length, y);
_end.SetTo(fWidth, y);
return true;
}
if (x > 0 && line->cells[x - 1].attributes.IsWidth())
x--;
int type = classifier->Classify(line->cells[x].character);
if (type != CHAR_TYPE_WORD_CHAR && !findNonWords)
return false;
TermPos start(x, y);
TermPos end(x + (line->cells[x].attributes.IsWidth()
? FULL_WIDTH : HALF_WIDTH), y);
for (;;) {
TermPos previousPos = start;
if (!_PreviousLinePos(lineBuffer, line, previousPos)
|| classifier->Classify(line->cells[previousPos.x].character)
!= type) {
break;
}
start = previousPos;
}
line = _HistoryLineAt(end.y, lineBuffer);
for (;;) {
TermPos nextPos = end;
if (!_NormalizeLinePos(lineBuffer, line, nextPos))
break;
if (classifier->Classify(line->cells[nextPos.x].character) != type)
break;
nextPos.x += line->cells[nextPos.x].attributes.IsWidth()
? FULL_WIDTH : HALF_WIDTH;
end = nextPos;
}
_start = start;
_end = end;
return true;
}
bool
BasicTerminalBuffer::PreviousLinePos(TermPos& pos) const
{
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer);
if (line == NULL || pos.x < 0 || pos.x >= fWidth)
return false;
return _PreviousLinePos(lineBuffer, line, pos);
}
bool
BasicTerminalBuffer::NextLinePos(TermPos& pos, bool normalize) const
{
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer);
if (line == NULL || pos.x < 0 || pos.x > fWidth)
return false;
if (!_NormalizeLinePos(lineBuffer, line, pos))
return false;
pos.x += line->cells[pos.x].attributes.IsWidth() ? FULL_WIDTH : HALF_WIDTH;
return !normalize || _NormalizeLinePos(lineBuffer, line, pos);
}
int32
BasicTerminalBuffer::LineLength(int32 index) const
{
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
TerminalLine* line = _HistoryLineAt(index, lineBuffer);
return line != NULL ? line->length : 0;
}
void
BasicTerminalBuffer::GetLineColor(int32 index, Attributes& attr) const
{
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
TerminalLine* line = _HistoryLineAt(index, lineBuffer);
if (line != NULL)
attr = line->attributes;
else
attr.Reset();
}
bool
BasicTerminalBuffer::Find(const char* _pattern, const TermPos& start,
bool forward, bool caseSensitive, bool matchWord, TermPos& _matchStart,
TermPos& _matchEnd) const
{
TermPos pos(start);
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer);
if (line != NULL) {
if (forward) {
while (line != NULL && pos.x >= line->length && line->softBreak) {
pos.x = 0;
pos.y++;
line = _HistoryLineAt(pos.y, lineBuffer);
}
} else {
if (pos.x > line->length)
pos.x = line->length;
}
}
int32 patternByteLen = strlen(_pattern);
BStackOrHeapArray<UTF8Char, 64> pattern(patternByteLen);
if (!pattern.IsValid())
return false;
int32 patternLen = 0;
while (*_pattern != '\0') {
int32 charLen = UTF8Char::ByteCount(*_pattern);
if (charLen > 0) {
pattern[patternLen].SetTo(_pattern, charLen);
if (!caseSensitive && charLen == 1)
pattern[patternLen] = pattern[patternLen].ToLower();
patternLen++;
_pattern += charLen;
} else
_pattern++;
}
if (patternLen == 0)
return false;
if (!forward) {
for (int32 i = 0; i < patternLen / 2; i++)
std::swap(pattern[i], pattern[patternLen - i - 1]);
}
int32 matchIndex = 0;
TermPos matchStart;
while (true) {
TermPos previousPos(pos);
UTF8Char c;
if (!(forward ? _NextChar(pos, c) : _PreviousChar(pos, c)))
return false;
if (caseSensitive ? (c == pattern[matchIndex])
: (c.ToLower() == pattern[matchIndex])) {
if (matchIndex == 0)
matchStart = previousPos;
matchIndex++;
if (matchIndex == patternLen) {
TermPos matchEnd(pos);
if (!forward)
std::swap(matchStart, matchEnd);
if (matchWord) {
TermPos tempPos(matchStart);
if ((_PreviousChar(tempPos, c) && !c.IsSpace())
|| (_NextChar(tempPos = matchEnd, c) && !c.IsSpace())) {
continue;
}
}
_matchStart = matchStart;
_matchEnd = matchEnd;
return true;
}
} else if (matchIndex > 0) {
pos = matchStart;
if (forward)
_NextChar(pos, c);
else
_PreviousChar(pos, c);
matchIndex = 0;
}
}
}
void
BasicTerminalBuffer::InsertChar(UTF8Char c)
{
fLast = c;
int32 width = c.IsFullWidth() ? FULL_WIDTH : HALF_WIDTH;
if (fSoftWrappedCursor || (fCursor.x + width) > fWidth)
_SoftBreakLine();
else
_PadLineToCursor();
fSoftWrappedCursor = false;
if (!fOverwriteMode)
_InsertGap(width);
TerminalLine* line = _LineAt(fCursor.y);
line->cells[fCursor.x].character = c;
line->cells[fCursor.x].attributes = fAttributes;
line->cells[fCursor.x].attributes.state |= (width == FULL_WIDTH ? A_WIDTH : 0);
if (line->length < fCursor.x + width)
line->length = fCursor.x + width;
_Invalidate(fCursor.y, fCursor.y);
fCursor.x += width;
if (fCursor.x == fWidth) {
fCursor.x -= width;
fSoftWrappedCursor = true;
}
}
void
BasicTerminalBuffer::FillScreen(UTF8Char c, Attributes &attributes)
{
uint32 width = HALF_WIDTH;
if (c.IsFullWidth()) {
attributes |= A_WIDTH;
width = FULL_WIDTH;
}
fSoftWrappedCursor = false;
for (int32 y = 0; y < fHeight; y++) {
TerminalLine *line = _LineAt(y);
for (int32 x = 0; x < fWidth / (int32)width; x++) {
line->cells[x].character = c;
line->cells[x].attributes = attributes;
}
line->length = fWidth / width;
}
_Invalidate(0, fHeight - 1);
}
void
BasicTerminalBuffer::InsertCR()
{
TerminalLine* line = _LineAt(fCursor.y);
line->attributes = fAttributes;
line->softBreak = false;
fSoftWrappedCursor = false;
fCursor.x = 0;
_Invalidate(fCursor.y, fCursor.y);
_CursorChanged();
}
void
BasicTerminalBuffer::InsertLF()
{
fSoftWrappedCursor = false;
if (fCursor.y == fScrollBottom) {
_Scroll(fScrollTop, fScrollBottom, 1);
} else {
if (fCursor.y < fHeight - 1)
fCursor.y++;
_CursorChanged();
}
}
void
BasicTerminalBuffer::InsertRI()
{
fSoftWrappedCursor = false;
if (fCursor.y == fScrollTop) {
_Scroll(fScrollTop, fScrollBottom, -1);
} else {
if (fCursor.y > 0)
fCursor.y--;
_CursorChanged();
}
}
void
BasicTerminalBuffer::InsertTab()
{
int32 x;
fSoftWrappedCursor = false;
for (x = fCursor.x + 1; x < fWidth && !fTabStops[x]; x++)
;
x = restrict_value(x, 0, fWidth - 1);
if (x != fCursor.x) {
TerminalLine* line = _LineAt(fCursor.y);
for (int32 i = fCursor.x; i <= x; i++) {
if (line->length <= i) {
line->cells[i].character = ' ';
line->cells[i].attributes = fAttributes;
}
}
fCursor.x = x;
if (line->length < fCursor.x)
line->length = fCursor.x;
_CursorChanged();
}
}
void
BasicTerminalBuffer::InsertCursorBackTab(int32 numTabs)
{
int32 x = fCursor.x - 1;
fSoftWrappedCursor = false;
while (numTabs-- > 0)
for (; x >=0 && !fTabStops[x]; x--)
;
x = restrict_value(x, 0, fWidth - 1);
if (x != fCursor.x) {
fCursor.x = x;
_CursorChanged();
}
}
void
BasicTerminalBuffer::InsertLines(int32 numLines)
{
if (fCursor.y >= fScrollTop && fCursor.y < fScrollBottom) {
fSoftWrappedCursor = false;
_Scroll(fCursor.y, fScrollBottom, -numLines);
}
}
void
BasicTerminalBuffer::SetInsertMode(int flag)
{
fOverwriteMode = flag == MODE_OVER;
}
void
BasicTerminalBuffer::InsertSpace(int32 num)
{
if (fCursor.x + num > fWidth)
num = fWidth - fCursor.x;
if (num > 0) {
fSoftWrappedCursor = false;
_PadLineToCursor();
_InsertGap(num);
TerminalLine* line = _LineAt(fCursor.y);
for (int32 i = fCursor.x; i < fCursor.x + num; i++) {
line->cells[i].character = kSpaceChar;
line->cells[i].attributes = line->cells[fCursor.x - 1].attributes;
}
line->attributes = fAttributes;
_Invalidate(fCursor.y, fCursor.y);
}
}
void
BasicTerminalBuffer::EraseCharsFrom(int32 first, int32 numChars)
{
TerminalLine* line = _LineAt(fCursor.y);
int32 end = min_c(first + numChars, fWidth);
for (int32 i = first; i < end; i++)
line->cells[i].attributes = fAttributes;
line->attributes = fAttributes;
fSoftWrappedCursor = false;
end = min_c(first + numChars, line->length);
if (first > 0 && line->cells[first - 1].attributes.IsWidth())
first--;
if (end > 0 && line->cells[end - 1].attributes.IsWidth())
end++;
for (int32 i = first; i < end; i++) {
line->cells[i].character = kSpaceChar;
line->cells[i].attributes = fAttributes;
}
_Invalidate(fCursor.y, fCursor.y);
}
void
BasicTerminalBuffer::EraseAbove()
{
if (fCursor.y > 0)
_ClearLines(0, fCursor.y - 1);
fSoftWrappedCursor = false;
TerminalLine* line = _LineAt(fCursor.y);
if (fCursor.x < line->length) {
int32 to = fCursor.x;
if (line->cells[fCursor.x].attributes.IsWidth())
to++;
for (int32 i = 0; i <= to; i++) {
line->cells[i].attributes = fAttributes;
line->cells[i].character = kSpaceChar;
}
} else
line->Clear(fAttributes, fWidth);
_Invalidate(fCursor.y, fCursor.y);
}
void
BasicTerminalBuffer::EraseBelow()
{
fSoftWrappedCursor = false;
if (fCursor.y < fHeight - 1)
_ClearLines(fCursor.y + 1, fHeight - 1);
DeleteColumns();
}
void
BasicTerminalBuffer::EraseAll()
{
fSoftWrappedCursor = false;
_Scroll(0, fHeight - 1, fHeight);
}
void
BasicTerminalBuffer::EraseScrollback()
{
fSoftWrappedCursor = false;
if (fHistory != NULL)
fHistory->Clear();
_Invalidate(0, 0);
}
void
BasicTerminalBuffer::DeleteChars(int32 numChars)
{
fSoftWrappedCursor = false;
TerminalLine* line = _LineAt(fCursor.y);
if (fCursor.x < line->length) {
if (fCursor.x + numChars < line->length) {
int32 left = line->length - fCursor.x - numChars;
memmove(line->cells + fCursor.x, line->cells + fCursor.x + numChars,
left * sizeof(TerminalCell));
line->length = fCursor.x + left;
for (int i = 0; i < numChars; i++)
line->cells[fCursor.x + left + i].attributes = fAttributes;
} else {
for (int i = 0; i < line->length - fCursor.x; i++)
line->cells[fCursor.x + i].attributes = fAttributes;
line->length = fCursor.x;
}
_Invalidate(fCursor.y, fCursor.y);
}
}
void
BasicTerminalBuffer::DeleteColumnsFrom(int32 first)
{
fSoftWrappedCursor = false;
TerminalLine* line = _LineAt(fCursor.y);
for (int32 i = first; i < fWidth; i++)
line->cells[i].attributes = fAttributes;
if (first <= line->length) {
line->length = first;
line->attributes = fAttributes;
}
_Invalidate(fCursor.y, fCursor.y);
}
void
BasicTerminalBuffer::DeleteLines(int32 numLines)
{
if (fCursor.y >= fScrollTop && fCursor.y <= fScrollBottom) {
fSoftWrappedCursor = false;
_Scroll(fCursor.y, fScrollBottom, numLines);
}
}
void
BasicTerminalBuffer::SaveCursor()
{
fSavedCursors.push(fCursor);
}
void
BasicTerminalBuffer::RestoreCursor()
{
if (fSavedCursors.size() == 0)
return;
_SetCursor(fSavedCursors.top().x, fSavedCursors.top().y, true);
fSavedCursors.pop();
}
void
BasicTerminalBuffer::SetScrollRegion(int32 top, int32 bottom)
{
fScrollTop = restrict_value(top, 0, fHeight - 1);
fScrollBottom = restrict_value(bottom, fScrollTop, fHeight - 1);
_SetCursor(0, 0, false);
}
void
BasicTerminalBuffer::SetOriginMode(bool enabled)
{
fOriginMode = enabled;
_SetCursor(0, 0, false);
}
void
BasicTerminalBuffer::SaveOriginMode()
{
fSavedOriginMode = fOriginMode;
}
void
BasicTerminalBuffer::RestoreOriginMode()
{
fOriginMode = fSavedOriginMode;
}
void
BasicTerminalBuffer::SetTabStop(int32 x)
{
x = restrict_value(x, 0, fWidth - 1);
fTabStops[x] = true;
}
void
BasicTerminalBuffer::ClearTabStop(int32 x)
{
x = restrict_value(x, 0, fWidth - 1);
fTabStops[x] = false;
}
void
BasicTerminalBuffer::ClearAllTabStops()
{
for (int32 i = 0; i < fWidth; i++)
fTabStops[i] = false;
}
void
BasicTerminalBuffer::NotifyListener()
{
}
void
BasicTerminalBuffer::_SetCursor(int32 x, int32 y, bool absolute)
{
fSoftWrappedCursor = false;
x = restrict_value(x, 0, fWidth - 1);
if (fOriginMode && !absolute) {
y += fScrollTop;
y = restrict_value(y, fScrollTop, fScrollBottom);
} else {
y = restrict_value(y, 0, fHeight - 1);
}
if (x != fCursor.x || y != fCursor.y) {
fCursor.x = x;
fCursor.y = y;
_CursorChanged();
}
}
void
BasicTerminalBuffer::_InvalidateAll()
{
fDirtyInfo.invalidateAll = true;
if (!fDirtyInfo.messageSent) {
NotifyListener();
fDirtyInfo.messageSent = true;
}
}
TerminalLine**
BasicTerminalBuffer::_AllocateLines(int32 width, int32 count)
{
TerminalLine** lines = (TerminalLine**)malloc(sizeof(TerminalLine*) * count);
if (lines == NULL)
return NULL;
for (int32 i = 0; i < count; i++) {
const int32 size = sizeof(TerminalLine)
+ sizeof(TerminalCell) * (width - 1);
lines[i] = (TerminalLine*)malloc(size);
if (lines[i] == NULL) {
_FreeLines(lines, i);
return NULL;
}
lines[i]->Clear(width);
}
return lines;
}
void
BasicTerminalBuffer::_FreeLines(TerminalLine** lines, int32 count)
{
if (lines != NULL) {
for (int32 i = 0; i < count; i++)
free(lines[i]);
free(lines);
}
}
void
BasicTerminalBuffer::_ClearLines(int32 first, int32 last)
{
int32 firstCleared = -1;
int32 lastCleared = -1;
for (int32 i = first; i <= last; i++) {
TerminalLine* line = _LineAt(i);
if (line->length > 0) {
if (firstCleared == -1)
firstCleared = i;
lastCleared = i;
}
line->Clear(fAttributes, fWidth);
}
if (firstCleared >= 0)
_Invalidate(firstCleared, lastCleared);
}
status_t
BasicTerminalBuffer::_ResizeHistory(int32 width, int32 historyCapacity)
{
if (width == fWidth && historyCapacity == HistoryCapacity())
return B_OK;
if (historyCapacity <= 0) {
delete fHistory;
fHistory = NULL;
return B_OK;
}
HistoryBuffer* history = new(std::nothrow) HistoryBuffer;
if (history == NULL)
return B_NO_MEMORY;
status_t error = history->Init(width, historyCapacity);
if (error != B_OK) {
delete history;
return error;
}
if (fHistory != NULL) {
int32 historySize = min_c(HistorySize(), historyCapacity);
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
for (int32 i = historySize - 1; i >= 0; i--) {
TerminalLine* line = fHistory->GetTerminalLineAt(i, lineBuffer);
if (line->length > width)
_TruncateLine(line, width);
history->AddLine(line);
}
}
delete fHistory;
fHistory = history;
return B_OK;
}
status_t
BasicTerminalBuffer::_ResizeSimple(int32 width, int32 height,
int32 historyCapacity)
{
if (width == fWidth && height == fHeight)
return B_OK;
if (width != fWidth || historyCapacity != HistoryCapacity()) {
status_t error = _ResizeHistory(width, historyCapacity);
if (error != B_OK)
return error;
}
TerminalLine** lines = _AllocateLines(width, height);
if (lines == NULL)
return B_NO_MEMORY;
int32 endLine = min_c(fHeight, height);
int32 firstLine = 0;
if (height < fHeight) {
if (endLine <= fCursor.y) {
endLine = fCursor.y + 1;
firstLine = endLine - height;
}
if (fHistory != NULL) {
for (int32 i = 0; i < firstLine; i++) {
TerminalLine* line = _LineAt(i);
if (width < fWidth)
_TruncateLine(line, width);
fHistory->AddLine(line);
}
}
}
for (int32 i = firstLine; i < endLine; i++) {
TerminalLine* sourceLine = _LineAt(i);
TerminalLine* destLine = lines[i - firstLine];
if (width < fWidth)
_TruncateLine(sourceLine, width);
memcpy(destLine, sourceLine, (int32)sizeof(TerminalLine)
+ (sourceLine->length - 1) * (int32)sizeof(TerminalCell));
}
for (int32 i = endLine - firstLine; i < height; i++)
lines[i]->Clear(fAttributes, width);
_FreeLines(fScreen, fHeight);
fScreen = lines;
if (fWidth != width) {
status_t error = _ResetTabStops(width);
if (error != B_OK)
return error;
}
fWidth = width;
fHeight = height;
fScrollTop = 0;
fScrollBottom = fHeight - 1;
fOriginMode = fSavedOriginMode = false;
fScreenOffset = 0;
if (fCursor.x > width)
fCursor.x = width;
fCursor.y -= firstLine;
fSoftWrappedCursor = false;
return B_OK;
}
status_t
BasicTerminalBuffer::_ResizeRewrap(int32 width, int32 height,
int32 historyCapacity)
{
if (width == fWidth)
return _ResizeSimple(width, height, historyCapacity);
TerminalLine** screen = _AllocateLines(width, height);
if (screen == NULL)
return B_NO_MEMORY;
HistoryBuffer* history = NULL;
if (historyCapacity > 0) {
history = new(std::nothrow) HistoryBuffer;
if (history == NULL) {
_FreeLines(screen, height);
return B_NO_MEMORY;
}
status_t error = history->Init(width, historyCapacity);
if (error != B_OK) {
_FreeLines(screen, height);
delete history;
return error;
}
}
int32 historySize = HistorySize();
int32 totalLines = historySize + fHeight;
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
TermPos cursor;
int32 destIndex = 0;
int32 sourceIndex = 0;
int32 sourceX = 0;
int32 destTotalLines = 0;
int32 destScreenOffset = 0;
int32 maxDestTotalLines = INT_MAX;
bool newDestLine = true;
bool cursorSeen = false;
TerminalLine* sourceLine = _HistoryLineAt(-historySize, lineBuffer);
while (sourceIndex < totalLines) {
TerminalLine* destLine = screen[destIndex];
if (newDestLine) {
if (history != NULL && destTotalLines >= height)
history->AddLine(screen[destIndex]);
destLine->Clear(fAttributes, width);
newDestLine = false;
}
int32 sourceLeft = sourceLine->length - sourceX;
int32 destLeft = width - destLine->length;
if (sourceIndex == historySize && sourceX == 0) {
destScreenOffset = destTotalLines;
if (destLeft == 0 && sourceLeft > 0)
destScreenOffset++;
maxDestTotalLines = destScreenOffset + height;
}
int32 toCopy = min_c(sourceLeft, destLeft);
if (toCopy > 0 && sourceLine->cells[sourceX + toCopy - 1].attributes.IsWidth()) {
toCopy--;
}
if (fCursor.y + historySize == sourceIndex
&& fCursor.x >= sourceX
&& (fCursor.x < sourceX + toCopy
|| (destLeft >= sourceLeft
&& sourceX + sourceLeft <= fCursor.x))) {
cursor.x = destLine->length + fCursor.x - sourceX;
cursor.y = destTotalLines;
if (cursor.x >= width) {
cursor.x = width - 1;
}
cursorSeen = true;
}
if (toCopy > 0) {
memcpy(destLine->cells + destLine->length,
sourceLine->cells + sourceX, toCopy * sizeof(TerminalCell));
destLine->length += toCopy;
}
destLine->attributes = sourceLine->attributes;
bool nextDestLine = false;
if (toCopy == sourceLeft) {
if (!sourceLine->softBreak)
nextDestLine = true;
sourceIndex++;
sourceX = 0;
sourceLine = _HistoryLineAt(sourceIndex - historySize,
lineBuffer);
} else {
destLine->softBreak = true;
nextDestLine = true;
sourceX += toCopy;
}
if (nextDestLine) {
destIndex = (destIndex + 1) % height;
destTotalLines++;
newDestLine = true;
if (cursorSeen && destTotalLines >= maxDestTotalLines)
break;
}
}
if (!newDestLine) {
destIndex = (destIndex + 1) % height;
destTotalLines++;
}
if (destTotalLines - destScreenOffset > height)
destScreenOffset = destTotalLines - height;
cursor.y -= destScreenOffset;
for (int32 i = destTotalLines; i < destScreenOffset + height; i++) {
TerminalLine* line = screen[i % height];
if (history != NULL && i >= height)
history->AddLine(line);
line->Clear(fAttributes, width);
}
_FreeLines(fScreen, fHeight);
delete fHistory;
fScreen = screen;
fHistory = history;
if (fWidth != width) {
status_t error = _ResetTabStops(width);
if (error != B_OK)
return error;
}
fCursor.x = cursor.x;
fCursor.y = cursor.y;
fSoftWrappedCursor = false;
fScreenOffset = destScreenOffset % height;
fHeight = height;
fWidth = width;
fScrollTop = 0;
fScrollBottom = fHeight - 1;
fOriginMode = fSavedOriginMode = false;
return B_OK;
}
status_t
BasicTerminalBuffer::_ResetTabStops(int32 width)
{
if (fTabStops != NULL)
delete[] fTabStops;
fTabStops = new(std::nothrow) bool[width];
if (fTabStops == NULL)
return B_NO_MEMORY;
for (int32 i = 0; i < width; i++)
fTabStops[i] = (i % TAB_WIDTH) == 0;
return B_OK;
}
void
BasicTerminalBuffer::_Scroll(int32 top, int32 bottom, int32 numLines)
{
if (numLines == 0)
return;
if (numLines > 0) {
if (top == 0) {
if (fHistory != NULL) {
int32 toHistory = min_c(numLines, bottom - top + 1);
for (int32 i = 0; i < toHistory; i++)
fHistory->AddLine(_LineAt(i));
if (toHistory < numLines)
fHistory->AddEmptyLines(numLines - toHistory);
}
if (numLines >= bottom - top + 1) {
_ClearLines(top, bottom);
} else if (bottom == fHeight - 1) {
fScreenOffset = (fScreenOffset + numLines) % fHeight;
for (int32 i = bottom - numLines + 1; i <= bottom; i++)
_LineAt(i)->Clear(fAttributes, fWidth);
} else {
for (int32 i = fHeight - 1; i > bottom; i--) {
std::swap(fScreen[_LineIndex(i)],
fScreen[_LineIndex(i + numLines)]);
}
fScreenOffset = (fScreenOffset + numLines) % fHeight;
for (int32 i = bottom - numLines + 1; i <= bottom; i++)
_LineAt(i)->Clear(fAttributes, fWidth);
}
if (fDirtyInfo.dirtyTop != INT_MAX) {
if (fDirtyInfo.dirtyTop <= bottom) {
fDirtyInfo.dirtyTop -= numLines;
if (fDirtyInfo.dirtyBottom <= bottom)
fDirtyInfo.dirtyBottom -= numLines;
}
_Invalidate(bottom - numLines + 1, bottom);
}
fDirtyInfo.linesScrolled += numLines;
_Invalidate(bottom + 1 - numLines, bottom);
if (bottom < fHeight - 1)
_Invalidate(bottom + 1, fHeight - 1);
} else if (numLines >= bottom - top + 1) {
_ClearLines(top, bottom);
} else {
for (int32 i = top + numLines; i <= bottom; i++) {
int32 lineToDrop = _LineIndex(i - numLines);
int32 lineToKeep = _LineIndex(i);
fScreen[lineToDrop]->Clear(fAttributes, fWidth);
std::swap(fScreen[lineToDrop], fScreen[lineToKeep]);
}
for (int32 i = bottom - numLines + 1; i < top + numLines; i++)
_LineAt(i)->Clear(fAttributes, fWidth);
_Invalidate(top, bottom);
}
} else {
numLines = -numLines;
if (numLines >= bottom - top + 1) {
_ClearLines(top, bottom);
} else {
for (int32 i = bottom - numLines; i >= top; i--) {
int32 lineToKeep = _LineIndex(i);
int32 lineToDrop = _LineIndex(i + numLines);
fScreen[lineToDrop]->Clear(fAttributes, fWidth);
std::swap(fScreen[lineToDrop], fScreen[lineToKeep]);
}
for (int32 i = bottom - numLines + 1; i < top + numLines; i++)
_LineAt(i)->Clear(fAttributes, fWidth);
_Invalidate(top, bottom);
}
}
}
void
BasicTerminalBuffer::_SoftBreakLine()
{
TerminalLine* line = _LineAt(fCursor.y);
line->softBreak = true;
fCursor.x = 0;
if (fCursor.y == fScrollBottom)
_Scroll(fScrollTop, fScrollBottom, 1);
else
fCursor.y++;
}
void
BasicTerminalBuffer::_PadLineToCursor()
{
TerminalLine* line = _LineAt(fCursor.y);
if (line->length < fCursor.x)
for (int32 i = line->length; i < fCursor.x; i++)
line->cells[i].character = kSpaceChar;
}
void
BasicTerminalBuffer::_TruncateLine(TerminalLine* line, int32 length)
{
if (line->length <= length)
return;
if (length > 0 && line->cells[length - 1].attributes.IsWidth())
length--;
line->length = length;
}
void
BasicTerminalBuffer::_InsertGap(int32 width)
{
TerminalLine* line = _LineAt(fCursor.y);
int32 toMove = min_c(line->length - fCursor.x, fWidth - fCursor.x - width);
if (toMove > 0) {
memmove(line->cells + fCursor.x + width,
line->cells + fCursor.x, toMove * sizeof(TerminalCell));
}
line->length = min_c(line->length + width, fWidth);
}
*/
TerminalLine*
BasicTerminalBuffer::_GetPartialLineString(BString& string, int32 row,
int32 startColumn, int32 endColumn) const
{
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
TerminalLine* line = _HistoryLineAt(row, lineBuffer);
if (line == NULL)
return NULL;
if (endColumn > line->length)
endColumn = line->length;
for (int32 x = startColumn; x < endColumn; x++) {
const TerminalCell& cell = line->cells[x];
string.Append(cell.character.bytes, cell.character.ByteCount());
if (cell.attributes.IsWidth())
x++;
}
return line;
}
*/
bool
BasicTerminalBuffer::_PreviousChar(TermPos& pos, UTF8Char& c) const
{
pos.x--;
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer);
while (true) {
if (pos.x < 0) {
pos.y--;
line = _HistoryLineAt(pos.y, lineBuffer);
if (line == NULL)
return false;
pos.x = line->length;
if (line->softBreak) {
pos.x--;
} else {
c = '\n';
return true;
}
} else {
c = line->cells[pos.x].character;
return true;
}
}
}
*/
bool
BasicTerminalBuffer::_NextChar(TermPos& pos, UTF8Char& c) const
{
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer);
if (line == NULL)
return false;
if (pos.x >= line->length) {
c = '\n';
pos.x = 0;
pos.y++;
return true;
}
c = line->cells[pos.x].character;
pos.x++;
while (line != NULL && pos.x >= line->length && line->softBreak) {
pos.x = 0;
pos.y++;
line = _HistoryLineAt(pos.y, lineBuffer);
}
return true;
}
bool
BasicTerminalBuffer::_PreviousLinePos(TerminalLine* lineBuffer,
TerminalLine*& line, TermPos& pos) const
{
if (--pos.x < 0) {
pos.y--;
if ((line = _HistoryLineAt(pos.y, lineBuffer)) == NULL
|| !line->softBreak || line->length == 0) {
return false;
}
pos.x = line->length - 1;
}
if (pos.x > 0 && line->cells[pos.x - 1].attributes.IsWidth())
pos.x--;
return true;
}
bool
BasicTerminalBuffer::_NormalizeLinePos(TerminalLine* lineBuffer,
TerminalLine*& line, TermPos& pos) const
{
if (pos.x < line->length)
return true;
if (!line->softBreak)
return false;
pos.y++;
pos.x = 0;
line = _HistoryLineAt(pos.y, lineBuffer);
return line != NULL;
}
#ifdef USE_DEBUG_SNAPSHOTS
void
BasicTerminalBuffer::MakeLinesSnapshots(time_t timeStamp, const char* fileName)
{
BString str("/var/log/");
struct tm* ts = gmtime(&timeStamp);
str << ts->tm_hour << ts->tm_min << ts->tm_sec;
str << fileName;
FILE* fileOut = fopen(str.String(), "w");
bool dumpHistory = false;
do {
if (dumpHistory && fHistory == NULL) {
fprintf(fileOut, "> History is empty <\n");
break;
}
int countLines = dumpHistory ? fHistory->Size() : fHeight;
fprintf(fileOut, "> %s lines dump begin <\n",
dumpHistory ? "History" : "Terminal");
TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
for (int i = 0; i < countLines; i++) {
TerminalLine* line = dumpHistory
? fHistory->GetTerminalLineAt(i, lineBuffer)
: fScreen[_LineIndex(i)];
if (line == NULL) {
fprintf(fileOut, "line: %d is NULL!!!\n", i);
continue;
}
fprintf(fileOut, "%02" B_PRId16 ":%02" B_PRId16 ":%08" B_PRIx32 ":\n",
i, line->length, line->attributes.state);
for (int j = 0; j < line->length; j++)
if (line->cells[j].character.bytes[0] != 0)
fwrite(line->cells[j].character.bytes, 1,
line->cells[j].character.ByteCount(), fileOut);
fprintf(fileOut, "\n");
for (int s = 28; s >= 0; s -= 4) {
for (int j = 0; j < fWidth; j++)
fprintf(fileOut, "%01" B_PRIx32,
(line->cells[j].attributes.state >> s) & 0x0F);
fprintf(fileOut, "\n");
}
fprintf(fileOut, "\n");
}
fprintf(fileOut, "> %s lines dump finished <\n",
dumpHistory ? "History" : "Terminal");
dumpHistory = !dumpHistory;
} while (dumpHistory);
fclose(fileOut);
}
void
BasicTerminalBuffer::StartStopDebugCapture()
{
if (fCaptureFile >= 0) {
close(fCaptureFile);
fCaptureFile = -1;
return;
}
time_t timeStamp = time(NULL);
BString str("/var/log/");
struct tm* ts = gmtime(&timeStamp);
str << ts->tm_hour << ts->tm_min << ts->tm_sec;
str << ".Capture.log";
fCaptureFile = open(str.String(), O_CREAT | O_WRONLY,
S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
}
void
BasicTerminalBuffer::CaptureChar(char ch)
{
if (fCaptureFile >= 0)
write(fCaptureFile, &ch, 1);
}
void
BasicTerminalBuffer::InsertLastChar()
{
InsertChar(fLast);
}
#endif