shithub: vim

ref: 920685146057a91a66a5c00ecd6362edf5f9b3fc
dir: vim/os_plan9.c

View raw version
/* vi:set ts=8 sts=4 sw=4:
 *
 * VIM - Vi IMproved	by Bram Moolenaar
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 * See README.txt for an overview of the Vim source code.
 */

/*
 * os_plan9.c
 *
 * Plan 9 system-dependent routines.
 */

#include <signal.h>
#include <setjmp.h>
#include <fcntl.h>
#include <lib9.h>
#include <draw.h>
#include <keyboard.h>
#include <event.h>
#include <plumb.h>
#include "vim.h"

/* Vim defines Display.  We need it for libdraw. */
#undef Display

/* We need to access the native Plan 9 alarm() since
 * it takes milliseconds rather than seconds. */
extern int _ALARM(unsigned long);
extern int _SLEEP(long);

enum {
    /* Text modes are in sync with term.c */
    TMODE_NORMAL  = 0,
    TMODE_REVERSE = 1,
    TMODE_BOLD    = 2,

    NCOLORS       = 256
};

static int scr_inited;
static Point fontsize;
static Point shellsize;
static Rectangle scrollregion;
static int currow; /* TODO use a Point */
static int curcol;
static Image *cursorsave;
static Image *colortab[NCOLORS];
static Image *fgcolor;
static Image *bgcolor;
static Font *normalfont;
static Font *boldfont;
static Font *curfont;
static int curmode; /* TODO remove this, use cterm_normal_fg_color instead for comparing */
static int plumbkey;
static int done;

/* Timeouts are handled using alarm() and a signal handler.
 * When an alarm happens during a syscall, the syscall is
 * interrupted.  We use setjmp() to handle control flow from
 * interrupted library functions. */
static int interruptable;
static jmp_buf timeoutjmpbuf;

static void err9(char *msg) {
    char errs[256];
    /* TODO */
    errstr(errs, sizeof errs);
    fprintf(stderr, "%s: %s\n", msg, errs);
    exit(1);
}

/* Error handler for libdraw */
static void derr(Display*, char *msg) {
    if (interruptable && strcmp(msg, "eof on event pipe") == 0) {
        longjmp(timeoutjmpbuf, 1);
    }
    err9(msg);
}

static void drain_plumb_events(void) {
    Event e;
    Plumbmsg *m;
    char *addr;
    int l = ECMD_ONE;

    while(ecanread(plumbkey)){
	eread(plumbkey, &e);
	m = e.v;
	if(m == nil)
		return;
	addr = plumblookup(m->attr, "addr");
	if(addr)
		l = atoi(addr);
	do_ecmd(0, (char_u*)m->data, NULL, NULL, (linenr_T)l, ECMD_HIDE);
	shell_resized();
    }
}

static void start_plumber_thread(void)
{
	switch (rfork(RFPROC|RFMEM)){
	case -1:
		fprintf(stderr, "rfork failed\n");
		return;
	case 0:
		while(!done){
			drain_plumb_events();
			_SLEEP(100);
		}
		exit(0);
	default:
		break;
	}
}

int mch_has_wildcard(char_u *p) {
    for (; *p; mb_ptr_adv(p)) {
        if (*p == '\\' && p[1] != NUL) {
            ++p;
        } else if (vim_strchr((char_u *)"*?[{`'$", *p) != NULL ||
                (*p == '~' && p[1] != NUL)) {
            return TRUE;
        }
    }
    return FALSE;
}

int mch_has_exp_wildcard(char_u *p) {
    for (; *p; mb_ptr_adv(p))
    {
        if (*p == '\\' && p[1] != NUL)
            ++p;
        else
            if (vim_strchr((char_u *) "*?[{'", *p) != NULL)
                return TRUE;
    }
    return FALSE;
}

int mch_expandpath(garray_T *gap, char_u *pat, int flags) {
    return unix_expandpath(gap, pat, 0, flags, FALSE);
}

int mch_isdir(char_u *name) {
    struct stat st;
    if (stat((char*)name, &st) != 0) {
        return FALSE;
    }
    return S_ISDIR(st.st_mode) ? TRUE : FALSE;
}

