dst/source/st.d
2025-06-26 13:47:07 -05:00

3126 lines
84 KiB
D
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
}