~glacambre/firenvim

dd2b268601bbba7af41fa45e1765a8c9adeeab40 — glacambre 2 months ago eed5303
Improve build system
M package.json => package.json +1 -1
@@ 38,7 38,7 @@
  "license": "GPL-3.0",
  "name": "Firenvim",
  "scripts": {
    "build": "tslint --fix --project . && webpack && web-ext build --source-dir target/firefox --artifacts-dir target/xpi --overwrite-dest",
    "build": "tslint --fix --project . && webpack && web-ext build --source-dir target/firefox --artifacts-dir target/xpi --overwrite-dest -n fireofx-latest.xpi && web-ext build --source-dir target/thunderbird --artifacts-dir target/xpi --overwrite-dest -n thunderbird-latest.xpi",
    "clean": "rm -rf target",
    "install_manifests": "nvim --headless -u NORC -i NONE -n -c \":set rtp+=.\" -c \"call firenvim#install(1)\" -c \"quit\"",
    "jest": "jest",

M src/FirenvimElement.ts => src/FirenvimElement.ts +2 -2
@@ 1,4 1,4 @@
import { isFirefox } from "./utils/utils";
import { isChrome } from "./utils/utils";
import { AbstractEditor } from "./editors/AbstractEditor";
import { getEditor } from "./editors/editors";
import { computeSelector } from "./utils/CSSUtils";


@@ 218,7 218,7 @@ export class FirenvimElement {
                sel.addRange(range);
                // Then, attempt to "release" the focus from whatever element
                // is currently focused. This doesn't work on Chrome.
                if (isFirefox()) {
                if (!isChrome()) {
                    window.focus();
                    document.documentElement.focus();
                    document.body.focus();

M src/NeovimFrame.ts => src/NeovimFrame.ts +4 -4
@@ 3,7 3,7 @@ import { page } from "./page/proxy";
import { getGridId, getCurrentMode, onKeyPressed as rendererOnKeyPressed } from "./render/Redraw";
import { confReady, getConfForUrl, getGlobalConf } from "./utils/configuration";
import { addModifier, nonLiteralKeys, translateKey } from "./utils/keys";
import { getCharSize, getGridSize, isFirefox, toFileName } from "./utils/utils";
import { getCharSize, getGridSize, isChrome, toFileName } from "./utils/utils";

const frameIdPromise = browser
    .runtime


@@ 139,8 139,8 @@ export const isReady = new Promise((resolve, reject) => {
                    if (ignoreKeys[currentMode] !== undefined) {
                        keys = ignoreKeys[currentMode].slice();
                    }
                    if (ignoreKeys["all"] !== undefined) {
                        keys.push.apply(keys, ignoreKeys["all"]);
                    if (ignoreKeys.all !== undefined) {
                        keys.push.apply(keys, ignoreKeys.all);
                    }
                    if (!keys.includes(text)) {
                        nvim.input(text);


@@ 179,7 179,7 @@ export const isReady = new Promise((resolve, reject) => {
            // true! This means that we need to add a chrome-specific event
            // listener on compositionend to do what happens on input events for
            // Firefox.
            if (!isFirefox()) {
            if (isChrome()) {
                keyHandler.addEventListener("compositionend", (evt: any) => {
                    acceptInput(event);
                });

M src/background.ts => src/background.ts +96 -90
@@ 15,7 15,7 @@
 * content scripts. It rarely acts on its own.
 */
import { getGlobalConf, ISiteConfig } from "./utils/configuration";
import { getIconImageData, IconKind } from "./utils/utils";
import { getIconImageData, IconKind, isThunderbird } from "./utils/utils";

let preloadedInstance: Promise<any>;



@@ 52,6 52,9 @@ async function updateIcon(tabId?: number) {
    } else if (warning !== "") {
        name = "notification";
    }
    if (isThunderbird()) {
        return Promise.resolve();
    }
    return getIconImageData(name).then((imageData: any) => browser.browserAction.setIcon({ imageData }));
}



@@ 291,94 294,97 @@ browser.windows.onFocusChanged.addListener(async (windowId: number) => {

updateIcon();

browser.commands.onCommand.addListener(async (command: string) => {
    const tab = (await browser.tabs.query({ active: true, currentWindow: true }))[0];
    let p;
    switch (command) {
        case "focus_input":
            browser.tabs.sendMessage(
                tab.id,
                { args: [], funcName: ["focusInput"] },
                { frameId: 0 },
            );
            break;
        case "focus_page":
            browser.tabs.sendMessage(
                tab.id,
                { args: [], funcName: ["focusPage"] },
                { frameId: 0 },
            );
            break;
        case "nvimify":
            browser.tabs.sendMessage(
                tab.id,
                { args: [], funcName: ["forceNvimify"] },
                { frameId: 0 }
            );
            break;
        case "send_C-n":
            p = browser.tabs.sendMessage(
                tab.id,
                { args: ["<C-n>"], funcName: ["sendKey"] },
                { frameId: 0 }
            );
            if (getGlobalConf()["<C-n>"] === "default") {
                p.catch(() => browser.windows.create());
            }
            break;
        case "send_C-t":
            p = browser.tabs.sendMessage(
                tab.id,
                { args: ["<C-t>"], funcName: ["sendKey"] },
                { frameId: 0 }
            );
            if (getGlobalConf()["<C-t>"] === "default") {
                p.catch(() => browser.tabs.create({ "windowId": tab.windowId }));
            }
            break;
        case "send_C-w":
            p = browser.tabs.sendMessage(
                tab.id,
                { args: ["<C-w>"], funcName: ["sendKey"] },
                { frameId: 0 }
            );
            if (getGlobalConf()["<C-w>"] === "default") {
                p.catch(() => browser.tabs.remove(tab.id));
            }
            break;
        case "send_CS-n":
            p = browser.tabs.sendMessage(
                tab.id,
                { args: ["<CS-n>"], funcName: ["sendKey"] },
                { frameId: 0 }
            );
            if (getGlobalConf()["<CS-n>"] === "default") {
                p.catch(() => browser.windows.create({ "incognito": true }));
            }
            break;
        case "send_CS-t":
            // <CS-t> can't be emulated without the sessions API.
            browser.tabs.sendMessage(
                tab.id,
                { args: ["<CS-t>"], funcName: ["sendKey"] },
                { frameId: 0 }
            );
            break;
        case "send_CS-w":
            p = browser.tabs.sendMessage(
                tab.id,
                { args: ["<CS-w>"], funcName: ["sendKey"] },
                { frameId: 0 }
            );
            if (getGlobalConf()["<CS-w>"] === "default") {
                p.catch(() => browser.windows.remove(tab.windowId));
            }
            break;
        case "toggle_firenvim":
            toggleDisabled();
            break;
    }
});
// browser.commmands doesn't exist in thunderbird
if (!isThunderbird()) {
    browser.commands.onCommand.addListener(async (command: string) => {
        const tab = (await browser.tabs.query({ active: true, currentWindow: true }))[0];
        let p;
        switch (command) {
            case "focus_input":
                browser.tabs.sendMessage(
                    tab.id,
                    { args: [], funcName: ["focusInput"] },
                    { frameId: 0 },
                );
                break;
            case "focus_page":
                browser.tabs.sendMessage(
                    tab.id,
                    { args: [], funcName: ["focusPage"] },
                    { frameId: 0 },
                );
                break;
            case "nvimify":
                browser.tabs.sendMessage(
                    tab.id,
                    { args: [], funcName: ["forceNvimify"] },
                    { frameId: 0 }
                );
                break;
            case "send_C-n":
                p = browser.tabs.sendMessage(
                    tab.id,
                    { args: ["<C-n>"], funcName: ["sendKey"] },
                    { frameId: 0 }
                );
                if (getGlobalConf()["<C-n>"] === "default") {
                    p.catch(() => browser.windows.create());
                }
                break;
            case "send_C-t":
                p = browser.tabs.sendMessage(
                    tab.id,
                    { args: ["<C-t>"], funcName: ["sendKey"] },
                    { frameId: 0 }
                );
                if (getGlobalConf()["<C-t>"] === "default") {
                    p.catch(() => browser.tabs.create({ "windowId": tab.windowId }));
                }
                break;
            case "send_C-w":
                p = browser.tabs.sendMessage(
                    tab.id,
                    { args: ["<C-w>"], funcName: ["sendKey"] },
                    { frameId: 0 }
                );
                if (getGlobalConf()["<C-w>"] === "default") {
                    p.catch(() => browser.tabs.remove(tab.id));
                }
                break;
            case "send_CS-n":
                p = browser.tabs.sendMessage(
                    tab.id,
                    { args: ["<CS-n>"], funcName: ["sendKey"] },
                    { frameId: 0 }
                );
                if (getGlobalConf()["<CS-n>"] === "default") {
                    p.catch(() => browser.windows.create({ "incognito": true }));
                }
                break;
            case "send_CS-t":
                // <CS-t> can't be emulated without the sessions API.
                browser.tabs.sendMessage(
                    tab.id,
                    { args: ["<CS-t>"], funcName: ["sendKey"] },
                    { frameId: 0 }
                );
                break;
            case "send_CS-w":
                p = browser.tabs.sendMessage(
                    tab.id,
                    { args: ["<CS-w>"], funcName: ["sendKey"] },
                    { frameId: 0 }
                );
                if (getGlobalConf()["<CS-w>"] === "default") {
                    p.catch(() => browser.windows.remove(tab.windowId));
                }
                break;
            case "toggle_firenvim":
                toggleDisabled();
                break;
        }
    });
}

async function updateIfPossible() {
    const tabs = await browser.tabs.query({});


@@ 402,7 408,7 @@ async function updateIfPossible() {
browser.runtime.onUpdateAvailable.addListener(updateIfPossible);

// In thunderbird, register the script to be loaded in the compose window
if ((browser as any).composeScripts !== undefined) {
if (isThunderbird()) {
    (browser as any).composeScripts.register({
        js: [{file: "compose.js"}],
    });

M src/manifest.json => src/manifest.json +2 -3
@@ 41,14 41,13 @@
      "matches": [ "<all_urls>" ],
      "run_at": "document_start"
   } ],
   "description": "PACKAGE_JSON_DESCRIPTION",
   "description": "replaced_at_compile_time",
   "icons": {
      "128": "firenvim.svg"
   },
   TARGET_SPECIFIC_SETTINGS,
   "manifest_version": 2,
   "name": "Firenvim",
   "permissions": [ "nativeMessaging", "storage", "tabs" ],
   "version": "FIRENVIM_VERSION",
   "version": "replaced_at_compile_time",
   "web_accessible_resources": [ "NeovimFrame.html", "ISSUE_TEMPLATE.md" ]
}

M src/utils/configuration.ts => src/utils/configuration.ts +1 -1
@@ 52,7 52,7 @@ browser.storage.onChanged.addListener((changes: any) => {
    Object
        .entries(changes)
        .forEach(([key, value]: [keyof IConfig, any]) => confReady.then(() => {
            conf[key] = value.newValue
            conf[key] = value.newValue;
        }));
});


M src/utils/utils.ts => src/utils/utils.ts +13 -3
@@ 1,11 1,21 @@
// Chrome doesn't have a "browser" object, instead it uses "chrome".
let curBrowser = "firefox";
let curHost = "firefox";
if (window.browser === undefined) {
    curBrowser = "chrome";
    curHost = "chrome";
} else {
    if ((browser as any).composeScripts !== undefined) {
        curHost = "thunderbird";
    }
}

export function isFirefox() {
    return curBrowser === "firefox";
    return curHost === "firefox";
}
export function isChrome() {
    return curHost === "chrome";
}
export function isThunderbird() {
    return curHost === "thunderbird";
}

// Runs CODE in the page's context by setting up a custom event listener,

M webpack.config.js => webpack.config.js +41 -41
@@ 91,20 91,20 @@ const chromeConfig = (config, env) => {
      to: chrome_target_dir,
      transform: (content, src) => {
        if (path.basename(src) === "manifest.json") {
          content = content.toString()
            .replace('TARGET_SPECIFIC_SETTINGS', '"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk3pkgh862ElxtREZVPLxVNbiFWo9SnvZtZXZavNvs2GsUTY/mB9yHTPBGJiBMJh6J0l+F5JZivXDG7xdQsVD5t39CL3JGtt93M2svlsNkOEYIMM8tHbp69shNUKKjZOfT3t+aZyigK2OUm7PKedcPeHtMoZAY5cC4L1ytvgo6lge+VYQiypKF87YOsO/BGcs3D+MMdS454tLBuMp6LxMqICQEo/Q7nHGC3eubtL3B09s0l17fJeq/kcQphczKbUFhTVnNnIV0JX++UCWi+BP4QOpyk5FqI6+SVi+gxUosbQPOmZR4xCAbWWpg3OqMk4LqHaWpsBfkW9EUt6EMMMAfQIDAQAB"')
            .replace("FIRENVIM_VERSION", package_json.version)
            .replace("PACKAGE_JSON_DESCRIPTION", package_json.description)
          // Chrome doesn't support svgs in its manifest
            .replace(/"128": *"firenvim\.svg"/g, '"128": "firenvim128.png",\n'
              + '      "16": "firenvim16.png",\n'
              + '      "48": "firenvim48.png"'
            )
            .replace(/"default_icon": "firenvim.svg"/, '"default_icon": "firenvim128.png"')
          ;
          const manifest = JSON.parse(content.toString())
          manifest["key"] = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk3pkgh862ElxtREZVPLxVNbiFWo9SnvZtZXZavNvs2GsUTY/mB9yHTPBGJiBMJh6J0l+F5JZivXDG7xdQsVD5t39CL3JGtt93M2svlsNkOEYIMM8tHbp69shNUKKjZOfT3t+aZyigK2OUm7PKedcPeHtMoZAY5cC4L1ytvgo6lge+VYQiypKF87YOsO/BGcs3D+MMdS454tLBuMp6LxMqICQEo/Q7nHGC3eubtL3B09s0l17fJeq/kcQphczKbUFhTVnNnIV0JX++UCWi+BP4QOpyk5FqI6+SVi+gxUosbQPOmZR4xCAbWWpg3OqMk4LqHaWpsBfkW9EUt6EMMMAfQIDAQAB";
          manifest["version"] = package_json.version;
          manifest["description"] = package_json.description;
          manifest["icons"] = {
            "128": "firenvim128.png",
            "16": "firenvim16.png",
            "48": "firenvim48.png"
          }
          manifest.browser_action["default_icon"] = "firenvim128.png";
          if (env.endsWith("testing")) {
            content = content.replace(`content.js`, `content.js", "testing.js`);
            manifest.content_scripts[0].js.push("testing.js");
          }
          content = JSON.stringify(manifest, undefined, 3);
        }
        return content;
      }


@@ 137,19 137,19 @@ const firefoxConfig = (config, env) => {
        transform: (content, src) => {
          switch(path.basename(src)) {
            case "manifest.json":
              content = content.toString().replace("TARGET_SPECIFIC_SETTINGS",
`  "browser_specific_settings": {
    "gecko": {
      "id": "firenvim@lacamb.re",
      "strict_min_version": "69.0"
    }
  }`)
                .replace("FIRENVIM_VERSION", package_json.version)
                .replace("PACKAGE_JSON_DESCRIPTION", package_json.description)
              ;
              const manifest = JSON.parse(content.toString());
              manifest.browser_specific_settings = {
                "gecko": {
                  "id": "firenvim@lacamb.re",
                  "strict_min_version": "69.0"
                }
              };
              manifest.version = package_json.version;
              manifest.description = package_json.description;
              if (env.endsWith("testing")) {
                content = content.replace(`content.js`, `content.js", "testing.js`);
                manifest.content_scripts[0].js.push("testing.js");
              }
              content = JSON.stringify(manifest, undefined, 3);
          }
          return content;
        }


@@ 165,6 165,7 @@ const firefoxConfig = (config, env) => {
}

const thunderbirdConfig = (config, env) => {
  config.entry.compose = "./src/compose.ts";
  const result = Object.assign(deepCopy(config), {
    output: {
      path: thunderbird_target_dir,


@@ 176,20 177,20 @@ const thunderbirdConfig = (config, env) => {
        transform: (content, src) => {
          switch(path.basename(src)) {
            case "manifest.json":
              content = content.toString().replace("TARGET_SPECIFIC_SETTINGS",
`  "browser_specific_settings": {
    "gecko": {
      "id": "firenvim@lacamb.re",
      "strict_min_version": "84.0a1"
    }
  }`)
                .replace("FIRENVIM_VERSION", package_json.version)
                .replace("PACKAGE_JSON_DESCRIPTION", package_json.description)
                .replace(`"permissions": [`, `"permissions": [ "compose",`);
              ;
              if (env.endsWith("testing")) {
                content = content.replace(`content.js`, `content.js", "testing.js`);
              }
              const manifest = JSON.parse(content.toString());
              manifest.browser_specific_settings = {
                "gecko": {
                  "id": "firenvim@lacamb.re",
                  "strict_min_version": "84.0a1"
                }
              };
              manifest.version = package_json.version;
              manifest.description = "Turn thunderbird into a Neovim GUI.";
              delete manifest.browser_action;
              delete manifest.commands;
              delete manifest.content_scripts;
              manifest.permissions.push("compose");
              content = JSON.stringify(manifest, undefined, 3);
          }
          return content;
        }


@@ 205,9 206,9 @@ const thunderbirdConfig = (config, env) => {
}

module.exports = args => {
  let env = Object.keys(args)[0];
  if (env === undefined){
    env = "";
  let env = "";
  if (args instanceof Object) {
    env = Object.keys(args)[0];
  }

  if (env.endsWith("testing")) {


@@ 220,7 221,6 @@ module.exports = args => {
  } else if (env.startsWith("firefox")) {
    return [firefoxConfig(config, env)];
  } else if (env.startsWith("thunderbird")) {
    config.entry.compose = "./src/compose.ts";
    return [thunderbirdConfig(config, env)];
  }
  return [chromeConfig(config, env), firefoxConfig(config, env), thunderbirdConfig(config, env)];