static int executable_file(char_u *name)
{
    struct stat	st;

    if (stat((char *)name, &st))
        return 0;
    return S_ISREG(st.st_mode) && mch_access((char *)name, X_OK) == 0;
}

int mch_isFullName(char_u *fname) {
    return fname[0] == '/' || fname[0] == '#';
}

int mch_can_exe(char_u *name) {
    char_u	*buf;
    char_u	*p, *e;
    int		retval;

    /* If it's an absolute or relative path don't need to use $path. */
    if (mch_isFullName(name) || (name[0] == '.' && (name[1] == '/'
				      || (name[1] == '.' && name[2] == '/'))))
        return executable_file(name);

    p = (char_u *)getenv("path");
    if (p == NULL || *p == NUL)
        return -1;
    buf = alloc((unsigned)(STRLEN(name) + STRLEN(p) + 2));
    if (buf == NULL)
        return -1;

    /*
     * Walk through all entries in $PATH to check if "name" exists there and
     * is an executable file.
     */
    for (;;)
    {
        e = (char_u *)strchr((char *)p, '\01');
        if (e == NULL)
            e = p + STRLEN(p);
        if (e - p <= 1)		/* empty entry means current dir */
            STRCPY(buf, "./");
        else
        {
            vim_strncpy(buf, p, e - p);
            add_pathsep(buf);
        }
        STRCAT(buf, name);
        retval = executable_file(buf);
        if (retval == 1)
            break;

        if (*e != '\01')
            break;
        p = e + 1;
    }

    vim_free(buf);
    return retval;
}

int mch_dirname(char_u *buf, int len) {
    return (getcwd((char*)buf, len) ? OK : FAIL);
}

long mch_getperm(char_u *name) {
    struct stat st;
    if (stat((char*)name, &st) < 0) {
        return -1;
    }
    return st.st_mode;
}

int mch_setperm(char_u *name, long perm) {
    return chmod((char*)name, (mode_t)perm) == 0 ? OK : FAIL;
}

int mch_remove(char_u *name) {
    return remove((char*)name);
}

void mch_hide(char_u*) {
    /* Do nothing */
}

/* getuser() and sysname() can be implemented using this. */
static int read_value_from_file(char *fname, char_u *s, int len) {
    int fd;
    int n;
    fd = open(fname, O_RDONLY);
    if (fd < 0) {
        vim_strncpy(s, (char_u*)"none", len - 1);
        return FAIL;
    }
    n = read(fd, s, len - 1);
    if (n <= 0) {
        vim_strncpy(s, (char_u*)"none", len - 1);
    } else {
        s[n] = '\0';
    }
    close(fd);
    return (n > 0) ? OK : FAIL;
}

int mch_get_user_name(char_u *s, int len) {
    return read_value_from_file("/dev/user", s, len);
}

void mch_get_host_name(char_u *s, int len) {
    read_value_from_file("/dev/sysname", s, len);
}

void mch_settmode(int) {
    /* Do nothing */
}

int mch_screenmode(char_u*) {
    /* Always fail */
    EMSG(_(e_screenmode));
    return FAIL;
}

static void scr_stamp_cursor(void) {
    if (cursorsave) {
        draw(cursorsave, Rect(0, 0, fontsize.x, fontsize.y),
                screen, nil, Pt(screen->clipr.min.x + curcol * fontsize.x,
                    screen->clipr.min.y + currow * fontsize.y));
        drawop(screen, Rect(screen->clipr.min.x + curcol * fontsize.x,
                    screen->clipr.min.y + currow * fontsize.y,
                    screen->clipr.min.x + (curcol + 1) * fontsize.x,
                    screen->clipr.min.y + (currow + 1) * fontsize.y),
                colortab[cterm_normal_fg_color - 1], cursorsave, ZP, SoverD);
    }
}

static void scr_unstamp_cursor(void) {
    if (cursorsave) {
        Rectangle r;
        r = Rect(screen->clipr.min.x + curcol * fontsize.x,
                screen->clipr.min.y + currow * fontsize.y,
                screen->clipr.min.x + (curcol + 1) * fontsize.x,
                screen->clipr.min.y + (currow + 1) * fontsize.y);
        draw(screen, r, cursorsave, nil, ZP);
    }
}

static void scr_pos(int row, int col) {
    currow = row;
    curcol = col;
}

