3126 lines
84 KiB
D
3126 lines
84 KiB
D
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;
|
||
}
|
||
|