From 52b1d543e80f5d48e6ef841fff4780c7c87bae11 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Mon, 9 Jun 2008 17:04:26 +0000 Subject: [PATCH] Terminal changes. This is still work in progress, some features are disabled, lots of commented debug code is still in there, and quite a bit of cleanup is needed, but basically things work at least as well as before with several improvements: * Changed TerminalBuffer from an interface to a complete implementation. Removed all related code from TermView. Removed the now obsolete TermBuffer. TermParse uses TerminalBuffer instead of TermView, and TerminalBuffer asynchronously notifies TermView. This avoids potential deadlocks, fixing #1918. It also speeds up tty-output-bound programs. E.g. a "seq 10000" is about twice at fast with the default terminal size in my setup, now. It's still horribly slow compared to e.g. Konsole, though. * Replaced CurPos by a more compact and fully inline class TermPos. * Removed the offset feature (that insets the used text area) from TermView, thus simplifying the code. Instead put the view into a new parent view which provides the insets. This also fixes artifacts that could sometimes be observed in the insets area. * Scrolling related changes: - When scrolling fully down, the (80x25 or whatever) terminal screen is seen. It is not possible to scroll below the screen as in Be's Terminal. Scrolling in Haiku's Terminal was weirdly broken in this respect. As a side effect this fixes #2070. - When not scrolled fully down, further output won't cause any scrolling. It is thus possible to read earlier output while something is still going on. Fixes #1772. - Particularly to avoid unnecessary scrolling in the not scrolled fully down case, TermView no longer actually scrolls. It only sets an internal offset and manually uses CopyBits() as needed. Introduced a (hacky) BScrollView subclass using a BScrollBar subclass to make that possible. * Selection related changes: - Double/triple click plus dragging allows for selecting multiple words/lines. - Word selection no longer selects ranges of non-space characters. Instead it knows that words are made of alpha numerical chars and a certain set of other chars, and selects a range of commonly classified characters (word chars, non-word non-whitespace chars, whitespace chars). The non-alpha-num word characters should be made user-settable. Due to missing multi-byte character classification multi-byte whitespace is not recognized. - Beyond the end of the line there no longer are invisible spaces. Trying to select the region selects the end of the line (i.e. line break). This is similar to how Konsole and xterm work. - Added auto-scrolling when selecting with the mouse. Formerly the Terminal scrolled only while moving the mouse. The scroll speed might need some fine-tuning. - Don't know what change exactly did that (likely the switch to non-end-inclusive text ranges used internally), but the occasional selection artifacts are gone. * Resizing the terminal window re-wraps soft-wrapped lines. * The find functionality seemed to be completely broken. At least it never found anything for me. Should work now, though multi-byte characters are not matched correctly in case-insensitive mode. Regressions: * Printing is disabled. * Cursor blinking is disabled. Do we want it anyway? * In several cases full-width characters are not handled correctly (in more cases than before). * Shrinking the terminal width doesn't work very well with "less" (and probably other full-screen terminal apps), due to line re-wrapping. "less" expects them to be truncated only. When supporting an alternate screen buffer re-wrapping should be disabled for it, which should solve the problem. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@25881 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/apps/terminal/CodeConv.h | 1 + src/apps/terminal/CurPos.cpp | 113 - src/apps/terminal/CurPos.h | 38 - src/apps/terminal/Jamfile | 4 +- src/apps/terminal/Shell.cpp | 2 +- src/apps/terminal/TermBuffer.cpp | 550 ----- src/apps/terminal/TermBuffer.h | 124 -- src/apps/terminal/TermConst.h | 6 +- src/apps/terminal/TermParse.cpp | 40 +- src/apps/terminal/TermParse.h | 4 +- src/apps/terminal/TermPos.h | 65 + src/apps/terminal/TermScrollView.cpp | 52 + src/apps/terminal/TermScrollView.h | 20 + src/apps/terminal/TermView.cpp | 1932 ++++++++---------- src/apps/terminal/TermView.h | 155 +- src/apps/terminal/TermWindow.cpp | 145 +- src/apps/terminal/TermWindow.h | 10 +- src/apps/terminal/TerminalBuffer.cpp | 1180 +++++++++++ src/apps/terminal/TerminalBuffer.h | 207 +- src/apps/terminal/TerminalCharClassifier.cpp | 11 + src/apps/terminal/TerminalCharClassifier.h | 24 + src/apps/terminal/UTF8Char.h | 77 + 22 files changed, 2640 insertions(+), 2120 deletions(-) delete mode 100644 src/apps/terminal/CurPos.cpp delete mode 100644 src/apps/terminal/CurPos.h delete mode 100644 src/apps/terminal/TermBuffer.cpp delete mode 100644 src/apps/terminal/TermBuffer.h create mode 100644 src/apps/terminal/TermPos.h create mode 100644 src/apps/terminal/TermScrollView.cpp create mode 100644 src/apps/terminal/TermScrollView.h create mode 100644 src/apps/terminal/TerminalCharClassifier.cpp create mode 100644 src/apps/terminal/TerminalCharClassifier.h create mode 100644 src/apps/terminal/UTF8Char.h 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