452 lines
9.2 KiB
C
452 lines
9.2 KiB
C
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "terminal.h"
|
|
|
|
//#define BUF_COLUMNS (100)
|
|
//#define BUF_LINES (75)
|
|
|
|
#define BUF_COLUMNS (80)
|
|
#define BUF_LINES (24)
|
|
|
|
typedef unsigned char flag;
|
|
|
|
struct modes {
|
|
flag keyboard_action : 1; // KAM - Keyboard Action Mode
|
|
flag insert_replace : 1; // IRM - Insertion-Replacement Mode
|
|
flag linefeed_newline : 1; // LNM - Linefeed/Newline Mode
|
|
flag local_echo : 1; // SRM - Send/Receive Mode
|
|
flag cursor_key : 1; // DECCKM - Cursor Key Mode
|
|
flag column : 1; // DECCOLM - Column Mode (80/132)
|
|
flag scroll : 1; // DECSCLM - Smooth Scroll Mode
|
|
flag screen : 1; // DECSCNM - Screen Mode (normal/reverse)
|
|
flag origin : 1; // DECOM - Origin Mode
|
|
flag auto_wrap : 1; // DECAWM - Auto Wrap Mode
|
|
flag auto_repeat : 1; // DECARM - Auto Repeat Mode
|
|
flag cursor_enable : 1; // DECTCEM - Text Cursor Enable Mode
|
|
};
|
|
|
|
struct char_attribs { // SGR
|
|
flag underline : 1;
|
|
flag reverse : 1;
|
|
flag blink : 1;
|
|
flag bold : 1;
|
|
};
|
|
|
|
struct line_attribs { // DECSWL when neither is set
|
|
flag double_width : 1; // DECDWL - Can be set without DHL
|
|
flag double_height_t : 1; // DECDHL Top Half - DHL is always also DWL
|
|
flag double_height_b : 1; // DECDHL Bottom Half
|
|
};
|
|
|
|
struct position {
|
|
int col;
|
|
int line;
|
|
};
|
|
|
|
struct bounds {
|
|
int min;
|
|
int max;
|
|
};
|
|
|
|
struct cell {
|
|
struct char_attribs att;
|
|
unsigned char glyph;
|
|
};
|
|
|
|
struct line {
|
|
struct line_attribs att;
|
|
struct cell cells[BUF_COLUMNS];
|
|
};
|
|
|
|
struct page {
|
|
int col_count;
|
|
int line_count;
|
|
struct bounds scroll;
|
|
struct line lines[BUF_LINES];
|
|
};
|
|
|
|
struct terminal {
|
|
struct page pagebuf;
|
|
struct modes modes;
|
|
unsigned char tabstops[BUF_COLUMNS];
|
|
struct position cursor;
|
|
};
|
|
|
|
static const struct char_attribs blank_char_att = {
|
|
.underline = 0,
|
|
.reverse = 0,
|
|
.blink = 0,
|
|
.bold = 0
|
|
};
|
|
|
|
static const struct line_attribs blank_line_att = {
|
|
.double_width = 0,
|
|
.double_height_t = 0,
|
|
.double_height_b = 0
|
|
};
|
|
|
|
static Terminal singleton_terminal = {
|
|
.pagebuf = { BUF_COLUMNS, BUF_LINES, {0, BUF_LINES-1}, { {}, {} } },
|
|
.modes = {},
|
|
.tabstops = {},
|
|
{0, 0}
|
|
};
|
|
|
|
int bounded_move(int *pos, int amt, struct bounds b)
|
|
{
|
|
int orig_pos = *pos;
|
|
int actual_move = amt;
|
|
|
|
*pos += amt;
|
|
if (amt < 0) {
|
|
if (*pos < b.min) *pos = b.min;
|
|
} else {
|
|
if (*pos > b.max) *pos = b.max;
|
|
}
|
|
actual_move = *pos - orig_pos;
|
|
|
|
return actual_move;
|
|
}
|
|
|
|
void bounded_set(int *pos, int new_pos, struct bounds b)
|
|
{
|
|
if (new_pos < b.min)
|
|
*pos = b.min;
|
|
else if (new_pos > b.max)
|
|
*pos = b.max;
|
|
else
|
|
*pos = new_pos;
|
|
}
|
|
|
|
struct bounds Terminal_cursorHBounds(Terminal *term)
|
|
{
|
|
struct bounds b = { 0, 0 };
|
|
|
|
b.min = 0;
|
|
b.max = term->pagebuf.col_count - 1;
|
|
|
|
return b;
|
|
}
|
|
|
|
struct bounds Terminal_cursorVBounds(Terminal *term, int scroll_region)
|
|
{
|
|
struct bounds b = { 0, 0 };
|
|
|
|
if (scroll_region) {
|
|
b.min = term->pagebuf.scroll.min;
|
|
b.max = term->pagebuf.scroll.max;
|
|
} else {
|
|
b.min = 0;
|
|
b.max = term->pagebuf.line_count - 1;
|
|
}
|
|
|
|
return b;
|
|
}
|
|
|
|
Terminal *Terminal_new(void)
|
|
{
|
|
Terminal_init(&singleton_terminal);
|
|
return &singleton_terminal;
|
|
}
|
|
|
|
void Terminal_defaultModes(Terminal *t)
|
|
{
|
|
t->modes.auto_wrap = 0;
|
|
t->modes.insert_replace = 0;
|
|
t->modes.cursor_enable = 1;
|
|
}
|
|
|
|
void Terminal_clearScreen(Terminal *t)
|
|
{
|
|
int i, j;
|
|
struct cell *c;
|
|
struct line *l;
|
|
|
|
for (i = 0; i < t->pagebuf.line_count; i++) {
|
|
l = &t->pagebuf.lines[i];
|
|
l->att = blank_line_att;
|
|
for (j = 0; j < t->pagebuf.col_count; j++) {
|
|
c = &l->cells[j];
|
|
|
|
c->att = blank_char_att;
|
|
c->glyph = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Terminal_clearTabs(Terminal *t)
|
|
{
|
|
int i;
|
|
for (i = 0; i < t->pagebuf.col_count; i++) {
|
|
t->tabstops[i] = 0;
|
|
}
|
|
}
|
|
|
|
void Terminal_defaultTabs(Terminal *t)
|
|
{
|
|
int i;
|
|
for (i = 8; i < t->pagebuf.col_count; i += 8) {
|
|
t->tabstops[i] = 1;
|
|
}
|
|
}
|
|
|
|
void Terminal_setTab(Terminal *t)
|
|
{
|
|
t->tabstops[t->cursor.col] = 1;
|
|
}
|
|
|
|
void Terminal_clearTab(Terminal *t)
|
|
{
|
|
t->tabstops[t->cursor.col] = 0;
|
|
}
|
|
|
|
void Terminal_defaultScrollRegion(Terminal *t)
|
|
{
|
|
t->pagebuf.scroll.min = 0;
|
|
t->pagebuf.scroll.max = t->pagebuf.line_count - 1;
|
|
}
|
|
|
|
void Terminal_init(Terminal *t)
|
|
{
|
|
t->pagebuf.col_count = BUF_COLUMNS;
|
|
t->pagebuf.line_count = BUF_LINES;
|
|
Terminal_defaultModes(t);
|
|
Terminal_clearScreen(t);
|
|
Terminal_clearTabs(t);
|
|
Terminal_defaultTabs(t);
|
|
Terminal_defaultScrollRegion(t);
|
|
t->cursor.col = 0;
|
|
t->cursor.line = 0;
|
|
}
|
|
|
|
void Terminal_bufferDump(Terminal *t)
|
|
{
|
|
int i, j;
|
|
struct cell *c;
|
|
for (i = 0; i < t->pagebuf.line_count; i++) {
|
|
for (j = 0; j < t->pagebuf.col_count; j++) {
|
|
c = &t->pagebuf.lines[i].cells[j];
|
|
if (c->glyph) {
|
|
putchar(c->glyph);
|
|
} else {
|
|
putchar(' ');
|
|
}
|
|
}
|
|
putchar('\n');
|
|
}
|
|
}
|
|
|
|
void Terminal_setAutoWrap(Terminal *t, int mode)
|
|
{
|
|
t->modes.auto_wrap = mode;
|
|
}
|
|
|
|
void Terminal_scroll(Terminal *t, int lines)
|
|
{
|
|
struct line *scroll_top;
|
|
struct line *scroll_keep;
|
|
struct line *scroll_clear;
|
|
int direction, total_lines, keep_lines, clear_lines;
|
|
struct bounds b;
|
|
size_t scroll_size, clear_size;
|
|
|
|
if (lines < 0) {
|
|
lines = -lines;
|
|
direction = -1;
|
|
} else {
|
|
direction = 1;
|
|
}
|
|
|
|
b = Terminal_cursorVBounds(t, 1);
|
|
total_lines = b.max + 1 - b.min;
|
|
keep_lines = total_lines - lines;
|
|
if (keep_lines < 0) {
|
|
keep_lines = 0;
|
|
}
|
|
clear_lines = total_lines - keep_lines;
|
|
scroll_size = keep_lines * (sizeof (struct line));
|
|
clear_size = clear_lines * (sizeof (struct line));
|
|
|
|
if (direction > 0) {
|
|
scroll_top = &t->pagebuf.lines[b.min];
|
|
scroll_clear = &t->pagebuf.lines[b.min + keep_lines];
|
|
|
|
if (keep_lines) {
|
|
scroll_keep = &t->pagebuf.lines[b.min + lines];
|
|
memmove(scroll_top, scroll_keep, scroll_size);
|
|
}
|
|
} else {
|
|
scroll_top = &t->pagebuf.lines[b.min + clear_lines];
|
|
scroll_clear = &t->pagebuf.lines[b.min];
|
|
|
|
if (keep_lines) {
|
|
scroll_keep = &t->pagebuf.lines[b.min];
|
|
memmove(scroll_top, scroll_keep, scroll_size);
|
|
}
|
|
}
|
|
memset(scroll_clear, 0, clear_size);
|
|
}
|
|
|
|
void Terminal_cursorRel(Terminal *t, int direction, int count, int scroll)
|
|
{
|
|
int *axis;
|
|
struct bounds b;
|
|
int move, result;
|
|
int obey_margins = 1;
|
|
|
|
switch (direction) {
|
|
case DIR_UP:
|
|
axis = &t->cursor.line;
|
|
move = -count;
|
|
b = Terminal_cursorVBounds(t, obey_margins);
|
|
break;
|
|
case DIR_DOWN:
|
|
axis = &t->cursor.line;
|
|
move = count;
|
|
b = Terminal_cursorVBounds(t, obey_margins);
|
|
break;
|
|
case DIR_FORWARD:
|
|
axis = &t->cursor.col;
|
|
move = count;
|
|
b = Terminal_cursorHBounds(t);
|
|
break;
|
|
case DIR_BACK:
|
|
axis = &t->cursor.col;
|
|
move = -count;
|
|
b = Terminal_cursorHBounds(t);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
result = bounded_move(axis, move, b);
|
|
|
|
if (result != move && scroll) {
|
|
if (direction == DIR_UP || direction == DIR_DOWN) {
|
|
Terminal_scroll(t, move - result);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Terminal_cursorAbsH(Terminal *t, int column)
|
|
{
|
|
bounded_set(&t->cursor.col, column, Terminal_cursorHBounds(t));
|
|
}
|
|
|
|
void Terminal_cursorAbsV(Terminal *t, int line)
|
|
{
|
|
int obey_margins = 0;
|
|
bounded_set(&t->cursor.line, line, Terminal_cursorVBounds(t, obey_margins));
|
|
}
|
|
|
|
void Terminal_cursorAbs(Terminal *t, int column, int line)
|
|
{
|
|
Terminal_cursorAbsH(t, column);
|
|
Terminal_cursorAbsV(t, line);
|
|
}
|
|
|
|
void Terminal_carriageReturn(Terminal *t)
|
|
{
|
|
Terminal_cursorAbsH(t, 0);
|
|
}
|
|
|
|
void Terminal_lineFeed(Terminal *t)
|
|
{
|
|
Terminal_cursorRel(t, DIR_DOWN, 1, 1);
|
|
}
|
|
|
|
void Terminal_newLine(Terminal *t)
|
|
{
|
|
Terminal_carriageReturn(t);
|
|
Terminal_lineFeed(t);
|
|
}
|
|
|
|
void Terminal_backspace(Terminal *t)
|
|
{
|
|
Terminal_cursorRel(t, DIR_BACK, 1, 0);
|
|
}
|
|
|
|
void Terminal_horizontalTab(Terminal *t, int count)
|
|
{
|
|
int i, target_col;
|
|
struct bounds b = Terminal_cursorHBounds(t);
|
|
|
|
target_col = -1;
|
|
for (i = t->cursor.col+1; i < b.max; i++) {
|
|
if (t->tabstops[i] == 1) {
|
|
if (count == 1) {
|
|
target_col = i;
|
|
break;
|
|
} else {
|
|
count--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (target_col == -1) {
|
|
t->cursor.col = b.max + 1;
|
|
} else {
|
|
Terminal_cursorAbsH(t, target_col);
|
|
}
|
|
}
|
|
|
|
void Terminal_backTab(Terminal *t, int count)
|
|
{
|
|
int i;
|
|
for (i = t->cursor.col-1; i >= 0; i--) {
|
|
if (t->tabstops[i] == 1) {
|
|
if (count == 1) {
|
|
t->cursor.col = i;
|
|
return;
|
|
} else {
|
|
count--;
|
|
}
|
|
}
|
|
}
|
|
t->cursor.col = 0;
|
|
}
|
|
|
|
|
|
void Terminal_insertGraphic(Terminal *t, unsigned char code, int count)
|
|
{
|
|
struct line *l;
|
|
int line, remain, from, to;
|
|
|
|
from = t->cursor.col;
|
|
line = t->pagebuf.col_count - from;
|
|
if (count > line) count = line;
|
|
remain = line - count;
|
|
|
|
if (remain) {
|
|
to = from + count;
|
|
l = &t->pagebuf.lines[t->cursor.line];
|
|
memmove(&l->cells[to], &l->cells[from], sizeof (struct cell) * remain);
|
|
}
|
|
|
|
while (count--) {
|
|
l->cells[from].att = blank_char_att;
|
|
l->cells[from++].glyph = code;
|
|
}
|
|
}
|
|
|
|
void Terminal_putGraphic(Terminal *t, unsigned char code)
|
|
{
|
|
int *col = &t->cursor.col;
|
|
int *line = &t->cursor.line;
|
|
|
|
/* Cursor pointing just past the end of line means we either need to wrap
|
|
or move back to the last position of the line */
|
|
|
|
if (*col == t->pagebuf.col_count) {
|
|
if (t->modes.auto_wrap == 1) {
|
|
Terminal_newLine(t);
|
|
} else {
|
|
*col -= 1;
|
|
}
|
|
}
|
|
|
|
t->pagebuf.lines[*line].cells[*col].glyph = code;
|
|
*col += 1;
|
|
}
|