~glacambre/firenvim

f8a025cb429f54a467247c0a8a42435e9e67a017 — glacambre 2 years ago ab969ba
Add colors
7 files changed, 247 insertions(+), 131 deletions(-)

A src/CSSUtils.ts
M src/Grid.ts
M src/Neovim.ts
M src/NeovimFrame.html
A src/Redraw.ts
M src/firenvim.d.ts
M tslint.json
A src/CSSUtils.ts => src/CSSUtils.ts +15 -0
@@ 0,0 1,15 @@

export function toHexCss(n: number) {
    const str = n.toString(16);
    // Pad with leading zeros
    return "#" + (new Array(6 - str.length)).fill("0").join("") + str;
}

export function toHighlightClassName(n: number) {
    return "nvim_highlight_" + n;
}

export function toCss(highlights: HighlightArray) {
    return highlights.reduce((css, elem, id) =>
        `${css} .${toHighlightClassName(id)} { background: ${elem.background}; color: ${elem.foreground}; }`, "");
}

M src/Grid.ts => src/Grid.ts +102 -7
@@ 1,14 1,109 @@
import { toHighlightClassName } from "./CSSUtils";
import { Cursor } from "./Cursor";

export class Grid {
    public cursor = new Cursor(0, 0);
    public data: any[][];
function new_cell_proxy(parent: any) {
    const span = document.createElement("span");
    span.innerText = " ";
    span.className = "nvim_cell";
    (span as any).highlight = 0;
    parent.appendChild(span);
    return new Proxy(span, {
        get: (target, prop, receiver) => {
            if (prop === "highlight") {
                return (target as any).highlight;
            }
            if (prop === "value") {
                return target.innerText;
            }
            if (prop === "clear") {
                return () => { target.innerText = " "; };
            }
            if (prop === "setCursor") {
                return () => { target.classList.add("nvim_cursor"); };
            }
            if (prop === "removeCursor") {
                return () => { target.classList.remove("nvim_cursor"); };
            }
            throw new Error(`Accessing non-existing property ${prop.toString()} of cell.`);
        },
        set: (target, prop, value) => {
            if (prop === "highlight") {
                target.classList.remove(toHighlightClassName((target as any).highlight));
                (target as any).highlight = value;
                target.classList.add(toHighlightClassName((target as any).highlight));
                return true;
            }
            if (prop === "value") {
                target.innerText = value;
                return true;
            }
            throw new Error(`Setting non-existing property ${prop.toString()} of cell.`);
        },
    });
}