static void scr_clear(void) {
    scr_pos(0, 0);
    draw(screen, screen->clipr, bgcolor, nil, ZP);
    draw(cursorsave, Rect(0, 0, fontsize.x, fontsize.y), bgcolor, nil, ZP);
}

static void scr_scroll_down(int nlines) {
    Rectangle r;
    Point pt;

    /* Copy up visible part of scroll region */
    r = Rect(screen->clipr.min.x + scrollregion.min.x * fontsize.x,
            screen->clipr.min.y + scrollregion.min.y * fontsize.y,
            screen->clipr.min.x + scrollregion.max.x * fontsize.x,
            screen->clipr.min.y + (scrollregion.max.y - nlines) * fontsize.y);
    pt = Pt(screen->clipr.min.x + scrollregion.min.x * fontsize.x,
            screen->clipr.min.y + (scrollregion.min.y + nlines) * fontsize.y);
    draw(screen, r, screen, nil, pt);

    /* Clear newly exposed part of scroll region */
    r.min.y = r.max.y;
    r.max.y = screen->clipr.min.y + scrollregion.max.y * fontsize.y;
    draw(screen, r, bgcolor, nil, ZP);
}

static void scr_scroll_up(int nlines) {
    Rectangle r;
    Point pt;

    /* Copy down visible part of scroll region */
    r = Rect(screen->clipr.min.x + scrollregion.min.x * fontsize.x,
            screen->clipr.min.y + (scrollregion.min.y + nlines) * fontsize.y,
            screen->clipr.min.x + scrollregion.max.x * fontsize.x,
            screen->clipr.min.y + scrollregion.max.y * fontsize.y);
    pt = Pt(screen->clipr.min.x + scrollregion.min.x * fontsize.x,
            screen->clipr.min.y + scrollregion.min.y * fontsize.y);
    draw(screen, r, screen, nil, pt);

    /* Clear newly exposed part of scroll region */
    r.max.y = r.min.y;
    r.min.y = screen->clipr.min.y + scrollregion.min.y * fontsize.y;
    draw(screen, r, bgcolor, nil, ZP);
}

static void scr_set_color(Image **cp, int c) {
    if (c >= 0 && c < NCOLORS) {
        *cp = colortab[c];
    }
}

void mch_set_normal_colors(void) {
    char_u	*p;
    int		n;

    if (cterm_normal_fg_color == 0) {
	cterm_normal_fg_color = 1;
    }
    if (cterm_normal_bg_color == 0) {
	cterm_normal_bg_color = 16;
    }
    if (T_ME[0] == ESC && T_ME[1] == '|')
    {
	p = T_ME + 2;
	n = getdigits(&p);
	if (*p == 'm' && n > 0)
	{
	    cterm_normal_fg_color = (n & 0xf) + 1;
	    cterm_normal_bg_color = ((n >> 4) & 0xf) + 1;
	}
    }

    scr_set_color(&bgcolor, cterm_normal_bg_color - 1);
    scr_set_color(&fgcolor, cterm_normal_fg_color - 1);
}

static void scr_tmode(int mode) {
    Image *tmp;
    if ((curmode & TMODE_REVERSE) != (mode & TMODE_REVERSE)) {
        tmp = fgcolor;
        fgcolor = bgcolor;
        bgcolor = tmp;
    }
    if (mode & TMODE_BOLD) {
        curfont = boldfont;
    } else {
        curfont = normalfont;
    }
    if (mode == TMODE_NORMAL) {
	scr_set_color(&bgcolor, cterm_normal_bg_color - 1);
	scr_set_color(&fgcolor, cterm_normal_fg_color - 1);
    }
    curmode = mode;
}

/* Read a number and return bytes consumed. */
static int scr_escape_number(char *p, int len, int *n) {
    int num;
    int chlen;
    if (len == 0) {
        return -1;
    }
    num = 0;
    chlen = 0;
    while (len && isdigit(*p)) {
        num = num * 10 + (*p - '0');
        p++;
        len--;
        chlen++;
    }
    *n = num;
    return chlen;
}

