diff --git a/src/apps/terminal/CodeConv.h b/src/apps/terminal/CodeConv.h index 12460e5e74..14994d5137 100644 --- a/src/apps/terminal/CodeConv.h +++ b/src/apps/terminal/CodeConv.h @@ -26,4 +26,5 @@ class CodeConv { static unsigned short UTF8toUnicode(const char *utf8); }; + #endif /* CODECONV_H */ diff --git a/src/apps/terminal/CurPos.cpp b/src/apps/terminal/CurPos.cpp deleted file mode 100644 index 9655268f47..0000000000 --- a/src/apps/terminal/CurPos.cpp +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2001-2005, Haiku, Inc. - * Copyright (c) 2003-4 Kian Duffy - * Parts Copyright (C) 1998,99 Kazuho Okui and Takashi Murai. - * Distributed under the terms of the MIT license. - * - * Authors: - * Kian Duffy - */ -#include "CurPos.h" - -CurPos::CurPos() -{ -} - -CurPos::CurPos(int32 X, int32 Y) -{ - x = X; - y = Y; -} - -CurPos::CurPos(const CurPos& cp) -{ - x = cp.x; - y = cp.y; -} - -CurPos & -CurPos::operator=(const CurPos& from) -{ - x = from.x; - y = from.y; - return *this; -} - -void -CurPos::Set(int32 X, int32 Y) -{ - x = X; - y = Y; -} - -bool -CurPos::operator!=(const CurPos& from) const -{ - return ((x != from.x) || (y != from.y)); -} - -bool -CurPos::operator==(const CurPos& from) const -{ - return ((x == from.x) && (y == from.y)); -} - -CurPos -CurPos::operator+(const CurPos& from) const -{ - return CurPos(x + from.x, y + from.y); -} - -CurPos -CurPos::operator- (const CurPos& from) const -{ - return CurPos(x - from.x, y - from.y); -} - -bool -CurPos::operator> (const CurPos& from) const -{ - if (y > from.y) - return true; - else - if (y == from.y && x > from.x) - return true; - - return false; -} - -bool -CurPos::operator>= (const CurPos& from) const -{ - if (y > from.y) - return true; - else - if (y == from.y && x >= from.x) - return true; - - return false; -} - -bool -CurPos::operator< (const CurPos& from) const -{ - if (y < from.y) - return true; - else - if (y == from.y && x < from.x) - return true; - - return false; -} - -bool -CurPos::operator<= (const CurPos& from) const -{ - if (y < from.y) - return true; - else - if (y == from.y && x <= from.x) - return true; - - return false; -} diff --git a/src/apps/terminal/CurPos.h b/src/apps/terminal/CurPos.h deleted file mode 100644 index af8681cf2a..0000000000 --- a/src/apps/terminal/CurPos.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2001-2005, Haiku, Inc. - * Copyright (c) 2003-4 Kian Duffy - * Parts Copyright (C) 1998,99 Kazuho Okui and Takashi Murai. - * Distributed under the terms of the MIT license. - * - * Authors: - * Kian Duffy - */ -#ifndef CURPOS_H_INCLUDED -#define CURPOS_H_INCLUDED - -#include - -class CurPos -{ -public: - CurPos(); - CurPos(int32 X, int32 Y); - CurPos(const CurPos& cp); - - void Set(int32 X, int32 Y); - - CurPos &operator= (const CurPos &from); - CurPos operator+ (const CurPos&) const; - CurPos operator- (const CurPos&) const; - bool operator!= (const CurPos&) const; - bool operator== (const CurPos&) const; - bool operator> (const CurPos&) const; - bool operator>= (const CurPos&) const; - bool operator< (const CurPos&) const; - bool operator<= (const CurPos&) const; - - int32 x; - int32 y; -}; - -#endif diff --git a/src/apps/terminal/Jamfile b/src/apps/terminal/Jamfile index 2e68edbc48..78266e81c2 100644 --- a/src/apps/terminal/Jamfile +++ b/src/apps/terminal/Jamfile @@ -9,7 +9,6 @@ Application Terminal : Arguments.cpp CodeConv.cpp Coding.cpp - CurPos.cpp FindWindow.cpp MenuUtil.cpp Terminal.cpp @@ -19,9 +18,10 @@ Application Terminal : Shell.cpp SmartTabView.cpp TermApp.cpp - TermBuffer.cpp TerminalBuffer.cpp + TerminalCharClassifier.cpp TermParse.cpp + TermScrollView.cpp TermView.cpp TermWindow.cpp TTextControl.cpp diff --git a/src/apps/terminal/Shell.cpp b/src/apps/terminal/Shell.cpp index 15357f0b4d..7bf4d63da5 100644 --- a/src/apps/terminal/Shell.cpp +++ b/src/apps/terminal/Shell.cpp @@ -234,7 +234,7 @@ Shell::ViewAttached(TermView *view) if (fAttached) return; - status_t status = fTermParse->StartThreads(view); + status_t status = fTermParse->StartThreads(view->TextBuffer()); if (status < B_OK) { // TODO: What can we do here ? fprintf(stderr, "Shell:ViewAttached():" diff --git a/src/apps/terminal/TermBuffer.cpp b/src/apps/terminal/TermBuffer.cpp deleted file mode 100644 index 1e7d3313f4..0000000000 --- a/src/apps/terminal/TermBuffer.cpp +++ /dev/null @@ -1,550 +0,0 @@ -/* - * Copyright (c) 2001-2006, Haiku, Inc. - * Copyright (c) 2003-4 Kian Duffy - * Parts Copyright (C) 1998,99 Kazuho Okui and Takashi Murai. - * Distributed under the terms of the MIT license. - * - * Authors: - * Kian Duffy - */ - -/************************************************************************ -MuTerminal Internal Buffer Format. - -a. 4bytes character buffer. - - 3bytes character buffer (UTF8). - 1byte status buffer. - -b. 2bytes extended buffer. - - 2byte attribute buffer. - -< attribute buffer> <------- character buffer --------> -WBURMfbF rrfffbbb / ssssssss 11111111 22222222 33333333 -|||||||| || | | | | | | -|||||||| || | | status utf8 1st utf8 2nd utf8 3rd -|||||||| || | | -|||||||| || | \--> background color -|||||||| || \-----> foreground color -|||||||| |\-------> dumped CR -|||||||| \--------> Reserve -|||||||| -|||||||| -|||||||\----------> Font information (Not use) -||||||\-----------> background color flag -|||||\------------> foreground color flag -||||\-------------> mouse selected -|||\--------------> reverse attr -||\---------------> underline attr -|\----------------> bold attr -\-----------------> character width - -c. 2byte status buffer. - -0x00 (A_CHAR): character available. -0x01 (NO_CHAR): buffer is empty. -0xFF (IN_STRING): before buffer is full width character (never draw). - -**Language environment. (Not impliment) - ---- half width character set --- -0 ... Western (Latin1 / ISO-8859-1, MacRoman) -1 ... Central European(Latin2 / ISO-8859-2) -2 ... Turkish (Latin3 / ISO-8859-3) -3 ... Baltic (Latin4 / ISO-8859-4) -4 ... Cyrillic (Cyrillic / ISO-8859-5) -5 ... Greek (Greek / ISO-8859-7) -6 ... Trukish (Latin5 / ISO-8859-9) - ---- full width character set --- -7 ... Japanese (EUC, SJIS, ISO-2022-jp / JIS X 0201, 0208, 0212) -8 ... Chinese (Big5, EUC-tw, ISO-2022-cn / GB2312, CNS-11643-1...7) -9 ... Korean (EUC-kr, ISO-2022-kr / KS C 5601) - ---- Universal character set --- -10 ... Unicode (UTF8) - - * Variables is set CodeConv class. - * Unicode character width sets CodeConv::UTF8FontWidth member. - -JIS X 0201 (half width kana ideograph) character use 1 column, -but it font is full width font on preference panel. - -************************************************************************/ - -#include "TermBuffer.h" - -#include "CurPos.h" -#include "TermConst.h" - -#include -#include - -#include -#include -#include - - -#define ROW(x) (((x) + fRowOffset) % fBufferSize) - - -TermBuffer::TermBuffer(int rows, int cols, int bufferSize) -{ - if (rows < 1) - rows = 1; - if (cols < MIN_COLS) - cols = MIN_COLS; - else if (cols > MAX_COLS) - cols = MAX_COLS; - - fColumnSize = MAX_COLS; - fCurrentColumnSize = cols; - fRowSize = rows; - - fRowOffset = 0; - - fSelStart.Set(-1,-1); - fSelEnd.Set(-1,-1); - - fBufferSize = bufferSize; - if (fBufferSize < 1000) - fBufferSize = 1000; - - // Allocate buffer - fBuffer = (term_buffer**)malloc(sizeof(term_buffer*) * fBufferSize); - - for (int i = 0; i < fBufferSize; i++) { - fBuffer[i] = (term_buffer *)calloc(fColumnSize + 1, sizeof(term_buffer)); - } -} - - -TermBuffer::~TermBuffer() -{ - for (int i = 0; i < fBufferSize; i++) { - free(fBuffer[i]); - } - - free(fBuffer); -} - - -//! Gets a character from TermBuffer -int -TermBuffer::GetChar(int row, int col, uchar *buf, ushort *attr) -{ - if (row < 0 || col < 0) - return -1; - - term_buffer *ptr = (fBuffer[row % fBufferSize] + col); - - if (ptr->status == A_CHAR) - memcpy(buf, (char *)(ptr->code), 4); - - *attr = ptr->attr; - - return ptr->status; -} - - -//! Get a string (length = num) from given position. -int -TermBuffer::GetString(int row, int col, int num, uchar *buf, - ushort *attr) -{ - int count = 0, all_count = 0; - term_buffer *ptr; - - ptr = (fBuffer[row % fBufferSize]); - ptr += col; - *attr = ptr->attr; - - if (ptr->status == NO_CHAR) { - // Buffer is empty and No selected by mouse. - do { - if (col >= fCurrentColumnSize) - return -1; - - col++; - ptr++; - count--; - if (ptr->status == A_CHAR) - return count; - - if (fSelStart.y == row) { - if (col == fSelStart.x) - return count; - } - - if (fSelEnd.y == row) { - if (col - 1 == fSelEnd.x) - return count; - } - } while (col <= num); - - return count; - } else if (IS_WIDTH(ptr->attr)) { - memcpy(buf, (char *)ptr->code, 4); - return 2; - } - - while (col <= num) { - memcpy(buf, ptr->code, 4); - all_count++; - - if (*buf == 0) { - *buf = ' '; - *(buf + 1) = '\0'; - } - if (ptr->attr != (ptr + 1)->attr) - return all_count; - - buf += strlen((char *)buf); - ptr++; - col++; - - if (fSelStart.y == row) { - if (col == fSelStart.x) - return all_count; - } - if (fSelEnd.y == row) { - if (col - 1 == fSelEnd.x) - return all_count; - } - } - - return all_count; -} - - -//! Write a character at the cursor point. -void -TermBuffer::WriteChar(const CurPos &pos, const uchar *u, ushort attr) -{ - const int row = pos.y; - const int col = pos.x; - - term_buffer *ptr = (fBuffer[ROW(row)] + col); - memcpy ((char *)ptr->code, u, 4); - - if (IS_WIDTH(attr)) - (ptr + 1)->status = IN_STRING; - - ptr->status = A_CHAR; - ptr->attr = attr; -} - - -//! Write CR status to buffer attribute. -void -TermBuffer::WriteCR(const CurPos &pos) -{ - int row = pos.y; - int col = pos.x; - - term_buffer *ptr = (fBuffer[ROW(row)] + col); - ptr->attr |= DUMPCR; -} - - -//! Insert 'num' spaces at cursor point. -void -TermBuffer::InsertSpace(const CurPos &pos, int num) -{ - const int row = pos.y; - const int col = pos.x; - - for (int i = fCurrentColumnSize - num; i >= col; i--) { - *(fBuffer[ROW(row)] + i + num) = *(fBuffer[ROW(row)] + i); - } - - memset(fBuffer[ROW(row)] + col, 0, num * sizeof(term_buffer)); -} - - -//! Delete 'num' characters at cursor point. -void -TermBuffer::DeleteChar(const CurPos &pos, int num) -{ - const int row = pos.y; - const int col = pos.x; - - term_buffer *ptr = fBuffer[ROW(row)]; - size_t movesize = fCurrentColumnSize - (col + num); - - memmove(ptr + col, ptr + col + num, movesize * sizeof(term_buffer)); - memset(ptr + (fCurrentColumnSize - num), 0, num * sizeof(term_buffer)); -} - - -//! Erase characters below cursor position. -void -TermBuffer::EraseBelow(const CurPos &pos) -{ - const int row = pos.y; - const int col = pos.x; - - memset(fBuffer[ROW(row)] + col, 0, (fColumnSize - col ) * sizeof(term_buffer)); - - for (int i = row; i < fRowSize; i++) { - _EraseLine(i); - } -} - - -//! Scroll the terminal buffer region -void -TermBuffer::ScrollRegion(int top, int bot, int dir, int num) -{ - if (dir == SCRUP) { - for (int i = 0; i < num; i++) { - term_buffer *ptr = fBuffer[ROW(top)]; - - for (int j = top; j < bot; j++) { - fBuffer[ROW(j)] = fBuffer[ROW(j+1)]; - } - - fBuffer[ROW(bot)] = ptr; - _EraseLine(bot); - } - } else { - // scroll up - for (int i = 0; i < num; i++) { - term_buffer *ptr = fBuffer[ROW(bot)]; - - for (int j = bot; j > top; j--) { - fBuffer[ROW(j)] = fBuffer[ROW(j-1)]; - } - - fBuffer[ROW(top)] = ptr; - _EraseLine(top); - } - } -} - - -//! Scroll the terminal buffer. -void -TermBuffer::ScrollLine() -{ - for (int i = fRowSize; i < fRowSize * 2; i++) { - _EraseLine(i); - } - - fRowOffset++; -} - - -//! Resize the terminal buffer. -void -TermBuffer::ResizeTo(int newRows, int newCols, int offset) -{ - int i; - - // make sure the new size is within the allowed limits - if (newRows < 1) - newRows = 1; - - if (newCols < MIN_COLS) - newCols = MIN_COLS; - else if (newCols > MAX_COLS) - newCols = MAX_COLS; - - if (newRows <= fRowSize) { - for (i = newRows; i <= fRowSize; i++) - _EraseLine(i); - } else { - for (i = fRowSize; i <= newRows * 2; i++) - _EraseLine(i); - } - - fCurrentColumnSize = newCols; - fRowOffset += offset; - fRowSize = newRows; -} - - -//! Get the buffer's size. -int32 -TermBuffer::Size() const -{ - return fBufferSize; -} - - -void -TermBuffer::_EraseLine(int row) -{ - memset(fBuffer[ROW(row)], 0, fColumnSize * sizeof(term_buffer)); -} - - -//! Clear the contents of the TermBuffer. -void -TermBuffer::Clear() -{ - for (int i = 0; i < fBufferSize; i++) { - memset(fBuffer[i], 0, fColumnSize * sizeof(term_buffer)); - } - - fRowOffset = 0; - DeSelect(); -} - - -//! Mark text in the given range as selected -void -TermBuffer::Select(const CurPos &start, const CurPos &end) -{ - if (end < start) { - fSelStart = end; - fSelEnd = start; - } else { - fSelStart = start; - fSelEnd = end; - } -} - - -//! Mark text in the given range as not selected -void -TermBuffer::DeSelect() -{ - fSelStart.Set(-1, -1); - fSelEnd.Set(-1, -1); -} - - - -bool -TermBuffer::FindWord(const CurPos &pos, CurPos *start, CurPos *end) -{ - uchar buf[5]; - ushort attr; - int x, y, start_x; - - y = pos.y; - - // Search start point - for (x = pos.x - 1; x >= 0; x--) { - if (GetChar(y, x, buf, &attr) == NO_CHAR || *buf == ' ') { - ++x; - break; - } - } - start_x = x; - - // Search end point - for (x = pos.x; x < fColumnSize; x++) { - if (GetChar(y, x, buf, &attr) == NO_CHAR || *buf == ' ') { - --x; - break; - } - } - - if (start_x > x) - return false; - - if (start != NULL) - start->Set(start_x, y); - - if (end != NULL) - end->Set(x, y); - - return true; -} - - -//! Get one character from the selected region. -void -TermBuffer::GetCharFromRegion(int x, int y, BString &str) -{ - uchar buf[5]; - ushort attr; - int status; - - status = GetChar(y, x, buf, &attr); - - switch (status) { - case NO_CHAR: - if (IS_CR (attr)) - str += '\n'; - else - str += ' '; - break; - - case IN_STRING: - break; - - default: - str += (const char *)&buf; - break; - } -} - - -//! Delete useless char at end of line and convert LF code. -/* static */ -inline void -TermBuffer::AvoidWaste(BString &str) -{ - // TODO: Remove the goto - - int32 len, point; - -start: - len = str.Length() - 1; - point = str.FindLast (' '); - - if (len > 0 && len == point) { - str.RemoveLast (" "); - goto start; - } - // str += '\n'; -} - - -//! Get a string from the given region. -void -TermBuffer::GetStringFromRegion(BString &str, const CurPos &start, const CurPos &end) -{ - int y; - - if (start.y == end.y) { - y = start.y; - for (int x = start.x ; x <= end.x; x++) { - GetCharFromRegion(x, y, str); - } - } else { - y = start.y; - for (int x = start.x ; x < fCurrentColumnSize; x++) { - GetCharFromRegion(x, y, str); - } - AvoidWaste(str); - - for (y = start.y + 1 ; y < end.y; y++) { - for (int x = 0 ; x < fCurrentColumnSize; x++) { - GetCharFromRegion(x, y, str); - } - AvoidWaste(str); - } - - y = end.y; - for (int x = 0 ; x <= end.x; x++) { - GetCharFromRegion(x, y, str); - } - } -} - -//Returns the complete internal buffer as a BString -void -TermBuffer::ToString(BString &str) -{ - for (int y = 0; y < fRowSize; y++) { - for (int x = 0; x < fCurrentColumnSize; x++) { - GetCharFromRegion(x, y, str); - } - } - AvoidWaste(str); -} diff --git a/src/apps/terminal/TermBuffer.h b/src/apps/terminal/TermBuffer.h deleted file mode 100644 index c3388a785e..0000000000 --- a/src/apps/terminal/TermBuffer.h +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2003-4 Kian Duffy - * Parts Copyright (C) 1998,99 Kazuho Okui and Takashi Murai. - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files or portions - * thereof (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, subject - * to the following conditions: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice - * in the binary, as well as this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with - * the distribution. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - */ -#ifndef _TERMBUFFER_H -#define _TERMBUFFER_H - -#include "CurPos.h" -#include "TermConst.h" - -#include - - -struct term_buffer -{ - uchar code[4]; - int16 attr; - int16 status; -}; - - -class CurPos; -class BString; - -class TermBuffer { -public: - TermBuffer(int row, int col, int bufferSize = 1000); - ~TermBuffer(); - - // - // Get and Put charactor. - // - int GetChar(int row, int col, uchar *u, ushort *attr); - int GetString(int row, int col, int num, uchar *u, ushort *attr); - void GetStringFromRegion(BString ©Str, const CurPos &start, const CurPos &end); - - void WriteChar(const CurPos &pos, const uchar *u, ushort attr); - void WriteCR(const CurPos &pos); - void InsertSpace(const CurPos &pos, int num); - - // - // Delete Character. - // - void DeleteChar(const CurPos &pos, int num); - void EraseBelow(const CurPos &pos); - - // - // Movement and Scroll buffer. - // - void ScrollRegion(int top, int bot, int dir, int num); - void ScrollLine(); - - // - // Resize buffer. - // - void ResizeTo(int newRows, int newCols, int offset); - - // - // Clear contents of TermBuffer. - // - void Clear(); - - // - // Selection Methods - // - void Select(const CurPos &start, const CurPos &end); - void DeSelect(); - - const CurPos &GetSelectionStart() { return fSelStart; }; - const CurPos &GetSelectionEnd() { return fSelEnd; }; - - // - // Other methods - // - bool FindWord(const CurPos &pos, CurPos *start, CurPos *end); - - void GetCharFromRegion (int x, int y, BString &str); -static void AvoidWaste (BString &str); - - void ToString(BString &str); - - int32 Size() const; - -private: - void _EraseLine(int line); - - term_buffer **fBuffer; - int fBufferSize; - - int fColumnSize; - int fRowSize; - int fCurrentColumnSize; - int fRowOffset; - - CurPos fSelStart; - CurPos fSelEnd; -}; - -#endif // _TERMBUFFER_H diff --git a/src/apps/terminal/TermConst.h b/src/apps/terminal/TermConst.h index a6e97d91f4..0c478fd495 100644 --- a/src/apps/terminal/TermConst.h +++ b/src/apps/terminal/TermConst.h @@ -83,6 +83,10 @@ const uint32 MSG_FONT_CHANGED = 'fntc'; const uint32 SAVE_AS_DEFAULT = 'sadf'; const uint32 MSG_CHECK_CHILDREN = 'ckch'; +const uint32 MSG_TERMINAL_BUFFER_CHANGED = 'bufc'; +const uint32 MSG_SET_TERMNAL_TITLE = 'sett'; +const uint32 MSG_QUIT_TERMNAL = 'qutt'; + // Preference Read/Write Keys const char* const PREF_HALF_FONT_FAMILY = "Half Font Family"; const char* const PREF_HALF_FONT_STYLE = "Half Font Style"; @@ -144,7 +148,7 @@ enum{ #define MIN_COLS 10 #define MAX_COLS 256 -#define MIN_ROWS 1 +#define MIN_ROWS 10 #define MAX_ROWS 256 // Insert mode flag diff --git a/src/apps/terminal/TermParse.cpp b/src/apps/terminal/TermParse.cpp index ad037fdcd6..5bf2fa6814 100644 --- a/src/apps/terminal/TermParse.cpp +++ b/src/apps/terminal/TermParse.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -117,6 +118,7 @@ status_t TermParse::GetReaderBuf(uchar &c) { status_t status; +#if 0 do { status = acquire_sem_etc(fReaderSem, 1, B_TIMEOUT, 10000); } while (status == B_INTERRUPTED); @@ -127,17 +129,23 @@ TermParse::GetReaderBuf(uchar &c) // Reset cursor blinking time and turn on cursor blinking. fBuffer->SetCurDraw(true); +#endif // wait new input from pty. +fBuffer->Unlock(); do { status = acquire_sem(fReaderSem); } while (status == B_INTERRUPTED); +fBuffer->Lock(); if (status < B_OK) return status; + +#if 0 } else if (status == B_OK) { // Do nothing } else return status; +#endif c = fReadBuffer[fBufferPosition % READ_BUF_SIZE]; fBufferPosition++; @@ -147,7 +155,7 @@ TermParse::GetReaderBuf(uchar &c) release_sem(fReaderLocker); } - fBuffer->SetCurDraw(false); +// fBuffer->SetCurDraw(false); return B_OK; } @@ -343,6 +351,9 @@ TermParse::EscParse() int *parsestate = groundtable; while (!fQuitting) { +// TODO: Fix the locking! + BAutolock locker(fBuffer); + uchar c; if (GetReaderBuf(c) < B_OK) break; @@ -383,6 +394,7 @@ TermParse::EscParse() now_coding = fBuffer->Encoding(); } +//debug_printf("TermParse: char: '%c' (%d), parse state: %d\n", c, c, parsestate[c]); switch (parsestate[c]) { case CASE_PRINT: cbuf[0] = c; @@ -767,7 +779,7 @@ TermParse::EscParse() case CASE_CPR: // Q & D hack by Y.Hayakawa (hida@sawada.riec.tohoku.ac.jp) // 21-JUL-99 - fBuffer->DeviceStatusReport(param[0]); + _DeviceStatusReport(param[0]); parsestate = groundtable; break; @@ -975,3 +987,27 @@ TermParse::_escparse_thread(void *data) { return reinterpret_cast(data)->EscParse(); } + + +void +TermParse::_DeviceStatusReport(int n) +{ + char sbuf[16] ; + int len; + + switch (n) { + case 5: + { + const char* toWrite = "\033[0n"; + write(fFd, toWrite, strlen(toWrite)); + break ; + } + case 6: + len = sprintf(sbuf, "\033[%ld;%ldR", fBuffer->Height(), + fBuffer->Width()) ; + write(fFd, sbuf, len); + break ; + default: + return; + } +} diff --git a/src/apps/terminal/TermParse.h b/src/apps/terminal/TermParse.h index c6383d085c..80f1a889fc 100644 --- a/src/apps/terminal/TermParse.h +++ b/src/apps/terminal/TermParse.h @@ -68,7 +68,9 @@ private: // Reading ReadBuf at one Char. status_t GetReaderBuf(uchar &c); - + + void _DeviceStatusReport(int n); + int fFd; thread_id fParseThread; diff --git a/src/apps/terminal/TermPos.h b/src/apps/terminal/TermPos.h new file mode 100644 index 0000000000..505d9332b9 --- /dev/null +++ b/src/apps/terminal/TermPos.h @@ -0,0 +1,65 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef TERM_POS_H +#define TERM_POS_H + +#include + + +class TermPos { +public: + int32 x; + int32 y; + + inline TermPos() : x(0), y(0) { } + inline TermPos(int32 x, int32 y) : x(x), y(y) { } + inline TermPos(const TermPos& other) : x(other.x), y(other.y) { } + + inline void SetTo(int32 x, int32 y) + { + this->x = x; + this->y = y; + } + + inline bool operator==(const TermPos& other) const + { + return x == other.x && y == other.y; + } + + inline bool operator!=(const TermPos& other) const + { + return x != other.x || y != other.y; + } + + inline bool operator<=(const TermPos& other) const + { + return y < other.y || y == other.y && x <= other.x; + } + + inline bool operator>=(const TermPos& other) const + { + return other <= *this; + } + + inline bool operator<(const TermPos& other) const + { + return !(*this >= other); + } + + inline bool operator>(const TermPos& other) const + { + return !(*this <= other); + } + + inline TermPos& operator=(const TermPos& other) + { + x = other.x; + y = other.y; + return *this; + } +}; + + +#endif // TERM_POS_H diff --git a/src/apps/terminal/TermScrollView.cpp b/src/apps/terminal/TermScrollView.cpp new file mode 100644 index 0000000000..a6de128a91 --- /dev/null +++ b/src/apps/terminal/TermScrollView.cpp @@ -0,0 +1,52 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + +// NOTE: Nasty hack to get access to BScrollView's private parts. +#include +#define private protected +#include +#undef private + +#include "TermScrollView.h" + + +class TermScrollBar : public BScrollBar { +public: + TermScrollBar(BRect frame, const char *name, BView *target, + float min, float max, orientation direction) + : + BScrollBar(frame, name, target, min, max, direction) + { + } + + virtual void ValueChanged(float newValue) + { + if (BView* target = Target()) + Target()->ScrollTo(0, newValue); + } +}; + + +TermScrollView::TermScrollView(const char* name, BView* child, BView* target, + uint32 resizingMode) + : + BScrollView(name, child, resizingMode, 0, false, true) +{ + // replace the vertical scroll bar with our own + if (fVerticalScrollBar != NULL) { + BRect frame(fVerticalScrollBar->Frame()); + RemoveChild(fVerticalScrollBar); + + TermScrollBar* scrollBar = new TermScrollBar(frame, "_VSB_", target, 0, + 1000, B_VERTICAL); + AddChild(scrollBar); + fVerticalScrollBar = scrollBar; + } +} + + +TermScrollView::~TermScrollView() +{ +} diff --git a/src/apps/terminal/TermScrollView.h b/src/apps/terminal/TermScrollView.h new file mode 100644 index 0000000000..0ac2a4e324 --- /dev/null +++ b/src/apps/terminal/TermScrollView.h @@ -0,0 +1,20 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef TERM_SCROLL_VIEW_H +#define TERM_SCROLL_VIEW_H + +#include + + +class TermScrollView : public BScrollView { +public: + TermScrollView(const char* name, BView* child, + BView* target, + uint32 resizingMode = B_FOLLOW_ALL); + ~TermScrollView(); +}; + + +#endif // TERM_SCROLL_VIEW_H diff --git a/src/apps/terminal/TermView.cpp b/src/apps/terminal/TermView.cpp index bae3452e1f..48d1a7898b 100644 --- a/src/apps/terminal/TermView.cpp +++ b/src/apps/terminal/TermView.cpp @@ -8,16 +8,20 @@ * Stefano Ceccherini * Kian Duffy, myob@users.sourceforge.net * Y.Hayakawa, hida@sawada.riec.tohoku.ac.jp + * Ingo Weinhold */ #include "TermView.h" -#include "CodeConv.h" -#include "Shell.h" -#include "TermBuffer.h" -#include "TermConst.h" -#include "VTkeymap.h" +#include +#include +#include +#include +#include + +#include +#include #include #include @@ -30,19 +34,18 @@ #include #include #include +#include #include #include #include #include -#include -#include -#include -#include -#include +#include "CodeConv.h" +#include "Shell.h" +#include "TermConst.h" +#include "TerminalCharClassifier.h" +#include "VTkeymap.h" -#include -#include // defined VTKeyTbl.c extern int function_keycode_table[]; @@ -59,12 +62,16 @@ const static rgb_color kTermColorTable[8] = { {245, 245, 245, 0}, // white }; -// Space at the borders of the view -const int32 kOffset = 3; - #define ROWS_DEFAULT 25 #define COLUMNS_DEFAULT 80 +// selection granularity +enum { + SELECT_CHARS, + SELECT_WORDS, + SELECT_LINES +}; + static property_info sPropList[] = { { "encoding", @@ -83,11 +90,98 @@ static property_info sPropList[] = { }; -const static uint32 kUpdateSigWinch = 'Rwin'; -const static uint32 kBlinkCursor = 'BlCr'; +static const uint32 kUpdateSigWinch = 'Rwin'; +static const uint32 kBlinkCursor = 'BlCr'; +static const uint32 kAutoScroll = 'AScr'; -const static rgb_color kBlackColor = { 0, 0, 0, 255 }; -const static rgb_color kWhiteColor = { 255, 255, 255, 255 }; +static const rgb_color kBlackColor = { 0, 0, 0, 255 }; +static const rgb_color kWhiteColor = { 255, 255, 255, 255 }; + +static const char* kDefaultSpecialWordChars = ":@-./_~"; + + +template +static inline Type +restrict_value(const Type& value, const Type& min, const Type& max) +{ + return value < min ? min : (value > max ? max : value); +} + + +class TermView::CharClassifier : public TerminalCharClassifier +{ +public: + CharClassifier(const char* specialWordChars) + : + fSpecialWordChars(specialWordChars) + { + } + + virtual int Classify(const char* character) + { + // TODO: Deal correctly with non-ASCII chars. + char c = *character; + if (UTF8Char::ByteCount(c) > 1) + return CHAR_TYPE_WORD_CHAR; + + if (isspace(c)) + return CHAR_TYPE_SPACE; + if (isalnum(c) || strchr(fSpecialWordChars, c) != NULL) + return CHAR_TYPE_WORD_CHAR; + + return CHAR_TYPE_WORD_DELIMITER; + } + +private: + const char* fSpecialWordChars; +}; + + +inline int32 +TermView::_LineAt(float y) +{ + int32 location = int32(y + fScrollOffset); + + // Make sure negative offsets are rounded towards the lower neighbor, too. + if (location < 0) + location -= fFontHeight - 1; + + return location / fFontHeight; +} + + +inline float +TermView::_LineOffset(int32 index) +{ + return index * fFontHeight - fScrollOffset; +} + + +// convert view coordinates to terminal text buffer position +inline TermPos +TermView::_ConvertToTerminal(const BPoint &p) +{ + return TermPos(p.x >= 0 ? (int32)p.x / fFontWidth : -1, _LineAt(p.y)); +} + + +// convert terminal text buffer position to view coordinates +inline BPoint +TermView::_ConvertFromTerminal(const TermPos &pos) +{ + return BPoint(fFontWidth * pos.x, _LineOffset(pos.y)); +} + + +inline void +TermView::_InvalidateTextRect(int32 x1, int32 y1, int32 x2, int32 y2) +{ + BRect rect(x1 * fFontWidth, _LineOffset(y1), + (x2 + 1) * fFontWidth - 1, _LineOffset(y2 + 1) - 1); +//debug_printf("Invalidate((%f, %f) - (%f, %f))\n", rect.left, rect.top, +//rect.right, rect.bottom); + Invalidate(rect); +} TermView::TermView(BRect frame, int32 argc, const char **argv, int32 historySize) @@ -96,13 +190,11 @@ TermView::TermView(BRect frame, int32 argc, const char **argv, int32 historySize fShell(NULL), fWinchRunner(NULL), fCursorBlinkRunner(NULL), + fAutoScrollRunner(NULL), + fCharClassifier(NULL), fFontWidth(0), fFontHeight(0), fFontAscent(0), - fUpdateFlag(false), - fInsertModeFlag(MODE_OVER), - fScrollUpCount(0), - fScrollBarRange(0), fFrameResized(false), fLastCursorTime(0), fCursorDrawFlag(true), @@ -110,13 +202,10 @@ TermView::TermView(BRect frame, int32 argc, const char **argv, int32 historySize fCursorBlinkingFlag(true), fCursorRedrawFlag(true), fCursorHeight(0), - fCurPos(0, 0), - fCurStack(0, 0), - fBufferStartPos(-1), + fCursor(0, 0), fTermRows(ROWS_DEFAULT), fTermColumns(COLUMNS_DEFAULT), fEncoding(M_UTF8), - fTop(0), fTextBuffer(NULL), fScrollBar(NULL), fTextForeColor(kBlackColor), @@ -125,11 +214,8 @@ TermView::TermView(BRect frame, int32 argc, const char **argv, int32 historySize fCursorBackColor(kBlackColor), fSelectForeColor(kWhiteColor), fSelectBackColor(kBlackColor), - fScrTop(0), - fScrBot(fTermRows - 1), + fScrollOffset(0), fScrBufSize(historySize), - fScrRegionSet(0), - fClickPoint(0, 0), fSelStart(-1, -1), fSelEnd(-1, -1), fMouseTracking(false), @@ -145,13 +231,11 @@ TermView::TermView(int rows, int columns, int32 argc, const char **argv, int32 h fShell(NULL), fWinchRunner(NULL), fCursorBlinkRunner(NULL), + fAutoScrollRunner(NULL), + fCharClassifier(NULL), fFontWidth(0), fFontHeight(0), fFontAscent(0), - fUpdateFlag(false), - fInsertModeFlag(MODE_OVER), - fScrollUpCount(0), - fScrollBarRange(0), fFrameResized(false), fLastCursorTime(0), fCursorDrawFlag(true), @@ -159,13 +243,10 @@ TermView::TermView(int rows, int columns, int32 argc, const char **argv, int32 h fCursorBlinkingFlag(true), fCursorRedrawFlag(true), fCursorHeight(0), - fCurPos(0, 0), - fCurStack(0, 0), - fBufferStartPos(-1), + fCursor(0, 0), fTermRows(rows), fTermColumns(columns), fEncoding(M_UTF8), - fTop(0), fTextBuffer(NULL), fScrollBar(NULL), fTextForeColor(kBlackColor), @@ -174,11 +255,8 @@ TermView::TermView(int rows, int columns, int32 argc, const char **argv, int32 h fCursorBackColor(kBlackColor), fSelectForeColor(kWhiteColor), fSelectBackColor(kBlackColor), - fScrTop(0), - fScrBot(fTermRows - 1), + fScrollOffset(0), fScrBufSize(historySize), - fScrRegionSet(0), - fClickPoint(0, 0), fSelStart(-1, -1), fSelEnd(-1, -1), fMouseTracking(false), @@ -206,13 +284,11 @@ TermView::TermView(BMessage *archive) fShell(NULL), fWinchRunner(NULL), fCursorBlinkRunner(NULL), + fAutoScrollRunner(NULL), + fCharClassifier(NULL), fFontWidth(0), fFontHeight(0), fFontAscent(0), - fUpdateFlag(false), - fInsertModeFlag(MODE_OVER), - fScrollUpCount(0), - fScrollBarRange(0), fFrameResized(false), fLastCursorTime(0), fCursorDrawFlag(true), @@ -220,13 +296,10 @@ TermView::TermView(BMessage *archive) fCursorBlinkingFlag(true), fCursorRedrawFlag(true), fCursorHeight(0), - fCurPos(0, 0), - fCurStack(0, 0), - fBufferStartPos(-1), + fCursor(0, 0), fTermRows(ROWS_DEFAULT), fTermColumns(COLUMNS_DEFAULT), fEncoding(M_UTF8), - fTop(0), fTextBuffer(NULL), fScrollBar(NULL), fTextForeColor(kBlackColor), @@ -235,11 +308,7 @@ TermView::TermView(BMessage *archive) fCursorBackColor(kBlackColor), fSelectForeColor(kWhiteColor), fSelectBackColor(kBlackColor), - fScrTop(0), - fScrBot(fTermRows - 1), fScrBufSize(1000), - fScrRegionSet(0), - fClickPoint(0, 0), fSelStart(-1, -1), fSelEnd(-1, -1), fMouseTracking(false), @@ -274,10 +343,20 @@ TermView::TermView(BMessage *archive) status_t TermView::_InitObject(int32 argc, const char **argv) { - fTextBuffer = new (std::nothrow) TermBuffer(fTermRows, fTermColumns, fScrBufSize); + fTextBuffer = new(std::nothrow) TerminalBuffer; if (fTextBuffer == NULL) return B_NO_MEMORY; + // TODO: Make the special word chars user-settable! + fCharClassifier = new(std::nothrow) CharClassifier( + kDefaultSpecialWordChars); + if (fCharClassifier == NULL) + return B_NO_MEMORY; + + status_t error = fTextBuffer->Init(fTermColumns, fTermRows, fScrBufSize); + if (error != B_OK) + return error; + fShell = new (std::nothrow) Shell(); if (fShell == NULL) return B_NO_MEMORY; @@ -299,8 +378,8 @@ TermView::_InitObject(int32 argc, const char **argv) SetLowColor(fTextBackColor); - SetViewColor(fTextBackColor); - + SetViewColor(B_TRANSPARENT_32_BIT); + return B_OK; } @@ -311,7 +390,9 @@ TermView::~TermView() // _DetachShell sets fShell to NULL _DetachShell(); - + + delete fAutoScrollRunner; + delete fCharClassifier; delete fTextBuffer; delete shell; } @@ -352,9 +433,9 @@ void TermView::GetPreferredSize(float *width, float *height) { if (width) - *width = fTermColumns * fFontWidth + 2 * kOffset; + *width = fTermColumns * fFontWidth; if (height) - *height = fTermRows * fFontHeight + 2 * kOffset; + *height = fTermRows * fFontHeight; } @@ -379,20 +460,35 @@ TermView::GetFontSize(int* _width, int* _height) //! Set number of rows and columns in terminal BRect -TermView::SetTermSize(int rows, int cols, bool resize) +TermView::SetTermSize(int rows, int columns, bool resize) { +//debug_printf("TermView::SetTermSize(%d, %d)\n", rows, columns); if (rows > 0) fTermRows = rows; - if (cols > 0) - fTermColumns = cols; + if (columns > 0) + fTermColumns = columns; - fTextBuffer->ResizeTo(fTermRows, fTermColumns, 0); + // To keep things simple, get rid of the selection first. + _Deselect(); - fScrTop = 0; - fScrBot = fTermRows - 1; + { + BAutolock _(fTextBuffer); + if (fTextBuffer->ResizeTo(columns, rows) != B_OK) + return Bounds(); + } - BRect rect(0, 0, fTermColumns * fFontWidth + 2 * kOffset, - fTermRows * fFontHeight + 2 * kOffset); + fTermRows = rows; + fTermColumns = columns; + +//debug_printf("Invalidate()\n"); + Invalidate(); + + if (fScrollBar != NULL) { + _UpdateScrollBarRange(); + fScrollBar->SetSteps(fFontHeight, fFontHeight * fTermRows); + } + + BRect rect(0, 0, fTermColumns * fFontWidth, fTermRows * fFontHeight); if (resize) ResizeTo(rect.Width(), rect.Height()); @@ -408,7 +504,6 @@ TermView::SetTextColor(rgb_color fore, rgb_color back) fTextBackColor = back; SetLowColor(fTextBackColor); - SetViewColor(fTextBackColor); } @@ -461,7 +556,7 @@ TermView::SetTermFont(const BFont *font) fHalfFont = font; - _FixFontAttributes(fHalfFont); + fHalfFont.SetSpacing(B_FIXED_SPACING); // calculate half font's max width // Not Bounding, check only A-Z(For case of fHalfFont is KanjiFont. ) @@ -485,19 +580,14 @@ TermView::SetTermFont(const BFont *font) if (font_leading == 0) font_leading = 1; - if (fTop) - fTop = fTop / fFontHeight; - fFontAscent = font_ascent; fFontHeight = font_ascent + font_descent + font_leading + 1; - fTop = fTop * fFontHeight; - fCursorHeight = fFontHeight; - if (fScrollBar != NULL) { + _ScrollTo(0, false); + if (fScrollBar != NULL) fScrollBar->SetSteps(fFontHeight, fFontHeight * fTermRows); - } } @@ -524,6 +614,8 @@ TermView::SetTitle(const char *title) void TermView::Copy(BClipboard *clipboard) { + BAutolock _(fTextBuffer); + if (!_HasSelection()) return; @@ -541,12 +633,6 @@ TermView::Copy(BClipboard *clipboard) } clipboard->Unlock(); } - - // Deselecting the current selection is not the behavior that - // R5's Terminal app displays. We want to mimic the behavior, so we will - // no longer do the deselection -// if (!fMouseTracking) -// _DeSelect(); } @@ -555,13 +641,11 @@ TermView::Paste(BClipboard *clipboard) { if (clipboard->Lock()) { BMessage *clipMsg = clipboard->Data(); - char *text; + const char* text; ssize_t numBytes; if (clipMsg->FindData("text/plain", B_MIME_TYPE, - (const void **)&text, &numBytes) == B_OK ) { - // Clipboard text doesn't attached EOF? - text[numBytes] = '\0'; - _WritePTY((uchar *)text, numBytes); + (const void**)&text, &numBytes) == B_OK ) { + _WritePTY(text, numBytes); } clipboard->Unlock(); @@ -572,432 +656,58 @@ TermView::Paste(BClipboard *clipboard) void TermView::SelectAll() { - int screen_top = fTop / fFontHeight; - int viewheight = fTermRows; - - int start_pos = screen_top -(fScrBufSize - viewheight * 2); - - CurPos start, end; - start.x = 0; - end.x = fTermColumns -1; - - if (start_pos > 0) - start.y = start_pos; - else - start.y = 0; - - end.y = fCurPos.y + screen_top; - - _Select(start, end); + BAutolock _(fTextBuffer); + + _Select(TermPos(0, -fTextBuffer->HistorySize()), + TermPos(0, fTextBuffer->Height()), false, true); } void TermView::Clear() { - _DeSelect(); - fTextBuffer->Clear(); - - fTop = 0; - ScrollTo(0, 0); - - if (LockLooper()) { - SetHighColor(fTextBackColor); - FillRect(Bounds()); - SetHighColor(fTextForeColor); - UnlockLooper(); + _Deselect(); + + { + BAutolock _(fTextBuffer); + fTextBuffer->Clear(); } - - // reset cursor pos - SetCurPos(0, 0); - - if (fScrollBar) { + +//debug_printf("Invalidate()\n"); + Invalidate(); + + _ScrollTo(0, false); + if (fScrollBar) { fScrollBar->SetRange(0, 0); fScrollBar->SetProportion(1); } } -//! Print one character -void -TermView::Insert(uchar *string, ushort attr) -{ - int width = CodeConv::UTF8GetFontWidth((char*)string); - if (width == FULL_WIDTH) - attr |= A_WIDTH; - - // check column over flow. - if (fCurPos.x + width > fTermColumns) { - UpdateLine(); - fCurPos.x = 0; - - if (fCurPos.y == fTermRows -1) - ScrollScreen(); - else - fCurPos.y++; - } - - if (fInsertModeFlag == MODE_INSERT) - fTextBuffer->InsertSpace(fCurPos, width); - - fTextBuffer->WriteChar(fCurPos, string, attr); - - if (!fUpdateFlag) - fBufferStartPos = fCurPos.x; - - fCurPos.x += width; - fUpdateFlag = true; -} - - -//! Print a CR and move the cursor -void -TermView::InsertCR() -{ - UpdateLine(); - fTextBuffer->WriteCR(fCurPos); - fCurPos.x = 0; -} - - -//! Print a LF and move the cursor -void -TermView::InsertLF() -{ - UpdateLine(); - - if (fScrRegionSet) { - if (fCurPos.y == fScrBot) { - ScrollRegion(-1, -1, SCRUP, 1); - return; - } - } - - if (fCurPos.x != fTermColumns){ - if (fCurPos.y == fTermRows -1) - ScrollScreen(); - else - fCurPos.y++; - } -} - - -//! Print a NL and move the cursor -void -TermView::InsertNewLine(int num) -{ - ScrollRegion(fCurPos.y, -1, SCRDOWN, num); -} - - -//! Print a space -void -TermView::InsertSpace(int num) -{ - UpdateLine(); - - fTextBuffer->InsertSpace(fCurPos, num); - _TermDraw(fCurPos, CurPos(fTermColumns - 1, fCurPos.y)); -} - - -//! Set or reset Insert mode -void -TermView::SetInsertMode(int flag) -{ - UpdateLine(); - fInsertModeFlag = flag; -} - - //! Draw region -inline int -TermView::_TermDraw(const CurPos &start, const CurPos &end) +void +TermView::_InvalidateTextRange(TermPos start, TermPos end) { - int x1 = start.x; - int y1 = start.y; - int x2 = end.x; - int y2 = end.y; - - _Redraw(x1, y1 + fTop / fFontHeight, - x2, y2 + fTop / fFontHeight); - - return 0; -} - - -//! Draw region -int -TermView::_TermDrawSelectedRegion(CurPos start, CurPos end) -{ - CurPos inPos; - - if (end < start) { - inPos = start; - start = end; - end = inPos; - } + if (end < start) + swap(start, end); if (start.y == end.y) { - _Redraw(start.x, start.y, end.x, end.y); + _InvalidateTextRect(start.x, start.y, end.x, end.y); } else { - _Redraw(start.x, start.y, fTermColumns, start.y); + _InvalidateTextRect(start.x, start.y, fTermColumns, start.y); if (end.y - start.y > 0) - _Redraw(0, start.y + 1, fTermColumns, end.y - 1); + _InvalidateTextRect(0, start.y + 1, fTermColumns, end.y - 1); - _Redraw(0, end.y, end.x, end.y); + _InvalidateTextRect(0, end.y, end.x, end.y); } - - return 0; -} - - -//! Draw region -int -TermView::_TermDrawRegion(CurPos start, CurPos end) -{ - CurPos inPos; - int top = fTop / fFontHeight; - - if (end < start) { - inPos = start; - start = end; - end = inPos; - } - - start.y += top; - end.y += top; - - if (start.y == end.y) { - _Redraw(start.x, start.y, end.x, end.y); - } else { - _Redraw(start.x, start.y, fTermColumns - 1, start.y); - - if (end.y - start.y > 0) { - _Redraw(0, start.y + 1, fTermColumns - 1, end.y - 1); - } - _Redraw(0, end.y, end.x, end.y); - } - - return 0; -} - - -//! Erase below cursor below. -void -TermView::EraseBelow() -{ - UpdateLine(); - - fTextBuffer->EraseBelow(fCurPos); - _TermDraw(fCurPos, CurPos(fTermColumns - 1, fCurPos.y)); - if (fCurPos.y != fTermRows - 1) - _TermDraw(CurPos(0, fCurPos.y + 1), CurPos(fTermColumns - 1, fTermRows - 1)); -} - - -//! Delete num characters from current position. -void -TermView::DeleteChar(int num) -{ - UpdateLine(); - - fTextBuffer->DeleteChar(fCurPos, num); - _TermDraw(fCurPos, CurPos(fTermColumns - 1, fCurPos.y)); -} - - -//! Delete cursor right characters. -void -TermView::DeleteColumns() -{ - UpdateLine(); - - fTextBuffer->DeleteChar(fCurPos, fTermColumns - fCurPos.x); - _TermDraw(fCurPos, CurPos(fTermColumns - 1, fCurPos.y)); -} - - -//! Delete 'num' lines from current position with scrolling. -void -TermView::DeleteLine(int num) -{ - ScrollRegion(fCurPos.y, -1, SCRUP, num); -} - - -//! Sets cursor position -void -TermView::SetCurPos(int x, int y) -{ - UpdateLine(); - - if (x >= 0 && x < fTermColumns) - fCurPos.x = x; - if (y >= 0 && y < fTermRows) - fCurPos.y = y; -} - - -//! Sets cursor x position -void -TermView::SetCurX(int x) -{ - if (x >= 0 && x < fTermColumns) { - UpdateLine(); - fCurPos.x = x; - } -} - - -//! Sets cursor y position -void -TermView::SetCurY(int y) -{ - if (y >= 0 && y < fTermRows) { - UpdateLine(); - fCurPos.y = y; - } -} - - -//! Gets cursor position -void -TermView::GetCurPos(CurPos *inCurPos) -{ - inCurPos->x = fCurPos.x; - inCurPos->y = fCurPos.y; -} - - -//! Gets cursor x position -int -TermView::GetCurX() -{ - return fCurPos.x; -} - - -//! Gets cursor y position -int -TermView::GetCurY() -{ - return fCurPos.y; -} - - -//! Saves cursor position -void -TermView::SaveCursor() -{ - fCurStack = fCurPos; -} - - -//! Restores cursor position -void -TermView::RestoreCursor() -{ - UpdateLine(); - fCurPos = fCurStack; -} - - -//! Move cursor right by 'num' steps. -void -TermView::MoveCurRight(int num) -{ - UpdateLine(); - - if (fCurPos.x + num >= fTermColumns) { - // Wrap around - fCurPos.x = 0; - InsertCR(); - InsertLF(); - } else - fCurPos.x += num; -} - - -//! Move cursor left by 'num' steps. -void -TermView::MoveCurLeft(int num) -{ - UpdateLine(); - - fCurPos.x -= num; - if (fCurPos.x < 0) - fCurPos.x = 0; -} - - -//! Move cursor up by 'num' steps. -void -TermView::MoveCurUp(int num) -{ - UpdateLine(); - - fCurPos.y -= num; - - if (fCurPos.y < 0) - fCurPos.y = 0; -} - - -//! Move cursor down by 'num' steps. -void -TermView::MoveCurDown(int num) -{ - UpdateLine(); - - fCurPos.y += num; - - if (fCurPos.y >= fTermRows) - fCurPos.y = fTermRows - 1; -} - - -// TODO: Cleanup the next 3 functions!!! -void -TermView::DrawCursor() -{ - BRect rect(fFontWidth * fCurPos.x + kOffset, - fFontHeight * fCurPos.y + fTop + kOffset, - fFontWidth * (fCurPos.x + 1) - 1 + kOffset, - fFontHeight * fCurPos.y + fTop + fCursorHeight - 1 + kOffset); - - uchar buf[4]; - ushort attr; - - int top = (fTop + kOffset) / fFontHeight; - bool m_flag = _CheckSelectedRegion(CurPos(fCurPos.x, fCurPos.y + top)); - if (fTextBuffer->GetChar(fCurPos.y + top, fCurPos.x, buf, &attr) == A_CHAR) { - int width; - if (IS_WIDTH(attr)) - width = 2; - else - width = 1; - - _DrawLines(fCurPos.x * fFontWidth, - fCurPos.y * fFontHeight + fTop, - attr, buf, width, m_flag, true, this); - } else { - if (m_flag) - SetHighColor(fSelectBackColor); - else - SetHighColor(fCursorBackColor); - - FillRect(rect); - } - - Sync(); } void TermView::BlinkCursor() { +#if 0 if (fCursorDrawFlag && fCursorBlinkingFlag && Window()->IsActive()) { @@ -1009,6 +719,7 @@ TermView::BlinkCursor() fCursorStatus = !fCursorStatus; fLastCursorTime = system_time(); } +#endif // 0 } @@ -1016,6 +727,7 @@ TermView::BlinkCursor() void TermView::SetCurDraw(bool flag) { +#if 0 if (!flag) { if (fCursorStatus) _TermDraw(fCurPos, fCurPos); @@ -1033,6 +745,7 @@ TermView::SetCurDraw(bool flag) } } } +#endif } @@ -1044,65 +757,6 @@ TermView::SetCurBlinking(bool flag) } -//! Scroll terminal dir directory by 'num' steps. -void -TermView::ScrollRegion(int top, int bot, int dir, int num) -{ - UpdateLine(); - - if (top == -1) - top = fScrTop; - - if (bot == -1) - bot = fScrBot; - - if (top < fScrTop) - top = fScrTop; - - if (bot > fScrBot) - bot = fScrBot; - - fTextBuffer->ScrollRegion(top, bot , dir ,num); - _TermDraw(CurPos(0, top), CurPos(fTermColumns - 1, bot)); -} - - -//! Sets terminal scroll region. -void -TermView::SetScrollRegion(int top, int bot) -{ - if (top >= 0 && top < fTermRows) { - if (bot >= 0 && bot < fTermRows) { - if (top > bot) { - fScrTop = bot; - fScrBot = top; - } else if (top < bot ) { - fScrTop = top; - fScrBot = bot; - } - } - } - - if (fScrTop != 0 || fScrBot != fTermRows -1 ) - fScrRegionSet = 1; - else - fScrRegionSet = 0; -} - - -//! Scroll to cursor position. -void -TermView::ScrollAtCursor() -{ - if (LockLooper()) { - _ResizeScrBarRange(); - fScrollUpCount = 0; - ScrollTo(0, fTop); - UnlockLooper(); - } -} - - status_t TermView::_AttachShell(Shell *shell) { @@ -1124,19 +778,15 @@ TermView::_DetachShell() } -//! Draw character on offscreen bitmap. +//! Draw part of a line in the given view. void -TermView::_DrawLines(int x1, int y1, ushort attr, uchar *buf, - int width, int mouse, int cursor, BView *inView) +TermView::_DrawLinePart(int32 x1, int32 y1, uint16 attr, char *buf, + int32 width, bool mouse, bool cursor, BView *inView) { rgb_color rgb_fore = fTextForeColor, rgb_back = fTextBackColor; inView->SetFont(&fHalfFont); - // Move the whole thing to the right - x1 += kOffset; - y1 += kOffset; - // Set pen point int x2 = x1 + fFontWidth * width; int y2 = y1 + fFontHeight; @@ -1196,59 +846,66 @@ TermView::_DrawLines(int x1, int y1, ushort attr, uchar *buf, } -//! Resize scroll bar range and knob size. +/*! Caller must have locked fTextBuffer. +*/ void -TermView::_ResizeScrBarRange() +TermView::_DrawCursor() +{ + BRect rect(fFontWidth * fCursor.x, _LineOffset(fCursor.y), 0, 0); + rect.right = rect.left + fFontWidth - 1; + rect.bottom = rect.top + fCursorHeight - 1; + + UTF8Char character; + uint16 attr; + + bool selected = _CheckSelectedRegion(TermPos(fCursor.x, fCursor.y)); + if (fTextBuffer->GetChar(fCursor.y, fCursor.x, character, attr) == A_CHAR) { + int32 width; + if (IS_WIDTH(attr)) + width = 2; + else + width = 1; + + char buffer[5]; + int32 bytes = UTF8Char::ByteCount(character.bytes[0]); + memcpy(buffer, character.bytes, bytes); + buffer[bytes] = '\0'; + + _DrawLinePart(fCursor.x * fFontWidth, _LineOffset(fCursor.y), + attr, buffer, width, selected, true, this); + } else { + if (selected) + SetHighColor(fSelectBackColor); + else + SetHighColor(fCursorBackColor); + + FillRect(rect); + } +} + + +//! Update scroll bar range and knob size. +void +TermView::_UpdateScrollBarRange() { if (fScrollBar == NULL) return; - float viewheight = fTermRows * fFontHeight; - float start_pos = fTop -(fScrBufSize - fTermRows * 2) * fFontHeight; - - if (start_pos > 0) { - fScrollBar->SetRange(start_pos, viewheight + fTop - fFontHeight); - } else { - fScrollBar->SetRange(0, viewheight + fTop - fFontHeight); - fScrollBar->SetProportion( viewheight /(viewheight + fTop)); + int32 historySize; + { + BAutolock _(fTextBuffer); + historySize = fTextBuffer->HistorySize(); } -} + float viewHeight = fTermRows * fFontHeight; + float historyHeight = (float)historySize * fFontHeight; -//! Scrolls screen. -void -TermView::ScrollScreen() -{ - fTop += fFontHeight; - fScrollUpCount++; - fTextBuffer->ScrollLine(); +//debug_printf("TermView::_UpdateScrollBarRange(): history: %ld, range: %f - 0\n", +//historySize, -historyHeight); - if (fScrollUpCount > fTermRows ) { - if (LockLooper()) { - _ResizeScrBarRange(); - fScrollBarRange += fScrollUpCount; - fScrollUpCount = 0; - ScrollTo(0, fTop); - UnlockLooper(); - } - } -} - - -//! Scrolls screen. -void -TermView::ScrollScreenDraw() -{ - if (fScrollUpCount){ - if (LockLooper()) { - _ResizeScrBarRange(); - - fScrollBarRange += fScrollUpCount; - fScrollUpCount = 0; - ScrollTo(0, fTop); - UnlockLooper(); - } - } + fScrollBar->SetRange(-historyHeight, 0); + if (historySize > 0) + fScrollBar->SetProportion(viewHeight / (viewHeight + historyHeight)); } @@ -1257,55 +914,8 @@ void TermView::_UpdateSIGWINCH() { if (fFrameResized) { - if (_HasSelection()) - _TermDrawSelectedRegion(fSelStart, fSelEnd); - ScrollTo(0, fTop); - _ResizeScrBarRange(); - fShell->UpdateWindowSize(fTermRows, fTermColumns); - fFrameResized = false; - if (fScrRegionSet == 0) - fScrBot = fTermRows - 1; - } -} - - -//! Device Status. -void -TermView::DeviceStatusReport(int n) -{ - char sbuf[16] ; - int len; - - switch (n) { - case 5: - len = sprintf(sbuf,"\033[0n") ; - fShell->Write(sbuf, len); - break ; - case 6: - len = sprintf(sbuf,"\033[%d;%dR", fTermRows, fTermColumns) ; - fShell->Write(sbuf, len); - break ; - default: - return; - } -} - - -//! Update line buffer. -void -TermView::UpdateLine() -{ - if (fUpdateFlag == true) { - if (fInsertModeFlag == MODE_INSERT) { - _TermDraw(CurPos(fBufferStartPos, fCurPos.y), - CurPos(fTermColumns - 1, fCurPos.y)); - } else { - _TermDraw(CurPos(fBufferStartPos, fCurPos.y), - CurPos(fCurPos.x - 1, fCurPos.y)); - } - fUpdateFlag = false; } } @@ -1314,19 +924,21 @@ void TermView::AttachedToWindow() { MakeFocus(true); - if (fScrollBar) + if (fScrollBar) { fScrollBar->SetSteps(fFontHeight, fFontHeight * fTermRows); - + _UpdateScrollBarRange(); + } + BMessenger thisMessenger(this); + fTextBuffer->SetListener(thisMessenger); + BMessage message(kUpdateSigWinch); - fWinchRunner = - new (std::nothrow) BMessageRunner(thisMessenger, - &message, 500000); + fWinchRunner = new (std::nothrow) BMessageRunner(thisMessenger, + &message, 500000); BMessage blinkMessage(kBlinkCursor); - fCursorBlinkRunner = - new (std::nothrow) BMessageRunner(thisMessenger, - &blinkMessage, 1000000); + fCursorBlinkRunner = new (std::nothrow) BMessageRunner(thisMessenger, + &blinkMessage, 1000000); } @@ -1335,7 +947,7 @@ TermView::DetachedFromWindow() { delete fWinchRunner; fWinchRunner = NULL; - + delete fCursorBlinkRunner; fCursorBlinkRunner = NULL; } @@ -1344,53 +956,85 @@ TermView::DetachedFromWindow() void TermView::Draw(BRect updateRect) { - if (IsPrinting()) { - _DoPrint(updateRect); - return; +// if (IsPrinting()) { +// _DoPrint(updateRect); +// return; +// } + + BAutolock _(fTextBuffer); +// debug_printf("TermView::Draw((%f, %f) - (%f, %f))\n", updateRect.left, +// updateRect.top, updateRect.right, updateRect.bottom); +// { +// BRect bounds(Bounds()); +// debug_printf("Bounds(): (%f, %f) - (%f - %f)\n", bounds.left, bounds.top, +// bounds.right, bounds.bottom); +// debug_printf("clipping region:\n"); +// BRegion region; +// GetClippingRegion(®ion); +// for (int32 i = 0; i < region.CountRects(); i++) { +// BRect rect(region.RectAt(i)); +// debug_printf(" (%f, %f) - (%f, %f)\n", rect.left, rect.top, rect.right, +// rect.bottom); +// } +// } + + // TODO: Work-around for #2355: updateRect is sometimes way larger than the + // clipping region frame). Determine the minimal update rect. + BRegion region; + GetClippingRegion(®ion); + if (region.CountRects() > 0) { + BRect clippingRect(region.Frame()); +//if (clippingRect != updateRect) +//debug_printf(" clipping rect: (%f, %f) - (%f, %f)\n", +//clippingRect.left, clippingRect.top, clippingRect.right, clippingRect.bottom); + updateRect = clippingRect; } - int x1 =(int)(updateRect.left - kOffset) / fFontWidth; - int x2 =(int)(updateRect.right - kOffset) / fFontWidth; + _SynchronizeWithTextBuffer(&updateRect); - int y1 =(int)(updateRect.top - kOffset) / fFontHeight; - int y2 =(int)(updateRect.bottom - kOffset) / fFontHeight; + int32 x1 = (int32)(updateRect.left) / fFontWidth; + int32 x2 = (int32)(updateRect.right) / fFontWidth; + + int32 y1 = _LineAt(updateRect.top); + int32 y2 = _LineAt(updateRect.bottom); + +//debug_printf("TermView::Draw(): (%ld, %ld) - (%ld, %ld), top: %f, fontHeight: %d, scrollOffset: %f\n", +//x1, y1, x2, y2, updateRect.top, fFontHeight, fScrollOffset); Window()->BeginViewTransaction(); - for (int j = y1; j <= y2; j++) { - // If(x1, y1) Buffer is in string full width character, - // alignment start position. + for (int32 j = y1; j <= y2; j++) { + int32 k = x1; + char buf[fTermColumns * 4 + 1]; - int k = x1; - uchar buf[256]; - - ushort attr; - if (fTextBuffer->GetChar(j, k, buf, &attr) == IN_STRING) + if (fTextBuffer->IsFullWidthChar(j, k)) k--; if (k < 0) k = 0; - for (int i = k; i <= x2;) { - int count = fTextBuffer->GetString(j, i, x2, buf, &attr); - bool insideSelection = _CheckSelectedRegion(CurPos(i, j)); + for (int32 i = k; i <= x2;) { + int32 lastColumn = x2; + bool insideSelection = _CheckSelectedRegion(j, i, lastColumn); + uint16 attr; + int32 count = fTextBuffer->GetString(j, i, lastColumn, buf, attr); +//debug_printf(" fTextBuffer->GetString(%ld, %ld, %ld) -> (%ld, \"%.*s\"), selected: %d\n", +//j, i, lastColumn, count, (int)count, buf, insideSelection); - if (count < 0) { - if (insideSelection) { - BRect eraseRect; - eraseRect.Set(fFontWidth * i + kOffset, - fFontHeight * j + kOffset, - fFontWidth * (i - count) -1 + kOffset, - fFontHeight * (j + 1) -1 + kOffset); + if (count == 0) { + BRect rect(fFontWidth * i, _LineOffset(j), + fFontWidth * (lastColumn + 1) - 1, 0); + rect.bottom = rect.top + fFontHeight - 1; - SetHighColor(fSelectBackColor); - FillRect(eraseRect); - } - i += abs(count); + SetHighColor(insideSelection ? fSelectBackColor + : fTextBackColor); + FillRect(rect); + + i = lastColumn + 1; continue; } - _DrawLines(fFontWidth * i, fFontHeight * j, + _DrawLinePart(fFontWidth * i, _LineOffset(j), attr, buf, count, insideSelection, false, this); i += count; if (i >= fTermColumns) @@ -1398,8 +1042,8 @@ TermView::Draw(BRect updateRect) } } - if (fCursorStatus) - DrawCursor(); + if (fCursor >= TermPos(x1, y1) && fCursor <= TermPos(x2, y2)) + _DrawCursor(); Window()->EndViewTransaction(); } @@ -1408,27 +1052,28 @@ TermView::Draw(BRect updateRect) void TermView::_DoPrint(BRect updateRect) { +#if 0 ushort attr; - uchar buf[256]; + uchar buf[1024]; const int numLines = (int)((updateRect.Height()) / fFontHeight); - int y1 = (int)(updateRect.top - kOffset) / fFontHeight; + int y1 = (int)(updateRect.top) / fFontHeight; y1 = y1 -(fScrBufSize - numLines * 2); if (y1 < 0) y1 = 0; const int y2 = y1 + numLines -1; - const int x1 = (int)(updateRect.left - kOffset) / fFontWidth; - const int x2 = (int)(updateRect.right - kOffset) / fFontWidth; + const int x1 = (int)(updateRect.left) / fFontWidth; + const int x2 = (int)(updateRect.right) / fFontWidth; for (int j = y1; j <= y2; j++) { // If(x1, y1) Buffer is in string full width character, // alignment start position. int k = x1; - if (fTextBuffer->GetChar(j, k, buf, &attr) == IN_STRING) + if (fTextBuffer->IsFullWidthChar(j, k)) k--; if (k < 0) @@ -1441,11 +1086,12 @@ TermView::_DoPrint(BRect updateRect) continue; } - _DrawLines(fFontWidth * i, fFontHeight * j, + _DrawLinePart(fFontWidth * i, fFontHeight * j, attr, buf, count, false, false, this); i += count; } } +#endif // 0 } @@ -1504,10 +1150,7 @@ TermView::KeyDown(const char *bytes, int32 numBytes) case B_UP_ARROW: if (mod & B_SHIFT_KEY) { - if (Bounds().top > 0) { - ScrollBy(0, -fFontHeight); - Window()->UpdateIfNeeded(); - } + _ScrollTo(fScrollOffset - fFontHeight, true); return; } if (rawChar == B_UP_ARROW) { @@ -1520,8 +1163,7 @@ TermView::KeyDown(const char *bytes, int32 numBytes) case B_DOWN_ARROW: if (mod & B_SHIFT_KEY) { - ScrollBy(0, fFontHeight); - Window()->UpdateIfNeeded(); + _ScrollTo(fScrollOffset + fFontHeight, true); return; } @@ -1550,10 +1192,7 @@ TermView::KeyDown(const char *bytes, int32 numBytes) case B_PAGE_UP: if (mod & B_SHIFT_KEY) { - if (Bounds().top > 0) { - ScrollBy(0, -fFontHeight * fTermRows ); - Window()->UpdateIfNeeded(); - } + _ScrollTo(fScrollOffset - fFontHeight * fTermRows, true); return; } if (rawChar == B_PAGE_UP) @@ -1562,8 +1201,7 @@ TermView::KeyDown(const char *bytes, int32 numBytes) case B_PAGE_DOWN: if (mod & B_SHIFT_KEY) { - ScrollBy(0, fFontHeight * fTermRows); - Window()->UpdateIfNeeded(); + _ScrollTo(fScrollOffset + fFontHeight * fTermRows, true); return; } @@ -1584,6 +1222,7 @@ TermView::KeyDown(const char *bytes, int32 numBytes) break; } if (toWrite) { + _ScrollTo(0, true); fShell->Write(toWrite, strlen(toWrite)); return; } @@ -1593,11 +1232,13 @@ TermView::KeyDown(const char *bytes, int32 numBytes) char destBuffer[16]; int cnum = CodeConv::ConvertFromInternal(bytes, numBytes, (char *)destBuffer, fEncoding); + _ScrollTo(0, true); fShell->Write(destBuffer, cnum); return; } } + _ScrollTo(0, true); fShell->Write(bytes, numBytes); } @@ -1605,24 +1246,16 @@ TermView::KeyDown(const char *bytes, int32 numBytes) void TermView::FrameResized(float width, float height) { - const int cols = ((int)width + 1 - 2 * kOffset) / fFontWidth; - const int rows = ((int)height + 1 - 2 * kOffset) / fFontHeight; - - int offset = 0; +//debug_printf("TermView::FrameResized(%f, %f)\n", width, height); + int32 columns = ((int32)width + 1) / fFontWidth; + int32 rows = ((int32)height + 1) / fFontHeight; - if (rows < fCurPos.y + 1) { - fTop += (fCurPos.y + 1 - rows) * fFontHeight; - offset = fCurPos.y + 1 - rows; - fCurPos.y = rows - 1; - } - fTextBuffer->ResizeTo(rows, cols, offset); - fTermRows = rows; - fTermColumns = cols; + if (columns == fTermColumns && rows == fTermRows) + return; + + SetTermSize(rows, columns, false); fFrameResized = true; - - if (fScrollBar != NULL) - fScrollBar->SetSteps(fFontHeight, fFontHeight * fTermRows); } @@ -1644,7 +1277,7 @@ TermView::MessageReceived(BMessage *msg) _DoFileDrop(ref); while (msg->FindRef("refs", i++, &ref) == B_OK) { - _WritePTY((const uchar*)" ", 1); + _WritePTY(" ", 1); _DoFileDrop(ref); } return; @@ -1658,7 +1291,7 @@ TermView::MessageReceived(BMessage *msg) #endif } else if (msg->FindData("text/plain", B_MIME_TYPE, (const void **)&text, &numBytes) == B_OK) { - _WritePTY((uchar *)text, numBytes); + _WritePTY(text, numBytes); return; } } @@ -1678,7 +1311,7 @@ TermView::MessageReceived(BMessage *msg) _DoFileDrop(ref); while (msg->FindRef("refs", i++, &ref) == B_OK) { - _WritePTY((const uchar*)" ", 1); + _WritePTY(" ", 1); _DoFileDrop(ref); } } else @@ -1771,6 +1404,30 @@ TermView::MessageReceived(BMessage *msg) case kUpdateSigWinch: _UpdateSIGWINCH(); break; + case kAutoScroll: + _AutoScrollUpdate(); + break; + case MSG_TERMINAL_BUFFER_CHANGED: + { + BAutolock _(fTextBuffer); + _SynchronizeWithTextBuffer(NULL); + break; + } + case MSG_SET_TERMNAL_TITLE: + { + const char* title; + if (msg->FindString("title", &title) == B_OK) + SetTitle(title); + break; + } + case MSG_QUIT_TERMNAL: + { + int32 reason; + if (msg->FindInt32("reason", &reason) != B_OK) + reason = 0; + NotifyQuit(reason); + break; + } default: BView::MessageReceived(msg); break; @@ -1788,6 +1445,29 @@ TermView::GetSupportedSuites(BMessage *message) } +void +TermView::ScrollTo(BPoint where) +{ +//debug_printf("TermView::ScrollTo(): %f -> %f\n", fScrollOffset, where.y); + float diff = where.y - fScrollOffset; + if (diff == 0) + return; + + fScrollOffset = where.y; + + // invalidate the current cursor position before scrolling + _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); + + // scroll contents + BRect destRect(Frame().OffsetToCopy(Bounds().LeftTop())); + BRect sourceRect(destRect.OffsetByCopy(0, diff)); +//debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n", +//sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom, +//destRect.left, destRect.top, destRect.right, destRect.bottom); + CopyBits(sourceRect, destRect); +} + + BHandler* TermView::ResolveSpecifier(BMessage *message, int32 index, BMessage *specifier, int32 what, const char *property) @@ -1810,7 +1490,112 @@ TermView::_DoFileDrop(entry_ref &ref) BString string(path.Path()); string.CharacterEscape(" ~`#$&*()\\|[]{};'\"<>?!",'\\'); - _WritePTY((const uchar *)string.String(), string.Length()); + _WritePTY(string.String(), string.Length()); +} + + +/*! Text buffer must already be locked. +*/ +void +TermView::_SynchronizeWithTextBuffer(BRect* invalidateWhenScrolling) +{ + TerminalBufferDirtyInfo& info = fTextBuffer->DirtyInfo(); + +//debug_printf("TermView::_SynchronizeWithTextBuffer(): dirty: %ld - %ld, " +//"scrolled: %ld\n", info.dirtyTop, info.dirtyBottom, info.linesScrolled); + + BRect bounds = Bounds(); + int32 firstVisible = _LineAt(0); + int32 lastVisible = _LineAt(bounds.bottom); + int32 historySize = fTextBuffer->HistorySize(); + int32 linesScrolled = info.linesScrolled; + + bool doScroll = false; + if (linesScrolled > 0) { + _UpdateScrollBarRange(); + + if (firstVisible < 0) { + firstVisible -= linesScrolled; + lastVisible -= linesScrolled; + + float scrollOffset; + if (firstVisible < -historySize) { + firstVisible = -historySize; + doScroll = true; + scrollOffset = -historySize * fFontHeight; + } else + scrollOffset = fScrollOffset - linesScrolled * fFontHeight; + + _ScrollTo(scrollOffset, false); + } else + doScroll = true; + + if (doScroll && lastVisible >= firstVisible + && !(info.IsDirtyRegionValid() && firstVisible >= info.dirtyTop + && lastVisible <= info.dirtyBottom)) { + // scroll manually + float scrollBy = linesScrolled * fFontHeight; + BRect destRect(Frame().OffsetToCopy(B_ORIGIN)); + BRect sourceRect(destRect.OffsetByCopy(0, scrollBy)); + + // invalidate the current cursor position before scrolling + _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); + +//debug_printf("CopyBits(((%f, %f) - (%f, %f)) -> (%f, %f) - (%f, %f))\n", +//sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom, +//destRect.left, destRect.top, destRect.right, destRect.bottom); + CopyBits(sourceRect, destRect); + + if (invalidateWhenScrolling != NULL) +{ + Invalidate(invalidateWhenScrolling->OffsetByCopy(0, -scrollBy)); +//BRect rect(invalidateWhenScrolling->OffsetByCopy(0, scrollBy)); +//debug_printf("Invalidate((%f, %f) - (%f, %f))\n", rect.left, rect.top, +//rect.right, rect.bottom); +} + } + + // move selection + if (fSelStart != fSelEnd) { + fSelStart.y -= linesScrolled; + fSelEnd.y -= linesScrolled; + fInitialSelectionStart.y -= linesScrolled; + fInitialSelectionEnd.y -= linesScrolled; + + if (fSelStart.y < -historySize) + _Deselect(); + } + } + + // invalidate dirty region + if (info.IsDirtyRegionValid()) { + _InvalidateTextRect(0, info.dirtyTop, fTextBuffer->Width() - 1, + info.dirtyBottom); + + // clear the selection, if affected + if (fSelStart != fSelEnd) { + // TODO: We're clearing the selection more often than necessary -- + // to avoid that, we'd also need to track the x coordinates of the + // dirty range. + int32 selectionBottom = fSelEnd.x > 0 ? fSelEnd.y : fSelEnd.y - 1; + if (fSelStart.y <= info.dirtyBottom + && info.dirtyTop <= selectionBottom) { + _Deselect(); + } + } + } + + // invalidate cursor, if it changed + TermPos cursor = fTextBuffer->Cursor(); + if (fCursor != cursor) { + // Before we scrolled we did already invalidate the old cursor. + if (!doScroll) + _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); + fCursor = cursor; + _InvalidateTextRect(fCursor.x, fCursor.y, fCursor.x, fCursor.y); + } + + info.Reset(); } @@ -1818,14 +1603,23 @@ TermView::_DoFileDrop(entry_ref &ref) encoding to UTF8 before writing PTY. */ void -TermView::_WritePTY(const uchar *text, int numBytes) +TermView::_WritePTY(const char* text, int32 numBytes) { if (fEncoding != M_UTF8) { - uchar *destBuffer = (uchar *)malloc(numBytes * 3); - numBytes = CodeConv::ConvertFromInternal((char*)text, numBytes, - (char*)destBuffer, fEncoding); - fShell->Write(destBuffer, numBytes); - free(destBuffer); + while (numBytes > 0) { + char buffer[1024]; + int32 bufferSize = sizeof(buffer); + int32 sourceSize = numBytes; + int32 state = 0; + if (convert_to_utf8(fEncoding, text, &sourceSize, buffer, + &bufferSize, &state) != B_OK || bufferSize == 0) { + break; + } + + fShell->Write(buffer, bufferSize); + text += sourceSize; + numBytes -= sourceSize; + } } else { fShell->Write(text, numBytes); } @@ -1846,8 +1640,10 @@ TermView::MouseDown(BPoint where) if (_HasSelection()) { // copy text from region BString copy; + fTextBuffer->Lock(); fTextBuffer->GetStringFromRegion(copy, fSelStart, fSelEnd); - _WritePTY((uchar *)copy.String(), copy.Length()); + fTextBuffer->Unlock(); + _WritePTY(copy.String(), copy.Length()); } else { // copy text from clipboard. Paste(be_clipboard); @@ -1862,7 +1658,7 @@ TermView::MouseDown(BPoint where) Window()->CurrentMessage()->FindInt32("clicks", &clicks); if (_HasSelection()) { - CurPos inPos = _ConvertToTerminal(where); + TermPos inPos = _ConvertToTerminal(where); if (_CheckSelectedRegion(inPos)) { if (mod & B_CONTROL_KEY) { BPoint p; @@ -1871,7 +1667,7 @@ TermView::MouseDown(BPoint where) GetMouse(&p, &bt); if (bt == 0) { - _DeSelect(); + _Deselect(); return; } @@ -1895,30 +1691,37 @@ TermView::MouseDown(BPoint where) SetMouseEventMask(B_POINTER_EVENTS | B_KEYBOARD_EVENTS, B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS); - fClickPoint = where; + TermPos clickPos = _ConvertToTerminal(where); - if (mod & B_SHIFT_KEY) - _AddSelectRegion(_ConvertToTerminal(where)); - else - _DeSelect(); - + if (mod & B_SHIFT_KEY) { + fInitialSelectionStart = clickPos; + fInitialSelectionEnd = clickPos; + _ExtendSelection(fInitialSelectionStart, true, false); + } else { + _Deselect(); + fInitialSelectionStart = clickPos; + fInitialSelectionEnd = clickPos; + } // If clicks larger than 3, reset mouse click counter. - clicks = clicks % 3; - if (clicks == 0) - clicks = 3; + clicks = (clicks - 1) % 3 + 1; switch (clicks) { case 1: fMouseTracking = true; - break; + fSelectGranularity = SELECT_CHARS; + break; case 2: - _SelectWord(where, mod); + _SelectWord(where, (mod & B_SHIFT_KEY) != 0, false); + fMouseTracking = true; + fSelectGranularity = SELECT_WORDS; break; case 3: - _SelectLine(where, mod); + _SelectLine(where, (mod & B_SHIFT_KEY) != 0, false); + fMouseTracking = true; + fSelectGranularity = SELECT_LINES; break; } return; @@ -1934,34 +1737,46 @@ TermView::MouseMoved(BPoint where, uint32 transit, const BMessage *message) BView::MouseMoved(where, transit, message); if (!fMouseTracking) return; - - CurPos startPos = _ConvertToTerminal(fClickPoint); - CurPos endPos = _ConvertToTerminal(where); - if (endPos.y < 0) - return; - _DeSelect(); - _Select(startPos, endPos); - - // Scroll check - if (fScrollBar != NULL) { - // Get now scroll point - float scrollStart, scrollEnd; - fScrollBar->GetRange(&scrollStart, &scrollEnd); - float scrollPos = fScrollBar->Value(); - - if (where.y < Bounds().LeftTop().y ) { - // mouse point left of window - if (scrollPos != scrollStart) - ScrollTo(0, where.y); - } - - if (where.y > Bounds().LeftBottom().y) { - // mouse point left of window - if (scrollPos != scrollEnd) - ScrollTo(0, where.y); - } + bool doAutoScroll = false; + + if (where.y < 0) { + doAutoScroll = true; + fAutoScrollSpeed = where.y; + where.x = 0; + where.y = 0; } + + BRect bounds(Bounds()); + if (where.y > bounds.bottom) { + doAutoScroll = true; + fAutoScrollSpeed = where.y - bounds.bottom; + where.x = bounds.right; + where.y = bounds.bottom; + } + + if (doAutoScroll) { + if (fAutoScrollRunner == NULL) { + BMessage message(kAutoScroll); + fAutoScrollRunner = new (std::nothrow) BMessageRunner( + BMessenger(this), &message, 10000); + } + } else { + delete fAutoScrollRunner; + fAutoScrollRunner = NULL; + } + + switch (fSelectGranularity) { + case SELECT_CHARS: + _ExtendSelection(_ConvertToTerminal(where), true, true); + break; + case SELECT_WORDS: + _SelectWord(where, true, true); + break; + case SELECT_LINES: + _SelectLine(where, true, true); + break; + } } @@ -1970,174 +1785,118 @@ TermView::MouseUp(BPoint where) { BView::MouseUp(where); fMouseTracking = false; + + if (fAutoScrollRunner != NULL) { + delete fAutoScrollRunner; + fAutoScrollRunner = NULL; + } } // Select a range of text void -TermView::_Select(CurPos start, CurPos end) +TermView::_Select(TermPos start, TermPos end, bool inclusive, + bool setInitialSelection) { + BAutolock _(fTextBuffer); + + _SynchronizeWithTextBuffer(NULL); + if (end < start) - std::swap(start, end); - + std::swap(start, end); + + if (inclusive) + end.x++; + +//debug_printf("TermView::_Select(): (%ld, %ld) - (%ld, %ld)\n", start.x, +//start.y, end.x, end.y); + if (start.x < 0) start.x = 0; if (end.x >= fTermColumns) end.x = fTermColumns - 1; + + TermPos minPos(0, -fTextBuffer->HistorySize()); + TermPos maxPos(0, fTextBuffer->Height()); + start = restrict_value(start, minPos, maxPos); + end = restrict_value(end, minPos, maxPos); + + // if the end is past the end of the line, select the line break, too + if (fTextBuffer->LineLength(end.y) < end.x + && end.y < fTextBuffer->Height()) { + end.y++; + end.x = 0; + } - uchar buf[4]; - ushort attr; - if (fTextBuffer->GetChar(start.y, start.x, buf, &attr) == IN_STRING) { + if (fTextBuffer->IsFullWidthChar(start.y, start.x)) { start.x--; if (start.x < 0) start.x = 0; } - if (fTextBuffer->GetChar(end.y, end.x, buf, &attr) == IN_STRING) { + if (fTextBuffer->IsFullWidthChar(end.y, end.x)) { end.x++; if (end.x >= fTermColumns) end.x = fTermColumns; } + + if (fSelStart != fSelEnd) + _InvalidateTextRange(fSelStart, fSelEnd); fSelStart = start; fSelEnd = end; - - fTextBuffer->Select(fSelStart, fSelEnd); - _TermDrawSelectedRegion(fSelStart, fSelEnd); + + if (setInitialSelection) { + fInitialSelectionStart = fSelStart; + fInitialSelectionEnd = fSelEnd; + } + + _InvalidateTextRange(fSelStart, fSelEnd); } -// Add select region(shift + mouse click) +// extend selection (shift + mouse click) void -TermView::_AddSelectRegion(CurPos pos) +TermView::_ExtendSelection(TermPos pos, bool inclusive, + bool useInitialSelection) { + if (!useInitialSelection && !_HasSelection()) + return; + + TermPos start = fSelStart; + TermPos end = fSelEnd; + + if (useInitialSelection) { + start = fInitialSelectionStart; + end = fInitialSelectionEnd; + } + + if (inclusive) { + if (pos >= start && pos >= end) + pos.x++; + } + + if (pos < start) + _Select(pos, end, false, !useInitialSelection); + else if (pos > end) + _Select(start, pos, false, !useInitialSelection); +} + + +// clear the selection. +void +TermView::_Deselect() +{ +//debug_printf("TermView::_Deselect(): has selection: %d\n", _HasSelection()); if (!_HasSelection()) return; - // error check, and if mouse point to a plase full width character, - // select point decliment. - if (pos.x >= fTermColumns) - pos.x = fTermColumns - 1; - else if (pos.x < 0) - pos.x = 0; + _InvalidateTextRange(fSelStart, fSelEnd); - if (pos.y < 0) - pos.y = 0; - - uchar buf[4]; - ushort attr; - if (fTextBuffer->GetChar(pos.y, pos.x, buf, &attr) == IN_STRING) { - pos.x++; - if (pos.x >= fTermColumns) - pos.x = fTermColumns - 1; - } - - CurPos start = fSelStart; - CurPos end = fSelEnd; - CurPos inPos; - - // Mouse point is same as selected line. - if (pos.y == fSelStart.y && pos.y == fSelEnd.y) { - - if (abs(pos.x - start.x) > abs(pos.x - end.x)) { - - fSelStart = start; - fSelEnd = pos; - inPos = end; - - } else { - - fSelStart = end; - fSelEnd = pos; - inPos = start; - } - // else, End point set to near the start or end point. - } else if (abs(pos.y - start.y) > abs(pos.y - end.y)) { - - fSelStart = start; - fSelEnd = pos; - inPos = end; - } else if (abs(pos.y - start.y) > abs(pos.y - end.y)) { - fSelStart = end; - fSelEnd = pos; - inPos = start; - - } else { - if (start > end) { - inPos = start; - start = end; - end = inPos; - } - - if (pos.y < start.y) { - fSelStart = end; - fSelEnd = pos; - inPos = start; - } else { - fSelStart = start; - fSelEnd = pos; - inPos = end; - } - } - - fTextBuffer->Select(fSelStart, fSelEnd); - _TermDrawSelectedRegion(inPos, fSelEnd); -} - - -// Resize select region (mouse drag) -void -TermView::_ResizeSelectRegion(CurPos pos) -{ - //TODO: Broken. Selecting from right to left doesn't work. - - CurPos inPos = fSelEnd; - - // error check, and if mouse point to a plase full width character, - // select point decliment. - if (pos.x >= fTermColumns) - pos.x = fTermColumns - 1; - else if (pos.x < 0) - pos.x = 0; - - if (pos.y < 0) - pos.y = 0; - - uchar buf[4]; - ushort attr; - if (fTextBuffer->GetChar(pos.y, pos.x, buf, &attr) == IN_STRING) { - pos.x++; - - if (pos == inPos) - return; - - if (pos.x >= fTermColumns) - pos.x = fTermColumns - 1; - } - - fSelEnd = pos; - - fTextBuffer->Select(fSelStart, fSelEnd); - _TermDrawSelectedRegion(inPos, pos); -} - - -// DeSelect a range of text -void -TermView::_DeSelect(void) -{ - if (!_HasSelection()) - return; - - fTextBuffer->DeSelect(); - - CurPos start = fSelStart; - CurPos end = fSelEnd; - - fSelStart.Set(-1, -1); - fSelEnd.Set(-1, -1); - - _TermDrawSelectedRegion(start, end); + fSelStart.SetTo(0, 0); + fSelEnd.SetTo(0, 0); + fInitialSelectionStart.SetTo(0, 0); + fInitialSelectionEnd.SetTo(0, 0); } @@ -2149,197 +1908,139 @@ TermView::_HasSelection() const void -TermView::_SelectWord(BPoint where, int mod) +TermView::_SelectWord(BPoint where, bool extend, bool useInitialSelection) { - CurPos start, end, pos; - bool flag; - - pos = _ConvertToTerminal(where); - flag = fTextBuffer->FindWord(pos, &start, &end); - fTextBuffer->Select(start, end); + BAutolock _(fTextBuffer); - if (mod & B_SHIFT_KEY) { - if (flag) { - if (start < fSelStart) - _AddSelectRegion(start); - else if (end > fSelEnd) - _AddSelectRegion(end); - } else - _AddSelectRegion(pos); - } else { - _DeSelect(); - if (flag) - _Select(start, end); - } + TermPos pos = _ConvertToTerminal(where); + TermPos start, end; + if (!fTextBuffer->FindWord(pos, fCharClassifier, true, start, end)) + return; + + if (extend) { + if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart)) + _ExtendSelection(start, false, useInitialSelection); + else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd)) + _ExtendSelection(end, false, useInitialSelection); + } else + _Select(start, end, false, !useInitialSelection); } void -TermView::_SelectLine(BPoint where, int mod) +TermView::_SelectLine(BPoint where, bool extend, bool useInitialSelection) { - CurPos pos = _ConvertToTerminal(where); - - if (mod & B_SHIFT_KEY) { + TermPos start = TermPos(0, _ConvertToTerminal(where).y); + TermPos end = TermPos(0, start.y + 1); + + if (extend) { + if (start < (useInitialSelection ? fInitialSelectionStart : fSelStart)) + _ExtendSelection(start, false, useInitialSelection); + else if (end > (useInitialSelection ? fInitialSelectionEnd : fSelEnd)) + _ExtendSelection(end, false, useInitialSelection); - CurPos start = CurPos(0, pos.y); - CurPos end = CurPos(fTermColumns - 1, pos.y); - - if (start < fSelStart) - _AddSelectRegion(start); - else if (end > fSelEnd) - _AddSelectRegion(end); - - } else { - _DeSelect(); - _Select(CurPos(0, pos.y), CurPos(fTermColumns - 1, pos.y)); + } else + _Select(start, end, false, !useInitialSelection); +} + + +void +TermView::_AutoScrollUpdate() +{ + if (fMouseTracking && fAutoScrollRunner != NULL && fScrollBar != NULL) { + float value = fScrollBar->Value(); + _ScrollTo(value + fAutoScrollSpeed, true); + if (fAutoScrollSpeed < 0) { + _ExtendSelection(_ConvertToTerminal(BPoint(0, 0)), true, true); + } else { + _ExtendSelection(_ConvertToTerminal(Bounds().RightBottom()), true, + true); + } } } -// Convert View visible area corrdination to cursor position. -CurPos -TermView::_ConvertToTerminal(const BPoint &p) -{ - return CurPos((p.x - kOffset) / fFontWidth, (p.y - kOffset) / fFontHeight); -} - - -// Convert cursor position to view coordination. -BPoint -TermView::_ConvertFromTerminal(const CurPos &pos) -{ - return BPoint(fFontWidth * pos.x + kOffset, - pos.y * fFontHeight + fTop + kOffset); -} - - bool -TermView::_CheckSelectedRegion(const CurPos &pos) +TermView::_CheckSelectedRegion(const TermPos &pos) const { - CurPos start, end; - - if (fSelStart > fSelEnd) { - start = fSelEnd; - end = fSelStart; - } else { - start = fSelStart; - end = fSelEnd; - } - - if (pos >= start && pos <= end) - return true; - - return false; - + return pos >= fSelStart && pos < fSelEnd; } + +bool +TermView::_CheckSelectedRegion(int32 row, int32 firstColumn, + int32& lastColumn) const +{ + if (fSelStart == fSelEnd) + return false; + + if (row == fSelStart.y && firstColumn < fSelStart.x + && lastColumn >= fSelStart.x) { + // region starts before the selection, but intersects with it + lastColumn = fSelStart.x - 1; + return false; + } + + if (row == fSelEnd.y && firstColumn < fSelEnd.x + && lastColumn >= fSelEnd.x) { + // region starts in the selection, but exceeds the end + lastColumn = fSelEnd.x - 1; + return true; + } + + TermPos pos(firstColumn, row); + return pos >= fSelStart && pos < fSelEnd; +} + + void TermView::GetFrameSize(float *width, float *height) { + int32 historySize; + { + BAutolock _(fTextBuffer); + historySize = fTextBuffer->HistorySize(); + } + if (width != NULL) - *width = 2 * kOffset + fTermColumns * fFontWidth; + *width = fTermColumns * fFontWidth; - if (height == NULL) - return; - - if (!fTop) { - *height = 2 * kOffset + fTermRows * fFontHeight; - return; - } - - if (fTop - fTermRows * fFontHeight > fScrBufSize * fFontHeight) { - - *height = fScrBufSize * fFontHeight + 2 * kOffset; - return; - } - - *height = kOffset + fTop + fTermRows * fFontHeight; + if (height != NULL) + *height = (fTermRows + historySize) * fFontHeight; } // Find a string, and select it if found bool -TermView::Find(const BString &str, bool forwardSearch, bool matchCase, bool matchWord) +TermView::Find(const BString &str, bool forwardSearch, bool matchCase, + bool matchWord) { - //Get the buffer contents - BString buffer; - fTextBuffer->ToString(buffer); + BAutolock _(fTextBuffer); + _SynchronizeWithTextBuffer(NULL); - CurPos selectionstart = fSelStart; - CurPos selectionend = fSelEnd; - - int offset = 0; - if (selectionstart.x >= 0 || selectionstart.y >= 0) { + TermPos start; + if (_HasSelection()) { if (forwardSearch) - //Set the offset to the end of the selection - offset = (selectionend.y) * fTermColumns + selectionend.x; + start = fSelEnd; else - offset = (selectionstart.y) * fTermColumns + selectionstart.x; + start = fSelStart; + } else { + // search from the very beginning/end + if (forwardSearch) + start = TermPos(0, -fTextBuffer->HistorySize()); + else + start = TermPos(0, fTextBuffer->Height()); } - int initialresult = -1; - int result = B_ERROR; - - for (;;) { - //Actual search - if (forwardSearch) { - if (matchCase) - result = buffer.FindFirst(str, offset); - else - result = buffer.IFindFirst(str, offset); - } else { - if (matchCase) - result = buffer.FindLast(str, offset); - else - result = buffer.IFindLast(str, offset); - } - - if (result == B_ERROR) { //Wrap search like Be's Terminal - if (forwardSearch) { - if (matchCase) - result = buffer.FindFirst(str, 0); - else - result = buffer.IFindFirst(str, 0); - } else { - if (matchCase) - result = buffer.FindLast(str, buffer.Length()); - else - result = buffer.IFindLast(str, buffer.Length()); - } - } - - if (result < 0) - return false; - - if (matchWord) { - if (isalnum(buffer.ByteAt(result - 1)) || isalnum(buffer.ByteAt(result + str.Length()))) { - if (initialresult == -1) //Set the initial offset to the first result to aid word matching - initialresult = result; - else if (initialresult == result) //We went round the buffer, nothing found - return false; - if (forwardSearch) - offset = result + str.Length(); - else - offset = result; - continue; - } - else - break; - } - else - break; + TermPos matchStart, matchEnd; + if (!fTextBuffer->Find(str.String(), start, forwardSearch, matchCase, + matchWord, matchStart, matchEnd)) { + return false; } - - //Select the found text - selectionstart.y = result / fTermColumns; - selectionstart.x = result % fTermColumns; - //Length -1 since it seems to count the \0 as well - selectionend.y = (result + str.Length() - 1) / fTermColumns; - selectionend.x = (result + str.Length() - 1) % fTermColumns; - //Update the contents of the view - _DeSelect(); - _Select(selectionstart, selectionend); - + + _Select(matchStart, matchEnd, false, true); + _ScrollToRange(fSelStart, fSelEnd); + return true; } @@ -2349,6 +2050,7 @@ void TermView::GetSelection(BString &str) { str.SetTo(""); + BAutolock _(fTextBuffer); fTextBuffer->GetStringFromRegion(str, fSelStart, fSelEnd); } @@ -2379,65 +2081,93 @@ TermView::CheckShellGone() void TermView::InitiateDrag() { + BAutolock _(fTextBuffer); + BString copyStr(""); fTextBuffer->GetStringFromRegion(copyStr, fSelStart, fSelEnd); BMessage message(B_MIME_DATA); - message.AddData("text/plain", B_MIME_TYPE, copyStr.String(), copyStr.Length()); + message.AddData("text/plain", B_MIME_TYPE, copyStr.String(), + copyStr.Length()); BPoint start = _ConvertFromTerminal(fSelStart); BPoint end = _ConvertFromTerminal(fSelEnd); - + BRect rect; - if (fSelStart.y == fSelEnd.y) { - rect.Set(start.x, start.y - fTop, end.x + fFontWidth, - end.y + fFontHeight - fTop); - - } else { - - rect.Set(0, start.y - fTop, fTermColumns * fFontWidth, - end.y + fFontHeight - fTop); - } - + if (fSelStart.y == fSelEnd.y) + rect.Set(start.x, start.y, end.x + fFontWidth, end.y + fFontHeight); + else + rect.Set(0, start.y, fTermColumns * fFontWidth, end.y + fFontHeight); + rect = rect & Bounds(); DragMessage(&message, rect); } -inline void -TermView::_Redraw(int x1, int y1, int x2, int y2) -{ - BRect rect(x1 * fFontWidth + kOffset, - y1 * fFontHeight + kOffset, - (x2 + 1) * fFontWidth + kOffset - 1, - (y2 + 1) * fFontHeight -1 + kOffset); - - if (LockLooper()) { - Invalidate(rect); - UnlockLooper(); - } -} - - -/* static */ -void -TermView::_FixFontAttributes(BFont &font) -{ - font.SetSpacing(B_FIXED_SPACING); -} - - void TermView::_AboutRequested() { BAlert *alert = new (std::nothrow) BAlert("about", - "Terminal\n" - "\twritten by Kazuho Okui and Takashi Murai\n" - "\tupdated by Kian Duffy and others\n\n" - "\tCopyright " B_UTF8_COPYRIGHT "2003-2007, Haiku.\n", "Ok"); + "Terminal\n" + "\twritten by Kazuho Okui and Takashi Murai\n" + "\tupdated by Kian Duffy and others\n\n" + "\tCopyright " B_UTF8_COPYRIGHT "2003-2008, Haiku.\n", "Ok"); if (alert != NULL) alert->Go(); } +void +TermView::_ScrollTo(float y, bool scrollGfx) +{ + if (!scrollGfx) + fScrollOffset = y; + + if (fScrollBar != NULL) + fScrollBar->SetValue(y); + else + ScrollTo(BPoint(0, y)); +} + + +void +TermView::_ScrollToRange(TermPos start, TermPos end) +{ + if (start > end) + std::swap(start, end); + + float startY = _LineOffset(start.y); + float endY = _LineOffset(end.y) + fFontHeight - 1; + float height = Bounds().Height(); + + if (endY - startY > height) { + // The range is greater than the height. Scroll to the closest border. + + // already as good as it gets? + if (startY <= 0 && endY >= height) + return; + + if (startY > 0) { + // scroll down to align the start with the top of the view + _ScrollTo(fScrollOffset + startY, true); + } else { + // scroll up to align the end with the bottom of the view + _ScrollTo(fScrollOffset + endY - height, true); + } + } else { + // The range is smaller than the height. + + // already visible? + if (startY >= 0 && endY <= height) + return; + + if (startY < 0) { + // scroll up to make the start visible + _ScrollTo(fScrollOffset + startY, true); + } else { + // scroll down to make the end visible + _ScrollTo(fScrollOffset + endY - height, true); + } + } +} diff --git a/src/apps/terminal/TermView.h b/src/apps/terminal/TermView.h index e7e5599d63..9317c3406b 100644 --- a/src/apps/terminal/TermView.h +++ b/src/apps/terminal/TermView.h @@ -12,12 +12,13 @@ #ifndef TERMVIEW_H #define TERMVIEW_H +#include #include #include #include -#include "CurPos.h" #include "TerminalBuffer.h" +#include "TermPos.h" class BClipboard; @@ -27,7 +28,7 @@ class BString; class Shell; class TermBuffer; -class TermView : public BView, public TerminalBuffer { +class TermView : public BView { public: TermView(BRect frame, int32 argc, const char **argv, int32 historySize = 1000); TermView(int rows, int columns, int32 argc, const char **argv, int32 historySize = 1000); @@ -40,6 +41,8 @@ public: virtual void GetPreferredSize(float *width, float *height); const char *TerminalName() const; + + inline TerminalBuffer* TextBuffer() const { return fTextBuffer; } void GetTermFont(BFont *font) const; void SetTermFont(const BFont *font); @@ -67,52 +70,12 @@ public: void SelectAll(); void Clear(); - // Output Charactor - void Insert(uchar *string, ushort attr); - void InsertCR(); - void InsertLF(); - void InsertNewLine(int num); - void SetInsertMode(int flag); - void InsertSpace(int num); - - // Delete Charactor - void EraseBelow(); - void DeleteChar(int num); - void DeleteColumns(); - void DeleteLine(int num); - - // Get and Set Cursor position - void SetCurPos(int x, int y); - void SetCurX(int x); - void SetCurY(int y); - void GetCurPos(CurPos *inCurPos); - int GetCurX(); - int GetCurY(); - void SaveCursor(); - void RestoreCursor(); - - // Move Cursor - void MoveCurRight(int num); - void MoveCurLeft(int num); - void MoveCurUp(int num); - void MoveCurDown(int num); - // Cursor setting - void DrawCursor(); void BlinkCursor(); void SetCurDraw(bool flag); void SetCurBlinking(bool flag); - // Scroll region - void ScrollRegion(int top, int bot, int dir, int num); - void SetScrollRegion(int top, int bot); - void ScrollAtCursor(); - // Other - void DeviceStatusReport(int); - void UpdateLine(); - void ScrollScreen(); - void ScrollScreenDraw(); void GetFrameSize(float *width, float *height); bool Find(const BString &str, bool forwardSearch, bool matchCase, bool matchWord); void GetSelection(BString &str); @@ -135,12 +98,22 @@ protected: virtual void FrameResized(float width, float height); virtual void MessageReceived(BMessage* message); + virtual void ScrollTo(BPoint where); + virtual status_t GetSupportedSuites(BMessage *msg); virtual BHandler* ResolveSpecifier(BMessage *msg, int32 index, BMessage *specifier, int32 form, const char *property); private: + // point and text offset conversion + inline int32 _LineAt(float y); + inline float _LineOffset(int32 index); + inline TermPos _ConvertToTerminal(const BPoint &p); + inline BPoint _ConvertFromTerminal(const TermPos &pos); + + inline void _InvalidateTextRect(int32 x1, int32 y1, int32 x2, int32 y2); + status_t _InitObject(int32 argc, const char **argv); status_t _AttachShell(Shell *shell); @@ -148,17 +121,18 @@ private: void _AboutRequested(); - void _DrawLines(int , int, ushort, uchar *, int, int, int, BView *); - int _TermDraw(const CurPos &start, const CurPos &end); - int _TermDrawRegion(CurPos start, CurPos end); - int _TermDrawSelectedRegion(CurPos start, CurPos end); - inline void _Redraw(int, int, int, int); + void _DrawLinePart(int32 x1, int32 y1, uint16 attr, char *buf, + int32 width, bool mouse, bool cursor, BView *inView); + void _DrawCursor(); + void _InvalidateTextRange(TermPos start, TermPos end); void _DoPrint(BRect updateRect); - void _ResizeScrBarRange(void); + void _UpdateScrollBarRange(); void _DoFileDrop(entry_ref &ref); - void _WritePTY(const uchar *text, int num_byteses); + void _SynchronizeWithTextBuffer(BRect* invalidateWhenScrolling); + + void _WritePTY(const char* text, int32 numBytes); // Comunicate Input Method // void _DoIMStart (BMessage* message); @@ -168,33 +142,36 @@ private: // void _DoIMConfirm (void); // void _ConfirmString(const char *, int32); - // Mouse select - void _Select(CurPos start, CurPos end); - void _AddSelectRegion(CurPos); - void _ResizeSelectRegion(CurPos); - - void _DeSelect(); + // selection + void _Select(TermPos start, TermPos end, bool inclusive, + bool setInitialSelection); + void _ExtendSelection(TermPos, bool inclusive, bool useInitialSelection); + void _Deselect(); bool _HasSelection() const; + void _SelectWord(BPoint where, bool extend, bool useInitialSelection); + void _SelectLine(BPoint where, bool extend, bool useInitialSelection); - // select word function - void _SelectWord(BPoint where, int mod); - void _SelectLine(BPoint where, int mod); + void _AutoScrollUpdate(); - // point and text offset conversion. - CurPos _ConvertToTerminal(const BPoint &p); - BPoint _ConvertFromTerminal(const CurPos &pos); + bool _CheckSelectedRegion(const TermPos &pos) const; + bool _CheckSelectedRegion(int32 row, int32 firstColumn, + int32& lastColumn) const; - bool _CheckSelectedRegion(const CurPos &pos); - void _UpdateSIGWINCH(); - static void _FixFontAttributes(BFont &font); - + void _ScrollTo(float y, bool scrollGfx); + void _ScrollToRange(TermPos start, TermPos end); + private: + class CharClassifier; + Shell *fShell; BMessageRunner *fWinchRunner; BMessageRunner *fCursorBlinkRunner; + BMessageRunner *fAutoScrollRunner; + + CharClassifier *fCharClassifier; // Font and Width BFont fHalfFont; @@ -203,19 +180,7 @@ private: int fFontAscent; struct escapement_delta fEscapement; - // Flags - - // Update flag (Set on Insert). - bool fUpdateFlag; - - // Terminal insertmode flag (use Insert). - bool fInsertModeFlag; - - // Scroll count, range. - int fScrollUpCount; - int fScrollBarRange; - - // Frame Resized flag. + // frame resized flag. bool fFrameResized; // Cursor Blinking, draw flag. @@ -227,10 +192,7 @@ private: int fCursorHeight; // Cursor position. - CurPos fCurPos; - CurPos fCurStack; - - int fBufferStartPos; + TermPos fCursor; // Terminal rows and columns. int fTermRows; @@ -238,12 +200,9 @@ private: int fEncoding; - // Terminal view pointer. - int fTop; - // Object pointer. - TermBuffer *fTextBuffer; - BScrollBar *fScrollBar; + TerminalBuffer *fTextBuffer; + BScrollBar *fScrollBar; // Color and Attribute. rgb_color fTextForeColor, fTextBackColor; @@ -251,22 +210,24 @@ private: rgb_color fSelectForeColor, fSelectBackColor; // Scroll Region - int fScrTop; - int fScrBot; + float fScrollOffset; int32 fScrBufSize; - bool fScrRegionSet; + // TODO: That's the history capacity -- only needed until the text + // buffer is created. + float fAutoScrollSpeed; - BPoint fClickPoint; - - // view selection - CurPos fSelStart; - CurPos fSelEnd; + // selection + TermPos fSelStart; + TermPos fSelEnd; + TermPos fInitialSelectionStart; + TermPos fInitialSelectionEnd; bool fMouseTracking; + int fSelectGranularity; // Input Method parameter. int fIMViewPtr; - CurPos fIMStartPos; - CurPos fIMEndPos; + TermPos fIMStartPos; + TermPos fIMEndPos; BString fIMString; bool fIMflag; BMessenger fIMMessenger; diff --git a/src/apps/terminal/TermWindow.cpp b/src/apps/terminal/TermWindow.cpp index c4df21b8ff..047aa24662 100644 --- a/src/apps/terminal/TermWindow.cpp +++ b/src/apps/terminal/TermWindow.cpp @@ -9,16 +9,9 @@ #include "TermWindow.h" -#include "Arguments.h" -#include "Coding.h" -#include "MenuUtil.h" -#include "FindWindow.h" -#include "PrefWindow.h" -#include "PrefView.h" -#include "PrefHandler.h" -#include "SmartTabView.h" -#include "TermConst.h" -#include "TermView.h" +#include +#include +#include #include #include @@ -35,12 +28,21 @@ #include #include -#include -#include -#include +#include "Arguments.h" +#include "Coding.h" +#include "MenuUtil.h" +#include "FindWindow.h" +#include "PrefWindow.h" +#include "PrefView.h" +#include "PrefHandler.h" +#include "SmartTabView.h" +#include "TermConst.h" +#include "TermScrollView.h" +#include "TermView.h" const static int32 kMaxTabs = 6; +const static int32 kTermViewOffset = 3; // messages constants const static uint32 kNewTab = 'NTab'; @@ -57,6 +59,34 @@ public: }; +class TermViewContainerView : public BView { +public: + TermViewContainerView(TermView* termView) + : + BView(BRect(), "term view container", B_FOLLOW_ALL, 0), + fTermView(termView) + { + termView->MoveTo(kTermViewOffset, kTermViewOffset); + BRect frame(termView->Frame()); + ResizeTo(frame.right + kTermViewOffset, frame.bottom + kTermViewOffset); + AddChild(termView); + } + + TermView* GetTermView() const { return fTermView; } + + virtual void GetPreferredSize(float* _width, float* _height) + { + float width, height; + fTermView->GetPreferredSize(&width, &height); + *_width = width + 2 * kTermViewOffset; + *_height = height + 2 * kTermViewOffset; + } + +private: + TermView* fTermView; +}; + + TermWindow::TermWindow(BRect frame, const char* title, Arguments *args) : BWindow(frame, title, B_DOCUMENT_WINDOW, B_CURRENT_WORKSPACE|B_QUIT_ON_WINDOW_CLOSE), fTabView(NULL), @@ -394,9 +424,11 @@ TermWindow::MessageReceived(BMessage *message) break; case MSG_COLOR_CHANGED: - _SetTermColors(_ActiveTermView()); + { + _SetTermColors(_ActiveTermViewContainerView()); _ActiveTermView()->Invalidate(); break; + } case SAVE_AS_DEFAULT: { @@ -460,7 +492,7 @@ TermWindow::MessageReceived(BMessage *message) _ResizeView(view); break; } - + default: BWindow::MessageReceived(message); break; @@ -476,16 +508,21 @@ TermWindow::WindowActivated(bool activated) void -TermWindow::_SetTermColors(TermView *termView) +TermWindow::_SetTermColors(TermViewContainerView *containerView) { - termView->SetTextColor(PrefHandler::Default()->getRGB(PREF_TEXT_FORE_COLOR), - PrefHandler::Default()->getRGB(PREF_TEXT_BACK_COLOR)); + PrefHandler* handler = PrefHandler::Default(); + rgb_color background = handler->getRGB(PREF_TEXT_BACK_COLOR); - termView->SetSelectColor(PrefHandler::Default()->getRGB(PREF_SELECT_FORE_COLOR), - PrefHandler::Default()->getRGB(PREF_SELECT_BACK_COLOR)); - - termView->SetCursorColor(PrefHandler::Default()->getRGB(PREF_CURSOR_FORE_COLOR), - PrefHandler::Default()->getRGB(PREF_CURSOR_BACK_COLOR)); + containerView->SetViewColor(background); + + TermView *termView = containerView->GetTermView(); + termView->SetTextColor(handler->getRGB(PREF_TEXT_FORE_COLOR), background); + + termView->SetSelectColor(handler->getRGB(PREF_SELECT_FORE_COLOR), + handler->getRGB(PREF_SELECT_BACK_COLOR)); + + termView->SetCursorColor(handler->getRGB(PREF_CURSOR_FORE_COLOR), + handler->getRGB(PREF_CURSOR_BACK_COLOR)); } @@ -564,10 +601,11 @@ TermWindow::_AddTab(Arguments *args) new CustomTermView(PrefHandler::Default()->getInt32(PREF_ROWS), PrefHandler::Default()->getInt32(PREF_COLS), argc, (const char **)argv); - - BScrollView *scrollView = new BScrollView("scrollView", view, B_FOLLOW_ALL, - B_WILL_DRAW|B_FRAME_EVENTS, false, true); - + + TermViewContainerView *containerView = new TermViewContainerView(view); + BScrollView *scrollView = new TermScrollView("scrollView", + containerView, view); + BTab *tab = new BTab; // TODO: Use a better name. For example, do like MacOsX's Terminal // and update the title using the last executed command ? @@ -585,9 +623,9 @@ TermWindow::_AddTab(Arguments *args) BFont font; _GetPreferredFont(font); view->SetTermFont(&font); - - _SetTermColors(view); - + + _SetTermColors(containerView); + int width, height; view->GetFontSize(&width, &height); @@ -603,8 +641,8 @@ TermWindow::_AddTab(Arguments *args) // If it's the first time we're called, setup the window if (fTabView->CountTabs() == 1) { float viewWidth, viewHeight; - view->GetPreferredSize(&viewWidth, &viewHeight); - + containerView->GetPreferredSize(&viewWidth, &viewHeight); + // Resize Window ResizeTo(viewWidth + B_V_SCROLL_BAR_WIDTH, viewHeight + fMenubar->Bounds().Height()); @@ -627,14 +665,36 @@ TermWindow::_RemoveTab(int32 index) PostMessage(B_QUIT_REQUESTED); } +TermViewContainerView* +TermWindow::_ActiveTermViewContainerView() const +{ + return _TermViewContainerViewAt(fTabView->Selection()); +} -TermView * -TermWindow::_ActiveTermView() + +TermViewContainerView* +TermWindow::_TermViewContainerViewAt(int32 index) const { // TODO: BAD HACK: // We should probably use the observer api to tell // the various "tabs" when settings are changed. Fix this. - return (TermView *)((BScrollView *)fTabView->ViewForTab(fTabView->Selection()))->Target(); + BScrollView* scrollView = (BScrollView*)fTabView->ViewForTab(index); + return scrollView ? (TermViewContainerView*)scrollView->Target() : NULL; +} + + +TermView * +TermWindow::_ActiveTermView() const +{ + return _ActiveTermViewContainerView()->GetTermView(); +} + + +TermView* +TermWindow::_TermViewAt(int32 index) const +{ + TermViewContainerView* view = _TermViewContainerViewAt(index); + return view != NULL ? view->GetTermView() : NULL; } @@ -647,12 +707,7 @@ TermWindow::_IndexOfTermView(TermView* termView) const // find the view int32 count = fTabView->CountTabs(); for (int32 i = count - 1; i >= 0; i--) { - BScrollView* scrollView - = dynamic_cast(fTabView->ViewForTab(i)); - if (!scrollView) - continue; - - if (termView == scrollView->Target()) + if (termView == _TermViewAt(i)) return i; } @@ -668,11 +723,7 @@ TermWindow::_CheckChildren() int32 count = fTabView->CountTabs(); for (int32 i = count - 1; i >= 0; i--) { // get the term view - BScrollView* scrollView - = dynamic_cast(fTabView->ViewForTab(i)); - if (!scrollView) - continue; - TermView* termView = dynamic_cast(scrollView->Target()); + TermView* termView = _TermViewAt(i); if (!termView) continue; @@ -698,7 +749,7 @@ TermWindow::_ResizeView(TermView *view) minimumHeight + MAX_ROWS * fontHeight); float width, height; - view->GetPreferredSize(&width, &height); + view->Parent()->GetPreferredSize(&width, &height); width += B_V_SCROLL_BAR_WIDTH; height += fMenubar->Bounds().Height() + 2; diff --git a/src/apps/terminal/TermWindow.h b/src/apps/terminal/TermWindow.h index 4eda92ff79..c8f9c3268f 100644 --- a/src/apps/terminal/TermWindow.h +++ b/src/apps/terminal/TermWindow.h @@ -41,8 +41,9 @@ class BMenu; class BMenuBar; class FindWindow; class PrefWindow; -class TermView; class SmartTabView; +class TermView; +class TermViewContainerView; class TermWindow : public BWindow { @@ -56,7 +57,7 @@ protected: virtual void MenusBeginning(); private: - void _SetTermColors(TermView *termView); + void _SetTermColors(TermViewContainerView *termView); void _InitWindow(); void _SetupMenu(); void _GetPreferredFont(BFont &font); @@ -64,7 +65,10 @@ private: void _DoPrint(); void _AddTab(Arguments *args); void _RemoveTab(int32 index); - TermView* _ActiveTermView(); + TermViewContainerView* _ActiveTermViewContainerView() const; + TermViewContainerView* _TermViewContainerViewAt(int32 index) const; + TermView* _ActiveTermView() const; + TermView* _TermViewAt(int32 index) const; int32 _IndexOfTermView(TermView* termView) const; void _CheckChildren(); void _ResizeView(TermView *view); diff --git a/src/apps/terminal/TerminalBuffer.cpp b/src/apps/terminal/TerminalBuffer.cpp index 2272150464..acf2d81658 100644 --- a/src/apps/terminal/TerminalBuffer.cpp +++ b/src/apps/terminal/TerminalBuffer.cpp @@ -5,12 +5,1192 @@ #include "TerminalBuffer.h" +#include +#include + +#include + +#include +#include +#include + +#include "CodeConv.h" +#include "TermConst.h" +#include "TerminalCharClassifier.h" + + +static const UTF8Char kSpaceChar(' '); + + +static inline int32 +restrict_value(int32 value, int32 min, int32 max) +{ + return value < min ? min : (value > max ? max : value); +} + + +// #pragma mark - private inline methods + + +inline int32 +TerminalBuffer::_LineIndex(int32 index) const +{ + return (index + fScreenOffset) % fHistoryCapacity; +} + + +inline TerminalBuffer::Line* +TerminalBuffer::_LineAt(int32 index) const +{ + return fHistory[_LineIndex(index)]; +} + + +inline TerminalBuffer::Line* +TerminalBuffer::_HistoryLineAt(int32 index) const +{ + if (index >= fHeight || index < -fHistorySize) + return NULL; + + return _LineAt(index + fHistoryCapacity); +} + + +inline void +TerminalBuffer::_Invalidate(int32 top, int32 bottom) +{ +//debug_printf("TerminalBuffer::_Invalidate(%ld, %ld)\n", top, bottom); + if (top < fDirtyInfo.dirtyTop) + fDirtyInfo.dirtyTop = top; + if (bottom > fDirtyInfo.dirtyBottom) + fDirtyInfo.dirtyBottom = bottom; + + if (!fDirtyInfo.messageSent) { +//debug_printf("TerminalBuffer::_Invalidate(): sending message\n"); + fListener.SendMessage(MSG_TERMINAL_BUFFER_CHANGED); + fDirtyInfo.messageSent = true; + } +} + + +inline void +TerminalBuffer::_CursorChanged() +{ + if (!fDirtyInfo.messageSent) { + fListener.SendMessage(MSG_TERMINAL_BUFFER_CHANGED); + fDirtyInfo.messageSent = true; + } +} + + +// #pragma mark - public methods + TerminalBuffer::TerminalBuffer() + : + BLocker("terminal buffer"), + fHistory(NULL) { } TerminalBuffer::~TerminalBuffer() { + _FreeLines(fHistory, fHistoryCapacity); +} + + +status_t +TerminalBuffer::Init(int32 width, int32 height, int32 historySize) +{ + if (Sem() < 0) + return Sem(); + + if (historySize < 2 * height) + historySize = 2 * height; + + fWidth = width; + fHeight = height; + + fScrollTop = 0; + fScrollBottom = fHeight - 1; + + fCursor.x = 0; + fCursor.y = 0; + + fOverwriteMode = true; + + fHistoryCapacity = historySize; + fHistorySize = 0; + + fHistory = _AllocateLines(width, historySize); + if (fHistory == NULL) + return B_NO_MEMORY; + + _ClearLines(0, fHeight - 1); + + fDirtyInfo.Reset(); + + return B_OK; +} + + +void +TerminalBuffer::SetListener(BMessenger listener) +{ + fListener = listener; +} + + +int +TerminalBuffer::Encoding() const +{ +// TODO: Remove, if not needed! + return M_UTF8; +} + + +status_t +TerminalBuffer::ResizeTo(int32 width, int32 height) +{ + BAutolock _(this); + + if (height < MIN_ROWS || height > MAX_ROWS || width < MIN_COLS + || width > MAX_COLS) { + return B_BAD_VALUE; + } + +//debug_printf("TerminalBuffer::ResizeTo(): (%ld, %ld) -> (%ld, %ld)\n", +//fWidth, fHeight, width, height); + + if (width != fWidth) { + // The width changes. We have to allocate a new line array and re-wrap + // all lines. + Line** history = _AllocateLines(width, fHistoryCapacity); + if (history == NULL) + return B_NO_MEMORY; + + int32 totalLines = fHistorySize + fHeight; + int32 historyOffset = fHistoryCapacity - fHistorySize; + // base for _LineAt() invocations to iterate through the history + + // re-wrap + TermPos cursor; + int32 destIndex = 0; + int32 sourceIndex = 0; + int32 sourceX = 0; + int32 destTotalLines = 0; + int32 destScreenOffset = 0; + int32 maxDestTotalLines = INT_MAX; + bool newDestLine = true; + while (sourceIndex < totalLines) { + Line* sourceLine = _LineAt(historyOffset + sourceIndex); + Line* destLine = history[destIndex]; + + if (newDestLine) { + destLine->Clear(); + newDestLine = false; + } + + int32 sourceLeft = sourceLine->length - sourceX; + int32 destLeft = width - destLine->length; +//debug_printf(" source: %ld, left: %ld, dest: %ld, left: %ld\n", +//sourceIndex, sourceLeft, destIndex, destLeft); + + if (sourceIndex == fHistorySize && sourceX == 0) { + destScreenOffset = destTotalLines; + if (destLeft == 0 && sourceLeft > 0) + destScreenOffset++; +//debug_printf(" destScreenOffset: %ld\n", destScreenOffset); + } + + int32 toCopy = min_c(sourceLeft, destLeft); + if (toCopy > 0) { + // If the last cell to copy is the first cell of a + // full-width char, don't copy it yet. + if (sourceX + toCopy > 0 && IS_WIDTH( + sourceLine->cells[sourceX + toCopy - 1].attributes)) { +//debug_printf(" -> last char is full-width -- don't copy it\n"); + toCopy--; + } + + // translate the cursor position + if (fCursor.y + fHistorySize == sourceIndex + && fCursor.x >= sourceX + && (fCursor.x < sourceX + toCopy + || sourceX + sourceLeft <= fCursor.x)) { + cursor.x = destLine->length + fCursor.x - sourceX; + cursor.y = destTotalLines; + + if (cursor.x >= width) { + // The cursor was in free space after the official end + // of line. + cursor.x = width - 1; + } +//debug_printf(" cursor: (%ld, %ld)\n", cursor.x, cursor.y); + + // don't allow the cursor to get out of screen + maxDestTotalLines = cursor.y + fHeight; + } + + memcpy(destLine->cells + destLine->length, + sourceLine->cells + sourceX, toCopy * sizeof(Cell)); + destLine->length += toCopy; + } + + bool nextDestLine = false; + if (toCopy == sourceLeft) { + if (!sourceLine->softBreak) + nextDestLine = true; + sourceIndex++; + sourceX = 0; + } else { + destLine->softBreak = true; + nextDestLine = true; + sourceX += toCopy; + } + + if (nextDestLine) { + destIndex = (destIndex + 1) % fHistoryCapacity; + destTotalLines++; + newDestLine = true; + if (destTotalLines == maxDestTotalLines) + break; + } + } + + // If the last source line had a soft break, the last dest line + // won't have been counted yet. + if (!newDestLine) { + destIndex = (destIndex + 1) % fHistoryCapacity; + destTotalLines++; + } + +//debug_printf(" total lines: %ld -> %ld\n", totalLines, destTotalLines); + + int32 tempHeight = destTotalLines - destScreenOffset; + cursor.y -= destScreenOffset; + + // Re-wrapping might have produced more lines than we have room for. + if (destTotalLines > fHistoryCapacity) + destTotalLines = fHistoryCapacity; + + // Update the values +//debug_printf(" cursor: (%ld, %ld) -> (%ld, %ld)\n", fCursor.x, fCursor.y, +//cursor.x, cursor.y); + fCursor.x = cursor.x; + fCursor.y = cursor.y; +//debug_printf(" screen offset: %ld -> %ld\n", fScreenOffset, +//destScreenOffset % fHistoryCapacity); + fScreenOffset = destScreenOffset % fHistoryCapacity; +//debug_printf(" history size: %ld -> %ld\n", fHistorySize, destTotalLines - fHeight); + fHistorySize = destTotalLines - tempHeight; +//debug_printf(" height %ld -> %ld\n", fHeight, tempHeight); + fHeight = tempHeight; + fWidth = width; + + _FreeLines(fHistory, fHistoryCapacity); + fHistory = history; + } + + if (height == fHeight) + return B_OK; + + // The height changes. We just add/remove lines at the end of the screen. + + if (height < fHeight) { + // The screen shrinks. We just drop the lines at the end of the screen, + // but we keep the cursor on screen at all costs. + if (fCursor.y >= height) { + int32 toShift = fCursor.y - height + 1; + fScreenOffset = (fScreenOffset + fHistoryCapacity + toShift) + % fHistoryCapacity; + fHistorySize += toShift; + fCursor.y -= toShift; + } + } else { + // The screen grows. We add empty lines at the end of the current + // screen. + if (fHistorySize + height > fHistoryCapacity) + fHistorySize = fHistoryCapacity - height; + + for (int32 i = fHeight; i < height; i++) + _LineAt(i)->Clear(); + } + +//debug_printf(" cursor: -> (%ld, %ld)\n", fCursor.x, fCursor.y); +//debug_printf(" screen offset: -> %ld\n", fScreenOffset); +//debug_printf(" history size: -> %ld\n", fHistorySize); + + fHeight = height; + + // reset scroll range to keep it simple + fScrollTop = 0; + fScrollBottom = fHeight - 1; + + return B_OK; +} + + +void +TerminalBuffer::Clear() +{ + BAutolock _(this); + + fHistorySize = 0; + fScreenOffset = 0; + + _ClearLines(0, fHeight - 1); + + fCursor.SetTo(0, 0); + + fDirtyInfo.linesScrolled = 0; + _Invalidate(0, fHeight - 1); +} + + +bool +TerminalBuffer::IsFullWidthChar(int32 row, int32 column) const +{ + Line* line = _HistoryLineAt(row); + return line != NULL && column > 0 && column < line->length + && (line->cells[column - 1].attributes & A_WIDTH) != 0; +} + + +int +TerminalBuffer::GetChar(int32 row, int32 column, UTF8Char& character, + uint16& attributes) const +{ + Line* line = _HistoryLineAt(row); + if (line == NULL) + return NO_CHAR; + + if (column < 0 || column >= line->length) + return NO_CHAR; + + if (column > 0 && (line->cells[column - 1].attributes & A_WIDTH) != 0) + return IN_STRING; + + Cell& cell = line->cells[column]; + character = cell.character; + attributes = cell.attributes; + return A_CHAR; +} + + +int32 +TerminalBuffer::GetString(int32 row, int32 firstColumn, int32 lastColumn, + char* buffer, uint16& attributes) const +{ + Line* line = _HistoryLineAt(row); + if (line == NULL) + return NO_CHAR; + + if (lastColumn >= line->length) + lastColumn = line->length - 1; + + int32 column = firstColumn; + if (column <= lastColumn) + attributes = line->cells[column].attributes; + + for (; column <= lastColumn; column++) { + Cell& 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 +TerminalBuffer::GetStringFromRegion(BString& string, const TermPos& start, + const TermPos& end) const +{ +//debug_printf("TerminalBuffer::GetStringFromRegion((%ld, %ld), (%ld, %ld))\n", +//start.x, start.y, end.x, end.y); + if (start >= end) + return; + + TermPos pos(start); + + if (IsFullWidthChar(pos.y, pos.x)) + pos.x--; + + // get all but the last line + while (pos.y < end.y) { + if (_GetPartialLineString(string, pos.y, pos.x, fWidth)) + string.Append('\n', 1); + pos.x = 0; + pos.y++; + } + + // get the last line, if not empty + if (end.x > 0) + _GetPartialLineString(string, end.y, pos.x, end.x); +} + + +bool +TerminalBuffer::FindWord(const TermPos& pos, TerminalCharClassifier* classifier, + bool findNonWords, TermPos& _start, TermPos& _end) const +{ + int32 x = pos.x; + int32 y = pos.y; + + Line* line = _HistoryLineAt(y); + if (line == NULL || x < 0 || x >= fWidth) + return false; + + if (x >= line->length) { + // beyond the end of the line -- select all space + if (!findNonWords) + return false; + + _start.SetTo(line->length, y); + _end.SetTo(fWidth, y); + return true; + } + + if (x > 0 && IS_WIDTH(line->cells[x - 1].attributes)) + x--; + + // get the char type at the given position + int type = classifier->Classify(line->cells[x].character.bytes); + + // check whether we are supposed to find words only + if (type != CHAR_TYPE_WORD_CHAR && !findNonWords) + return false; + + // find the beginning + TermPos start(x, y); + TermPos end(x + (IS_WIDTH(line->cells[x].attributes) ? 2 : 1), y); + while (true) { + if (--x < 0) { + // Hit the beginning of the line -- continue at the end of the + // previous line, if it soft-breaks. + y--; + if ((line = _HistoryLineAt(y)) == NULL || !line->softBreak + || line->length == 0) { + break; + } + x = line->length - 1; + } + if (x > 0 && IS_WIDTH(line->cells[x - 1].attributes)) + x--; + + if (classifier->Classify(line->cells[x].character.bytes) != type) + break; + + start.SetTo(x, y); + } + + // find the end + x = end.x; + y = end.y; + line = _HistoryLineAt(y); + + while (true) { + if (x >= line->length) { + // Hit the end of the line -- if it soft-breaks continue with the + // next line. + if (!line->softBreak) + break; + y++; + x = 0; + if ((line = _HistoryLineAt(y)) == NULL) + break; + } + + if (classifier->Classify(line->cells[x].character.bytes) != type) + break; + + x += IS_WIDTH(line->cells[x].attributes) ? 2 : 1; + end.SetTo(x, y); + } + + _start = start; + _end = end; + return true; +} + + +int32 +TerminalBuffer::LineLength(int32 index) const +{ + Line* line = _HistoryLineAt(index); + return line != NULL ? line->length : 0; +} + + +bool +TerminalBuffer::Find(const char* _pattern, const TermPos& start, bool forward, + bool caseSensitive, bool matchWord, TermPos& _matchStart, + TermPos& _matchEnd) const +{ +//debug_printf("TerminalBuffer::Find(\"%s\", (%ld, %ld), forward: %d, case: %d, " +//"word: %d)\n", _pattern, start.x, start.y, forward, caseSensitive, matchWord); + // normalize pos, so that _NextChar() and _PreviousChar() are happy + TermPos pos(start); + Line* line = _HistoryLineAt(pos.y); + if (line != NULL) { + if (forward) { + while (line != NULL && pos.x >= line->length && line->softBreak) { + pos.x = 0; + pos.y++; + line = _HistoryLineAt(pos.y); + } + } else { + if (pos.x > line->length) + pos.x = line->length; + } + } + + int32 patternByteLen = strlen(_pattern); + + // convert pattern to UTF8Char array + UTF8Char pattern[patternByteLen]; + int32 patternLen = 0; + while (*_pattern != '\0') { + int32 charLen = UTF8Char::ByteCount(*_pattern); + if (charLen > 0) { + pattern[patternLen].bytes = (char[]){ 0, 0, 0, 0 }; + memcpy(pattern[patternLen].bytes, _pattern, charLen); + + // if not case sensitive, convert to lower case + if (!caseSensitive && charLen == 1) + pattern[patternLen] = pattern[patternLen].ToLower(); + + patternLen++; + _pattern += charLen; + } else + _pattern++; + } +//debug_printf(" pattern byte len: %ld, pattern len: %ld\n", patternByteLen, patternLen); + + if (patternLen == 0) + return false; + + // reverse pattern, if searching backward + if (!forward) { + for (int32 i = 0; i < patternLen / 2; i++) + std::swap(pattern[i], pattern[patternLen - i - 1]); + } + + // search loop + int32 matchIndex = 0; + TermPos matchStart; + while (true) { +//debug_printf(" (%ld, %ld): matchIndex: %ld\n", pos.x, pos.y, matchIndex); + 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) { +//debug_printf(" match!\n"); + // compute the match range + TermPos matchEnd(pos); + if (!forward) + std::swap(matchStart, matchEnd); + + // check word match + if (matchWord) { + TermPos tempPos(matchStart); + if (_PreviousChar(tempPos, c) && !c.IsSpace() + || _NextChar(tempPos = matchEnd, c) && !c.IsSpace()) { +//debug_printf(" but no word match!\n"); + continue; + } + } + + _matchStart = matchStart; + _matchEnd = matchEnd; +//debug_printf(" -> (%ld, %ld) - (%ld, %ld)\n", matchStart.x, matchStart.y, +//matchEnd.x, matchEnd.y); + return true; + } + } else if (matchIndex > 0) { + // continue after the position where we started matching + pos = matchStart; + if (forward) + _NextChar(pos, c); + else + _PreviousChar(pos, c); + matchIndex = 0; + } + } +} + + +void +TerminalBuffer::InsertChar(UTF8Char c, uint32 attributes) +{ +//debug_printf("TerminalBuffer::InsertChar('%.*s' (%d), %#lx)\n", +//(int)c.ByteCount(), c.bytes, c.bytes[0], attributes); + int width = CodeConv::UTF8GetFontWidth(c.bytes); + if (width == FULL_WIDTH) + attributes |= A_WIDTH; + + if (fCursor.x + width > fWidth) + _SoftBreakLine(); + else + _PadLineToCursor(); + + if (!fOverwriteMode) + _InsertGap(width); + + Line* line = _LineAt(fCursor.y); + line->cells[fCursor.x].character = c; + line->cells[fCursor.x].attributes = attributes; + + if (line->length < fCursor.x + width) + line->length = fCursor.x + width; + + _Invalidate(fCursor.y, fCursor.y); + + fCursor.x += width; + +// TODO: Deal correctly with full-width chars! We must take care not to +// overwrite half of a full-width char. This holds also for other methods. + + if (fCursor.x == fWidth) + _SoftBreakLine(); + // TODO: Handle a subsequent CR correctly! +} + + +void +TerminalBuffer::Insert(uchar* string, ushort attr) +{ +// TODO: Remove! Use InsertChar instead! + UTF8Char character; + character.bytes = (char[]){ string[0], string[1], string[2], string[3] }; + InsertChar(character, attr); +} + + +void +TerminalBuffer::InsertCR() +{ + _LineAt(fCursor.y)->softBreak = false; + fCursor.x = 0; + _CursorChanged(); +} + + +void +TerminalBuffer::InsertLF() +{ + // If we're at the end of the scroll region, scroll. Otherwise just advance + // the cursor. + if (fCursor.y == fScrollBottom) { + _Scroll(fScrollTop, fScrollBottom, 1); + } else { + fCursor.y++; + _CursorChanged(); + } +} + + +void +TerminalBuffer::InsertNewLine(int numLines) +{ + if (fCursor.y >= fScrollTop && fCursor.y < fScrollBottom) + _Scroll(fCursor.y, fScrollBottom, -numLines); +} + + +void +TerminalBuffer::SetInsertMode(int flag) +{ + fOverwriteMode = flag == MODE_OVER; +} + + +void +TerminalBuffer::InsertSpace(int num) +{ +// TODO: Deal with full-width chars! + if (fCursor.x + num > fWidth) + num = fWidth - fCursor.x; + + if (num > 0) { + _PadLineToCursor(); + _InsertGap(num); + + Line* line = _LineAt(fCursor.y); + for (int32 i = fCursor.x; i < fCursor.x + num; i++) { + line->cells[i].character = kSpaceChar; + line->cells[i].attributes = 0; + } + } +} + + +void +TerminalBuffer::EraseBelow() +{ + _Scroll(fCursor.y, fHeight - 1, fHeight); +} + + +void +TerminalBuffer::DeleteChar(int numChars) +{ + Line* 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(Cell)); + line->length = fCursor.x + left; + } else { + // remove all remaining chars + line->length = fCursor.x; + } + + _Invalidate(fCursor.y, fCursor.y); + } +} + + +void +TerminalBuffer::DeleteColumns() +{ + Line* line = _LineAt(fCursor.y); + if (fCursor.x < line->length) { + line->length = fCursor.x; + _Invalidate(fCursor.y, fCursor.y); + } +} + + +void +TerminalBuffer::DeleteLine(int numLines) +{ + if (fCursor.y >= fScrollTop && fCursor.y <= fScrollBottom) + _Scroll(fCursor.y, fScrollBottom, numLines); +} + + +void +TerminalBuffer::SetCurPos(int x, int y) +{ +//debug_printf("TerminalBuffer::SetCurPos(%d, %d)\n", x, y); + x = restrict_value(x, 0, fWidth - 1); + y = restrict_value(y, fScrollTop, fScrollBottom); + if (x != fCursor.x || y != fCursor.y) { + fCursor.x = x; + fCursor.y = y; + _CursorChanged(); + } +} + + +void +TerminalBuffer::SetCurX(int x) +{ + SetCurPos(x, fCursor.y); +} + + +void +TerminalBuffer::SetCurY(int y) +{ + SetCurPos(fCursor.x, y); +} + + +int +TerminalBuffer::GetCurX() +{ + return fCursor.x; +} + + +void +TerminalBuffer::SaveCursor() +{ + fSavedCursor = fCursor; +} + + +void +TerminalBuffer::RestoreCursor() +{ + SetCurPos(fSavedCursor.x, fSavedCursor.y); +} + + +void +TerminalBuffer::MoveCurRight(int num) +{ + SetCurPos(fCursor.x + num, fCursor.y); +} + + +void +TerminalBuffer::MoveCurLeft(int num) +{ + SetCurPos(fCursor.x - num, fCursor.y); +} + + +void +TerminalBuffer::MoveCurUp(int num) +{ + SetCurPos(fCursor.x, fCursor.y - num); +} + + +void +TerminalBuffer::MoveCurDown(int num) +{ + SetCurPos(fCursor.x, fCursor.y + num); +} + + +void +TerminalBuffer::ScrollRegion(int top, int bot, int dir, int numLines) +{ +// TODO: Is only invoked with SCRDOWN and numLines = 1 + _Scroll(fScrollTop, fScrollBottom, -1); +} + + +void +TerminalBuffer::SetScrollRegion(int top, int bottom) +{ + fScrollTop = restrict_value(top, 0, fHeight - 1); + fScrollBottom = restrict_value(bottom, fScrollTop, fHeight - 1); + + // also sets the cursor position + SetCurPos(0, 0); +} + + +void +TerminalBuffer::SetTitle(const char* title) +{ + BMessage message(MSG_SET_TERMNAL_TITLE); + message.AddString("title", title); + fListener.SendMessage(&message); +} + + +void +TerminalBuffer::NotifyQuit(int32 reason) +{ + BMessage message(MSG_QUIT_TERMNAL); + message.AddInt32("reason", reason); + fListener.SendMessage(&message); +} + + +// #pragma mark - private methods + + +/* static */ TerminalBuffer::Line** +TerminalBuffer::_AllocateLines(int32 width, int32 count) +{ + Line** lines = (Line**)malloc(sizeof(Line*) * count); + if (lines == NULL) + return NULL; + + for (int32 i = 0; i < count; i++) { + lines[i] = (Line*)malloc(sizeof(Line) + sizeof(Cell) * (width - 1)); + if (lines[i] == NULL) { + _FreeLines(lines, i); + return NULL; + } + } + + return lines; +} + + +/* static */ void +TerminalBuffer::_FreeLines(Line** lines, int32 count) +{ + if (lines != NULL) { + for (int32 i = 0; i < count; i++) + free(lines[i]); + + free(lines); + } +} + + +void +TerminalBuffer::_ClearLines(int32 first, int32 last) +{ + int32 firstCleared = -1; + int32 lastCleared = -1; + + for (int32 i = first; i <= last; i++) { + Line* line = _LineAt(i); + if (line->length > 0) { + if (firstCleared == -1) + firstCleared = i; + lastCleared = i; + + line->Clear(); + } + } + + if (firstCleared >= 0) + _Invalidate(firstCleared, lastCleared); +} + + +void +TerminalBuffer::_Scroll(int32 top, int32 bottom, int32 numLines) +{ + if (numLines == 0) + return; + + if (numLines > 0) { + if (numLines > fHistoryCapacity - fHeight) { + numLines = fHistoryCapacity - fHeight; + // TODO: This is not quite correct -- we should replace the complete + // history and screen with empty lines. + } + + // scroll text up + if (top == 0) { + // The lines scrolled out of the screen range are transferred to + // the history. + + // make room for numLines new lines + if (fHistorySize + fHeight + numLines > fHistoryCapacity) { + int32 toDrop = fHistorySize + fHeight + numLines + - fHistoryCapacity; + fHistorySize -= toDrop; + } + + // clear numLines after the current screen + for (int32 i = fHeight; i < fHeight + numLines; i++) + _LineAt(i)->Clear(); + + if (bottom < fHeight - 1) { + // Only scroll part of the screen. Move the unscrolled lines to + // their new location. + for (int32 i = bottom + 1; i < fHeight; i++) { + std::swap(fHistory[_LineIndex(i)], + fHistory[_LineIndex(i) + numLines]); + } + } + + // all lines are in place -- offset the screen + fScreenOffset = (fScreenOffset + numLines) % fHistoryCapacity; + fHistorySize += numLines; + + // scroll/extend dirty range + if (fDirtyInfo.dirtyTop != INT_MAX) { + if (fDirtyInfo.dirtyTop <= bottom + && top <= fDirtyInfo.dirtyBottom) { + // intersects with the scroll region -- extend + if (top < fDirtyInfo.dirtyTop) + fDirtyInfo.dirtyTop = top - numLines; + else + fDirtyInfo.dirtyTop -= numLines; + if (bottom > fDirtyInfo.dirtyBottom) + fDirtyInfo.dirtyBottom = bottom; + } else if (fDirtyInfo.dirtyBottom < top) { + // does not intersect with the scroll region -- just offset + fDirtyInfo.dirtyTop -= numLines; + fDirtyInfo.dirtyBottom -= numLines; + } + } + + fDirtyInfo.linesScrolled += numLines; +// TODO: The linesScrolled might be suboptimal when scrolling partially +// only, since we would scroll the whole visible area, including unscrolled +// lines, which invalidates them, too. + + // invalidate new empty lines + _Invalidate(bottom + 1 - numLines, bottom); + + } else if (numLines >= bottom - top + 1) { + // all lines are completely scrolled out of range -- just clear + // them + _ClearLines(top, bottom); + } else { + // partial scroll -- clear the lines scrolled out of range and move + // the other ones + for (int32 i = top + numLines; i <= bottom; i++) { + int32 lineToDrop = _LineIndex(i - numLines); + int32 lineToKeep = _LineIndex(i); + fHistory[lineToDrop]->Clear(); + std::swap(fHistory[lineToDrop], fHistory[lineToKeep]); + } + + _Invalidate(top, bottom); + } + } else { + // scroll text down + numLines = -numLines; + + if (numLines >= bottom - top + 1) { + // all lines are completely scrolled out of range -- just clear + // them + _ClearLines(top, bottom); + } else { + // partial scroll -- clear the lines scrolled out of range and move + // the other ones + for (int32 i = bottom - numLines; i >= top; i--) { + int32 lineToKeep = _LineIndex(i); + int32 lineToDrop = _LineIndex(i + numLines); + fHistory[lineToDrop]->Clear(); + std::swap(fHistory[lineToDrop], fHistory[lineToKeep]); + } + + _Invalidate(top, bottom); + } + } +} + + +void +TerminalBuffer::_SoftBreakLine() +{ + Line* line = _LineAt(fCursor.y); + line->length = fCursor.x; + line->softBreak = true; + + fCursor.x = 0; + if (fCursor.y == fScrollBottom) + _Scroll(fScrollTop, fScrollBottom, 1); + else + fCursor.y++; +} + + +void +TerminalBuffer::_PadLineToCursor() +{ + Line* line = _LineAt(fCursor.y); + if (line->length < fCursor.x) { + for (int32 i = line->length; i < fCursor.x; i++) { + line->cells[i].character = kSpaceChar; + line->cells[i].attributes = 0; + // TODO: Other attributes? + } + } +} + + +void +TerminalBuffer::_InsertGap(int32 width) +{ +// ASSERT(fCursor.x + width <= fWidth) + Line* 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(Cell)); + } + + line->length += width; +} + + +/*! \a endColumn is not inclusive. +*/ +bool +TerminalBuffer::_GetPartialLineString(BString& string, int32 row, + int32 startColumn, int32 endColumn) const +{ + Line* line = _HistoryLineAt(row); + if (line == NULL) + return false; + + if (endColumn > line->length) + endColumn = line->length; + + for (int32 x = startColumn; x < endColumn; x++) { + const Cell& cell = line->cells[x]; + string.Append(cell.character.bytes, cell.character.ByteCount()); + + if (IS_WIDTH(cell.attributes)) + x++; + } + + return true; +} + + +/*! Decrement \a pos and return the char at that location. +*/ +bool +TerminalBuffer::_PreviousChar(TermPos& pos, UTF8Char& c) const +{ + pos.x--; + + Line* line = _HistoryLineAt(pos.y); + + while (true) { + if (pos.x < 0) { + pos.y--; + line = _HistoryLineAt(pos.y); + 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; + } + } +} + + +/*! Return the char at \a pos and increment it. +*/ +bool +TerminalBuffer::_NextChar(TermPos& pos, UTF8Char& c) const +{ + Line* line = _HistoryLineAt(pos.y); + 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); + } + + return true; } diff --git a/src/apps/terminal/TerminalBuffer.h b/src/apps/terminal/TerminalBuffer.h index 3cdcc88c85..4cfaa61b2e 100644 --- a/src/apps/terminal/TerminalBuffer.h +++ b/src/apps/terminal/TerminalBuffer.h @@ -5,59 +5,186 @@ #ifndef TERMINAL_BUFFER_H #define TERMINAL_BUFFER_H -#include +#include + +#include +#include + +#include "TermPos.h" +#include "UTF8Char.h" -class TerminalBuffer { +class BString; +class TerminalCharClassifier; + + +struct TerminalBufferDirtyInfo { + int32 linesScrolled; // number of lines added to the history + int32 dirtyTop; // dirty line range + int32 dirtyBottom; // + bool messageSent; // listener has been notified + + bool IsDirtyRegionValid() const + { + return dirtyTop <= dirtyBottom; + } + + void Reset() + { + linesScrolled = 0; + dirtyTop = INT_MAX; + dirtyBottom = INT_MIN; + messageSent = false; + } +}; + + +class TerminalBuffer : public BLocker { public: TerminalBuffer(); - virtual ~TerminalBuffer(); + ~TerminalBuffer(); - virtual int Encoding() const = 0; + status_t Init(int32 width, int32 height, + int32 historySize); - // Output Character - virtual void Insert(uchar* string, ushort attr) = 0; - virtual void InsertCR() = 0; - virtual void InsertLF() = 0; - virtual void InsertNewLine(int num) = 0; - virtual void SetInsertMode(int flag) = 0; - virtual void InsertSpace(int num) = 0; + void SetListener(BMessenger listener); + + int Encoding() const; + + int32 Width() const { return fWidth; } + int32 Height() const { return fHeight; } + TermPos Cursor() const { return fCursor; } + int32 HistorySize() const { return fHistorySize; } + + TerminalBufferDirtyInfo& DirtyInfo() { return fDirtyInfo; } + + status_t ResizeTo(int32 width, int32 height); + void Clear(); + + bool IsFullWidthChar(int32 row, int32 column) const; + int GetChar(int32 row, int32 column, + UTF8Char& character, + uint16& attributes) const; + int32 GetString(int32 row, int32 firstColumn, + int32 lastColumn, char* buffer, + uint16& attributes) const; + void GetStringFromRegion(BString& string, + const TermPos& start, + const TermPos& end) const; + bool FindWord(const TermPos& pos, + TerminalCharClassifier* classifier, + bool findNonWords, TermPos& start, + TermPos& end) const; + int32 LineLength(int32 index) const; + + bool Find(const char* pattern, const TermPos& start, + bool forward, bool caseSensitive, + bool matchWord, TermPos& matchStart, + TermPos& matchEnd) const; + + // output character + void InsertChar(UTF8Char c, uint32 attributes); + void Insert(uchar* string, ushort attr); + void InsertCR(); + void InsertLF(); + void InsertNewLine(int numLines); + void SetInsertMode(int flag); + void InsertSpace(int num); - // Delete Character - virtual void EraseBelow() = 0; - virtual void DeleteChar(int num) = 0; - virtual void DeleteColumns() = 0; - virtual void DeleteLine(int num) = 0; + // delete character + void EraseBelow(); + void DeleteChar(int num); + void DeleteColumns(); + void DeleteLine(int num); - // Get and Set Cursor position - virtual void SetCurPos(int x, int y) = 0; - virtual void SetCurX(int x) = 0; - virtual void SetCurY(int y) = 0; - virtual int GetCurX() = 0; - virtual void SaveCursor() = 0; - virtual void RestoreCursor() = 0; + // get and set cursor position +// TODO: Inline most of these! + void SetCurPos(int x, int y); + void SetCurX(int x); + void SetCurY(int y); + int GetCurX(); + void SaveCursor(); + void RestoreCursor(); - // Move Cursor - virtual void MoveCurRight(int num) = 0; - virtual void MoveCurLeft(int num) = 0; - virtual void MoveCurUp(int num) = 0; - virtual void MoveCurDown(int num) = 0; + // move cursor + void MoveCurRight(int num); + void MoveCurLeft(int num); + void MoveCurUp(int num); + void MoveCurDown(int num); - // Cursor setting - virtual void SetCurDraw(bool flag) = 0; + // scroll region + void ScrollRegion(int top, int bot, int dir, + int num); + void SetScrollRegion(int top, int bot); - // Scroll region - virtual void ScrollRegion(int top, int bot, int dir, - int num) = 0; - virtual void SetScrollRegion(int top, int bot) = 0; - virtual void ScrollAtCursor() = 0; + // other + void SetTitle(const char* title); + void NotifyQuit(int32 reason); - // Other - virtual void DeviceStatusReport(int) = 0; - virtual void UpdateLine() = 0; +private: + struct Cell { + UTF8Char character; + uint16 attributes; + }; - virtual void SetTitle(const char* title) = 0; - virtual void NotifyQuit(int32 reason) = 0; + struct Line { + int16 length; + bool softBreak; // soft line break + Cell cells[1]; + + inline void Clear() + { + length = 0; + softBreak = false; + } + }; + + inline int32 _LineIndex(int32 index) const; + inline Line* _LineAt(int32 index) const; + inline Line* _HistoryLineAt(int32 index) const; + + inline void _Invalidate(int32 top, int32 bottom); + inline void _CursorChanged(); + + static Line** _AllocateLines(int32 width, int32 count); + static void _FreeLines(Line** lines, int32 count); + void _ClearLines(int32 first, int32 last); + + void _Scroll(int32 top, int32 bottom, + int32 numLines); + void _SoftBreakLine(); + void _PadLineToCursor(); + void _InsertGap(int32 width); + bool _GetPartialLineString(BString& string, + int32 row, int32 startColumn, + int32 endColumn) const; + bool _PreviousChar(TermPos& pos, UTF8Char& c) const; + bool _NextChar(TermPos& pos, UTF8Char& c) const; + +private: + // screen width/height + int32 fWidth; + int32 fHeight; + + // scroll region top/bottom + int32 fScrollTop; // first line to scroll + int32 fScrollBottom; // last line to scroll (incl.) + + // line buffers for the history (ring buffer) + Line** fHistory; + int32 fHistoryCapacity; + int32 fScreenOffset; // index of screen line 0 + int32 fHistorySize; + + // cursor position (origin: (0, 0)) + TermPos fCursor; + TermPos fSavedCursor; + + bool fOverwriteMode; // false for insert + + // listener/dirty region management + BMessenger fListener; + TerminalBufferDirtyInfo fDirtyInfo; }; #endif // TERMINAL_BUFFER_H diff --git a/src/apps/terminal/TerminalCharClassifier.cpp b/src/apps/terminal/TerminalCharClassifier.cpp new file mode 100644 index 0000000000..acdd2f59fb --- /dev/null +++ b/src/apps/terminal/TerminalCharClassifier.cpp @@ -0,0 +1,11 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + +#include "TerminalCharClassifier.h" + + +TerminalCharClassifier::~TerminalCharClassifier() +{ +} diff --git a/src/apps/terminal/TerminalCharClassifier.h b/src/apps/terminal/TerminalCharClassifier.h new file mode 100644 index 0000000000..2a8ebc017d --- /dev/null +++ b/src/apps/terminal/TerminalCharClassifier.h @@ -0,0 +1,24 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef TERMINAL_CHAR_CLASSIFIER_H +#define TERMINAL_CHAR_CLASSIFIER_H + + +enum { + CHAR_TYPE_SPACE, + CHAR_TYPE_WORD_CHAR, + CHAR_TYPE_WORD_DELIMITER +}; + + +class TerminalCharClassifier { +public: + virtual ~TerminalCharClassifier(); + + virtual int Classify(const char* character) = 0; +}; + + +#endif // TERMINAL_CHAR_CLASSIFIER_H diff --git a/src/apps/terminal/UTF8Char.h b/src/apps/terminal/UTF8Char.h new file mode 100644 index 0000000000..bf494a59c5 --- /dev/null +++ b/src/apps/terminal/UTF8Char.h @@ -0,0 +1,77 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ +#ifndef UTF8_CHAR_H +#define UTF8_CHAR_H + +#include +#include + + +struct UTF8Char { + char bytes[4]; + + UTF8Char() + { + } + + UTF8Char(char c) + { + bytes[0] = c; + } + + static int32 ByteCount(char firstChar) + { + // Note, this does not recognize invalid chars + uint32 c = firstChar; + if (c < 0x80) + return 1; + if (c < 0xe0) + return 2; + return c < 0xf0 ? 3 : 4; + } + + int32 ByteCount() const + { + return ByteCount(bytes[0]); + } + + bool IsSpace() const + { + // TODO: Support multi-byte chars! + return ByteCount() == 1 ? isspace(bytes[0]) : false; + } + + UTF8Char ToLower() const + { + // TODO: Support multi-byte chars! + if (ByteCount() > 1) + return *this; + + return UTF8Char((char)tolower(bytes[0])); + } + + bool operator==(const UTF8Char& other) const + { + int32 byteCount = ByteCount(); + bool equals = bytes[0] == other.bytes[0]; + if (byteCount > 1 && equals) { + equals = bytes[1] == other.bytes[1]; + if (byteCount > 2 && equals) { + equals = bytes[2] == other.bytes[2]; + if (byteCount > 3 && equals) + equals = bytes[3] == other.bytes[3]; + } + } + return equals; + } + + bool operator!=(const UTF8Char& other) const + { + return !(*this == other); + } +}; + + +#endif // UTF8_CHAR_H