    constructor(public width: number, public height: number) {
        this.clear();
function new_row_proxy(width: number, parent: any) {
    const row: any[] = [];
    const span = document.createElement("span");
    span.className = "nvim_row";
    for (let i = 0; i < width; ++i) {
        row.push(new_cell_proxy(span));
    }
    parent.appendChild(span);
    function clear() {
        row.forEach(cell => cell.clear());
    }
    return new Proxy(row, {
        get: (target: any, prop: any, receiver) => {
            const p = Number.parseInt(prop, 10);
            if (p >= 0 && p < target.length) {
                return target[p];
            }
            if (prop === "clear") {
                return clear;
            }
            throw new Error(`Accessing non-exisiting property ${prop} of row.`);
        },
        set: (target: any, prop: number, value) => {
            target[prop].value = value;
            return true;
        },
    });
}

    public clear() {
        this.data = (new Array(this.height)).fill("").map(_ => (new Array(this.width)).fill(" "));
export function new_grid(width: number, height: number, elem: any) {
    const gridElem = document.createElement("div");
    const grid: any[] = [];
    for (let i = 0; i < height; ++i) {
        grid.push(new_row_proxy(width, gridElem));
    }
    elem.appendChild(gridElem);
    let cursor = new Cursor(0, 0);
    function clear() {
        grid.forEach(row => row.clear());
    }
    function cursor_goto(x: number, y: number) {
        grid[cursor.y][cursor.x].removeCursor();
        cursor = new Cursor(x, y);
        grid[cursor.y][cursor.x].setCursor();
    }
    return new Proxy(grid, {
        get: (target, prop: any, receiver) => {
            const p = Number.parseInt(prop, 10);
            if (p >= 0 && p < target.length) {
                return target[p];
            }
            if (prop === "clear") {
                return clear;
            }
            if (prop === "cursor_goto") {
                return cursor_goto;
            }
            throw new Error(`Accessing non-exisiting property ${prop} of row.`);
        },
        set: (target, prop: any, value) => {
            console.log(target, prop, value);
            return true;
        },
    });
}

M src/Neovim.ts => src/Neovim.ts +3 -116
@@ 1,127 1,14 @@
import { Grid } from "./Grid";
import { onRedraw } from "./Redraw";
import { Stdin } from "./Stdin";
import { Stdout } from "./Stdout";

type NvimParameters = Array<[string, string]>;

interface INvimApiInfo {
    0: number;
    1: {
        error_types: {[key: string]: { id: number }},
        functions: Array<{
            deprecated_since?: number,
            method: boolean,
            name: string,
            parameters: NvimParameters,
            return_type: string,
            since: number,
        }>,
        types: {
            [key: string]: { id: number, prefix: string },
        },
        ui_events: Array<{
            name: string,
            parameters: NvimParameters,
            since: number,
        }>,
        ui_options: string[],
        version: {
            api_compatible: number,
            api_level: number,
            api_prerelease: boolean,
            major: number,
            minor: number,
            patch: number,
        },
    };
}

type HighlightUpdate = [number, { foreground: number, background: number }];
type ResizeUpdate = [number, number, number];
type GotoUpdate = [number, number, number];
type LineUpdate = [number, number, number, Array<[string, number, number?]>];

type HighlightMap = Map<number, { foreground: string, background: string }>;

function toHexCss(n: number) {
    const str = n.toString(16);
    // Pad with leading zeros
    return "#" + (new Array(6 - str.length)).join("0") + str;
}

function onRedraw(events: any[], elem: HTMLPreElement, grids: Grid[], highlights: HighlightMap) {
    events.forEach(evt => {
        const [name, ...evts] = evt;
        switch (name) {
            case "option_set":
                // console.log("option_set:", evts);
                break;
            case "hl_attr_define":
                evts.forEach((highlight: HighlightUpdate) => {
                    const [id, { foreground, background }] = highlight;
                    highlights.set(id, {
                        background: toHexCss(background || 16777215),
                        foreground: toHexCss(foreground || 0),
                    });
                });
                break;
            case "default_colors_set":
                // console.log("default_colors_set:", evts);
                break;
            case "grid_resize":
                evts.forEach((resize: ResizeUpdate) => {
                    const [id, width, height] = resize;
                    grids[id] = new Grid(width, height);
                });
                break;
            case "grid_clear":
                evts.forEach((clear: [number]) => grids[clear[0]].clear());
                break;
            case "grid_cursor_goto":
                evts.forEach((cgoto: GotoUpdate) => {
                    const [id, x, y] = cgoto;
                    grids[id].cursor.x = x;
                    grids[id].cursor.y = y;
                });
                break;
            case "grid_line":
                evts.forEach((line: LineUpdate) => {
                    const [id, row, col, contents] = line;
                    contents.reduce((prevCol, content) => {
                        const [chara, high, repeat = 1] = content;
                        const before = grids[id].data[row].slice(0, prevCol);
                        const after = grids[id].data[row].slice(prevCol + repeat);
                        grids[id].data[row] = before
                            .concat((new Array(repeat)).fill(chara))
                            .concat(after);
                        return prevCol + repeat;
                    }, col);
                });
            case "mode_info_set":
                // console.log("mode_info_set:", evts);
                break;
            case "flush":
                const firstGrid = grids.find(g => !!g);
                if (firstGrid) {
                    elem.innerHTML = "";
                    firstGrid.data.forEach((row: any[]) => elem.innerHTML += (row.join("") + "\n"));
                }
                break;
            default:
                // console.log("Unhandled evt:", evt);
                break;
        }
    });
    // console.log(grids, highlights);
}

export async function neovim(element: HTMLPreElement) {
    let stdin: Stdin;
    let stdout: Stdout;
    let reqId = 0;
    const requests = new Map<number | string, ((...args: any[]) => any)>();
    const highlights: HighlightMap = new Map();
    const grids: Grid[] = [];
    const highlights: HighlightArray = [{ background: "#FFFFFF", foreground: "#000000" }];
    const grids: any[] = [];

    const port = browser.runtime.connect();
    stdin = new Stdin(port);

M src/NeovimFrame.html => src/NeovimFrame.html +13 -1
@@ 1,4 1,4 @@
<html>
<html class="nvim_highlight_0">
    <head>
        <meta charset="utf-8"/>
        <script type="application/javascript" src="nvimui.js"></script>


@@ 10,7 10,19 @@
        padding: 0px;
        overflow: hidden;
    }
    .nvim_row {
        display: block;
    }
    .nvim_cell {
        display: inline-block;
        width: 1ch;
    }
    html body .nvim_cursor {
        background: red;
        color: red;
    }
    </style>
    <style id="neovim_highlights"></style>
    </head>
    <body>
        <pre id="pre"></pre>

A src/Redraw.ts => src/Redraw.ts +73 -0
@@ 0,0 1,73 @@
import { toCss, toHexCss } from "./CSSUtils";
import { new_grid } from "./Grid";

export function onRedraw(events: any[], elem: HTMLPreElement, grids: any[], highlights: HighlightArray) {
    events.forEach(evt => {
        const [name, ...evts] = evt;
        switch (name) {
            case "option_set":
                // console.log("option_set:", evts);
                break;
            case "hl_attr_define":
                evts.forEach((highlight: HighlightUpdate) => {
                    const [id, { foreground, background }] = highlight;
                    highlights[id] = {
                        background: toHexCss(background || 16777215),
                        foreground: toHexCss(foreground || 0),
                    };
                });
                break;
            case "default_colors_set":
                const [[fg, bg, sp, _, __]] = evts;
                highlights[0] = {
                    background: toHexCss(bg),
                    foreground: toHexCss(fg),
                };
                const styleElem = document.getElementById("neovim_highlights");
                if (styleElem) {
                    styleElem.innerText = toCss(highlights);
                }
                break;
            case "grid_resize":
                evts.forEach((resize: ResizeUpdate) => {
                    const [id, width, height] = resize;
                    grids[id] = new_grid(width, height, elem);
                });
                break;
            case "grid_clear":
                evts.forEach(([id]: [number]) => grids[id].clear());
                break;
            case "grid_cursor_goto":
                // console.log("cursor_goto:", evt);
                evts.forEach(([id, x, y]: GotoUpdate) => grids[id].cursor_goto(y, x) );
                break;
            case "grid_line":
                evts.forEach((line: LineUpdate) => {
                    const [id, row, col, contents] = line;
                    contents.reduce((prevCol, content) => {
                        const [chara, high = 0, repeat = 1] = content;
                        const limit = prevCol + repeat;
                        for (let i = prevCol; i < limit; i += 1) {
                            grids[id][row][i] = chara;
                            grids[id][row][i].highlight = high;
                        }
                        return limit;
                    }, col);
                });
                break;
            case "mode_info_set":
                // console.log("mode_info_set:", evts);
                break;
            case "flush":
                const style = document.getElementById("neovim_highlights");
                if (style) {
                    style.innerText = toCss(highlights);
                }
                break;
            default:
                console.log("Unhandled evt:", evt);
                break;
        }
    });
    // console.log(grids, highlights);
}

M src/firenvim.d.ts => src/firenvim.d.ts +40 -7
@@ 28,11 28,44 @@ declare namespace browser.runtime {
    let onConnect: RuntimeOnConnect;
}

interface WritableStream {
    write: (str: string) => void;
    pipe: () => void;
    cork: () => void;
    uncork: () => void;
    setDefaultEncoding: () => void;
    end: () => void;
type NvimParameters = Array<[string, string]>;

interface INvimApiInfo {
    0: number;
    1: {
        error_types: {[key: string]: { id: number }},
        functions: Array<{
            deprecated_since?: number,
            method: boolean,
            name: string,
            parameters: NvimParameters,
            return_type: string,
            since: number,
        }>,
        types: {
            [key: string]: { id: number, prefix: string },
        },
        ui_events: Array<{
            name: string,
            parameters: NvimParameters,
            since: number,
        }>,
        ui_options: string[],
        version: {
            api_compatible: number,
            api_level: number,
            api_prerelease: boolean,
            major: number,
            minor: number,
            patch: number,
        },
    };
}

type HighlightUpdate = [number, { foreground: number, background: number }];
type ResizeUpdate = [number, number, number];
type GotoUpdate = [number, number, number];
type LineUpdate = [number, number, number, Array<[string, number, number?]>];

type HighlightArray = { foreground: string, background: string }[];


M tslint.json => tslint.json +1 -0
@@ 4,6 4,7 @@
    "arrow-parens": false,
    "no-console": false,
    "no-namespace": false,
    "no-conditional-assignment": false,
    "semicolon": [true, "always"]
  }
}