/* Handle escape sequence and return number of bytes consumed. */
static int scr_escape_sequence(char *p, int len) {
    int nlines;
    int n1;
    int n2;
    int i;
    int chlen;

    if (len == 0) {
        return 0;
    }

    chlen = 1;
    switch (*p) {
        case 'J': /* clear screen */
            scr_clear();
            break;

        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
            n1 = -1;
            n2 = -1;

            /* First number */
            i = scr_escape_number(p, len, &n1);
            if (i == -1) {
                /* TODO */
                fprintf(stderr, "scr_escape_sequence: escape at end of string\n");
                exit(1);
            }
            p += i;
            len -= i;
            chlen += i;

            /* Optional second number */
            if (len && *p == ';') {
                p += 1;
                len -= 1;
                chlen += 1;
                i = scr_escape_number(p, len, &n2);
                if (i == -1) {
                    /* TODO */
                    fprintf(stderr, "scr_escape_sequence: missing second number\n");
                    exit(1);
                }
                p += i;
                len -= i;
                chlen += i;
            }

            if (len == 0) {
                /* TODO */
                fprintf(stderr, "scr_escape_sequence: early end of escape sequence\n");
		trash_input_buf();
		return 0;
            }

            switch (*p) {
                case 'b': /* background color */
                    scr_set_color(&bgcolor, n1);
                    break;

                case 'f': /* foreground color */
                    scr_set_color(&fgcolor, n1);
                    break;

                case 'H': /* cursor motion */
                    scr_pos(n1, n2);
                    break;

                case 'm': /* mode */
                    scr_tmode(n1);
                    break;

                case 'R': /* scroll region */
                    scrollregion.min.y = n1;
                    scrollregion.max.y = n2 + 1;
                    break;

                case 'V': /* scroll region vertical */
                    scrollregion.min.x = n1;
                    scrollregion.max.x = n2 + 1;
                    break;

                default:
                    /* TODO */
                    fprintf(stderr, "scr_escape_sequence: unimplemented (p=%c)\n", *p);
                    exit(1);
            }
            break;

        case 'K': /* clear to end of line */
            draw(screen, Rect(screen->clipr.min.x + curcol * fontsize.x,
                        screen->clipr.min.y + currow * fontsize.y,
                        screen->clipr.max.x,
                        screen->clipr.min.y + (currow + 1) * fontsize.y),
                    bgcolor, nil, ZP);
            break;

        case 'L': /* add new blank line */
            p++;
            nlines = 1;
            while (len >= 3 && p[0] == '\x1b' && p[1] == '[' && p[2] == 'L') {
                nlines++;
                len -= 3;
                p += 3;
                chlen += 3;
            }
            /* TODO what if nlines >= scroll region size */
            scr_scroll_up(nlines);
            break;

        default:
            /* TODO */
            fprintf(stderr, "scr_escape_sequence: unhandled sequence (p=%c)\n", *p);
            exit(1);
    }

    return chlen;
}

/* Handle special characters. */
static int write_special(char *p, int len) {
    int n;
    int nbytes;
    if (len == 0) {
        return 0;
    }
    nbytes = 0;
    while (len > 0) {
        switch (*p) {
            case '\a': /* Bell */
                /* There is no bell on Plan 9. */
                break;

            case '\b': /* Backspace */
                if (curcol > scrollregion.min.x) {
                    scr_pos(currow, curcol - 1);
                } else if (currow > scrollregion.min.y) {
                    scr_pos(currow - 1, scrollregion.max.x - 1);
                }
                break;

            case '\n': /* New line */
                for (n = 0; n < len && *p == '\n'; n++) {
                    p++;
                }
                if (currow + n >= scrollregion.max.y) {
                    scr_scroll_down(currow + n - scrollregion.max.y + 1);
                    scr_pos(scrollregion.max.y - 1, scrollregion.min.x);
                } else {
                    scr_pos(currow + n, scrollregion.min.x);
                }
                len -= n;
                nbytes += n;
                continue; /* process next character */

            case '\r': /* Carriage return */
                curcol = scrollregion.min.x;
                break;

            case '\x1b': /* Escape sequences */
                if (len > 1 && p[1] == '[') {
                    n = 2;
                    n += scr_escape_sequence(p + 2, len - 2);
                    p += n;
                    len -= n;
                    nbytes += n;
                    continue; /* process next character */
                }
                break;

            default: /* Normal character */
                return nbytes;
        }
        p++;
        len--;
        nbytes++;
    }
    return nbytes;
}

