~glacambre/firenvim

ref: 4907b8d3554aae7f7f2c298a73f7f512f0ead2ed firenvim/src/utils/CSSUtils.ts -rw-r--r-- 4.8 KiB
4907b8d3glacambre Improve testsuite 2 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import * as browser from "webextension-polyfill"; //lgtm [js/unused-local-variable]

// Make tslint happy
const fontFamily = "font-family";

// Parses a guifont declaration as described in `:h E244`
// defaults: default value for each of
export function parseGuifont(guifont: string, defaults: any) {
    const options = guifont.split(":");
    const result = Object.assign({}, defaults);
    result[fontFamily] = JSON.stringify(options[0]);
    if (defaults[fontFamily]) {
        result[fontFamily] += `, ${defaults[fontFamily]}`;
    }
    return options.slice(1).reduce((acc, option) => {
            switch (option[0]) {
                case "h":
                    acc["font-size"] = `${option.slice(1)}pt`;
                    break;
                case "b":
                    acc["font-weight"] = "bold";
                    break;
                case "i":
                    acc["font-style"] = "italic";
                    break;
                case "u":
                    acc["text-decoration"] = "underline";
                    break;
                case "s":
                    acc["text-decoration"] = "line-through";
                    break;
                case "w": // Can't set font width. Would have to adjust cell width.
                case "c": // Can't set character set
                    break;
            }
            return acc;
        }, result as any);
}

// Takes a `guifont` declaration and returns that same font declaration but as
// a bunch of CSS declarations.
export function guifontToMultiDecl(guifont: string) {
    const defaults: any = {};
    defaults[fontFamily] = "monospace";
    defaults["font-size"] = "9pt";
    return Object.entries(parseGuifont(guifont, defaults))
        .map(([key, value]) => `${key}: ${value};\n`)
        .join("\n");
}

// Takes an array of `guifont` declarations and returns them as a single CSS
// declaration, using font-family for font fallback.
export function guifontsToFontFamily(guifonts: string[]) {
    const defaults: any = {};
    defaults[fontFamily] = "monospace";
    defaults["font-size"] = "9pt";
    const reducedGuifonts = guifonts
        .slice()
        .reverse()
        .reduce((acc, cur) => parseGuifont(cur, acc), defaults);
    return `font-family: ${reducedGuifonts[fontFamily]}; font-size: ${reducedGuifonts["font-size"]};`;
}

// Takes a string formatted according to the `guifont` spec and returns a CSS
// declaration that matches it.
export function guifontsToCSS(guifont: string) {
    const guifonts = (guifont + ",")
        .match(/.+?[^\\],/g) // split on non-escaped commas
        .map(s => s.slice(0, -1)); // remove last comma of each font
    if (guifonts.length > 1) {
        // If there are multiple font declarations, we use a CSS declaration
        // like this: `font-family: font-family1 font-size font-style
        // font-weight, font-family2...`. This prevents us from setting
        // size/bold/italics/underlnie/strikethrough but enables letting the
        // browser fallback to other fonts if one can't be found.
        return guifontsToFontFamily(guifonts);
    }
    return guifontToMultiDecl(guifonts[0]);
}

// Computes a unique selector for its argument.
export function computeSelector(element: HTMLElement) {
    function uniqueSelector(e: HTMLElement): string {
        // Only matching alphanumeric selectors because others chars might have special meaning in CSS
        if (e.id && e.id.match("^[a-zA-Z0-9_-]+$")) { return "#" + e.id; }
        // If we reached the top of the document
        if (!e.parentElement) { return "HTML"; }
        // Compute the position of the element
        const index =
            Array.from(e.parentElement.children)
                .filter(child => child.tagName === e.tagName)
                .indexOf(e) + 1;
        return `${uniqueSelector(e.parentElement)} > ${e.tagName}:nth-of-type(${index})`;
    }
    return uniqueSelector(element);
}

// Turns a number into its hash+6 number hexadecimal representation.
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;
}

// Computes a CSS stylesheet that represents the HighlightArray
export function toCss(highlights: HighlightArray) {
    const bg = highlights[0].background;
    const fg = highlights[0].foreground;
    return highlights.reduce((css, elem, id) => css +
        `.${toHighlightClassName(id)}{background: ${elem.background || bg};color:${elem.foreground || fg};font-style:${elem.italic ? "italic" : "normal"};font-weight:${elem.bold ? "bold" : "normal"};text-decoration-line:${(elem.undercurl || elem.underline) ? "underline" : (elem.strikethrough ? "line-through" : "none")};text-decoration-style:${elem.undercurl ? "wavy" : "solid"};}`
        , "");
}