diff --git a/2Term.xcodeproj/project.pbxproj b/2Term.xcodeproj/project.pbxproj index 30090ac..bc07f8a 100644 --- a/2Term.xcodeproj/project.pbxproj +++ b/2Term.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ B675F4A81E540394004B0D9C /* Screen.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B612F44D12DD5DAD005D1B77 /* Screen.cpp */; }; B675F4A91E561D20004B0D9C /* Apple80.mm.ragel in Sources */ = {isa = PBXBuildFile; fileRef = B612F45712DD5DF1005D1B77 /* Apple80.mm.ragel */; }; B675F4AA1E562159004B0D9C /* GNOConsole.mm.ragel in Sources */ = {isa = PBXBuildFile; fileRef = B612F45B12DD5DF1005D1B77 /* GNOConsole.mm.ragel */; }; + B675F4AC1E56A7F2004B0D9C /* GSOSConsole.mm.ragel in Sources */ = {isa = PBXBuildFile; fileRef = B6C173911D31D2B80024E360 /* GSOSConsole.mm.ragel */; }; B676063B11DEAD3500D6B66C /* TermWindowController.mm in Sources */ = {isa = PBXBuildFile; fileRef = B676063A11DEAD3500D6B66C /* TermWindowController.mm */; }; B676065111DEBAE900D6B66C /* TermWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = B676065011DEBAE900D6B66C /* TermWindow.xib */; }; B67B3CE512B6FA040033AE07 /* a2-charset-40.png in Resources */ = {isa = PBXBuildFile; fileRef = B67B3CE312B6FA040033AE07 /* a2-charset-40.png */; }; @@ -116,7 +117,7 @@ B612F45812DD5DF1005D1B77 /* Emulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Emulator.h; sourceTree = ""; }; B612F45912DD5DF1005D1B77 /* EmulatorManager.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = EmulatorManager.mm; sourceTree = ""; }; B612F45A12DD5DF1005D1B77 /* GNOConsole.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GNOConsole.h; sourceTree = ""; }; - B612F45B12DD5DF1005D1B77 /* GNOConsole.mm.ragel */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = GNOConsole.mm.ragel; sourceTree = ""; }; + B612F45B12DD5DF1005D1B77 /* GNOConsole.mm.ragel */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = GNOConsole.mm.ragel; sourceTree = ""; }; B612F45C12DD5DF1005D1B77 /* PTSE.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PTSE.h; sourceTree = ""; }; B612F45D12DD5DF1005D1B77 /* PTSE.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PTSE.mm; sourceTree = ""; }; B612F45E12DD5DF1005D1B77 /* VT05.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VT05.h; sourceTree = ""; }; @@ -438,6 +439,7 @@ files = ( B675F4A91E561D20004B0D9C /* Apple80.mm.ragel in Sources */, B675F4AA1E562159004B0D9C /* GNOConsole.mm.ragel in Sources */, + B675F4AC1E56A7F2004B0D9C /* GSOSConsole.mm.ragel in Sources */, 8D11072D0486CEB800E47090 /* main.m in Sources */, 256AC3DA0F4B6AC300CF3369 /* TwoTermAppDelegate.mm in Sources */, B676063B11DEAD3500D6B66C /* TermWindowController.mm in Sources */, diff --git a/Emulators/GNOConsole.mm.ragel b/Emulators/GNOConsole.mm.ragel index 4d74ca0..ecc2a09 100644 --- a/Emulators/GNOConsole.mm.ragel +++ b/Emulators/GNOConsole.mm.ragel @@ -116,7 +116,6 @@ */ - %%{ machine console; alphtype unsigned int; @@ -164,8 +163,8 @@ _scratch[2] = std::max(0, _scratch[2]); - _scratch[1] = std::min(80, _scratch[1]+1); - _scratch[3] = std::min(24, _scratch[3]+1); + _scratch[1] = std::min(80-1, _scratch[1])+1; + _scratch[3] = std::min(24-1, _scratch[3])+1; if (_scratch[1] <= _scratch[0]) _scratch[1] = 80; @@ -385,6 +384,7 @@ write data; }%% + @implementation GNOConsole diff --git a/Emulators/GSOSConsole.h b/Emulators/GSOSConsole.h index 586c507..b398c29 100644 --- a/Emulators/GSOSConsole.h +++ b/Emulators/GSOSConsole.h @@ -12,19 +12,26 @@ #include "iGeometry.h" #include "Screen.h" +struct gsos_context : public context { + bool consWrap = true; + bool consAdvance = true; + bool consLF = true; + bool consScroll = true; + bool consVideo = true; + bool consDLE = true; + bool consMouse = false; + uint8_t consFill = 0xa0; +}; + @interface GSOSConsole : NSObject { + + + gsos_context _context; + std::vector _context_stack; + unsigned cs; - - TextPort _textPort; - iRect _window; - - std::vector _tpStack; - int _scratch[4]; - int _cursorType; - bool _consLF; - bool _consDLE; } @end diff --git a/Emulators/GSOSConsole.mm.ragel b/Emulators/GSOSConsole.mm.ragel index aca9c81..bae4be0 100644 --- a/Emulators/GSOSConsole.mm.ragel +++ b/Emulators/GSOSConsole.mm.ragel @@ -13,6 +13,22 @@ #include "algorithm.h" +// currently no way to set consFill :-) +static char_info remap_fill(const gsos_context &ctx) { return char_info(' ', 0); } + +static void advance(Screen *screen, gsos_context &ctx) { + + if (ctx.consAdvance) { + if (ctx.cursor.x < ctx.window.maxX() - 1) ctx.cursor.x++; + else if (ctx.consWrap) { + ctx.cursor.x = ctx.window.minX(); + if (ctx.cursor.y < ctx.window.maxY() - 1) ctx.cursor.y++; + else if (ctx.consScroll) screen->scrollUp(ctx.window); + } + } + +} + %%{ machine console; alphtype unsigned int; @@ -24,21 +40,32 @@ arg3 = any ${ _scratch[2] = (fc - 32); }; arg4 = any ${ _scratch[3] = (fc - 32); }; + + action advance_if { + if (_context.consAdvance) advance(screen, _context); + } + main := ( 0x00 $nop | 0x01 ${ - /* save current textport and reset text port to default. */ + /* + Save Current Text Port and Reset Default Text Port + + Saves the current text port and resets to the default text port. If the system is out of memory, no error is returned, and the text port is simply reset. + */ - _tpStack.push_back(_textPort); - // todo -- consLF, consDLE? - _textPort = TextPort(); - + _context_stack.push_back(_context); + _context = gsos_context(); + _context.cursor = iPoint(0,0); + _context.window = iRect(0, 0, 80, 24); } | 0x02 arg1 arg2 arg3 arg4 ${ - /* set the current text port */ + /* + Set Text Port Size - // left, top, right, bottom + Accepts the next four bytes as absolute screen coordinates + 32. Sets the current text port to the new parameters. The parameters are in the following order: windLeft, windTop, windRight, windBottom. Any parameter outside the screen boundaries is clipped to a legal value. The cursor is set to the upper-left corner of the new text port. + */ _scratch[0] = clamp(_scratch[0], 0, 80-1); _scratch[1] = clamp(_scratch[1], 0, 24-1); @@ -47,101 +74,193 @@ _scratch[3] = clamp(_scratch[3], 0, 24-1)+1; - iRect r(_scratch[0], + window = iRect(_scratch[0], _scratch[1], _scratch[2] - _scratch[0], _scratch[3] - _scratch[1] ); - - _textPort.frame = r; - - screen->setCursor(&_textPort, 0, 0); + cursor = window.origin; } - - | 0x03 ${ - /* clear from beginning of line */ - // todo -- should also include the cursor. - screen->erase(&_textPort, Screen::EraseLineBeforeCursor); - } - - | 0x04 ${ - /* pop text port */ - if (!_tpStack.empty()) { - _textPort = _tpStack.back(); - _tpStack.pop_back(); + | 0x03 ${ + /* + Clear from Beginning of Line + + Clears all characters from the left edge to and including the cursor. Sets them to the current consFill character. + */ + + char_info ci = remap_fill(_context); + iRect tmp( + iPoint(window.minX(), cursor.y), + iPoint(cursor.x+1, cursor.y+1) + ); + screen->fillRect(tmp, ci); + + } + + | 0x04 ${ + /* + Pop Text Port + + Restores the text port to the most recently saved value (see code $01). If no saved ports exist, resets the text port to the default values. If an 80-column text port is pushed and subsequently restored in 40-column mode, the text port may be altered to fit in the 40-column screen (see code $11, Set 40-Column Mode). + */ + + if (!_context_stack.empty()) { + _context = _context_stack.back(); + _context_stack.pop_back(); } else { - _textPort = TextPort(); + _context = gsos_context(); } } | 0x05 any ${ - /* horizontal scroll */ + /* + Horizontal Scroll + + Interprets the next byte as an 8-bit signed integer depicting the number (N) of + columns to shift. N equal to zero is a null operation. If N is less than zero, the text + port is shifted to the left; A greater than zero shifts to the right. If the shift magnitude is equal to or greater than windWidth, the text port is cleared. + The shifted characters are moved directly to their destination location. The space vacated by the shifted characters is set to the current consFill character (see the description of consFill earlier in this chapter). Characters shifted out of the text port are removed from the screen and are not recoverable. + */ int8_t n = fc; - // .... + // .... TODO } | 0x06 any ${ - /* set vertical position */ - unsigned n = clamp(fc - 32, 0, 24-1); - screen->setY(&_textPort, n); + /* + Set Vertical Position + + Interprets the next byte as a text port-relative vertical position + 32. If the destination is outside the current text port, the cursor is moved to the nearest edge. + */ + unsigned n = clamp(fc - 32, 0, window.height()-1); + cursor.y = window.minY() + n; } | 0x07 ${ + /* + Ring Bell + + Causes the System Beep to be played. It has no effect on the screen. + */ NSBeep(); } | 0x08 ${ - /* back space */ - screen->decrementX(&_textPort); + /* + Backspace + + Moves the cursor one position to the left. If the cursor was on the left edge of the + text port and consWrap is TRUE, the cursor is placed one row higher and at the right edge. If the cursor was also on the topmost row and consScroll is TRUE, thetext port will scroll backward one line. + */ + if (cursor.x > window.minX()) cursor.x--; + else if (_context.consWrap) { + cursor.x = window.maxX() - 1; + if (cursor.y > window.minY()) cursor.y--; + else if (_context.consScroll) screen->scrollDown(window); + } + } | 0x09 $nop | 0x0a ${ - /* line feed */ - screen->lineFeed(&_textPort); + /* + Line Feed + + Causes the cursor to move down one line. If the cursor was at the bottom edge of the text port and consScroll is TRUE, the text port scrolls up one line. + */ + if (cursor.y < window.maxY()-1) cursor.y++; + else if (_context.consScroll) + screen->scrollUp(window); } | 0x0b ${ - /* clear to end of text port */ - /* actually sets them to consFill */ - screen->erase(&_textPort, Screen::EraseAfterCursor); + /* + Clear to End of Text Port + + Clears all characters from the cursor to the end of the current text port and sets them to be equal to the current consFill character. + */ + + iRect tmp; + tmp.origin = cursor; + tmp.size = iSize(window.size.width - cursor.x, 1); + + screen->eraseRect(tmp); + + tmp = window; + tmp.origin.y = cursor.y+1; + tmp.size.height -= cursor.y+1; + screen->eraseRect(tmp); } - + | 0x0c ${ - /* clear text port and home */ - screen->erase(&_textPort, Screen::EraseAll); - screen->setCursor(&_textPort, 0, 0); + /* + Clear Text Port and Home Cursor + + Clears the entire text port and resets the cursor to windLeft, windTop. + */ + + screen->eraseRect(window); + cursor = window.origin; } | 0x0d ${ - /* carriage return */ - screen->setX(&_textPort, 0); - - if (_consLF) { - screen->lineFeed(&_textPort); + /* + Carriage Return + + Resets the cursor to the left edge of the text port; if consLF is TRUE, performs a line feed (see $0A, Line Feed). + */ + + cursor.x = window.minX(); + if (_context.consLF) { + if (cursor.y < window.maxY() - 1) cursor.y++; + else if (_context.consScroll) screen->scrollUp(window); } - } | 0x0e ${ - /* set normal display */ - screen->clearFlagBit(Screen::FlagInverse); + /* + Set Normal Display Mode + + After this character, displays all subsequent characters in normal mode, + */ + _context.clearFlagBit(Screen::FlagInverse); + _context.consFill = 0xa0; + _context.consVideo = true; } | 0x0f ${ - /* set inverse display */ - screen->setFlagBit(Screen::FlagInverse); + /* + Set Inverse Display Mode + After this character, displays all subsequent characters in inverse mode. + */ + _context.setFlagBit(Screen::FlagInverse); + _context.consFill = 0x20; + _context.consVideo = false; } | 0x10 any ${ - /* DLE expansion */ - if (_consDLE) { + /* + DLE Space Expansion + + If consDLE is TRUE, interprets the next character as number of spaces + 32, and the + correct number of spaces is issued to die screen. If consDLE is FALSE, the DLE character is ignored and the following character is processed normally. + */ + if (_context.consDLE) { unsigned count = (fc - 32) & 0xff; - while (count--) screen->putc(&_textPort, ' '); + char_info ci = remap_fill(_context); + + if (_context.consAdvance) { + while (count--) { + screen->putc(' ', _context); + if (_context.consAdvance) advance(screen, _context); + } + } + else { + if (count) screen->putc(' ', _context); + } } else { fhold; } } @@ -150,110 +269,173 @@ | 0x12 ${ /* 80 column mode */ } | 0x13 ${ - /* clear from beginning of text port */ - screen->erase(&_textPort, Screen::EraseBeforeCursor); - // todo -- clears up to and including cursor location! + } | 0x14 any ${ - /* set horizontal position */ - unsigned n = clamp(fc - 32, 0, 80 - 1); - screen->setX(&_textPort, n); + /* + Set Horizontal Position + + Interprets the next byte as a text port-relative horizontal position + 32. If the destination is outside the current text port, the cursor is moved to the nearest edge. + */ + unsigned n = clamp(fc - 32, 0, window.width() - 1); + cursor.x = window.minX() + n; } | 0x15 any ${ - /* set cursor movement */ + /* + Set Cursor Movement Word + + Interprets the next byte as cursor movement control, and sets the values of these Boolean flags: + */ + unsigned flags = fc; - _textPort.advanceCursor = flags & 0x01; - _consLF = flags & 0x02; - if (flags & 0x04) { - _textPort.leftMargin = TextPort::MarginWrap; - _textPort.rightMargin = TextPort::MarginWrap; - } else { - _textPort.leftMargin = TextPort::MarginTruncate; - _textPort.rightMargin = TextPort::MarginTruncate; - } - _textPort.scroll = flags & 0x08; - - _consDLE = flags & 0x10; + _context.consAdvance = flags & 0x01; + _context.consLF = flags & 0x02; + _context.consWrap = flags & 0x04; + _context.consScroll = flags & 0x08; + _context.consDLE = flags & 0x10; } - | 0x16 ${ /* scroll down 1 line */ } + | 0x16 ${ + /* + Scroll Down One Line + + Scrolls the text port down one line. Does not move the cursor. + */ + screen->scrollDown(window); + } - | 0x17 ${ /* scroll up one line */ } + | 0x16 ${ + /* + Scroll Up One Line + + Scrolls the text port up one line. Does not move the cursor. + */ + screen->scrollUp(window); + } | 0x18 ${ - /* disable mouse text */ - screen->clearFlagBit(Screen::FlagMouseText); + /* + Disable MouseText Mapping + + When MouseText is disabled, uppercase inverse characters are displayed as such (see the section "Character Set Mapping" earlier in this chapter). + */ + _context.clearFlagBit(Screen::FlagMouseText); + _context.consMouse = false; } | 0x19 ${ - /* home cursor */ - screen->setCursor(&_textPort, 0, 0); + /* + Home Cursor + + Resets the cursor to the upper-left corner of the text port. + */ + cursor = window.origin; } | 0x1a ${ - /* clear line */ - screen->erase(&_textPort, Screen::EraseLineAll); - screen->setX(&_textPort, 0); + /* + Clear Line + + Clears the line that the cursor is on. Resets the cursor to the leftmost column in the window. + */ + iRect tmp; + tmp.origin = iPoint(window.origin.x, cursor.y); + tmp.size = iSize(window.size.width, 1); + screen->eraseRect(tmp); } | 0x1b ${ - /* enable mouse text mapping */ - screen->setFlagBit(Screen::FlagMouseText); + /* + Enable MouseText Mapping + + When MouseText is enabled, uppercase inverse letters are displayed as MouseText symbols (see the section "Character Set Mapping" earlier in this chapter). + */ + _context.setFlagBit(Screen::FlagMouseText); + _context.consMouse = true; } | 0x1c ${ - /* move cursor right */ - screen->incrementX(&_textPort); + /* + Move Cursor Right + + Performs a nondestructive forward-space of the cursor. If consWrap is TRUE, + the cursor may go to the next line; if consScroll is TRUE, the screen may scroll up one line. + */ + advance(screen, _context); } | 0x1d ${ - /* clear to end of line */ - screen->erase(&_textPort, Screen::EraseLineAfterCursor); + /* + Clear to End of Line + + Clears from the position underneath the cursor to the end of the current line. + */ + iRect tmp; + tmp.origin = cursor; + tmp.size = iSize(window.size.width - cursor.x, 1); + + screen->eraseRect(tmp); + } - | 0x1e arg1 arg2 - ${ /* goto x y */ + | 0x1e arg1 arg2 ${ + /* + Go to X,Y + + Adjusts the cursor position relative to the text port. The parameters passed are X+32 + and Y+32. If the new locations are outside the current text port, the cursor is placed on the nearest edge. + */ + iPoint dca; - dca.x = clamp(_scratch[0], 0, 80 - 1); - dca.y = clamp(_scratch[1], 0, 24 - 1); - screen->setCursor(&_textPort, dca); + + dca.x = clamp(_scratch[0], 0, window.width() - 1); + dca.y = clamp(_scratch[1], 0, window.height() - 1); + cursor.x = dca.x + window.minX(); + cursor.y = dca.y + window.minY(); + cursor = dca; } | 0x1f ${ - /* move cursor up */ - screen->reverseLineFeed(&_textPort); + /* + Move Cursor Up + + Moves the cursor up one line (reverse line feed). If the cursor is already on the + uppermost line of the textport and consScroll isTRUE, it will cause a reverse scroll. + */ + if (cursor.y > window.minY()) cursor.y--; + else if (_context.consScroll) screen->scrollDown(window); } | 0x20 .. 0x7f ${ - screen->putc(&_textPort, fc); - } + screen->putc(fc, _context); + } $advance_if | 0x80 .. 0x9f ${ /* uppercase inverse/normal */ - uint8_t flag = ~(screen->flag() & Screen::FlagInverse); - screen->putc(&_textPort, fc - 0x40, flag); - } + uint8_t flags = ~(_context.flags & Screen::FlagInverse); + screen->putc(fc - 0x40, cursor, flags); + } $advance_if | 0xa0 .. 0xbf ${ /* special inverse/normal */ - uint8_t flag = ~(screen->flag() & Screen::FlagInverse); - screen->putc(&_textPort, fc - 0x80, flag); - } + uint8_t flags = ~(_context.flags & Screen::FlagInverse); + screen->putc(fc - 0x80, cursor, flags); + } $advance_if | 0xc0 .. 0xdf ${ /* uppercase normal / mouse text. */ - uint8_t flag = ~(screen->flag() & Screen::FlagInverse); - if (flag) flag |= Screen::FlagMouseText; - screen->putc(&_textPort, fc - 0x80, flag); - } + uint8_t flags = ~(_context.flags & Screen::FlagInverse); + if (flags) flags |= Screen::FlagMouseText; + screen->putc(fc - 0x80, cursor, flags); + } $advance_if | 0xe0 .. 0xff ${ /* special inverse/normal */ - uint8_t flag = ~(screen->flag() & Screen::FlagInverse); - screen->putc(&_textPort, fc - 0x80, flag); - } + uint8_t flags = ~(_context.flags & Screen::FlagInverse); + screen->putc(fc - 0x80, cursor, flags); + } $advance_if )* $err{ fgoto main; }; @@ -319,21 +501,24 @@ %%write init; - _textPort.frame = iRect(0, 0, 80, 24); - _textPort.cursor = iPoint(0,0); - - _textPort.scroll = true; - _textPort.advanceCursor = true; - _textPort.leftMargin = TextPort::MarginWrap; - _textPort.rightMargin = TextPort::MarginWrap; + _context.window = iRect(0, 0, 80, 24); + _context.cursor = iPoint(0,0); + + _context.consWrap = true; + _context.consAdvance = true; + _context.consLF = true; + _context.consScroll = true; + _context.consVideo = true; + _context.consDLE = true; + _context.consMouse = false; + _context.consFill = 0xa0; + _cursorType = Screen::CursorTypeUnderscore; - _consLF = true; - _consDLE = true; - - // set flags to plain text. + } + -(void)processData:(const uint8_t *)data length: (size_t)length screen:(Screen *)screen output:(OutputChannel *)output { @@ -341,8 +526,10 @@ const uint8_t *p = data; const uint8_t *pe = data + length; + auto &cursor = _context.cursor; + auto &window = _context.window; %%write exec; - + screen->setCursor(cursor); } -(void)keyDown:(NSEvent *)event screen:(Screen *)screen output:(OutputChannel *)output diff --git a/cpp/Screen.cpp b/cpp/Screen.cpp index e10b95c..c1c886e 100644 --- a/cpp/Screen.cpp +++ b/cpp/Screen.cpp @@ -89,24 +89,30 @@ void Screen::putc(uint8_t c, iPoint cursor, uint8_t flags) #pragma mark - #pragma mark Erase -void Screen::eraseScreen() -{ +void Screen::eraseScreen() { fillScreen(char_info()); } +void Screen::eraseRect(iRect rect) { fillRect(rect, char_info()); } + +void Screen::fillScreen(char_info ci) { + for (auto &line : _screen) { - std::fill(line.begin(), line.end(), char_info()); + std::fill(line.begin(), line.end(), ci); } _updates.push_back(iPoint(0,0)); _updates.push_back(iPoint(width() - 1, height() - 1)); } - -void Screen::eraseRect(iRect rect) { + + + + +void Screen::fillRect(iRect rect, char_info ci) { rect = rect.intersection(_frame); if (!rect.valid()) return; - if (rect == _frame) return eraseScreen(); + if (rect == _frame) return fillScreen(ci); auto yIter = _screen.begin() + rect.origin.y; auto yEnd = yIter + rect.size.height; @@ -116,7 +122,7 @@ void Screen::eraseRect(iRect rect) { auto xIter = line.begin() + rect.origin.x; auto xEnd = xIter + rect.size.width; - std::fill(xIter, xEnd, char_info()); + std::fill(xIter, xEnd, ci); } _updates.push_back(rect.origin); _updates.push_back(iPoint(rect.maxX()-1, rect.maxY()-1)); diff --git a/cpp/Screen.h b/cpp/Screen.h index 07c6f13..418e5d2 100644 --- a/cpp/Screen.h +++ b/cpp/Screen.h @@ -101,6 +101,9 @@ public: void eraseScreen(); void eraseRect(iRect rect); + void fillScreen(char_info ci); + void fillRect(iRect rect, char_info ci); + void scrollUp(); void scrollUp(iRect window); @@ -187,6 +190,9 @@ inline int Screen::width() const inline void Screen::setCursor(iPoint point) { + if (point.x >= _frame.width()) point.x = _frame.width() - 1; + if (point.y >= _frame.height()) point.y = _frame.height() - 1; + _cursor = point; }