/* Draw normal characters. */
static int write_str(char *p, int len) {
    int nbytes, n, m, rn, rl;
    Rune r;

    if (len == 0) {
        return 0;
    }
    for (nbytes = 0; nbytes < len &&
            p[nbytes] != '\a' && p[nbytes] != '\b' &&
            p[nbytes] != '\n' && p[nbytes] != '\r' &&
            p[nbytes] != '\x1b';
            len--, nbytes++)
        ;
    n = nbytes;
    rl = 0;
    while (n > 0) {
	rn = chartorune(&r, p);
	rl += rn;
        m = (curcol + 1 >= scrollregion.max.x) ?
            scrollregion.max.x - curcol : 1;
        if (m == 0) {
            break;
        }
        runestringnbg(screen, Pt(screen->clipr.min.x + curcol * fontsize.x,
                    screen->clipr.min.y + currow * fontsize.y),
                fgcolor, ZP, curfont, &r, 1, bgcolor, ZP);
        curcol += m;
        if (curcol == scrollregion.max.x) {
            curcol = scrollregion.min.x;
            if (currow == scrollregion.max.y - 1) {
                scr_scroll_down(1);
            } else {
                currow++;
            }
        }
        p += rn;
        n -= rn;
    }
    return rl;
}

void mch_write(char_u *p, int len) {
    int slen;
    scr_unstamp_cursor();
    while (len > 0) {
        /* Handle special characters. */
        slen = write_special((char*)p, len);
        p += slen;
        len -= slen;

        /* Write as many normal characters as possible. */
        slen = write_str((char*)p, len);
        p += slen;
        len -= slen;
    }
    scr_stamp_cursor();
    flushimage(display, 1);
}

static void sigalrm(int, char*, void*) {
    /* Do nothing */
}

static void send_mouse_event(int e, int x, int y) {
    char_u string[8];
    int row;
    int col;
    int n;

    n = 0;
    row = y/fontsize.y;
    col = x/fontsize.x;
    string[n++] = CSI;
    string[n++] = 'M';
    string[n++] = e;
    string[n++] = (char_u)(col + ' ' + 1);
    string[n++] = (char_u)(row + ' ' + 1);
    add_to_input_buf(string, n);
}

/* Don't allow mouse events to accumulate. */
static void process_mouse_events(void) {
    static int last_button = 0;
    Mouse m;
    Point pt;
    int code;

    while (ecanmouse()) {
	m = emouse();
	pt = subpt(m.xy, screen->r.min);
	if(last_button) {
	    if(m.buttons) {
		code = MOUSE_DRAG;
	    } else {
		code = MOUSE_RELEASE;
		last_button = 0;
	    }
	} else {
	    if(m.buttons&1) {
		code = MOUSE_LEFT;
		last_button = 1;
	    } else if(m.buttons&2) {
		code = MOUSE_MIDDLE;
		last_button = 2;
	    } else if(m.buttons&4) {
		code = MOUSE_RIGHT;
		last_button = 4;
	    } else if(m.buttons&8) {
		code = MOUSEWHEEL_LOW;
		last_button = 8;
	    } else if(m.buttons&16) {
		code = MOUSEWHEEL_LOW|1;
		last_button = 16;
	    } else {
		continue;
	    }
	    code |= 0x20;
	}
	send_mouse_event(code, pt.x, pt.y);
    }
}

int RealWaitForChar(int, long msec, int*) {
    Rune rune;
    char utf[UTFmax];
    int len;

    if (msec == 0 && !ecankbd() && !ecanmouse()) {
        return 0;
    }
    if (msec > 0) {
        if (setjmp(timeoutjmpbuf)) {
            /* We arrive here if the alarm occurred and a library
             * function called drawerror() due to an interrupted
             * syscall. */
            _ALARM(0);
            interruptable = 0;
            return 0;
        }
        interruptable = 1;
        _ALARM((unsigned long)msec);
    }
    if(ecankbd()) {
	/* TODO garbage collect */
	rune = ekbd();
	if (msec > 0) {
	    _ALARM(0);
	    interruptable = 0;
	}
	if (rune == Ctrl_C && ctrl_c_interrupts) {
	    got_int = TRUE;
	    return 0;
	}
	if(rune == '\n'){
	    utf[0] = CAR;
	    len = 1;
	}else
	    len = runetochar(utf, &rune);
	add_to_input_buf((char_u*)utf, len); /* TODO escape K_SPECIAL? */
	return len > 0;
    } else if(ecanmouse()) {
        process_mouse_events();
    }
    return 0;
}

