dst/source/st.d

3126 lines
84 KiB
D
Raw Normal View History

2025-06-26 18:47:07 +00:00
module st;
import core.stdc.stdint;
import core.stdc.time : time_t;
import core.sys.posix.sys.types;
import core.sys.posix.time : timespec;
import core.sys.posix.termios;
import core.sys.posix.unistd;
import core.sys.posix.fcntl;
import core.sys.posix.sys.ioctl;
import core.sys.posix.sys.select;
import core.sys.posix.sys.wait;
import core.sys.posix.signal;
import core.stdc.errno;
import core.stdc.stdio;
import core.stdc.stdlib;
import core.stdc.stdlib : strtol, atoi;
import core.stdc.string;
import core.stdc.string : strchr;
import core.stdc.wchar_;
import core.stdc.ctype;
import core.stdc.limits;
import patches;
import win : WinMode, MOUSE, xbell, xsetmode, xsetpointermotion, xsetcursor;
import config;
import xft_types;
import std.conv : octal, to;
import std.process : environment;
import std.logger;
// Additional POSIX imports
public import core.sys.posix.sys.select : fd_set, FD_ZERO, FD_SET, FD_CLR, FD_ISSET, timeval;
// External C functions
extern(C) {
pid_t setsid();
int system(const(char)*);
void perror(const(char)*);
int execvp(const(char)*, char**);
void _exit(int) nothrow @nogc;
int dup2(int, int);
int wcwidth(wchar_t);
// Platform-specific openpty
version(linux) {
int openpty(int* master, int* slave, char* name, const(termios)* termp, const(winsize)* winp);
} else version(OSX) {
int openpty(int* master, int* slave, char* name, const(termios)* termp, const(winsize)* winp);
} else version(FreeBSD) {
int openpty(int* master, int* slave, char* name, const(termios)* termp, const(winsize)* winp);
} else version(OpenBSD) {
int openpty(int* master, int* slave, char* name, const(termios)* termp, const(winsize)* winp);
} else {
static assert(0, "openpty not available on this platform");
}
}
// Terminal constants
enum {
UTF_INVALID = 0xFFFD,
UTF_SIZ = 4,
ESC_BUF_SIZ = 128 * UTF_SIZ,
ESC_ARG_SIZ = 16,
STR_BUF_SIZ = ESC_BUF_SIZ,
STR_ARG_SIZ = ESC_ARG_SIZ,
BUFSIZ = 8192, // Standard buffer size
}
static if (isPatchEnabled!"UNDERCURL_PATCH") {
enum CAR_PER_ARG = 4;
}
immutable string STR_TERM_ST = "\033\\";
immutable string STR_TERM_BEL = "\007";
// Helper macros converted to functions
bool IS_SET(uint flag) { return (term.mode & flag) != 0; }
bool ISCONTROLC0(uint c) { return between(c, 0, 0x1f) || c == 0x7f; }
bool ISCONTROLC1(uint c) { return between(c, 0x80, 0x9f); }
bool ISCONTROL(uint c) { return ISCONTROLC0(c) || ISCONTROLC1(c); }
bool ISDELIM(wchar_t u) {
if (u == 0) return false;
for (wchar* p = worddelimiters; *p; p++) {
if (*p == u) return true;
}
return false;
}
// Terminal modes
enum TermMode : uint {
WRAP = 1 << 0,
INSERT = 1 << 1,
ALTSCREEN = 1 << 2,
CRLF = 1 << 3,
ECHO = 1 << 4,
PRINT = 1 << 5,
UTF8 = 1 << 6,
}
// Escape state flags
enum : uint {
ESC_START = 1,
ESC_CSI = 2,
ESC_STR = 4,
ESC_ALTCHARSET = 8,
ESC_STR_END = 16,
ESC_TEST = 32,
ESC_UTF8 = 64,
}
static if (isPatchEnabled!"SIXEL_PATCH") {
enum TermMode SIXEL = cast(TermMode)(1 << 7);
enum TermMode SIXEL_CUR_RT = cast(TermMode)(1 << 8);
enum TermMode SIXEL_SDM = cast(TermMode)(1 << 9);
}
static if (isPatchEnabled!"REFLOW_PATCH") {
enum ScrollMode {
RESIZE = -1,
NOSAVEHIST = 0,
SAVEHIST = 1,
}
}
enum CursorMovement {
SAVE,
LOAD,
}
enum CursorState {
DEFAULT = 0,
WRAPNEXT = 1,
ORIGIN = 2,
}
enum SelMode {
IDLE = 0,
EMPTY = 1,
READY = 2
}
enum SelType {
REGULAR = 1,
RECTANGULAR = 2
}
enum SnapMode {
WORD = 1,
LINE = 2
}
enum Charset {
GRAPHIC0,
GRAPHIC1,
UK,
USA,
MULTI,
GER,
FIN,
}
enum EscapeState {
START = 1,
CSI = 2,
STR = 4, /* DCS, OSC, PM, APC */
ALTCHARSET = 8,
STR_END = 16, /* a final string was encountered */
TEST = 32, /* Enter in test mode */
UTF8 = 64,
}
static if (isPatchEnabled!"SIXEL_PATCH") {
enum EscapeState DCS = cast(EscapeState)128;
}
// Selection structure
struct Selection {
int mode;
int type;
int snap;
struct Point { int x, y; }
Point nb, ne, ob, oe;
int alt;
}
// CSI Escape sequence struct
struct CSIEscape {
char[ESC_BUF_SIZ] buf;
size_t len;
char priv;
int[ESC_ARG_SIZ] arg;
int narg;
char[2] mode;
static if (isPatchEnabled!"UNDERCURL_PATCH") {
int[ESC_ARG_SIZ][CAR_PER_ARG] carg;
}
}
// STR Escape sequence struct
struct STREscape {
char type;
char* buf;
size_t siz;
size_t len;
char*[STR_ARG_SIZ] args;
int narg;
char* term;
}
// Global state
__gshared {
Selection sel;
CSIEscape csiescseq;
STREscape strescseq;
int cmdfd;
int csdfd; // slave fd of the pty
pid_t pid;
int iofd = 1;
char* base64_digits = cast(char*)"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
}
// Terminal I/O definitions
enum TIOCSCTTY = 0x540E; // Linux value, may need adjustment for other platforms
enum TIOCSWINSZ = 0x5414; // Linux value
// Wait status macros
bool WIFEXITED(int status) nothrow @nogc { return (status & 0x7f) == 0; }
int WEXITSTATUS(int status) nothrow @nogc { return (status >> 8) & 0xff; }
bool WIFSIGNALED(int status) nothrow @nogc { return ((status & 0x7f) + 1) >> 1 > 0; }
int WTERMSIG(int status) nothrow @nogc { return status & 0x7f; }
// Import X11 types from deimos
import deimos.X11.X;
import deimos.X11.Xlib;
// Additional X11 types
struct FcPattern;
// FcFontSet structure
struct FcFontSet {
int nfont;
int sfont;
FcPattern** fonts;
}
// Helper functions/macros
T min(T)(T a, T b) { return a < b ? a : b; }
T max(T)(T a, T b) { return a < b ? b : a; }
size_t len(T)(T[] a) { return a.length; }
bool between(T)(T x, T a, T b) { return a <= x && x <= b; }
T divceil(T)(T n, T d) { return (n + d - 1) / d; }
void defaultTo(T)(ref T a, T b) { if (!a) a = b; }
void DEFAULT(T)(ref T a, T b) { if (a == 0) a = b; }
void limit(T)(ref T x, T a, T b) {
if (x < a) x = a;
else if (x > b) x = b;
}
T clamp(T)(T x, T a, T b) {
if (x < a) return a;
else if (x > b) return b;
return x;
}
// Define ATTR_WRAP and ATTR_LIGA for easier use
enum uint ATTR_WRAP = GlyphAttribute.WRAP;
static if (isPatchEnabled!"LIGATURES_PATCH") {
enum uint ATTR_LIGA = cast(uint)(1 << 15);
}
static if (isPatchEnabled!"LIGATURES_PATCH") {
bool attrCmp(Glyph a, Glyph b) {
return ((a.mode & ~ATTR_WRAP & ~ATTR_LIGA) != (b.mode & ~ATTR_WRAP & ~ATTR_LIGA)) ||
a.fg != b.fg || a.bg != b.bg;
}
} else {
bool attrCmp(Glyph a, Glyph b) {
return a.mode != b.mode || a.fg != b.fg || a.bg != b.bg;
}
}
long timeDiff(timespec t1, timespec t2) {
return (t1.tv_sec - t2.tv_sec) * 1000 + (t1.tv_nsec - t2.tv_nsec) / 1_000_000;
}
void modBit(T)(ref T x, bool set, T bit) {
if (set) x |= bit;
else x &= ~bit;
}
uint trueColor(ubyte r, ubyte g, ubyte b) {
return (1 << 24) | (r << 16) | (g << 8) | b;
}
alias TRUECOLOR = trueColor;
bool isTrueCol(uint x) {
return ((1 << 24) & x) != 0;
}
enum GlyphAttribute : uint {
NULL = 0,
SET = 1 << 0,
BOLD = 1 << 1,
FAINT = 1 << 2,
ITALIC = 1 << 3,
UNDERLINE = 1 << 4,
BLINK = 1 << 5,
REVERSE = 1 << 6,
INVISIBLE = 1 << 7,
STRUCK = 1 << 8,
WRAP = 1 << 9,
WIDE = 1 << 10,
WDUMMY = 1 << 11,
}
static if (isPatchEnabled!"SELECTION_COLORS_PATCH") {
enum GlyphAttribute ATTR_SELECTED = cast(GlyphAttribute)(1 << 12);
}
static if (isPatchEnabled!"BOXDRAW_PATCH") {
enum GlyphAttribute ATTR_BOXDRAW = cast(GlyphAttribute)(1 << 13);
}
static if (isPatchEnabled!"UNDERCURL_PATCH") {
enum GlyphAttribute ATTR_DIRTYUNDERLINE = cast(GlyphAttribute)(1 << 14);
}
static if (isPatchEnabled!"LIGATURES_PATCH") {
enum GlyphAttribute ATTR_LIGA = cast(GlyphAttribute)(1 << 15);
}
static if (isPatchEnabled!"SIXEL_PATCH") {
enum GlyphAttribute ATTR_SIXEL = cast(GlyphAttribute)(1 << 16);
}
static if (isPatchEnabled!"KEYBOARDSELECT_PATCH" && isPatchEnabled!"REFLOW_PATCH") {
enum GlyphAttribute ATTR_HIGHLIGHT = cast(GlyphAttribute)(1 << 17);
}
enum GlyphAttribute ATTR_BOLD_FAINT = GlyphAttribute.BOLD | GlyphAttribute.FAINT;
static if (isPatchEnabled!"SIXEL_PATCH") {
struct ImageList {
ImageList* next, prev;
ubyte* pixels;
void* pixmap;
void* clipmask;
int width;
int height;
int x;
int y;
static if (isPatchEnabled!"REFLOW_PATCH") {
int reflow_y;
}
int cols;
int cw;
int ch;
int transparent;
}
}
static if (isPatchEnabled!"WIDE_GLYPHS_PATCH") {
enum DrawingMode {
NONE = 0,
BG = 1 << 0,
FG = 1 << 1,
}
}
enum Screen {
PRI = -1,
ALL = 0,
ALT = 1
}
enum SelectionMode {
IDLE = 0,
EMPTY = 1,
READY = 2
}
enum SelectionType {
REGULAR = 1,
RECTANGULAR = 2
}
enum SelectionSnap {
WORD = 1,
LINE = 2
}
alias Rune = uint_least32_t;
struct Glyph {
Rune u;
uint mode;
uint fg;
uint bg;
static if (isPatchEnabled!"UNDERCURL_PATCH") {
int ustyle;
int[3] ucolor;
}
this(Rune u, uint mode, uint fg, uint bg) {
this.u = u;
this.mode = mode;
this.fg = fg;
this.bg = bg;
}
}
alias Line = Glyph*;
static if (isPatchEnabled!"LIGATURES_PATCH") {
struct GlyphFontSeq {
int ox;
int charlen;
int numspecs;
Glyph base;
}
}
struct TCursor {
Glyph attr;
int x;
int y;
char state = CursorState.DEFAULT;
}
struct Term {
int row;
int col;
static if (isPatchEnabled!"COLUMNS_PATCH") {
int maxcol;
}
Line* line;
Line* alt;
static if (isPatchEnabled!"REFLOW_PATCH" || isPatchEnabled!"SCROLLBACK_PATCH") {
Line[HISTSIZE] hist;
int histi;
static if (isPatchEnabled!"REFLOW_PATCH") {
int histf;
int[2] wrapcwidth;
} else {
int histn;
}
int scr;
}
int* dirty;
TCursor c;
int ocx;
int ocy;
int top;
int bot;
int mode;
int esc;
char[4] trantbl;
int charset;
int icharset;
int* tabs;
static if (isPatchEnabled!"SIXEL_PATCH") {
ImageList* images;
ImageList* images_alt;
}
Rune lastc;
}
union Arg {
int i;
uint ui;
float f;
const(void)* v;
const(char)* s;
}
// Scrollback support
Line TLINE(int y) {
static if (isPatchEnabled!"SCROLLBACK_PATCH") {
if (y < term.scr) {
int idx = (y + term.histi - term.scr + HISTSIZE + 1) % HISTSIZE;
return term.hist[idx];
} else {
int idx = y - term.scr;
if (idx < 0 || idx >= term.row) {
import std.logger : error;
error("TLINE: invalid line index ", idx, " (y=", y, ", term.scr=", term.scr, ", term.row=", term.row, ")");
return term.line[0]; // Return first line as fallback
}
return term.line[idx];
}
} else {
return term.line[y];
}
}
static if (isPatchEnabled!"SCROLLBACK_PATCH" ||
isPatchEnabled!"SCROLLBACK_MOUSE_PATCH" ||
isPatchEnabled!"SCROLLBACK_MOUSE_ALTSCREEN_PATCH") {
extern(C) void kscrolldown(const(Arg)* a) {
int n = a.i;
if (n < 0)
n = term.row + n;
if (n > term.scr)
n = term.scr;
if (term.scr > 0) {
term.scr -= n;
selscroll(0, -n);
tfulldirt();
}
static if (isPatchEnabled!"OPENURLONCLICK_PATCH") {
if (n > 0)
restoremousecursor();
}
}
extern(C) void kscrollup(const(Arg)* a) {
int n = a.i;
if (n < 0)
n = term.row + n;
static if (isPatchEnabled!"REFLOW_PATCH") {
if (term.scr + n > term.histf)
n = term.histf - term.scr;
} else {
if (term.scr + n > term.histn)
n = term.histn - term.scr;
}
if (!n)
return;
if (term.scr <= HISTSIZE - n) {
term.scr += n;
selscroll(0, n);
tfulldirt();
}
static if (isPatchEnabled!"OPENURLONCLICK_PATCH") {
if (n > 0)
restoremousecursor();
}
}
}
// Stub for openurlonclick patch
static if (isPatchEnabled!"OPENURLONCLICK_PATCH") {
void restoremousecursor() {
// TODO: Implement when needed
}
}
struct TermWindow {
int tw, th;
int w, h;
static if (isPatchEnabled!"BACKGROUND_IMAGE_PATCH") {
int x, y;
}
static if (isPatchEnabled!"ANYSIZE_PATCH") {
int hborderpx, vborderpx;
}
int ch;
int cw;
static if (isPatchEnabled!"VERTCENTER_PATCH") {
int cyo;
}
int mode;
int cursor;
}
struct XWindow {
Display* dpy;
Colormap cmap;
Window win;
Drawable buf;
GlyphFontSpec* specbuf;
static if (isPatchEnabled!"LIGATURES_PATCH") {
GlyphFontSeq* specseq;
}
Atom xembed, wmdeletewin, netwmname, netwmiconname, netwmpid;
static if (isPatchEnabled!"FULLSCREEN_PATCH") {
Atom netwmstate, netwmfullscreen;
}
static if (isPatchEnabled!"NETWMICON_PATCH" || isPatchEnabled!"NETWMICON_LEGACY_PATCH" ||
isPatchEnabled!"NETWMICON_FF_PATCH") {
Atom netwmicon;
}
struct Ime {
XIM xim;
XIC xic;
XPoint spot;
XVaNestedList spotlist;
}
Ime ime;
Draw draw;
static if (isPatchEnabled!"BACKGROUND_IMAGE_PATCH") {
GC bggc;
}
Visual* vis;
XSetWindowAttributes attrs;
static if (isPatchEnabled!"HIDECURSOR_PATCH" || isPatchEnabled!"OPENURLONCLICK_PATCH") {
Cursor vpointer, bpointer;
int pointerisvisible;
}
static if (isPatchEnabled!"OPENURLONCLICK_PATCH") {
Cursor upointer;
}
int scr;
int isfixed;
static if (isPatchEnabled!"ALPHA_PATCH") {
int depth;
}
int l, t;
int gm;
}
struct XSelection {
Atom xtarget;
char* primary, clipboard;
timespec tclick1;
timespec tclick2;
}
struct Shortcut {
uint mod;
uint keysym;
extern(C) void function(const(Arg)*) func;
Arg arg;
int screen;
}
struct MouseShortcut {
uint mod;
uint button;
extern(C) void function(const(Arg)*) func;
Arg arg;
uint release;
int screen;
}
struct Key {
uint k;
uint mask;
char* s;
byte appkey;
byte appcursor;
}
struct Font {
int height;
int width;
int ascent;
int descent;
int badslant;
int badweight;
short lbearing;
short rbearing;
XftFont* match;
FcFontSet* set;
FcPattern* pattern;
}
struct DC {
Color* col;
size_t collen;
Font font, bfont, ifont, ibfont;
GC gc;
}
// Function implementations
extern(C) void die(const(char)* errstr, ...) {
import core.stdc.stdio : vfprintf, stderr;
import core.stdc.stdlib : exit;
import core.stdc.stdarg;
va_list ap;
va_start(ap, errstr);
vfprintf(stderr, errstr, ap);
va_end(ap);
exit(1);
}
// Terminal functions implementation
extern(C) void tnew(int cols, int rows) {
term = Term.init;
tresize(cols, rows);
treset();
// Initialize history buffer
static if (isPatchEnabled!"SCROLLBACK_PATCH" || isPatchEnabled!"REFLOW_PATCH") {
for (int i = 0; i < HISTSIZE; i++) {
term.hist[i] = cast(Line)xmalloc(cols * Glyph.sizeof);
// Initialize with empty glyphs
for (int j = 0; j < cols; j++) {
term.hist[i][j] = Glyph(' ', 0, defaultfg, defaultbg);
}
}
}
// Force full redraw
tfulldirt();
}
void tresize(int col, int row) {
int i;
int minrow = min(row, term.row);
int mincol = min(col, term.col);
int* bp;
TCursor c;
if (col < 1 || row < 1) {
return;
}
// Backup cursor
c = term.c;
// Ensure cursor is within bounds
if (term.c.x >= col)
term.c.x = col - 1;
if (term.c.y >= row)
term.c.y = row - 1;
// Resize to new width
static if (isPatchEnabled!"COLUMNS_PATCH") {
term.maxcol = max(col, term.maxcol);
col = term.maxcol;
}
// Handle shrinking height - need to save lines to scrollback
if (row < term.row) {
tcursor(CursorMovement.SAVE);
tsetscroll(0, term.row - 1);
// Handle both screens - swap between them
for (int screen = 0; screen < 2; screen++) {
if (term.c.y >= row) {
static if (isPatchEnabled!"SCROLLBACK_PATCH") {
tscrollup(0, term.c.y - row + 1, !IS_SET(TermMode.ALTSCREEN));
} else {
tscrollup(0, term.c.y - row + 1);
}
}
// Free the lines that are now outside the new size
for (i = row; i < term.row; i++) {
free(term.line[i]);
}
// Swap screens and restore cursor
tswapscreen();
tcursor(CursorMovement.LOAD);
}
}
// Resize buffers - do this AFTER freeing lines when shrinking
term.line = cast(Line*)xrealloc(term.line, row * (Line*).sizeof);
term.alt = cast(Line*)xrealloc(term.alt, row * (Line*).sizeof);
term.dirty = cast(int*)xrealloc(term.dirty, row * int.sizeof);
term.tabs = cast(int*)xrealloc(term.tabs, col * int.sizeof);
// Resize each row to new width, zero-pad if needed
for (i = 0; i < minrow; i++) {
term.line[i] = cast(Line)xrealloc(term.line[i], col * Glyph.sizeof);
term.alt[i] = cast(Line)xrealloc(term.alt[i], col * Glyph.sizeof);
// Clear newly allocated cells if width increased
if (col > term.col) {
for (int j = term.col; j < col; j++) {
term.line[i][j] = Glyph(' ', 0, defaultfg, defaultbg);
term.alt[i][j] = Glyph(' ', 0, defaultfg, defaultbg);
}
}
}
// Allocate new rows
for (/* i = minrow */; i < row; i++) {
term.line[i] = cast(Line)xmalloc(col * Glyph.sizeof);
term.alt[i] = cast(Line)xmalloc(col * Glyph.sizeof);
// Initialize new rows with empty cells
for (int j = 0; j < col; j++) {
term.line[i][j] = Glyph(' ', 0, defaultfg, defaultbg);
term.alt[i][j] = Glyph(' ', 0, defaultfg, defaultbg);
}
}
if (col > term.col) {
bp = term.tabs + term.col;
memset(bp, 0, (col - term.col) * int.sizeof);
while (--bp > term.tabs && !*bp) {}
for (bp += tabspaces; bp < term.tabs + col; bp += tabspaces)
*bp = 1;
}
// Initialize dirty flags for new rows
if (row > term.row) {
memset(term.dirty + term.row, 0, (row - term.row) * int.sizeof);
}
// Resize history buffer if needed
static if (isPatchEnabled!"SCROLLBACK_PATCH" || isPatchEnabled!"REFLOW_PATCH") {
if (col != term.col) {
for (i = 0; i < HISTSIZE; i++) {
if (term.hist[i] !is null) {
term.hist[i] = cast(Line)xrealloc(term.hist[i], col * Glyph.sizeof);
// Initialize new columns if expanding
if (col > term.col) {
for (int j = term.col; j < col; j++) {
term.hist[i][j] = Glyph(' ', 0, defaultfg, defaultbg);
}
}
} else {
// Allocate if not already allocated
term.hist[i] = cast(Line)xmalloc(col * Glyph.sizeof);
for (int j = 0; j < col; j++) {
term.hist[i][j] = Glyph(' ', 0, defaultfg, defaultbg);
}
}
}
}
}
// Check if we're expanding horizontally and have scrollback to pull from
static if (isPatchEnabled!"SCROLLBACK_PATCH" && isPatchEnabled!"REFLOW_PATCH") {
if (col > term.col && term.scr > 0) {
// When expanding horizontally, lines that were wrapped might now fit on fewer lines
// This means we can pull more content from scrollback
int lines_to_pull = 0;
// Count wrapped lines that might unwrap with more width
// Check current visible lines for ATTR_WRAP
int wrapped_lines = 0;
for (i = 0; i < term.row - 1; i++) {
// Check if last character has ATTR_WRAP set
if (term.line[i] && term.col > 0 &&
(term.line[i][term.col - 1].mode & ATTR_WRAP)) {
wrapped_lines++;
}
}
// Estimate how many lines we can pull based on wrapped lines
if (wrapped_lines > 0) {
// Rough estimate: if doubling width, half the wrapped lines might unwrap
if (col >= term.col * 2) {
lines_to_pull = min(term.scr, wrapped_lines / 2);
} else {
// For smaller increases, proportionally fewer lines
float ratio = cast(float)(col - term.col) / term.col;
lines_to_pull = min(term.scr, cast(int)(wrapped_lines * ratio * 0.5));
}
}
// Also add some lines just for the width increase
lines_to_pull += min(term.scr - lines_to_pull, (col - term.col) / 10);
if (lines_to_pull > 0) {
// Scroll down to make room for lines from history
for (i = 0; i < lines_to_pull && term.scr > 0; i++) {
// Decrease scroll position
term.scr--;
// Shift existing lines down
Line temp = term.line[term.row - 1];
for (int j = term.row - 1; j > 0; j--) {
term.line[j] = term.line[j - 1];
}
// Pull line from history
int hist_idx = (term.histi - term.scr + HISTSIZE) % HISTSIZE;
// The old bottom line goes to history (it was already saved earlier)
// Just free it since we're not maintaining that
free(temp);
// Allocate new line for first position
term.line[0] = cast(Line)xmalloc(col * Glyph.sizeof);
// Copy from history and expand to new width
Line hist_line = term.hist[hist_idx];
if (hist_line) {
for (int k = 0; k < min(term.col, col); k++) {
term.line[0][k] = hist_line[k];
}
}
// Clear any new columns
for (int k = term.col; k < col; k++) {
term.line[0][k] = Glyph(' ', 0, defaultfg, defaultbg);
}
}
// Update selection if needed
if (lines_to_pull > 0) {
selscroll(0, lines_to_pull);
// Mark all lines as dirty to force redraw
tfulldirt();
}
}
}
}
// Update terminal size
term.col = col;
term.row = row;
// Fix scroll region
tsetscroll(0, row - 1);
// Restore cursor if applicable
if (c.x >= col) {
c.state &= ~CursorState.WRAPNEXT;
c.x = col - 1;
}
term.c = c;
}
void treset() {
uint i;
term.c = TCursor();
term.c.attr = Glyph(' ', 0, defaultfg, defaultbg);
term.c.x = 0;
term.c.y = 0;
term.c.state = CursorState.DEFAULT;
memset(term.tabs, 0, term.col * int.sizeof);
for (i = tabspaces; i < cast(uint)term.col; i += tabspaces)
term.tabs[i] = 1;
term.top = 0;
term.bot = term.row - 1;
term.mode = TermMode.WRAP | TermMode.UTF8;
memset(term.trantbl.ptr, Charset.USA, term.trantbl.sizeof);
term.charset = 0;
// Clear both primary and alternate screens
for (i = 0; i < 2; i++) {
tmoveto(0, 0);
tcursor(CursorMovement.SAVE);
static if (isPatchEnabled!"COLUMNS_PATCH") {
tclearregion(0, 0, term.maxcol-1, term.row-1);
} else {
tclearregion(0, 0, term.col-1, term.row-1);
}
tswapscreen();
}
static if (isPatchEnabled!"REFLOW_PATCH") {
treflow(-1, -1);
}
}
uint tcellattr() {
return term.c.attr.mode & (
GlyphAttribute.BOLD |
GlyphAttribute.FAINT |
GlyphAttribute.ITALIC |
GlyphAttribute.UNDERLINE |
GlyphAttribute.BLINK |
GlyphAttribute.REVERSE |
GlyphAttribute.INVISIBLE |
GlyphAttribute.STRUCK
);
}
static if (isPatchEnabled!"REFLOW_PATCH") {
void treflow(int col, int row) {
// Stub for reflow patch
}
}
extern(C) void selinit() {
// Initialize selection
xsel.primary = null;
xsel.clipboard = null;
// Note: xsel.xtarget is initialized in xinit(), don't reset it here
}
extern(C) void* xmalloc(size_t size) {
import core.stdc.stdlib : malloc;
void* p = malloc(size);
if (!p)
die("malloc: %s\n".ptr, "out of memory".ptr);
return p;
}
extern(C) void* xrealloc(void* p, size_t size) {
import core.stdc.stdlib : realloc;
p = realloc(p, size);
if (!p)
die("realloc: %s\n".ptr, "out of memory".ptr);
return p;
}
extern(C) char* xstrdup(const(char)* s) {
import core.stdc.string : strlen, strcpy;
size_t len = strlen(s) + 1;
char* p = cast(char*)xmalloc(len);
strcpy(p, s);
return p;
}
// TTY functions
extern(C) int ttynew(const(char)* line, char* cmd, const(char)* outfile, char** args) {
int m, s;
if (outfile) {
term.mode |= TermMode.PRINT;
iofd = (strcmp(outfile, "-") == 0) ? 1 : open(outfile, O_WRONLY | O_CREAT | O_TRUNC, octal!666);
if (iofd < 0) {
fprintf(stderr, "Error opening %s:%s\n", outfile, strerror(errno));
}
}
if (line) {
cmdfd = open(line, O_RDWR);
if (cmdfd < 0)
die("open line '%s' failed: %s\n", line, strerror(errno));
dup2(cmdfd, 0);
stty(args);
return cmdfd;
}
if (openpty(&m, &s, null, null, null) < 0)
die("openpty failed: %s\n", strerror(errno));
switch (pid = fork()) {
case -1:
die("fork failed: %s\n", strerror(errno));
break;
case 0:
close(iofd);
close(m);
setsid();
dup2(s, 0);
dup2(s, 1);
dup2(s, 2);
if (ioctl(s, TIOCSCTTY, null) < 0)
die("ioctl TIOCSCTTY failed: %s\n", strerror(errno));
if (s > 2)
close(s);
version(Posix) {
import core.sys.posix.signal : signal, SIG_DFL;
signal(SIGCHLD, SIG_DFL);
signal(SIGHUP, SIG_DFL);
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGALRM, SIG_DFL);
}
execsh(cmd, args);
break;
default:
static if (isPatchEnabled!"EXTERNALPIPEIN_PATCH" && isPatchEnabled!"EXTERNALPIPE_PATCH") {
csdfd = s;
} else {
close(s);
}
cmdfd = m;
version(Posix) {
import core.sys.posix.signal : signal;
signal(SIGCHLD, &sigchld);
}
}
return cmdfd;
}
void stty(char** args) {
char[4096] cmd; // Use larger buffer like _POSIX_ARG_MAX
char* p = cmd.ptr;
char* s;
size_t n, siz;
n = strlen(stty_args);
if (n > cmd.sizeof - 1)
die("incorrect stty parameters\n");
memcpy(cmd.ptr, stty_args, n);
p = cmd.ptr + n;
siz = cmd.sizeof - n;
for (char** ap = args; ap && *ap; ap++) {
s = *ap;
n = strlen(s);
if (n > siz - 1)
die("stty parameter length too long\n");
*p++ = ' ';
memcpy(p, s, n);
p += n;
siz -= n + 1;
}
*p = '\0';
if (system(cmd.ptr) != 0)
perror("Couldn't call stty");
}
void execsh(char* cmd, char** args) {
import core.sys.posix.pwd : passwd, getpwuid;
import core.sys.posix.unistd : getuid;
char* sh;
char* prog;
char* arg;
const(passwd)* pw;
errno = 0;
pw = getpwuid(getuid());
if (pw is null) {
if (errno)
die("getpwuid: %s\n", strerror(errno));
else
die("who are you?\n");
}
string shellEnv = environment.get("SHELL", null);
if (shellEnv !is null) {
sh = cast(char*)shellEnv.ptr;
} else {
if (pw.pw_shell[0])
sh = cast(char*)pw.pw_shell;
else
sh = cast(char*)"/bin/sh"; // fallback to /bin/sh
}
if (args) {
prog = args[0];
arg = null;
} else if (scroll) {
prog = scroll;
arg = utmp ? utmp : sh;
} else if (utmp) {
prog = utmp;
arg = null;
} else {
prog = sh;
arg = null;
}
char*[3] argv;
argv[0] = prog;
argv[1] = arg;
argv[2] = null;
// DEFAULT(args, argv) - if args is null, use our constructed argv
if (!args)
args = argv.ptr;
environment.remove("COLUMNS");
environment.remove("LINES");
environment.remove("TERMCAP");
import std.string : fromStringz;
environment["LOGNAME"] = fromStringz(pw.pw_name).idup;
environment["USER"] = fromStringz(pw.pw_name).idup;
environment["SHELL"] = fromStringz(sh).idup;
environment["HOME"] = fromStringz(pw.pw_dir).idup;
environment["TERM"] = fromStringz(termname).idup;
environment["COLORTERM"] = "truecolor";
execvp(prog, args);
_exit(1);
}
extern(C) void sigchld(int a) nothrow @nogc {
int stat;
pid_t p;
p = wait(&stat);
if (pid != p)
return;
if (WIFEXITED(stat) && WEXITSTATUS(stat))
_exit(1);
else if (WIFSIGNALED(stat))
_exit(1);
_exit(0);
}
extern(C) size_t ttyread() {
static char[BUFSIZ] buf;
static int buflen = 0;
ssize_t ret;
// Read from tty
ret = read(cmdfd, buf.ptr + buflen, buf.length - buflen);
switch (ret) {
case 0:
return 0;
case -1:
die("couldn't read from shell: %s\n", strerror(errno));
break;
default:
buflen += cast(int)ret;
break;
}
twrite(buf.ptr, buflen, 0);
buflen = 0;
return cast(size_t)ret;
}
void twrite(const(char)* buf, int n, int show_ctrl) {
int charsize;
Rune u;
int len;
for (int i = 0; i < n; i += charsize) {
if (IS_SET(TermMode.UTF8)) {
// UTF-8 decoding
charsize = utf8decode(buf + i, &u, n - i);
if (charsize == 0) {
charsize = 1;
u = buf[i] & 0x7f;
}
} else {
charsize = 1;
u = buf[i] & 0xff;
}
if (show_ctrl && ISCONTROL(u)) {
if (u & 0x80) {
u &= 0x7f;
tputc('^');
tputc('[');
} else if (u != '\n' && u != '\r' && u != '\t') {
u ^= 0x40;
tputc('^');
}
}
tputc(u);
}
}
void tputc(Rune u) {
char[UTF_SIZ] c;
int width;
size_t len;
bool control;
control = ISCONTROL(u);
if (u < 127 || !IS_SET(TermMode.UTF8)) {
c[0] = cast(char)u;
width = len = 1;
} else {
len = utf8encode(u, c.ptr);
if (!control && (width = wcwidth(u)) == -1)
width = 1;
}
/*
* STR sequence must be checked before anything else
* because it uses all following characters until it
* receives a ESC, a SUB, a ST or any other C1 control
* character.
*/
if (term.esc & ESC_STR) {
if (u == '\a' || u == octal!"30" || u == octal!"32" || u == octal!"33" ||
ISCONTROLC1(u)) {
term.esc &= ~(ESC_START|ESC_STR);
term.esc |= ESC_STR_END;
goto check_control_code;
}
if (strescseq.len+len >= strescseq.siz) {
/*
* Here is a bug in terminals. If the user never sends
* some code to stop the str or esc command, then st
* will stop responding. But this is better than
* silently failing with unknown characters. At least
* then users will report back.
*
* In the case users ever get fixed, here is the code:
*/
/*
* term.esc = 0;
* strhandle();
*/
if (strescseq.siz > (size_t.max - UTF_SIZ) / 2)
return;
strescseq.siz *= 2;
strescseq.buf = cast(char*)xrealloc(strescseq.buf, strescseq.siz);
}
memmove(&strescseq.buf[strescseq.len], c.ptr, len);
strescseq.len += len;
return;
}
check_control_code:
/*
* Actions of control codes must be performed as soon they arrive
* because they can be embedded inside a control sequence, and
* they must not cause conflicts with sequences.
*/
if (control) {
/* in UTF-8 mode ignore handling C1 control characters */
if (IS_SET(TermMode.UTF8) && ISCONTROLC1(u))
return;
tcontrolcode(u);
/*
* control codes are not shown ever
*/
if (!term.esc)
term.lastc = 0;
return;
} else if (term.esc & ESC_START) {
if (term.esc & ESC_CSI) {
csiescseq.buf[csiescseq.len++] = cast(char)u;
if (between(u, 0x40, 0x7E) ||
csiescseq.len >= ESC_BUF_SIZ-1) {
// Removed verbose CSI sequence trace
term.esc = 0;
csiparse();
csihandle();
}
return;
} else if (term.esc & ESC_ALTCHARSET) {
tdeftran(cast(char)u);
} else if (term.esc & ESC_TEST) {
tdectest(cast(char)u);
} else {
if (!eschandle(u))
return;
/* sequence already finished */
}
term.esc = 0;
/*
* All characters which form part of a sequence are not
* printed
*/
return;
}
// Handle normal character
if (IS_SET(TermMode.WRAP) && term.c.state & CursorState.WRAPNEXT) {
term.line[term.c.y][term.c.x].mode |= GlyphAttribute.WRAP;
tnewline(1);
}
if (IS_SET(TermMode.INSERT) && term.c.x + width < term.col) {
memmove(&term.line[term.c.y][term.c.x + width],
&term.line[term.c.y][term.c.x],
(term.col - term.c.x - width) * Glyph.sizeof);
}
if (term.c.x + width > term.col) {
if (IS_SET(TermMode.WRAP))
tnewline(1);
else
tmoveto(term.col - width, term.c.y);
}
tsetchar(u, &term.c.attr, term.c.x, term.c.y);
term.dirty[term.c.y] = 1; // Mark line as dirty
if (width == 2) {
term.line[term.c.y][term.c.x].mode |= GlyphAttribute.WIDE;
if (term.c.x + 1 < term.col) {
term.line[term.c.y][term.c.x + 1].u = 0;
term.line[term.c.y][term.c.x + 1].mode |= GlyphAttribute.WDUMMY;
}
}
if (term.c.x + width < term.col) {
tmoveto(term.c.x + width, term.c.y);
} else {
term.c.state |= CursorState.WRAPNEXT;
}
}
void tcontrolcode(uint ascii) {
switch (ascii) {
case '\t': // HT
tputtab(1);
return;
case '\b': // BS
tmoveto(term.c.x - 1, term.c.y);
return;
case '\r': // CR
tmoveto(0, term.c.y);
return;
case '\f': // LF
case '\v': // VT
case '\n': // LF
tnewline(IS_SET(TermMode.CRLF));
return;
case '\a': // BEL
if (term.esc & ESC_STR_END) {
term.esc = 0;
} else {
xbell();
}
return;
case '\033': // ESC
csireset();
term.esc &= ~(ESC_CSI | ESC_ALTCHARSET | ESC_TEST);
term.esc |= ESC_START;
return;
case '\016': // SO (LS1 -- Locking shift 1)
case '\017': // SI (LS0 -- Locking shift 0)
// charset handling
return;
case '\032': // SUB
tsetchar('?', &term.c.attr, term.c.x, term.c.y);
goto case;
case '\030': // CAN
csireset();
break;
case '\005': // ENQ (IGNORED)
case '\000': // NUL (IGNORED)
case '\021': // XON (IGNORED)
case '\023': // XOFF (IGNORED)
case octal!"177": // DEL (IGNORED)
return;
case 0x80: // TODO: PAD
case 0x81: // TODO: HOP
case 0x82: // TODO: BPH
case 0x83: // TODO: NBH
case 0x84: // TODO: IND
break;
case 0x85: // NEL -- Next line
tnewline(1);
break;
case 0x86: // TODO: SSA
case 0x87: // TODO: ESA
break;
case 0x88: // HTS -- Horizontal tab stop
term.tabs[term.c.x] = 1;
break;
case 0x89: // TODO: HTJ
case 0x8a: // TODO: VTS
case 0x8b: // TODO: PLD
case 0x8c: // TODO: PLU
case 0x8d: // TODO: RI
case 0x8e: // TODO: SS2
case 0x8f: // TODO: SS3
case 0x91: // TODO: PU1
case 0x92: // TODO: PU2
case 0x93: // TODO: STS
case 0x94: // TODO: CCH
case 0x95: // TODO: MW
case 0x96: // TODO: SPA
case 0x97: // TODO: EPA
case 0x98: // TODO: SOS
case 0x99: // TODO: SGCI
break;
case 0x9a: // DECID -- Identify Terminal
ttywrite(vtiden, cast(int)strlen(vtiden), 0);
break;
case 0x9b: // TODO: CSI
case 0x9c: // TODO: ST
break;
case 0x90: // DCS -- Device Control String
case 0x9d: // OSC -- Operating System Command
case 0x9e: // PM -- Privacy Message
case 0x9f: // APC -- Application Program Command
tstrsequence(ascii);
return;
default:
break;
}
term.esc &= ~(ESC_STR_END | ESC_STR);
}
int utf8decode(const(char)* c, Rune* u, size_t clen) {
ubyte[4] seq;
size_t len;
if (clen == 0)
return 0;
seq[0] = cast(ubyte)c[0];
// Determine UTF-8 sequence length
if ((seq[0] & 0x80) == 0) {
*u = seq[0];
return 1;
} else if ((seq[0] & 0xE0) == 0xC0) {
len = 2;
} else if ((seq[0] & 0xF0) == 0xE0) {
len = 3;
} else if ((seq[0] & 0xF8) == 0xF0) {
len = 4;
} else {
*u = UTF_INVALID;
return 1;
}
if (clen < len) {
*u = UTF_INVALID;
return 1;
}
for (size_t i = 1; i < len; i++) {
seq[i] = cast(ubyte)c[i];
if ((seq[i] & 0xC0) != 0x80) {
*u = UTF_INVALID;
return 1;
}
}
// Decode UTF-8 sequence
switch (len) {
case 2:
*u = ((seq[0] & 0x1F) << 6) | (seq[1] & 0x3F);
break;
case 3:
*u = ((seq[0] & 0x0F) << 12) | ((seq[1] & 0x3F) << 6) | (seq[2] & 0x3F);
break;
case 4:
*u = ((seq[0] & 0x07) << 18) | ((seq[1] & 0x3F) << 12) |
((seq[2] & 0x3F) << 6) | (seq[3] & 0x3F);
break;
default:
return 1;
}
// Validate the decoded codepoint
if (*u > 0x10FFFF || (*u >= 0xD800 && *u <= 0xDFFF)) {
*u = UTF_INVALID;
}
return cast(int)len;
}
extern(C) void ttywrite(const(char)* s, size_t n, int may_echo) {
fd_set wfd, rfd;
ssize_t r;
size_t lim = 256;
if (IS_SET(TermMode.ECHO) && may_echo) {
twrite(s, cast(int)n, 1);
}
if (!IS_SET(TermMode.CRLF)) {
ttywriteraw(s, n);
return;
}
// Handle CRLF conversion
// Stub implementation
ttywriteraw(s, n);
}
void ttywriteraw(const(char)* s, size_t n) {
fd_set wfd;
fd_set* rfd;
ssize_t r;
size_t lim = 256;
if (n > lim) {
// Handle flow control
n = lim;
}
// Write to tty
for (;;) {
r = write(cmdfd, s, n);
//printf("ttywriteraw: write returned %zd\n", r);
//fflush(stdout);
if (r < 0) {
if (errno == EAGAIN || errno == EINTR) {
continue;
}
die("write error on tty: %s\n", strerror(errno));
}
if (r < cast(ssize_t)n) {
s += r;
n -= r;
} else {
break;
}
}
}
extern(C) void ttyresize(int tw, int th) {
winsize w;
w.ws_row = cast(ushort)term.row;
w.ws_col = cast(ushort)term.col;
w.ws_xpixel = cast(ushort)tw;
w.ws_ypixel = cast(ushort)th;
if (ioctl(cmdfd, TIOCSWINSZ, &w) < 0)
fprintf(stderr, "Couldn't set window size: %s\n", strerror(errno));
}
// Function declarations
extern(C):
void redraw();
void draw();
void drawregion(int, int, int, int);
void tfulldirt() {
tsetdirt(0, term.row-1);
}
void printscreen(const(Arg)*);
void printsel(const(Arg)*);
void sendbreak(const(Arg)*);
void toggleprinter(const(Arg)*);
int tattrset(int);
int tisaltscr();
void tsetdirtattr(int);
void ttyhangup();
extern(C) void resettitle();
extern(C) int tisaltscr() {
return IS_SET(TermMode.ALTSCREEN) ? 1 : 0;
}
void selclear();
void selremove();
void selstart(int, int, int);
void selextend(int, int, int, int);
int selected(int x, int y) {
if (sel.mode == SelMode.EMPTY || sel.ob.x == -1 ||
sel.alt != IS_SET(TermMode.ALTSCREEN)) {
return 0;
}
int result;
if (sel.type == SelType.RECTANGULAR) {
result = between(y, sel.nb.y, sel.ne.y) && between(x, sel.nb.x, sel.ne.x);
} else {
result = between(y, sel.nb.y, sel.ne.y)
&& (y != sel.nb.y || x >= sel.nb.x)
&& (y != sel.ne.y || x <= sel.ne.x);
}
return result;
}
char* getsel() {
char* str;
char* ptr;
int y, bufsize, lastx, linelen;
const(Glyph)* gp;
const(Glyph)* last;
if (sel.ob.x == -1)
return null;
bufsize = (term.col+1) * (sel.ne.y-sel.nb.y+1) * UTF_SIZ;
ptr = str = cast(char*)xmalloc(bufsize);
/* append every set & selected glyph to the selection */
for (y = sel.nb.y; y <= sel.ne.y; y++) {
if ((linelen = tlinelen(y)) == 0) {
*ptr++ = '\n';
continue;
}
if (sel.type == SelType.RECTANGULAR) {
gp = &TLINE(y)[sel.nb.x];
lastx = sel.ne.x;
} else {
gp = &TLINE(y)[sel.nb.y == y ? sel.nb.x : 0];
lastx = (sel.ne.y == y) ? sel.ne.x : term.col-1;
}
last = &TLINE(y)[min(lastx, linelen-1)];
while (last >= gp && last.u == ' ')
--last;
for ( ; gp <= last; ++gp) {
if (gp.mode & GlyphAttribute.WDUMMY)
continue;
ptr += utf8encode(gp.u, ptr);
}
/*
* Copy and pasting of line endings is inconsistent
* in the inconsistent terminal and GUI world.
* The best solution seems like to produce '\n' when
* something is copied from st and convert '\n' to
* '\r', when something to be pasted is received by
* st.
* FIXME: Fix the computer world.
*/
if ((y < sel.ne.y || lastx >= linelen) &&
(!(last.mode & GlyphAttribute.WRAP) || sel.type == SelType.RECTANGULAR))
*ptr++ = '\n';
}
*ptr = 0;
return str;
}
size_t utf8encode(Rune, char*);
int xgetcolor(int x, ubyte* r, ubyte* g, ubyte* b);
// UTF-8 constants
__gshared immutable ubyte[UTF_SIZ + 1] utfbyte = [0x80, 0, 0xC0, 0xE0, 0xF0];
__gshared immutable ubyte[UTF_SIZ + 1] utfmask = [0xC0, 0x80, 0xE0, 0xF0, 0xF8];
__gshared immutable Rune[UTF_SIZ + 1] utfmin = [0, 0, 0x80, 0x800, 0x10000];
__gshared immutable Rune[UTF_SIZ + 1] utfmax = [0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF];
// Helper macro for scrollback
static if (isPatchEnabled!"BOXDRAW_PATCH") {
int isboxdraw(Rune);
ushort boxdrawindex(const(Glyph)*);
void boxdraw_xinit(Display*, Colormap, XftDraw*, Visual*);
void drawboxes(int, int, int, int, XftColor*, XftColor*, const(XftGlyphFontSpec)*, int);
}
// Global variables - defined in config module
extern(C) __gshared {
extern char* utmp;
extern char* scroll;
extern char* stty_args;
extern char* vtiden;
extern wchar* worddelimiters;
static if (isPatchEnabled!"KEYBOARDSELECT_PATCH" && isPatchEnabled!"REFLOW_PATCH") {
extern wchar* kbds_sdelim;
extern wchar* kbds_ldelim;
}
extern int allowaltscreen;
extern int allowwindowops;
extern char* termname;
extern uint tabspaces;
extern uint defaultfg;
extern uint defaultbg;
extern uint defaultcs;
static if (isPatchEnabled!"EXTERNALPIPE_PATCH") {
int extpipeactive;
}
static if (isPatchEnabled!"BOXDRAW_PATCH") {
extern const int boxdraw, boxdraw_bold, boxdraw_braille;
}
static if (isPatchEnabled!"ALPHA_PATCH") {
extern float alpha;
static if (isPatchEnabled!"ALPHA_FOCUS_HIGHLIGHT_PATCH") {
extern float alphaUnfocused;
}
}
DC dc;
XWindow xw;
XSelection xsel;
TermWindow win;
Term term;
}
// Additional functions needed by x.d
extern(C) int tattrset(int attr) {
// Check if any terminal cells have the given attribute
for (int i = 0; i < term.row; i++) {
for (int j = 0; j < term.col; j++) {
if (term.line[i][j].mode & attr)
return 1;
}
}
return 0;
}
extern(C) void tsetdirtattr(int attr) {
// Mark lines dirty that contain the given attribute
for (int i = 0; i < term.row; i++) {
for (int j = 0; j < term.col; j++) {
if (term.line[i][j].mode & attr) {
term.dirty[i] = 1;
break;
}
}
}
}
extern(C) void ttyhangup() {
// Send SIGHUP to shell
import core.sys.posix.signal : kill, SIGHUP;
if (pid != 0) {
kill(pid, SIGHUP);
}
}
// Helper functions
void tmoveto(int x, int y) {
int miny, maxy;
if (term.c.state & CursorState.ORIGIN) {
miny = term.top;
maxy = term.bot;
} else {
miny = 0;
maxy = term.row - 1;
}
term.c.state &= ~CursorState.WRAPNEXT;
term.c.x = clamp(x, 0, term.col-1);
term.c.y = clamp(y, miny, maxy);
}
void tsetchar(Rune u, Glyph* attr, int x, int y) {
if (term.line[y][x].mode & GlyphAttribute.WIDE) {
if (x+1 < term.col) {
term.line[y][x+1].u = ' ';
term.line[y][x+1].mode &= ~GlyphAttribute.WDUMMY;
}
} else if (term.line[y][x].mode & GlyphAttribute.WDUMMY) {
term.line[y][x-1].u = ' ';
term.line[y][x-1].mode &= ~GlyphAttribute.WIDE;
}
term.dirty[y] = 1;
term.line[y][x] = *attr;
term.line[y][x].u = u;
static if (isPatchEnabled!"BOXDRAW_PATCH") {
import patch.boxdraw : isboxdraw;
if (isboxdraw(u))
term.line[y][x].mode |= ATTR_BOXDRAW;
}
}
void tnewline(int first_col) {
int y = term.c.y;
if (y == term.bot) {
static if (isPatchEnabled!"SCROLLBACK_PATCH") {
tscrollup(term.top, 1, 1); // Save to history
} else {
tscrollup(term.top, 1);
}
} else {
y++;
}
tmoveto(first_col ? 0 : term.c.x, y);
}
void tputtab(int n) {
uint x = term.c.x;
if (n > 0) {
while (x < cast(uint)term.col && n--)
for (++x; x < cast(uint)term.col && !term.tabs[x]; ++x) {}
} else if (n < 0) {
while (x > 0 && n++)
for (--x; x > 0 && !term.tabs[x]; --x) {}
}
term.c.x = min(x, cast(uint)term.col - 1);
}
static if (isPatchEnabled!"SCROLLBACK_PATCH") {
void tscrollup(int orig, int n, int copyhist = 0) {
int i;
Line temp;
trace("tscrollup (SCROLLBACK): orig=", orig, " n=", n, " copyhist=", copyhist,
" term.bot=", term.bot, " term.top=", term.top);
static if (isPatchEnabled!"OPENURLONCLICK_PATCH") {
restoremousecursor();
}
n = clamp(n, 0, term.bot-orig+1);
if (copyhist && !IS_SET(TermMode.ALTSCREEN)) {
for (i = 0; i < n; i++) {
term.histi = (term.histi + 1) % HISTSIZE;
temp = term.hist[term.histi];
term.hist[term.histi] = term.line[orig+i];
term.line[orig+i] = temp;
}
static if (isPatchEnabled!"REFLOW_PATCH") {
term.histf = min(term.histf + n, HISTSIZE);
} else {
term.histn = min(term.histn + n, HISTSIZE);
}
if (term.scr > 0 && term.scr < HISTSIZE)
term.scr = min(term.scr + n, HISTSIZE-1);
}
tclearregion(0, orig, term.col-1, orig+n-1);
tsetdirt(orig+n, term.bot);
for (i = orig; i <= term.bot-n; i++) {
temp = term.line[i];
term.line[i] = term.line[i+n];
term.line[i+n] = temp;
}
selscroll(orig, -n);
}
} else {
void tscrollup(int orig, int n) {
int i;
Line temp;
n = clamp(n, 0, term.bot-orig+1);
tclearregion(0, orig, term.col-1, orig+n-1);
tsetdirt(orig+n, term.bot);
for (i = orig; i <= term.bot-n; i++) {
temp = term.line[i];
term.line[i] = term.line[i+n];
term.line[i+n] = temp;
}
selscroll(orig, -n);
}
}
void tscrolldown(int orig, int n) {
int i;
Line temp;
n = clamp(n, 0, term.bot-orig+1);
tsetdirt(orig, term.bot-n);
tclearregion(0, term.bot-n+1, term.col-1, term.bot);
for (i = term.bot; i >= orig+n; i--) {
temp = term.line[i];
term.line[i] = term.line[i-n];
term.line[i-n] = temp;
}
selscroll(orig, n);
}
void csireset() {
memset(&csiescseq, 0, CSIEscape.sizeof);
}
void strreset() {
strescseq.buf = cast(char*)xrealloc(strescseq.buf, STR_BUF_SIZ);
strescseq.siz = STR_BUF_SIZ;
strescseq.len = 0;
strescseq.type = ' ';
strescseq.narg = 0;
}
void tstrsequence(uint c) {
switch (c) {
case 0x90: // DCS -- Device Control String
c = 'P';
break;
case 0x9f: // APC -- Application Program Command
c = '_';
break;
case 0x9e: // PM -- Privacy Message
c = '^';
break;
case 0x9d: // OSC -- Operating System Command
c = ']';
break;
default:
return;
}
strreset();
strescseq.type = cast(char)c;
term.esc |= ESC_STR;
}
void selscroll(int orig, int n) {
if (sel.ob.x == -1 || sel.alt != IS_SET(TermMode.ALTSCREEN))
return;
if (between(sel.nb.y, orig, term.bot) != between(sel.ne.y, orig, term.bot)) {
selclear();
} else if (between(sel.nb.y, orig, term.bot)) {
sel.ob.y += n;
sel.oe.y += n;
if (sel.ob.y < term.top || sel.ob.y > term.bot ||
sel.oe.y < term.top || sel.oe.y > term.bot) {
selclear();
} else {
selnormalize();
}
}
}
void selstart(int col, int row, int snap) {
selclear();
sel.mode = SelMode.EMPTY;
sel.type = SelType.REGULAR;
sel.alt = IS_SET(TermMode.ALTSCREEN);
sel.snap = snap;
sel.oe.x = sel.ob.x = col;
sel.oe.y = sel.ob.y = row;
selnormalize();
if (sel.snap != 0)
sel.mode = SelMode.READY;
tsetdirt(sel.nb.y, sel.ne.y);
}
void selextend(int col, int row, int type, int done) {
int oldey, oldex, oldsby, oldsey, oldtype;
if (sel.mode == SelMode.IDLE) {
return;
}
if (done && sel.mode == SelMode.EMPTY) {
selclear();
return;
}
oldey = sel.oe.y;
oldex = sel.oe.x;
oldsby = sel.nb.y;
oldsey = sel.ne.y;
oldtype = sel.type;
sel.oe.x = col;
sel.oe.y = row;
sel.type = type;
selnormalize();
if (oldey != sel.oe.y || oldex != sel.oe.x || oldtype != sel.type || sel.mode == SelMode.EMPTY)
tsetdirt(min(sel.nb.y, oldsby), max(sel.ne.y, oldsey));
sel.mode = done ? SelMode.IDLE : SelMode.READY;
}
void selclear() {
if (sel.ob.x == -1)
return;
sel.mode = SelMode.IDLE;
sel.ob.x = -1;
tsetdirt(sel.nb.y, sel.ne.y);
}
void selnormalize() {
int i;
if (sel.type == SelType.REGULAR && sel.ob.y != sel.oe.y) {
sel.nb.x = sel.ob.y < sel.oe.y ? sel.ob.x : sel.oe.x;
sel.ne.x = sel.ob.y < sel.oe.y ? sel.oe.x : sel.ob.x;
} else {
sel.nb.x = min(sel.ob.x, sel.oe.x);
sel.ne.x = max(sel.ob.x, sel.oe.x);
}
sel.nb.y = min(sel.ob.y, sel.oe.y);
sel.ne.y = max(sel.ob.y, sel.oe.y);
selsnap(&sel.nb.x, &sel.nb.y, -1);
selsnap(&sel.ne.x, &sel.ne.y, +1);
/* expand selection over line breaks */
if (sel.type == SelType.RECTANGULAR)
return;
i = tlinelen(sel.nb.y);
if (i < sel.nb.x)
sel.nb.x = i;
if (tlinelen(sel.ne.y) <= sel.ne.x)
sel.ne.x = term.col - 1;
}
void selsnap(int* x, int* y, int direction) {
// Simple implementation - could be enhanced for word/line selection
switch (sel.snap) {
case SnapMode.WORD:
// Implement word snapping
wchar_t prevdelim = direction > 0 ? ' ' : '\0';
wchar_t curdelim;
for (;;) {
if (direction < 0 && *x <= 0) {
if (*y > 0 && term.line[*y - 1][term.col - 1].mode & ATTR_WRAP) {
*y -= 1;
*x = term.col - 1;
} else {
break;
}
}
if (direction > 0 && *x >= term.col - 1) {
if (*y < term.row - 1 && term.line[*y][term.col - 1].mode & ATTR_WRAP) {
*y += 1;
*x = 0;
} else {
break;
}
}
if (term.line[*y][*x].mode & GlyphAttribute.WDUMMY) {
*x += direction;
continue;
}
curdelim = term.line[*y][*x].u;
if (ISDELIM(curdelim) != ISDELIM(prevdelim))
break;
*x += direction;
prevdelim = curdelim;
}
break;
case SnapMode.LINE:
*x = (direction < 0) ? 0 : term.col - 1;
break;
default:
break;
}
}
void tsetdirt(int top, int bot) {
int i;
top = clamp(top, 0, term.row-1);
bot = clamp(bot, 0, term.row-1);
for (i = top; i <= bot; i++)
term.dirty[i] = 1;
}
// Implementation of missing functions
extern(C) ptrdiff_t xwrite(int fd, const(char)* s, size_t len) {
size_t aux = len;
ptrdiff_t r;
while (len > 0) {
r = write(fd, s, len);
if (r < 0)
return r;
len -= r;
s += r;
}
return aux;
}
extern(C) int tlinelen(int y) {
int i = term.col;
static if (isPatchEnabled!"SCROLLBACK_PATCH") {
if (TLINE(y)[i - 1].mode & GlyphAttribute.WRAP)
return i;
while (i > 0 && TLINE(y)[i - 1].u == ' ')
--i;
} else {
if (term.line[y][i - 1].mode & GlyphAttribute.WRAP)
return i;
while (i > 0 && term.line[y][i - 1].u == ' ')
--i;
}
return i;
}
char utf8encodebyte(Rune u, size_t i) {
return cast(char)(utfbyte[i] | (u & ~utfmask[i]));
}
size_t utf8validate(Rune* u, size_t i) {
if (!between(*u, utfmin[i], utfmax[i]) || between(*u, 0xD800, 0xDFFF))
*u = UTF_INVALID;
for (i = 1; *u > utfmax[i]; ++i) {}
return i;
}
extern(C) size_t utf8encode(Rune u, char* c) {
size_t len, i;
len = utf8validate(&u, 0);
if (len > UTF_SIZ)
return 0;
for (i = len - 1; i != 0; --i) {
c[i] = utf8encodebyte(u, 0);
u >>= 6;
}
c[0] = utf8encodebyte(u, len);
return len;
}
// Terminal manipulation functions
void tmoveato(int x, int y) {
int actual_y = y + ((term.c.state & CursorState.ORIGIN) ? term.top : 0);
trace("tmoveato: x=", x, " y=", y, " actual_y=", actual_y, " term.row=", term.row, " term.bot=", term.bot);
tmoveto(x, actual_y);
}
void tclearregion(int x1, int y1, int x2, int y2) {
int x, y, temp;
if (x1 > x2) {
temp = x1;
x1 = x2;
x2 = temp;
}
if (y1 > y2) {
temp = y1;
y1 = y2;
y2 = temp;
}
x1 = clamp(x1, 0, term.col-1);
x2 = clamp(x2, 0, term.col-1);
y1 = clamp(y1, 0, term.row-1);
y2 = clamp(y2, 0, term.row-1);
for (y = y1; y <= y2; y++) {
term.dirty[y] = 1;
for (x = x1; x <= x2; x++) {
Glyph* gp = &term.line[y][x];
if (selected(x, y))
selclear();
gp.fg = term.c.attr.fg;
gp.bg = term.c.attr.bg;
gp.mode = 0;
gp.u = ' ';
}
}
}
void tdeletechar(int n) {
int dst, src, size;
Glyph* line;
n = clamp(n, 0, term.col - term.c.x);
dst = term.c.x;
src = term.c.x + n;
size = term.col - src;
line = term.line[term.c.y];
memmove(&line[dst], &line[src], size * Glyph.sizeof);
tclearregion(term.col-n, term.c.y, term.col-1, term.c.y);
}
void tinsertblank(int n) {
int dst, src, size;
Glyph* line;
n = clamp(n, 0, term.col - term.c.x);
dst = term.c.x + n;
src = term.c.x;
size = term.col - dst;
line = term.line[term.c.y];
memmove(&line[dst], &line[src], size * Glyph.sizeof);
tclearregion(src, term.c.y, dst - 1, term.c.y);
}
void tinsertblankline(int n) {
if (between(term.c.y, term.top, term.bot))
tscrolldown(term.c.y, n);
}
void tdeleteline(int n) {
if (between(term.c.y, term.top, term.bot))
tscrollup(term.c.y, n);
}
void tsetattr(const(int)* attr, int l) {
int i;
uint idx;
// Handle CSI m with no parameters (reset)
if (l == 0) {
term.c.attr.mode &= ~(GlyphAttribute.BOLD | GlyphAttribute.FAINT |
GlyphAttribute.ITALIC | GlyphAttribute.UNDERLINE | GlyphAttribute.BLINK |
GlyphAttribute.REVERSE | GlyphAttribute.INVISIBLE | GlyphAttribute.STRUCK);
term.c.attr.fg = defaultfg;
term.c.attr.bg = defaultbg;
return;
}
for (i = 0; i < l; i++) {
switch (attr[i]) {
case 0:
term.c.attr.mode &= ~(GlyphAttribute.BOLD | GlyphAttribute.FAINT |
GlyphAttribute.ITALIC | GlyphAttribute.UNDERLINE | GlyphAttribute.BLINK |
GlyphAttribute.REVERSE | GlyphAttribute.INVISIBLE | GlyphAttribute.STRUCK);
term.c.attr.fg = defaultfg;
term.c.attr.bg = defaultbg;
break;
case 1:
term.c.attr.mode |= GlyphAttribute.BOLD;
break;
case 2:
term.c.attr.mode |= GlyphAttribute.FAINT;
break;
case 3:
term.c.attr.mode |= GlyphAttribute.ITALIC;
break;
case 4:
term.c.attr.mode |= GlyphAttribute.UNDERLINE;
break;
case 5: /* slow blink */
case 6: /* rapid blink */
term.c.attr.mode |= GlyphAttribute.BLINK;
break;
case 7:
term.c.attr.mode |= GlyphAttribute.REVERSE;
break;
case 8:
term.c.attr.mode |= GlyphAttribute.INVISIBLE;
break;
case 9:
term.c.attr.mode |= GlyphAttribute.STRUCK;
break;
case 22:
term.c.attr.mode &= ~(GlyphAttribute.BOLD | GlyphAttribute.FAINT);
break;
case 23:
term.c.attr.mode &= ~GlyphAttribute.ITALIC;
break;
case 24:
term.c.attr.mode &= ~GlyphAttribute.UNDERLINE;
break;
case 25:
term.c.attr.mode &= ~GlyphAttribute.BLINK;
break;
case 27:
term.c.attr.mode &= ~GlyphAttribute.REVERSE;
break;
case 28:
term.c.attr.mode &= ~GlyphAttribute.INVISIBLE;
break;
case 29:
term.c.attr.mode &= ~GlyphAttribute.STRUCK;
break;
case 38:
if (i + 2 < l && attr[i + 1] == 5) {
term.c.attr.fg = attr[i + 2];
i += 2;
} else if (i + 4 < l && attr[i + 1] == 2) {
idx = attr[i + 2] << 16 | attr[i + 3] << 8 | attr[i + 4];
term.c.attr.fg = TRUECOLOR(cast(ubyte)attr[i + 2], cast(ubyte)attr[i + 3], cast(ubyte)attr[i + 4]);
i += 4;
}
break;
case 39:
term.c.attr.fg = defaultfg;
break;
case 48:
if (i + 2 < l && attr[i + 1] == 5) {
term.c.attr.bg = attr[i + 2];
i += 2;
} else if (i + 4 < l && attr[i + 1] == 2) {
idx = attr[i + 2] << 16 | attr[i + 3] << 8 | attr[i + 4];
term.c.attr.bg = TRUECOLOR(cast(ubyte)attr[i + 2], cast(ubyte)attr[i + 3], cast(ubyte)attr[i + 4]);
i += 4;
}
break;
case 49:
term.c.attr.bg = defaultbg;
break;
default:
if (between(attr[i], 30, 37)) {
term.c.attr.fg = attr[i] - 30;
} else if (between(attr[i], 40, 47)) {
term.c.attr.bg = attr[i] - 40;
} else if (between(attr[i], 90, 97)) {
term.c.attr.fg = attr[i] - 90 + 8;
} else if (between(attr[i], 100, 107)) {
term.c.attr.bg = attr[i] - 100 + 8;
} else {
fprintf(stderr, "erresc: unhandled CSI SGR %d\n", attr[i]);
}
break;
}
}
}
void tsetscroll(int t, int b) {
int temp;
t = clamp(t, 0, term.row-1);
b = clamp(b, 0, term.row-1);
if (t > b) {
temp = t;
t = b;
b = temp;
}
term.top = t;
term.bot = b;
}
void tdump() {
int i;
for (i = 0; i < term.row; ++i)
tdumpline(i);
}
void tdumpline(int n) {
int i;
Glyph* line;
char[UTF_SIZ] buf;
int len;
line = term.line[n];
for (i = 0; i < term.col; ++i) {
if (line[i].u == ' ')
continue;
len = cast(int)utf8encode(line[i].u, buf.ptr);
write(1, buf.ptr, len);
}
printf("\n");
}
void tdumpsel() {
char* ptr;
if ((ptr = getsel()) !is null) {
printf("%s", ptr);
free(ptr);
}
}
void csidump() {
size_t i;
uint c;
fprintf(stderr, "ESC[");
for (i = 0; i < csiescseq.len; i++) {
c = csiescseq.buf[i] & 0xff;
if (isprint(c)) {
putc(c, stderr);
} else if (c == '\n') {
fprintf(stderr, "(\\n)");
} else if (c == '\r') {
fprintf(stderr, "(\\r)");
} else if (c == 0x1b) {
fprintf(stderr, "(\\e)");
} else {
fprintf(stderr, "(%02x)", c);
}
}
putc('\n', stderr);
}
void tswapscreen() {
Line* tmp = term.line;
term.line = term.alt;
term.alt = tmp;
term.mode ^= TermMode.ALTSCREEN;
tfulldirt();
}
void tcursor(int mode) {
static TCursor[2] c;
int alt = IS_SET(TermMode.ALTSCREEN) ? 1 : 0;
if (mode == CursorMovement.SAVE) {
c[alt] = term.c;
} else if (mode == CursorMovement.LOAD) {
term.c = c[alt];
tmoveto(c[alt].x, c[alt].y);
}
}
void tsetmode(int priv, int set, const(int)* args, int narg) {
int* lim;
int mode;
int alt;
for (lim = cast(int*)args + narg; args < lim; ++args) {
if (priv) {
switch (*args) {
case 1: /* DECCKM -- Cursor key */
xsetmode(set, WinMode.APPCURSOR);
break;
case 5: /* DECSCNM -- Reverse video */
mode = IS_SET(WinMode.REVERSE) ? 1 : 0;
if (mode != set) {
xsetmode(set, WinMode.REVERSE);
redraw();
}
break;
case 6: /* DECOM -- Origin */
term.c.state = set ? (term.c.state | CursorState.ORIGIN) : (term.c.state & ~CursorState.ORIGIN);
tmoveato(0, 0);
break;
case 7: /* DECAWM -- Auto wrap */
term.mode = set ? (term.mode | TermMode.WRAP) : (term.mode & ~TermMode.WRAP);
break;
case 0: /* Error (IGNORED) */
case 2: /* DECANM -- ANSI/VT52 (IGNORED) */
case 3: /* DECCOLM -- Column (IGNORED) */
case 4: /* DECSCLM -- Scroll (IGNORED) */
case 8: /* DECARM -- Auto repeat (IGNORED) */
case 18: /* DECPFF -- Printer feed (IGNORED) */
case 19: /* DECPEX -- Printer extent (IGNORED) */
case 42: /* DECNRCM -- National characters (IGNORED) */
case 12: /* att610 -- Start blinking cursor (IGNORED) */
break;
case 25: /* DECTCEM -- Text Cursor Enable Mode */
xsetmode(!set, WinMode.HIDE);
break;
case 9: /* X10 mouse compatibility mode */
xsetpointermotion(0);
xsetmode(0, MOUSE);
xsetmode(set, WinMode.MOUSEX10);
break;
case 1000: /* 1000: report button press */
xsetpointermotion(0);
xsetmode(0, MOUSE);
xsetmode(set, WinMode.MOUSEBTN);
break;
case 1002: /* 1002: report motion on button press */
xsetpointermotion(0);
xsetmode(0, MOUSE);
xsetmode(set, WinMode.MOUSEMOTION);
break;
case 1003: /* 1003: enable all mouse motions */
xsetpointermotion(set);
xsetmode(0, MOUSE);
xsetmode(set, WinMode.MOUSEMANY);
break;
case 1004: /* 1004: send focus events to tty */
xsetmode(set, WinMode.FOCUS);
break;
case 1006: /* 1006: extended reporting mode */
xsetmode(set, WinMode.MOUSESGR);
break;
case 1034:
xsetmode(set, WinMode.MODE_8BIT);
break;
case 1049: /* swap screen & set/restore cursor as xterm */
if (!allowaltscreen)
break;
tcursor((set) ? CursorMovement.SAVE : CursorMovement.LOAD);
goto case 47;
case 47: /* swap screen */
case 1047:
if (!allowaltscreen)
break;
alt = IS_SET(TermMode.ALTSCREEN) ? 1 : 0;
if (alt) {
tclearregion(0, 0, term.col-1, term.row-1);
}
if (set ^ alt) /* set is always 1 or 0 */
tswapscreen();
if (*args != 1049)
break;
goto case 1048;
case 1048:
tcursor((set) ? CursorMovement.SAVE : CursorMovement.LOAD);
break;
case 2004: /* 2004: bracketed paste mode */
xsetmode(set, WinMode.BRCKTPASTE);
break;
/* Not implemented mouse modes. See comments there. */
case 1001: /* mouse highlight mode; can hang the
terminal by design when implemented. */
case 1005: /* UTF-8 mouse mode; will confuse
applications not supporting UTF-8
and luit. */
case 1015: /* urxvt mangled mouse mode; incompatible
and can be mistaken for other control
codes. */
case 7727: /* Application escape key mode (rxvt-unicode specific) */
break;
default:
fprintf(stderr, "erresc: unknown private set/reset mode %d\n", *args);
break;
}
} else {
switch (*args) {
case 0: /* Error (IGNORED) */
break;
case 2:
term.mode = set ? (term.mode | TermMode.INSERT) : (term.mode & ~TermMode.INSERT);
break;
case 4: /* IRM -- Insertion-replacement */
term.mode = set ? (term.mode | TermMode.INSERT) : (term.mode & ~TermMode.INSERT);
break;
case 12: /* SRM -- Send/Receive */
term.mode = set ? (term.mode | TermMode.ECHO) : (term.mode & ~TermMode.ECHO);
break;
case 20: /* LNM -- Linefeed/new line */
term.mode = set ? (term.mode | TermMode.CRLF) : (term.mode & ~TermMode.CRLF);
break;
default:
fprintf(stderr, "erresc: unknown set/reset mode %d\n", *args);
break;
}
}
}
}
void csihandle() {
char[40] buffer;
int n = 0;
// Debug log CSI sequence
trace("csihandle: mode='", cast(char)csiescseq.mode[0], "' (0x", to!string(cast(uint)csiescseq.mode[0], 16),
") priv=", csiescseq.priv, " narg=", csiescseq.narg);
for (int i = 0; i < csiescseq.narg; i++) {
trace(" arg[", i, "]=", csiescseq.arg[i]);
}
switch (csiescseq.mode[0]) {
default:
unknown:
fprintf(stderr, "erresc: unknown csi ");
csidump();
break;
case '@': /* ICH -- Insert <n> blank char */
DEFAULT(csiescseq.arg[0], 1);
tinsertblank(csiescseq.arg[0]);
break;
case 'A': /* CUU -- Cursor <n> Up */
DEFAULT(csiescseq.arg[0], 1);
tmoveto(term.c.x, term.c.y-csiescseq.arg[0]);
break;
case 'B': /* CUD -- Cursor <n> Down */
case 'e': /* VPR --Cursor <n> Down */
DEFAULT(csiescseq.arg[0], 1);
tmoveto(term.c.x, term.c.y+csiescseq.arg[0]);
break;
case 'i': /* MC -- Media Copy */
switch (csiescseq.arg[0]) {
case 0:
tdump();
break;
case 1:
tdumpline(term.c.y);
break;
case 2:
tdumpsel();
break;
case 4:
term.mode &= ~TermMode.PRINT;
break;
case 5:
term.mode |= TermMode.PRINT;
break;
default:
break;
}
break;
case 'c': /* DA -- Device Attributes */
if (csiescseq.arg[0] == 0) {
if (csiescseq.priv) {
/* Secondary DA -- ignore for now */
break;
}
ttywrite(vtiden, strlen(vtiden), 0);
}
break;
case 'q': /* DECSCUSR and other 'q' sequences */
if (csiescseq.priv) {
/* Tertiary DA or other private 'q' sequences -- ignore */
break;
}
/* Other 'q' sequences */
goto unknown;
case 'b': /* REP -- if last char is printable print it <n> more times */
DEFAULT(csiescseq.arg[0], 1);
if (csiescseq.arg[0] > 0) csiescseq.arg[0] = min(csiescseq.arg[0], 65535);
if (term.lastc)
while (csiescseq.arg[0]-- > 0)
tputc(term.lastc);
break;
case 'C': /* CUF -- Cursor <n> Forward */
case 'a': /* HPR -- Cursor <n> Forward */
DEFAULT(csiescseq.arg[0], 1);
tmoveto(term.c.x+csiescseq.arg[0], term.c.y);
break;
case 'D': /* CUB -- Cursor <n> Backward */
DEFAULT(csiescseq.arg[0], 1);
tmoveto(term.c.x-csiescseq.arg[0], term.c.y);
break;
case 'E': /* CNL -- Cursor <n> Down and first col */
DEFAULT(csiescseq.arg[0], 1);
tmoveto(0, term.c.y+csiescseq.arg[0]);
break;
case 'F': /* CPL -- Cursor <n> Up and first col */
DEFAULT(csiescseq.arg[0], 1);
tmoveto(0, term.c.y-csiescseq.arg[0]);
break;
case 'g': /* TBC -- Tabulation clear */
switch (csiescseq.arg[0]) {
case 0: /* clear current tab stop */
term.tabs[term.c.x] = 0;
break;
case 3: /* clear all the tabs */
memset(term.tabs, 0, term.col * int.sizeof);
break;
default:
goto unknown;
}
break;
case 'G': /* CHA -- Move to <col> */
case '`': /* HPA */
DEFAULT(csiescseq.arg[0], 1);
tmoveto(csiescseq.arg[0]-1, term.c.y);
break;
case 'H': /* CUP -- Move to <row> <col> */
case 'f': /* HVP */
DEFAULT(csiescseq.arg[0], 1);
DEFAULT(csiescseq.arg[1], 1);
tmoveato(csiescseq.arg[1]-1, csiescseq.arg[0]-1);
break;
case 'I': /* CHT -- Cursor Forward Tabulation <n> tab stops */
DEFAULT(csiescseq.arg[0], 1);
tputtab(csiescseq.arg[0]);
break;
case 'J': /* ED -- Clear screen */
switch (csiescseq.arg[0]) {
case 0: /* below */
tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
if (term.c.y < term.row-1)
tclearregion(0, term.c.y+1, term.col-1, term.row-1);
break;
case 1: /* above */
if (term.c.y > 0)
tclearregion(0, 0, term.col-1, term.c.y-1);
tclearregion(0, term.c.y, term.c.x, term.c.y);
break;
case 2: /* all */
tclearregion(0, 0, term.col-1, term.row-1);
break;
default:
goto unknown;
}
break;
case 'K': /* EL -- Clear line */
switch (csiescseq.arg[0]) {
case 0: /* right */
tclearregion(term.c.x, term.c.y, term.col-1, term.c.y);
break;
case 1: /* left */
tclearregion(0, term.c.y, term.c.x, term.c.y);
break;
case 2: /* all */
tclearregion(0, term.c.y, term.col-1, term.c.y);
break;
default:
break;
}
break;
case 'S': /* SU -- Scroll <n> line up */
DEFAULT(csiescseq.arg[0], 1);
tscrollup(term.top, csiescseq.arg[0]);
break;
case 'T': /* SD -- Scroll <n> line down */
DEFAULT(csiescseq.arg[0], 1);
tscrolldown(term.top, csiescseq.arg[0]);
break;
case 'L': /* IL -- Insert <n> blank lines */
DEFAULT(csiescseq.arg[0], 1);
tinsertblankline(csiescseq.arg[0]);
break;
case 'l': /* RM -- Reset Mode */
tsetmode(csiescseq.priv, 0, csiescseq.arg.ptr, csiescseq.narg);
break;
case 'M': /* DL -- Delete <n> lines */
DEFAULT(csiescseq.arg[0], 1);
tdeleteline(csiescseq.arg[0]);
break;
case 'X': /* ECH -- Erase <n> char */
DEFAULT(csiescseq.arg[0], 1);
tclearregion(term.c.x, term.c.y,
term.c.x + csiescseq.arg[0] - 1, term.c.y);
break;
case 'P': /* DCH -- Delete <n> char */
DEFAULT(csiescseq.arg[0], 1);
tdeletechar(csiescseq.arg[0]);
break;
case 'Z': /* CBT -- Cursor Backward Tabulation <n> tab stops */
DEFAULT(csiescseq.arg[0], 1);
tputtab(-csiescseq.arg[0]);
break;
case 'd': /* VPA -- Move to <row> */
DEFAULT(csiescseq.arg[0], 1);
tmoveato(term.c.x, csiescseq.arg[0]-1);
break;
case 'h': /* SM -- Set terminal mode */
tsetmode(csiescseq.priv, 1, csiescseq.arg.ptr, csiescseq.narg);
break;
case 'm': /* SGR -- Terminal attribute (color) */
tsetattr(csiescseq.arg.ptr, csiescseq.narg);
break;
case 'n': /* DSR Device Status Report (cursor position) */
if (csiescseq.arg[0] == 6) {
n = snprintf(buffer.ptr, buffer.sizeof, "\033[%d;%dR",
term.c.y+1, term.c.x+1);
ttywrite(buffer.ptr, n, 0);
}
break;
case 'p': /* DECRQM -- Request Mode */
if (csiescseq.priv) {
/* Mode status request - respond with mode not recognized */
/* Format: CSI ? mode ; value $ y
where value is:
0 - not recognized
1 - set
2 - reset
3 - permanently set
4 - permanently reset */
DEFAULT(csiescseq.arg[0], 0);
n = snprintf(buffer.ptr, buffer.sizeof, "\033[?%d;0$y", csiescseq.arg[0]);
ttywrite(buffer.ptr, n, 0);
}
break;
case 'r': /* DECSTBM -- Set Scrolling Region */
if (csiescseq.priv) {
goto unknown;
} else {
DEFAULT(csiescseq.arg[0], 1);
DEFAULT(csiescseq.arg[1], term.row);
tsetscroll(csiescseq.arg[0]-1, csiescseq.arg[1]-1);
tmoveto(0, 0);
}
break;
case 's': /* DECSC -- Save cursor position (ANSI.SYS) */
tcursor(CursorMovement.SAVE);
break;
case 'u': /* DECRC -- Restore cursor position (ANSI.SYS) */
tcursor(CursorMovement.LOAD);
break;
case ' ':
switch (csiescseq.mode[1]) {
case 'q': /* DECSCUSR -- Set Cursor Style */
if (xsetcursor(csiescseq.arg[0]))
goto unknown;
break;
default:
goto unknown;
}
break;
}
}
void csiparse() {
char* p = csiescseq.buf.ptr;
char* np;
long v;
csiescseq.narg = 0;
if (*p == '?') {
csiescseq.priv = 1;
p++;
} else if (*p == '>') {
csiescseq.priv = 1;
p++;
}
csiescseq.buf[csiescseq.len] = '\0';
while (p < csiescseq.buf.ptr + csiescseq.len) {
np = null;
v = strtol(p, &np, 10);
if (np == p)
v = 0;
if (v == long.max || v < 0)
v = 0;
csiescseq.arg[csiescseq.narg++] = cast(int)v;
p = np;
if (*p != ';' || csiescseq.narg == ESC_ARG_SIZ) {
// Check for '$' separator used in mode status requests
if (*p == '$' && p + 1 < csiescseq.buf.ptr + csiescseq.len) {
p++; // Skip the '$'
}
break;
}
p++;
}
csiescseq.mode[0] = *p++;
csiescseq.mode[1] = (p < csiescseq.buf.ptr + csiescseq.len) ? *p : '\0';
// Removed verbose CSI parsed trace
}
void strparse() {
int c;
char* p = strescseq.buf;
strescseq.narg = 0;
strescseq.buf[strescseq.len] = '\0';
if (*p == '\0')
return;
while (strescseq.narg < STR_ARG_SIZ) {
strescseq.args[strescseq.narg++] = p;
while ((c = *p) != ';' && c != '\0')
++p;
if (c == '\0')
return;
*p++ = '\0';
}
}
void strhandle() {
char* p = null;
int j, narg, par;
term.esc &= ~(ESC_STR_END | ESC_STR);
strparse();
narg = strescseq.narg;
par = narg ? atoi(strescseq.args[0]) : 0;
switch (strescseq.type) {
case ']': /* OSC -- Operating System Command */
switch (par) {
case 0:
case 1:
case 2:
if (narg > 1) {
import x : xsettitle;
xsettitle(strescseq.args[1]);
}
return;
case 4: /* color set */
if (narg < 3)
break;
p = strescseq.args[2];
/* FALLTHROUGH */
goto case;
case 104: /* color reset */
j = (narg > 1) ? atoi(strescseq.args[1]) : -1;
if (xsetcolorname(j, p)) {
import x : xloadcols;
xloadcols();
redraw();
}
return;
default:
break;
}
break;
case 'k': /* old title set compatibility */
import x : xsettitle;
xsettitle(strescseq.args[0]);
return;
case 'P': /* DCS -- Device Control String */
case '_': /* APC -- Application Program Command */
case '^': /* PM -- Privacy Message */
return;
default:
break;
}
fprintf(stderr, "erresc: unknown str ");
strdump();
}
void strdump() {
int i;
uint c;
fprintf(stderr, "ESC%c", strescseq.type);
for (i = 0; i < strescseq.len; i++) {
c = strescseq.buf[i] & 0xff;
if (c == '\0') {
putc('\n', stderr);
return;
} else if (isprint(c)) {
putc(c, stderr);
} else if (c == '\n') {
fprintf(stderr, "(\\n)");
} else if (c == '\r') {
fprintf(stderr, "(\\r)");
} else if (c == 0x1b) {
fprintf(stderr, "(\\e)");
} else {
fprintf(stderr, "(%02x)", c);
}
}
fprintf(stderr, "ESC\\\n");
}
int xsetcolorname(int x, char* name) {
// Stub implementation - would set color palette
return 0;
}
void tdeftran(char ascii) {
static immutable char[] CS_GRAPHIC0 = "0";
static immutable char[] CS_UK = "A";
static immutable char[] CS_USA = "B";
static immutable char[] CS_MULTI = "5";
static immutable char[] CS_GER = "7";
static immutable char[] CS_FIN = "C";
const(char)* p = strchr(CS_GRAPHIC0.ptr, ascii);
if (p)
term.trantbl[term.icharset] = cast(char)(Charset.GRAPHIC0 + (p - CS_GRAPHIC0.ptr));
else if ((p = strchr(CS_UK.ptr, ascii)) != null)
term.trantbl[term.icharset] = cast(char)Charset.UK;
else if ((p = strchr(CS_USA.ptr, ascii)) != null)
term.trantbl[term.icharset] = cast(char)Charset.USA;
else if ((p = strchr(CS_MULTI.ptr, ascii)) != null)
term.trantbl[term.icharset] = cast(char)Charset.MULTI;
else if ((p = strchr(CS_GER.ptr, ascii)) != null)
term.trantbl[term.icharset] = cast(char)Charset.GER;
else if ((p = strchr(CS_FIN.ptr, ascii)) != null)
term.trantbl[term.icharset] = cast(char)Charset.FIN;
else
fprintf(stderr, "esc unhandled charset: ESC ( %c\n", ascii);
}
void tdectest(char c) {
int x, y;
if (c == '8') { /* DEC screen alignment test. */
for (x = 0; x < term.col; ++x) {
for (y = 0; y < term.row; ++y)
tsetchar('E', &term.c.attr, x, y);
}
}
}
int eschandle(uint ascii) {
switch (ascii) {
case '[': // CSI -- Control Sequence Introducer
term.esc |= ESC_CSI;
return 0;
case '#': // DEC test
term.esc |= ESC_TEST;
return 0;
case '%': // charset
term.esc |= ESC_UTF8;
return 0;
case 'P': // DCS -- Device Control String
case '_': // APC -- Application Program Command
case '^': // PM -- Privacy Message
case ']': // OSC -- Operating System Command
case 'k': // old title set compatibility
tstrsequence(ascii);
return 0;
case 'n': // LS2 -- Locking shift 2
case 'o': // LS3 -- Locking shift 3
term.charset = 2 + (ascii - 'n');
break;
case '(': // GZD4 -- set primary charset G0
case ')': // G1D4 -- set secondary charset G1
case '*': // G2D4 -- set tertiary charset G2
case '+': // G3D4 -- set quaternary charset G3
term.icharset = ascii - '(';
term.esc |= ESC_ALTCHARSET;
return 0;
case 'D': // IND -- Linefeed
if (term.c.y == term.bot) {
static if (isPatchEnabled!"SCROLLBACK_PATCH") {
tscrollup(term.top, 1, 1); // Save to history
} else {
tscrollup(term.top, 1);
}
} else {
tmoveto(term.c.x, term.c.y+1);
}
break;
case 'E': // NEL -- Next line
tnewline(1);
break;
case 'H': // HTS -- Horizontal tab stop
term.tabs[term.c.x] = 1;
break;
case 'M': // RI -- Reverse index
if (term.c.y == term.top) {
tscrolldown(term.top, 1);
} else {
tmoveto(term.c.x, term.c.y-1);
}
break;
case 'Z': // DECID -- Identify Terminal
ttywrite(vtiden, strlen(vtiden), 0);
break;
case 'c': // RIS -- Reset to initial state
treset();
resettitle();
import x : xloadcols;
xloadcols();
break;
case '=': // DECPAM -- Application keypad
xsetmode(1, WinMode.APPKEYPAD);
break;
case '>': // DECPNM -- Normal keypad
xsetmode(0, WinMode.APPKEYPAD);
break;
case '7': // DECSC -- Save Cursor
tcursor(CursorMovement.SAVE);
break;
case '8': // DECRC -- Restore Cursor
tcursor(CursorMovement.LOAD);
break;
case '\\': // ST -- String Terminator
if (term.esc & ESC_STR_END) {
strescseq.term = cast(char*)(STR_TERM_ST ~ "\0").ptr;
strhandle();
}
break;
case '1': // Possible application cursor keys or other mode
// Some terminals use ESC 1 for various purposes
// Just consume it silently to avoid display corruption
break;
default:
fprintf(stderr, "erresc: unknown sequence ESC 0x%02X '%c'\n",
cast(ubyte)ascii, isprint(ascii) ? cast(char)ascii : '.');
break;
}
return 1;
}