ansi_terminal/textmode_buffer.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;
}