int mch_inchar(char_u *buf, int maxlen, long msec, int) {
    if (vim_is_input_buf_empty() && RealWaitForChar(0, msec, NULL) == 0) {
	return 0;
    }
    return read_from_input_buf(buf, maxlen);
}

int mch_char_avail(void) {
    return ecankbd() || ecanmouse();
}

void mch_delay(long msec, int ignoreinput) {
    if (ignoreinput) {
        sleep(msec / 1000);
    } else {
        RealWaitForChar(0, msec, NULL);
    }
}

int mch_nodetype(char_u *name) {
    struct stat st;
    if (stat((char*)name, &st) < 0) {
        return NODE_NORMAL;
    }
    if (S_ISREG(st.st_mode) || S_ISDIR(st.st_mode)) {
        return NODE_NORMAL;
    }
    return NODE_WRITABLE;
}

int mch_FullName(char_u *fname, char_u *buf, int len, int force) {
    int		l;
    char_u	olddir[MAXPATHL];
    char_u	*p;
    int		retval = OK;

    /* expand it if forced or not an absolute path */
    if (force || !mch_isFullName(fname))
    {
        /*
         * If the file name has a path, change to that directory for a moment,
         * and then do the getwd() (and get back to where we were).
         * This will get the correct path name with "../" things.
         */
        if ((p = vim_strrchr(fname, '/')) != NULL)
        {
            /* Only change directory when we are sure we can return to where
             * we are now.  After doing "su" chdir(".") might not work. */
            if ( (mch_dirname(olddir, MAXPATHL) == FAIL ||
                        mch_chdir((char *)olddir) != 0))
            {
                p = NULL;	/* can't get current dir: don't chdir */
                retval = FAIL;
            }
            else
            {
                /* The directory is copied into buf[], to be able to remove
                 * the file name without changing it (could be a string in
                 * read-only memory) */
                if (p - fname >= len)
                    retval = FAIL;
                else
                {
                    vim_strncpy(buf, fname, p - fname);
                    if (mch_chdir((char *)buf))
                        retval = FAIL;
                    else
                        fname = p + 1;
                    *buf = NUL;
                }
            }
        }
        if (mch_dirname(buf, len) == FAIL)
        {
            retval = FAIL;
            *buf = NUL;
        }
        if (p != NULL)
        {
            l = mch_chdir((char *)olddir);
            if (l != 0)
                EMSG(_(e_prev_dir));
        }

        l = STRLEN(buf);
        if (l >= len)
            retval = FAIL;
        else
        {
            if (l > 0 && buf[l - 1] != '/' && *fname != NUL
                    && STRCMP(fname, ".") != 0)
                STRCAT(buf, "/");
        }
    }

    /* Catch file names which are too long. */
    if (retval == FAIL || STRLEN(buf) + STRLEN(fname) >= len)
        return FAIL;

    /* Do not append ".", "/dir/." is equal to "/dir". */
    if (STRCMP(fname, ".") != 0)
        STRCAT(buf, fname);

    return OK;
}

long mch_get_pid(void) {
    return (long)getpid();
}

int mch_input_isatty(void) {
    return isatty(0) ? TRUE : FALSE;
}

int mch_setenv(char *var, char *value, int x) {
    char buf[100];
    int fd;
    int len;

    snprintf(buf, sizeof buf, "/env/%s", var);

    /* Check for overwrite */
    if (!x) {
        struct stat st;
        if (stat(buf, &st) == 0) {
            return -1;
        }
    }

    /* Write the value */
    fd = creat(buf, 0666);
    if (fd < 0) {
        return -1;
    }
    len = strlen(value);
    if (write(fd, value, len) != len) {
        close(fd);
        return -1;
    }
    close(fd);
    return 0;
}

void mch_suspend(void) {
    suspend_shell();
}

static void update_shellsize(void) {
    shellsize = Pt((screen->clipr.max.x - screen->clipr.min.x) / fontsize.x,
                   (screen->clipr.max.y - screen->clipr.min.y) / fontsize.y);
}

int mch_get_shellsize(void) {
    update_shellsize();
    Rows = shellsize.y;
    Columns = shellsize.x;
    scrollregion.min.x = 0;
    scrollregion.min.y = 0;
    scrollregion.max.x = shellsize.x;
    scrollregion.max.y = shellsize.y;
    return OK;
}

void mch_set_shellsize(void) {
    /* Do nothing */
}

void mch_new_shellsize(void) {
    /* Do nothing */
}

void mch_breakcheck(void) {
    if (scr_inited) {
	/* Read into input buffer and check for Ctrl-C. */
	RealWaitForChar(0, 0, NULL);
    }
}

/* Called by libevent whenever a resize event occurs. */
void eresized(int renew) {
    if (renew) {
        if (getwindow(display, Refnone) < 0) {
            err9("resize failed");
        }
    }
    shell_resized();
}

static void init_colors(void) {
    int i;
    ulong colors[] = {
	/* gruvbox dark theme */
	0x282828ff, 0xcc241dff, 0x98971aff, 0xd79921ff,
	0x458588ff, 0xb16286ff, 0x689d6aff, 0xa89984ff,
	0x928374ff, 0xfb4934ff, 0xb8bb26ff, 0xfabd2fff,
	0x83a598ff, 0xd3869bff, 0x8ec07cff,0xebdbb2ff,
    };
    for (i = 0; i < 16; i++) {
        colortab[i] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, colors[i]);
        if (colortab[i] == nil) {
            err9("allocimage failed");
        }
    }
    colortab[24] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x076678ff);
    colortab[66] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x427b58ff);
    colortab[88] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x9d0006ff);
    colortab[96] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x8f3f71ff);
    colortab[100] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x79740eff);
    colortab[108] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x8ec07cff);
    colortab[109] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x83a598ff);
    colortab[130] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xaf3a03ff);
    colortab[136] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xb57614ff);
    colortab[142] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xb8bb26ff);
    colortab[167] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xfb4934ff);
    colortab[175] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xd3869bff);
    colortab[208] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xfe8019ff);
    colortab[214] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xfabd2fff);
    colortab[223] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xebdbb2ff);
    colortab[228] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xf2e5bcff);
    colortab[229] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xfbf1c7ff);
    colortab[230] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xf9f5d7ff);
    colortab[234] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x1d2021ff);
    colortab[235] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x282828ff);
    colortab[236] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x32302fff);
    colortab[237] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x3c3836ff);
    colortab[239] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x504945ff);
    colortab[241] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x665c54ff);
    colortab[243] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x7c6f64ff);
    colortab[244] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x928374ff);
    colortab[245] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x928374ff);
    colortab[246] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xa89984ff);
    colortab[248] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xbdae93ff);
    colortab[250] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xd5c4a1ff);

    mch_set_normal_colors();
}

static void free_colors(void) {
    int i;
    bgcolor = nil;
    fgcolor = nil;
    for (i = 0; i < NCOLORS; i++) {
        freeimage(colortab[i]);
        colortab[i] = nil;
    }
}

static void init_fonts(void) {
    char *n;

    n = getenv("font");
    if(n)
	normalfont = openfont(display, n);
    else
	normalfont = openfont(display, "/lib/font/bit/fixed/unicode.9x18.font");
    if (normalfont == nil) {
        err9("openfont normal failed");
    }
    boldfont = openfont(display, n ? n : "/lib/font/bit/fixed/unicode.9x18B.font");
    if (boldfont == nil) {
        err9("openfont bold failed");
    }
    curfont = normalfont;
}

static void free_fonts(void) {
    freefont(normalfont);
    freefont(boldfont);
    curfont = nil;
}

void mch_early_init(void) {
    rfork(RFENVG | RFNOTEG);
}

int mch_check_win(int, char**) {
    return OK;
}

static void scr_init(void) {
    if (initdraw(derr, nil, "Vim") == -1) {
        err9("initdraw failed");
    }

    init_colors();
    init_fonts();
    fontsize = stringsize(curfont, "A");
    cursorsave = allocimage(display, Rect(0, 0, fontsize.x, fontsize.y), screen->chan, 0, DBlack);
    mch_get_shellsize();
    scr_clear();
    scr_stamp_cursor();
    flushimage(display, 1);

    /* Mouse events must be enabled to receive window resizes. */
    einit(Emouse | Ekeyboard);
    plumbkey = eplumb(512, "edit");
    if(plumbkey >= 0)
	    start_plumber_thread();
    scr_inited = TRUE;
}

void mch_init(void) {
    done = FALSE;
    signal(SIGALRM, sigalrm);
    scr_init();

    /*
     * Force UTF-8 output no matter what the value of 'encoding' is.
     * did_set_string_option() in option.c prohibits changing 'termencoding'
     * to something else than UTF-8.
     */
    set_option_value((char_u *)"termencoding", 0L, (char_u *)"utf-8", 0);

#if defined(FEAT_CLIPBOARD)
    clip_init(TRUE);
#endif
}

void mch_exit(int r) {
    done = TRUE;
    ml_close_all(TRUE);    /* remove all memfiles */
    /* libdraw shuts down automatically on exit */
    exit(r);
}

#if defined(FEAT_TITLE)
void mch_settitle(char_u *title, char_u *) {
    int fd;
    fd = open("/dev/label", O_WRONLY);
    if (fd < 0) {
        /* Not running under rio. */
        return;
    }
    write(fd, (char*)title, strlen((char*)title));
    close(fd);
}

void mch_restore_title(int) {
    /* No need to restore - libdraw does this automatically. */
}

int mch_can_restore_title(void) {
    return TRUE;
}

int mch_can_restore_icon(void) {
    return FALSE;
}
#endif

#if defined(FEAT_CLIPBOARD)
int clip_mch_own_selection(VimClipboard*) {
    /* We never own the clipboard. */
    return FAIL;
}

void clip_mch_lose_selection(VimClipboard*) {
    /* Do nothing */
}

void clip_mch_request_selection(VimClipboard *cbd) {
    int fd;
    char_u *data;
    char_u *newdata;
    long_u len;  /* used length */
    long_u mlen; /* max allocated length */
    ssize_t n;
    int type;

    fd = open("/dev/snarf", O_RDONLY);
    if (fd < 0) {
        /* Not running under rio. */
        return;
    }
    n = 0;
    len = 0;
    mlen = 0;
    data = NULL;
    do {
        len += n;
        mlen += 4096;
        newdata = vim_realloc(data, mlen);
        if (!newdata) {
            n = -1;
            break;
        }
        data = newdata;
        n = read(fd, data, 4096);
        /* TODO breakcheck? */
    } while (n == 4096);
    if (n >= 0) {
        len += n;
        type = memchr(data, '\n', len) ? MLINE : MCHAR;
        clip_yank_selection(type, data, len, cbd);
    }
    vim_free(data);
    close(fd);
}

void clip_mch_set_selection(VimClipboard *cbd) {
    char_u *data;
    long_u len;
    int type;
    int fd;

    fd = open("/dev/snarf", O_RDONLY);
    if (fd < 0)
	return;
    /* If the '*' register isn't already filled in, fill it in now. */
    cbd->owned = TRUE;
    clip_get_selection(cbd);
    cbd->owned = FALSE;

    type = clip_convert_selection(&data, &len, cbd);
    if (type < 0) {
        return;
    }
    fd = open("/dev/snarf", O_WRONLY);
    if (fd >= 0) {
        write(fd, data, len);
        close(fd);
    }
    vim_free(data);
}
#endif

#if defined(FEAT_MOUSE)
void mch_setmouse(int) {
    /* Do nothing.  Mouse needed for clipboard. */
}
#endif

int
mch_call_shell(char_u *cmd, int options)
{
    pid_t pid;
    int status;

    if(options & SHELL_COOKED)
	settmode(TMODE_COOK);

    pid = rfork(RFPROC|RFFDG|RFENVG|RFNOTEG);
    if(pid < 0)
	return -1;
    if(pid == 0){
	if (cmd) 
	    execl("/bin/rc", "rc", "-c", cmd, NULL);
	 else 
	    execl("/bin/rc", "rc", NULL);
	
	_exit(122);
    }
    waitpid(pid, &status, 0);
    return status;
}