[
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  parser: '@typescript-eslint/parser',\n  plugins: ['jest'],\n  extends: [\n    'airbnb-base',\n    // Temporary disable it until upgrade to the Prettier 3\n    // 'plugin:prettier/recommended',\n    'plugin:jest/recommended',\n    'plugin:jest/style',\n    'plugin:import/typescript',\n  ],\n  env: {\n    browser: true,\n    'jest/globals': true,\n  },\n  ignorePatterns: [\n    '**/tmp/**',\n    '**/build/**',\n    '**/dist/**',\n    '**/node_modules/**',\n    '**/@types/**',\n    '**/__generated__/**',\n  ],\n  settings: {\n    'import/resolver': {\n      node: {\n        extensions: ['.js', '.ts'],\n      },\n    },\n  },\n\n  overrides: [\n    {\n      files: ['*.ts'],\n      plugins: ['jest', '@typescript-eslint'],\n      extends: [\n        'airbnb-base',\n        // 'plugin:prettier/recommended',\n        'plugin:jest/recommended',\n        'plugin:jest/style',\n        'plugin:import/typescript',\n        'plugin:@typescript-eslint/recommended',\n      ],\n      rules: {\n        'prefer-destructuring': 'off',\n        'no-console': 'error',\n        'no-unused-vars': 'off',\n        '@typescript-eslint/no-unused-vars': [\n          'error',\n          {\n            args: 'all',\n            argsIgnorePattern: '^_',\n          },\n        ],\n        '@typescript-eslint/ban-ts-comment': 'warn',\n        '@typescript-eslint/no-explicit-any': 'warn',\n        '@typescript-eslint/explicit-function-return-type': 'off',\n        'import/prefer-default-export': 'off',\n        'import/no-extraneous-dependencies': 'off',\n        'import/no-unresolved': 'off', // TypeScript handles this\n        'import/extensions': [\n          'error',\n          'ignorePackages',\n          {\n            js: 'never',\n            ts: 'never',\n          },\n        ],\n\n        // Styling. Remove after prettier upgrade\n        'object-curly-newline': 'off',\n        'function-paren-newline': 'off',\n        'implicit-arrow-linebreak': 'off',\n        'arrow-body-style': 'off',\n        'max-len': 'off',\n        'no-confusing-arrow': 'off',\n        quotes: 'off',\n        'operator-linebreak': 'off',\n        'quote-props': 'off',\n        indent: 'off',\n      },\n    },\n  ],\n\n  rules: {\n    'prefer-destructuring': 'off',\n    'no-console': 'error',\n    'no-unused-vars': [\n      'error',\n      {\n        args: 'all',\n        argsIgnorePattern: '^_',\n      },\n    ],\n    'no-mixed-operators': [\n      'error',\n      {\n        groups: [\n          ['&', '|', '^', '~', '<<', '>>', '>>>'],\n          ['==', '!=', '===', '!==', '>', '>=', '<', '<='],\n          ['&&', '||'],\n          ['in', 'instanceof'],\n        ],\n        allowSamePrecedence: true,\n      },\n    ],\n    'import/prefer-default-export': 'off',\n    'import/no-extraneous-dependencies': 'off',\n    'import/extensions': [\n      'error',\n      'ignorePackages',\n      {\n        js: 'never',\n        ts: 'never',\n      },\n    ],\n\n    // Styling. Remove after prettier upgrade\n    'object-curly-newline': 'off',\n    'function-paren-newline': 'off',\n    'implicit-arrow-linebreak': 'off',\n    'arrow-body-style': 'off',\n    'max-len': 'off',\n    'no-confusing-arrow': 'off',\n    quotes: 'off',\n    'operator-linebreak': 'off',\n    'quote-props': 'off',\n    indent: 'off',\n  },\n};\n"
  },
  {
    "path": ".github/workflows/link_typecheck.yml",
    "content": "name: Lint & Typecheck\n\non:\n  pull_request:\n\njobs:\n  build:\n    runs-on: macos-latest\n\n    steps:\n      - name: Checkout Repository\n        uses: actions/checkout@v2\n\n      - name: Read .nvmrc\n        run: echo \"##[set-output name=NVMRC;]$(cat .nvmrc)\"\n        id: nvm\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v1\n        with:\n          node-version: '${{ steps.nvm.outputs.NVMRC }}'\n\n      - name: Get Yarn cache directory path\n        id: yarn-cache-dir-path\n        run: echo \"::set-output name=dir::$(yarn cache dir)\"\n\n      - name: Cache Yarn\n        uses: actions/cache@v4\n        id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)\n        with:\n          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}\n          key: yarn-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}\n          restore-keys: |\n            yarn-${{ runner.os }}-\n\n      - name: Install Dependencies\n        run: yarn\n\n      - name: Build Required Packages\n        run: yarn bootstrap\n\n      - name: Run ESLint\n        run: yarn lint\n        if: always()\n\n      - name: Run TypeScript check\n        run: yarn typecheck\n        if: always()\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Tests\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  build:\n    runs-on: macos-latest\n\n    steps:\n      - name: Checkout Repository\n        uses: actions/checkout@v2\n\n      - name: Read .nvmrc\n        run: echo \"##[set-output name=NVMRC;]$(cat .nvmrc)\"\n        id: nvm\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '${{ steps.nvm.outputs.NVMRC }}'\n\n      - name: Install Nvim\n        run: brew install nvim\n\n      - name: Get Yarn cache directory path\n        id: yarn-cache-dir-path\n        run: echo \"::set-output name=dir::$(yarn cache dir)\"\n\n      - name: Cache Yarn\n        uses: actions/cache@v4\n        id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)\n        with:\n          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}\n          key: yarn-${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}\n          restore-keys: |\n            yarn-${{ runner.os }}-\n\n      - name: Install Dependencies\n        run: yarn\n\n      - name: Build Required Packages\n        run: yarn bootstrap\n\n      - name: Run Tests\n        run: yarn test --reporters=\"default\" --reporters=\"jest-github-actions-reporter\" --coverage\n\n      - name: Upload snapshot diffs\n        uses: actions/upload-artifact@v4\n        with:\n          name: failed-image-snapshots\n          path: packages/**/__diff_output__/*\n          retention-days: 5\n        if: failure()\n\n      - name: Upload coverage to Codecov\n        uses: codecov/codecov-action@v5\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n**/node_modules/**\n\n**/build/**\n**/dist/**\n**/coverage/**\n**/tmp/**\n\n.DS_Store\n.env\n.nyc_output\nyarn-error.log\n__diff_output__\n"
  },
  {
    "path": ".husky/.gitignore",
    "content": "_\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nyarn lint-staged\n"
  },
  {
    "path": ".nvmrc",
    "content": "20.9.0\n"
  },
  {
    "path": ".prettierrc.js",
    "content": "module.exports = {\n  trailingComma: 'all',\n  printWidth: 100,\n  singleQuote: true,\n};\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2018-present Igor Gladkoborodov\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# VV\n\nVV is a Neovim client for macOS. A pure, fast, minimalistic Vim experience with good macOS integration. Optimized for speed and nice font rendering.\n\n![VV screenshot](packages/electron/assets/screenshot.png)\n\n- Fast text render via WebGL.\n- OS integration: copy/paste, mouse, scroll.\n- Fullscreen support for native and simple (fast) mode.\n- All app settings configurable via Vimscript.\n- Command line launcher.\n- “Save All” dialog on quit and “Refresh” dialog on external changes.\n- Text zoom.\n\nVV is built on Electron. There are no barriers to porting it to Windows or Linux, or making plugins with Javascript, HTML, and CSS.\n\n## Installation\n\n### Install via Homebrew\n\nVV is available via Homebrew Cask:\n\n```\n$ brew install vv\n```\n\nNOTE: older versions of brew require a special command to install `vv`\n\n```\n$ brew cask install vv\n```\n\nIt will also install Neovim (if it is not installed) and command line launcher `vv`.\n\n### Download\n\nOr you can download the most recent release from the [Releases](https://github.com/vv-vim/vv/releases/latest) page.\n\nYou need Neovim to run VV. You can install it via Homebrew: `$ brew install neovim`. Or you can find Neovim installation instructions here: [https://github.com/neovim/neovim/wiki/Installing-Neovim](https://github.com/neovim/neovim/wiki/Installing-Neovim). Neovim version 0.4.0 and higher is required.\n\n### Build manually\n\nYou can also build it manually. You will need [Node.js](https://nodejs.org/en/download/) and [Yarn](https://yarnpkg.com/lang/en/) installed.\n\n```\n$ git clone git@github.com:vv-vim/vv.git\n$ cd vv\n$ yarn\n$ yarn build:electron\n```\n\nThis will generate a VV.app binary in the dist directory. Copy VV.app to your /Applications folder and add the CLI launcher `vv` to your `/usr/local/bin`.\n\n## Command Line Launcher\n\nYou can use the `vv` command to run VV in a Terminal. Install it via the `VV → Command Line Launcher...` menu item. VV will add the command to your `/usr/local/bin` folder. If you prefer another place, you can link the command manually:\n\n```\nln -s -f /Applications/VV.app/Contents/Resources/bin/vv [dir from $PATH]/vv\n```\n\nUsage: `vv [options] [file ...]`\n\nOptions are passed to `nvim`. You can check available options in nvim help: `nvim --help`.\n\n## Settings\n\nYou can setup VV-specific options via the `:VVset` command. It works the same as the vim built-in command `:set`. For example `:VVset nofullscreen` is the same as `:VVset fullscreen=0`. You can use `:help set` for syntax reference.\n\n- `fullscreen`, `fu`: Switch to fullscreen mode. You can also toggle fullscreen with `Cmd+Ctrl+F`. Default: `0`.\n- `simplefullscreen`, `sfu`: Use simple or standard fullscreen mode. Simple mode is faster than standard macOS fullscreen mode. It does not have any transition animation. Default: `1`.\n- `bold`: Allow bold font. You can completely disable bold even if the colorscheme uses it. Default: `1`.\n- `italic`: Allow italic. Default: `1`.\n- `underline`: Allow underline. Default: `1`.\n- `undercurl`: Allow undercurl. Default: `1`.\n- `strikethrough`: Allow strikethrough. Default: `1`.\n- `fontfamily`: Font family. Syntax is the same as CSS [`font-family`](https://developer.mozilla.org/en-US/docs/Web/CSS/font-family). You can use comma-separated list of fonts. It will use first installed font in the list and fallback to default monospace font if none of them installed. Spaces should be excaped by `\\`. For example: `:VVset fontfamily=Menlo,\\ Courier\\ New`. Default: `monospace`.\n- `fontsize`: Font size in pixels. Default: `12`.\n- `lineheight`: Line height related to font size. Pixel value is `fontsize * lineheight`. Default: `1.25`.\n- `letterspacing`: Fine-tuning letter spacing in retina pixels. Can be a negative value. For retina screens the value is physical pixels. For non-retina screens it works differently: it divides the value by 2 and rounds it. For example, `:VVset letterspacing=1` will make characters 1 pixel wider on retina displays and will do nothing on non-retina displays. Value 2 is 2 physical pixels on retina and 1 physical pixel on non-retina. Default: `0`.\n- `windowwidth`, `width`: Window width. Can be a number in pixels or percentage of display width.\n- `windowheight`, `height`: Window height.\n- `windowleft`, `left`: Window position from left. Can be a number in pixels or a percentage. Percent values work the same as the `background-position` rule in CSS. For example: `25%` means that the vertical line on the window that is 25% from the left will be placed at the line that is 25% from the display's left. 0% — the very left, 100% — the very right, 50% — center.\n- `windowtop`, `top`: Window position top.\n- `quitoncloselastwindow`: Quit app on close last window. Default: `0`.\n- `autoupdateinterval`: Autoupdate interval in minutes. `0` — disable autoupdate. Default: `1440`, one day.\n- `openinproject`: Open file in existing VV instance if this file is located inside current directory of this instance. By default it will obey [`switchbuf`](https://neovim.io/doc/user/options.html#'switchbuf') option, but you can set `switchbuf` override as a value of this option, for example: `:VVset openinproject=newtab`. Possible values are: `1` use switchbuf, `0` open in new instance, any valid `switchbuf` value. Default: `1`.\n\nYou can use these settings in your `init.vim` or change them any time. You can check if VV is loaded by checking the `g:vv` variable:\n\n```\nif exists('g:vv')\n  VVset nobold\n  VVset noitalic\n  VVset windowheight=100%\n  VVset windowwidth=60%\n  VVset windowleft=0\n  VVset windowtop=0\nendif\n```\n\nVV also sets `set termguicolors` on startup.\n\n## Development\n\nFirst, you need start a Webpack watch process in a separate terminal:\n\n```\nyarn dev\n```\n\nThen you can run the app:\n\n```\nyarn start:electron\n```\n\nYou can run tests with `yarn test` and ESLint with `yarn lint` commands.\n\nIt is written on TypeScript, but it uses Babel to build. It does not check types during the build. If you want do do type check manually you can run it with `yarn typecheck`.\n\n## Server\n\nYou can run Neovim remotely in browser via VV Server. More info: [packages/server/README.md](packages/server/README.md)\n\n## Browser Renderer\n\n[Browser Renderer](packages/browser-renderer/README.md) is a separate package used in Electron app and Server.\n\n## Name\n\nThe VV name comes from the bash shortcut `vv` that I use to start Vim.\n\n## License\n\nVV is released under the [MIT License](https://opensource.org/licenses/MIT).\n"
  },
  {
    "path": "babel.config.json",
    "content": "{\n  \"presets\": [[\"@babel/preset-env\", { \"modules\": \"commonjs\" }], \"@babel/preset-typescript\"],\n  \"plugins\": [\n    \"@babel/plugin-proposal-optional-chaining\",\n    \"@babel/plugin-proposal-class-properties\",\n    \"@babel/plugin-transform-runtime\",\n    [\n      \"module-resolver\",\n      {\n        \"root\": [\".\"],\n        \"alias\": {\n          \"src\": \"./src\"\n        }\n      }\n    ]\n  ]\n}\n"
  },
  {
    "path": "codecov.yml",
    "content": "comment:\n  layout: \"reach, diff, flags, files\"\n  require_changes: false\n\nignore:\n  - \"packages/browser-renderer/src/screen.ts\"  # Tested by Puppeteer\n\n"
  },
  {
    "path": "jest.config.js",
    "content": "module.exports = {\n  clearMocks: true,\n  testEnvironment: 'node',\n  collectCoverageFrom: ['src/**/*.{ts,js}'],\n  projects: ['<rootDir>/packages/*'],\n};\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"vv\",\n  \"description\": \"Neovim GUI Client\",\n  \"author\": \"Igor Gladkoborodov <igor.gladkoborodov@gmail.com>\",\n  \"keywords\": [\n    \"vim\",\n    \"neovim\",\n    \"client\",\n    \"gui\",\n    \"electron\"\n  ],\n  \"license\": \"MIT\",\n  \"main\": \"./build/main.js\",\n  \"sideEffects\": false,\n  \"private\": true,\n  \"workspaces\": [\n    \"packages/*\"\n  ],\n  \"scripts\": {\n    \"bootstrap\": \"yarn build:nvim; yarn build:browser-renderer; yarn build:server\",\n    \"build:nvim\": \"yarn workspace @vvim/nvim build\",\n    \"build:browser-renderer\": \"yarn workspace @vvim/browser-renderer build\",\n    \"build:electron\": \"yarn bootstrap; yarn workspace @vvim/electron build\",\n    \"build:server\": \"yarn workspace @vvim/server build\",\n    \"dev:nvim\": \"yarn workspace @vvim/nvim dev\",\n    \"dev:browser-renderer\": \"yarn workspace @vvim/browser-renderer dev\",\n    \"dev:electron\": \"yarn workspace @vvim/electron dev\",\n    \"dev:server\": \"yarn workspace @vvim/server dev\",\n    \"dev\": \"yarn bootstrap; npm-run-all --parallel dev:*\",\n    \"start:electron\": \"yarn workspace @vvim/electron start\",\n    \"start:server\": \"yarn workspace @vvim/server start\",\n    \"lint\": \"eslint . --ext .js,.ts\",\n    \"test\": \"jest\",\n    \"typecheck\": \"tsc -p packages/browser-renderer; tsc -p packages/electron; tsc -p packages/server\",\n    \"prepare\": \"husky install\",\n    \"codegen\": \"babel-node -x \\\".ts\\\" scripts/codegen.ts\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.24.0\",\n    \"@babel/node\": \"^7.23.9\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.13.0\",\n    \"@babel/plugin-proposal-optional-chaining\": \"^7.13.8\",\n    \"@babel/plugin-transform-runtime\": \"^7.24.0\",\n    \"@babel/preset-env\": \"^7.24.0\",\n    \"@babel/preset-typescript\": \"^7.23.3\",\n    \"@types/jest\": \"^30.0.0\",\n    \"@typescript-eslint/eslint-plugin\": \"^4.15.2\",\n    \"@typescript-eslint/parser\": \"^4.15.2\",\n    \"babel-jest\": \"^30.1.1\",\n    \"babel-loader\": \"^8.2.5\",\n    \"babel-plugin-module-resolver\": \"^4.1.0\",\n    \"codecov\": \"^3.8.1\",\n    \"eslint\": \"^7.21.0\",\n    \"eslint-config-airbnb-base\": \"^14.2.1\",\n    \"eslint-config-prettier\": \"^8.1.0\",\n    \"eslint-plugin-import\": \"^2.22.1\",\n    \"eslint-plugin-jest\": \"^24.1.5\",\n    \"eslint-plugin-prettier\": \"^3.3.1\",\n    \"husky\": \"^5.2.0\",\n    \"jest\": \"^30.1.1\",\n    \"jest-github-actions-reporter\": \"^1.0.3\",\n    \"lint-staged\": \"^10.5.4\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"prettier\": \"^2.2.1\",\n    \"regenerator\": \"^0.14.7\",\n    \"ts-jest\": \"^29.4.1\",\n    \"typescript\": \"^4.2.2\",\n    \"webpack\": \"^5.90.3\",\n    \"webpack-cli\": \"^5.1.4\",\n    \"webpack-merge\": \"^5.10.0\"\n  },\n  \"lint-staged\": {\n    \"*.{ts,js,css,json,md}\": [\n      \"prettier --write\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/browser-renderer/README.md",
    "content": "# @vvim/browser-renderer\n\nThis package is used to render Neovim in browser for [VV Electron App](../electron) and [VV server](../server).\n\nIt is in active development, the API is not stable yet.\n\n## Development\n\nRun in watch mode:\n\n```\nyarn dev\n```\n"
  },
  {
    "path": "packages/browser-renderer/babel.config.json",
    "content": "{\n  \"extends\": \"../../babel.config.json\",\n  \"plugins\": [\n    [\n      \"module-resolver\",\n      {\n        \"root\": [\".\"],\n        \"alias\": {\n          \"src\": \"./src\",\n          \"config\": \"./config\"\n        }\n      }\n    ]\n  ]\n}\n"
  },
  {
    "path": "packages/browser-renderer/config/jest/afterEnv.js",
    "content": "const { toMatchImageSnapshot } = require('jest-image-snapshot');\n\nexpect.extend({ toMatchImageSnapshot });\n"
  },
  {
    "path": "packages/browser-renderer/config/jest/globalSetup.js",
    "content": "/* eslint-disable no-underscore-dangle, no-undef */\n\nimport { setupTestServer } from './testServer';\n\nconst globalSetup = async () => {\n  globalThis.__PUPPETEER_SERVER__ = await setupTestServer();\n};\n\nexport default globalSetup;\n"
  },
  {
    "path": "packages/browser-renderer/config/jest/globalTeardown.js",
    "content": "/* eslint-disable no-underscore-dangle, no-undef */\n\nimport { teardownTestServer } from './testServer';\n\nconst globalTeardown = async () => {\n  await teardownTestServer(globalThis.__PUPPETEER_SERVER__);\n};\n\nexport default globalTeardown;\n"
  },
  {
    "path": "packages/browser-renderer/config/jest/testServer.js",
    "content": "import { setup, teardown } from 'jest-dev-server';\n\n// TODO: make it configurable\nexport const PORT = 3001;\n\nexport async function setupTestServer() {\n  const server = await setup({\n    command: `PORT=${PORT} yarn start:server -u NONE`,\n    launchTimeout: 10000,\n    port: PORT,\n    usedPortAction: 'kill',\n  });\n  return server;\n}\n\nexport async function teardownTestServer(server) {\n  if (server) {\n    await teardown(server);\n  }\n}\n"
  },
  {
    "path": "packages/browser-renderer/config/webpack.config.js",
    "content": "const path = require('path');\n\nconst buildPath = path.resolve(__dirname, './../dist');\n\nconst config = {\n  mode: 'development',\n  entry: './src/index.ts',\n  output: {\n    path: buildPath,\n    filename: 'index.js',\n    libraryTarget: 'umd',\n  },\n  target: 'web',\n  devtool: 'eval-cheap-source-map',\n  resolve: {\n    extensions: ['.ts', '.js'],\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts)$/,\n        exclude: /node_modules/,\n        loader: 'babel-loader',\n      },\n    ],\n  },\n};\n\nmodule.exports = config;\n"
  },
  {
    "path": "packages/browser-renderer/config/webpack.prod.config.js",
    "content": "const { merge } = require('webpack-merge');\n\nconst webpackConfig = require('./webpack.config');\n\nconst prod = {\n  mode: 'production',\n  devtool: 'source-map',\n};\n\nconst webpackConfigProd = merge(webpackConfig, prod);\n\nmodule.exports = webpackConfigProd;\n"
  },
  {
    "path": "packages/browser-renderer/jest.config.js",
    "content": "module.exports = {\n  testEnvironment: 'jsdom',\n  clearMocks: true,\n  moduleNameMapper: {\n    '\\\\./src/(.*)': ['<rootDir>/src/$1'],\n    'config/(.*)': ['<rootDir>/config/$1'],\n  },\n  testPathIgnorePatterns: ['/node_modules/', '/dist/'],\n  setupFilesAfterEnv: ['<rootDir>/config/jest/afterEnv.js'],\n  globalSetup: '<rootDir>/config/jest/globalSetup.js',\n  globalTeardown: '<rootDir>/config/jest/globalTeardown.js',\n};\n"
  },
  {
    "path": "packages/browser-renderer/package.json",
    "content": "{\n  \"name\": \"@vvim/browser-renderer\",\n  \"version\": \"0.0.1\",\n  \"description\": \"VV Browser Renderer\",\n  \"author\": \"Igor Gladkoborodov <igor.gladkoborodov@gmail.com>\",\n  \"keywords\": [\n    \"vim\",\n    \"neovim\",\n    \"client\",\n    \"gui\",\n    \"renderer\",\n    \"browser\",\n    \"webgl\"\n  ],\n  \"homepage\": \"https://github.com/vv-vim/vv#readme\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"sideEffects\": false,\n  \"scripts\": {\n    \"test\": \"jest\",\n    \"clean\": \"rm -rf dist/*\",\n    \"build:types\": \"tsc -p tsconfig.declaration.json\",\n    \"build:dev\": \"webpack --config ./config/webpack.config.js\",\n    \"build:prod\": \"webpack --config ./config/webpack.prod.config.js\",\n    \"build\": \"npm-run-all clean build:types build:prod\",\n    \"dev\": \"npm-run-all --parallel \\\"build:types --watch\\\" \\\"build:dev --watch\\\"\"\n  },\n  \"publishConfig\": {\n    \"registry\": \"https://registry.yarnpkg.com\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vv-vim/vv.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/vv-vim/vv/issues\"\n  },\n  \"browserslist\": [\n    \"defaults\",\n    \"last 2 electron versions\"\n  ],\n  \"devDependencies\": {\n    \"@types/express\": \"^4.17.11\",\n    \"@types/jest-dev-server\": \"^5.0.3\",\n    \"@types/jest-image-snapshot\": \"^6.4.0\",\n    \"@types/lodash\": \"^4.14.168\",\n    \"@types/node\": \"^16.0.0\",\n    \"@types/ws\": \"^7.4.0\",\n    \"jest-dev-server\": \"^11.0.0\",\n    \"jest-environment-jsdom\": \"^30.1.1\",\n    \"jest-image-snapshot\": \"^6.5.1\",\n    \"jsdom\": \"^26.1.0\",\n    \"puppeteer\": \"^24.17.1\"\n  },\n  \"dependencies\": {\n    \"@vvim/nvim\": \"0.0.1\",\n    \"lodash\": \"^4.17.21\",\n    \"ws\": \"^7.4.6\"\n  }\n}\n"
  },
  {
    "path": "packages/browser-renderer/src/__tests__/renderer.test.ts",
    "content": "import { EventEmitter } from 'events';\nimport initRenderer from 'src/renderer';\n\nimport Nvim from '@vvim/nvim';\nimport initScreen from 'src/screen';\nimport initKeyboard from 'src/input/keyboard';\nimport initMouse from 'src/input/mouse';\nimport hideMouseCursor from 'src/features/hideMouseCursor';\n\nconst mockTransport = new EventEmitter();\njest.mock('src/transport/transport', () => {\n  return jest.fn().mockImplementation(() => mockTransport);\n});\n\njest.mock('@vvim/nvim');\njest.mock('src/screen', () => jest.fn(() => 'fakeScreen'));\njest.mock('src/input/keyboard', () => jest.fn());\njest.mock('src/input/mouse', () => jest.fn());\njest.mock('src/features/hideMouseCursor', () => jest.fn());\n\ndescribe('renderer', () => {\n  const mockedNvim = (Nvim as unknown) as jest.Mock<Nvim>;\n\n  beforeEach(() => {\n    mockTransport.removeAllListeners();\n    initRenderer();\n  });\n\n  test('init screen', () => {\n    mockTransport.emit('initRenderer', 'settings');\n    expect(initScreen).toHaveBeenCalledWith({\n      nvim: mockedNvim.mock.instances[0],\n      settings: 'settings',\n      transport: mockTransport,\n    });\n  });\n\n  test('init nvim', () => {\n    mockTransport.emit('initRenderer', 'settings');\n    expect(Nvim).toHaveBeenCalledWith(mockTransport, true);\n  });\n\n  test('init keyboard', () => {\n    mockTransport.emit('initRenderer', 'settings');\n    expect(initKeyboard).toHaveBeenCalledWith({\n      nvim: mockedNvim.mock.instances[0],\n      screen: 'fakeScreen',\n    });\n  });\n\n  test('init mouse', () => {\n    mockTransport.emit('initRenderer', 'settings');\n    expect(initMouse).toHaveBeenCalledWith({\n      nvim: mockedNvim.mock.instances[0],\n      screen: 'fakeScreen',\n    });\n  });\n\n  test('init hideMouseCursor', () => {\n    mockTransport.emit('initRenderer', 'settings');\n    expect(hideMouseCursor).toHaveBeenCalledWith();\n  });\n});\n"
  },
  {
    "path": "packages/browser-renderer/src/__tests__/screen.test.ts",
    "content": "/** @jest-environment node */\n\nimport puppeteer from 'puppeteer';\nimport { PORT } from 'config/jest/testServer';\n\nimport type { Browser, Page } from 'puppeteer';\n\ndescribe('Screen', () => {\n  jest.setTimeout(30000);\n\n  let browser: Browser;\n  let page: Page;\n\n  beforeAll(async () => {\n    browser = await puppeteer.launch({\n      headless: true,\n      slowMo: 10,\n      args: ['--no-sandbox', '--disable-setuid-sandbox'],\n    });\n  });\n\n  afterAll(async () => {\n    // await browser.close();\n  });\n\n  beforeEach(async () => {\n    page = await browser.newPage();\n\n    await page.setViewport({\n      width: 300,\n      height: 200,\n      deviceScaleFactor: 2,\n    });\n\n    await page.goto(`http://localhost:${PORT}`);\n    await page.waitForSelector('input');\n    await page.keyboard.type(':VVset fontfamily=Courier\\\\ New');\n    await page.keyboard.press('Enter');\n  });\n\n  afterEach(async () => {\n    await page.close();\n  });\n\n  it('match snapshot', async () => {\n    await page.keyboard.type('iHello');\n    await page.keyboard.press('Escape');\n\n    const image = await page.screenshot();\n    expect(image).toMatchImageSnapshot();\n  });\n\n  it('redraw screen on default_colors_set', async () => {\n    await page.keyboard.type(':colorscheme desert');\n    await page.keyboard.press('Enter');\n\n    const image = await page.screenshot();\n    expect(image).toMatchImageSnapshot();\n  });\n\n  test('show undercurl behind the text', async () => {\n    await page.keyboard.type(':set filetype=javascript');\n    await page.keyboard.press('Enter');\n    await page.keyboard.type(':VVset lineheight=1');\n    await page.keyboard.press('Enter');\n    await page.keyboard.type(':syntax on');\n    await page.keyboard.press('Enter');\n    await page.keyboard.type(':hi Comment gui=undercurl guifg=white guisp=red');\n    await page.keyboard.press('Enter');\n    await page.keyboard.type('i// Hey!');\n\n    const image = await page.screenshot();\n    expect(image).toMatchImageSnapshot();\n  });\n\n  test('overlap chars', async () => {\n    await page.keyboard.type(':VVset letterspacing=-8');\n    await page.keyboard.press('Enter');\n    await page.keyboard.type(\n      'i\\n\\n\\nO   O  O O O O O O O O O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO',\n    );\n    await page.keyboard.press('Escape');\n\n    await page.keyboard.type('hhhi ');\n    await page.keyboard.press('Escape');\n    await page.keyboard.type('hhh');\n    const image = await page.screenshot();\n    expect(image).toMatchImageSnapshot();\n\n    await page.keyboard.type(':vs');\n    await page.keyboard.press('Enter');\n    await page.keyboard.type(':vs');\n    await page.keyboard.press('Enter');\n    await page.keyboard.type('i');\n    await page.keyboard.press('Escape');\n\n    await page.mouse.move(150, 100);\n    await page.mouse.wheel({ deltaY: 100 });\n\n    const image1 = await page.screenshot();\n    expect(image1).toMatchImageSnapshot();\n  });\n});\n"
  },
  {
    "path": "packages/browser-renderer/src/features/hideMouseCursor.ts",
    "content": "/**\n * Hides mouse cursor when you start typing. Shows it again when you move mouse.\n */\nfunction showCursor() {\n  document.body.style.cursor = 'auto';\n  document.addEventListener('keydown', hideCursor); // eslint-disable-line no-use-before-define\n  document.removeEventListener('mousemove', showCursor);\n}\n\nfunction hideCursor(): void {\n  document.body.style.cursor = 'none';\n  document.addEventListener('mousemove', showCursor);\n  document.removeEventListener('keydown', hideCursor);\n}\n\nexport default hideCursor;\n"
  },
  {
    "path": "packages/browser-renderer/src/index.ts",
    "content": "// Only use relative imports here because https://github.com/microsoft/TypeScript/issues/32999#issuecomment-523558695\nimport renderer from './renderer';\n\nexport default renderer;\n"
  },
  {
    "path": "packages/browser-renderer/src/input/__tests__/keyboard.test.ts",
    "content": "import initKeyboard from 'src/input/keyboard';\n\nimport Nvim from '@vvim/nvim';\nimport { Screen } from 'src/screen';\n\ndescribe('Keyboard input', () => {\n  const nvimOn = jest.fn();\n\n  const screen = ({\n    getCursorElement: jest.fn<HTMLDivElement, void[]>(),\n  } as unknown) as Screen;\n\n  const nvim = ({\n    input: jest.fn(),\n    on: nvimOn,\n  } as unknown) as Nvim;\n\n  const simulateKeyDown = (options: KeyboardEventInit) => {\n    const event = new KeyboardEvent('keydown', options);\n    document.dispatchEvent(event);\n  };\n\n  const addEventListenerSpy = jest.spyOn(document, 'addEventListener');\n\n  beforeEach(() => {\n    initKeyboard({ screen, nvim });\n  });\n\n  afterEach(() => {\n    const rootElm = document.documentElement;\n    rootElm.innerHTML = '<head></head><body></body>';\n    addEventListenerSpy.mock.calls.forEach(([event, callback, options]) =>\n      document.removeEventListener(event, callback, options),\n    );\n  });\n\n  describe('key input', () => {\n    test('sends key value to nvim input', () => {\n      simulateKeyDown({ key: 'i' });\n      expect(nvim.input).toHaveBeenCalledWith('i');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n\n    test('special key', () => {\n      simulateKeyDown({ code: 'Insert' });\n      expect(nvim.input).toHaveBeenCalledWith('<Insert>');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n\n    test('special key with Shift', () => {\n      simulateKeyDown({ code: 'Insert', shiftKey: true });\n      expect(nvim.input).toHaveBeenCalledWith('<S-Insert>');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n\n    test('special key with modifier', () => {\n      simulateKeyDown({ code: 'Insert', altKey: true });\n      expect(nvim.input).toHaveBeenCalledWith('<A-Insert>');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n\n    test('< key', () => {\n      simulateKeyDown({ key: '<', shiftKey: true });\n      expect(nvim.input).toHaveBeenCalledWith('<lt>');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n\n    test('\\\\ key', () => {\n      simulateKeyDown({ key: '\\\\' });\n      expect(nvim.input).toHaveBeenCalledWith('<Bslash>');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n\n    test('| key', () => {\n      simulateKeyDown({ key: '|' });\n      expect(nvim.input).toHaveBeenCalledWith('<Bar>');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n\n    test.todo('TODO: test all special keys');\n  });\n\n  describe('motifiers', () => {\n    test('CTRL key adds <C-*> modifier', () => {\n      simulateKeyDown({ key: 'i', ctrlKey: true });\n      expect(nvim.input).toHaveBeenCalledWith('<C-i>');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n\n    test('Option key adds <A-*> modifier', () => {\n      simulateKeyDown({ key: 'i', altKey: true });\n      expect(nvim.input).toHaveBeenCalledWith('<A-i>');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n\n    test('CMD key adds <D-*> modifier', () => {\n      simulateKeyDown({ key: 'i', metaKey: true });\n      expect(nvim.input).toHaveBeenCalledWith('<D-i>');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n\n    test('Shift key does not add modifier without other motifiers', () => {\n      simulateKeyDown({ key: 'I', shiftKey: true });\n      expect(nvim.input).toHaveBeenCalledWith('I');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n\n    test('Shift adds modifier with other motifiers', () => {\n      simulateKeyDown({ key: 'i', ctrlKey: true, shiftKey: true });\n      expect(nvim.input).toHaveBeenCalledWith('<C-S-i>');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n\n    test('multiple motifiers', () => {\n      simulateKeyDown({ key: 'i', ctrlKey: true, metaKey: true, altKey: true, shiftKey: true });\n      expect(nvim.input).toHaveBeenCalledWith('<D-A-C-S-i>');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n  });\n\n  describe('Option key modifier', () => {\n    test(\"Map Dead key with Option to it's latin value\", () => {\n      simulateKeyDown({ key: 'Dead', code: 'KeyI', altKey: true });\n      expect(nvim.input).toHaveBeenCalledWith('<A-i>');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n\n    test('Skip Dead key if Option is not pressed', () => {\n      simulateKeyDown({ key: 'Dead', code: 'KeyI' });\n      expect(nvim.input).toHaveBeenCalledTimes(0);\n    });\n\n    test('Skip Dead key if there are no latin code for it', () => {\n      simulateKeyDown({ key: 'Dead', code: 'NotKeyI', altKey: true });\n      expect(nvim.input).toHaveBeenCalledTimes(0);\n    });\n\n    test('Adds A- modifier for non-Dead key', () => {\n      simulateKeyDown({ key: '∆', altKey: true });\n      expect(nvim.input).toHaveBeenCalledWith('<A-∆>');\n      expect(nvim.input).toHaveBeenCalledTimes(1);\n    });\n\n    describe('with input mode', () => {\n      beforeEach(() => {\n        nvimOn.mock.calls[0][1]([['mode_change', ['insert']]]);\n      });\n\n      test('Does not add A- modifier', () => {\n        simulateKeyDown({ key: '∆', code: 'KeyJ', altKey: true });\n        expect(nvim.input).toHaveBeenCalledWith('∆');\n        expect(nvim.input).toHaveBeenCalledTimes(1);\n      });\n\n      test('Does not add A- modifier with Shift', () => {\n        simulateKeyDown({ key: 'Ô', code: 'KeyJ', altKey: true, shiftKey: true });\n        expect(nvim.input).toHaveBeenCalledWith('Ô');\n        expect(nvim.input).toHaveBeenCalledTimes(1);\n      });\n\n      test('Adds A- modifier with Control', () => {\n        simulateKeyDown({ key: '∆', code: 'KeyJ', altKey: true, ctrlKey: true });\n        expect(nvim.input).toHaveBeenCalledWith('<A-C-∆>');\n        expect(nvim.input).toHaveBeenCalledTimes(1);\n      });\n\n      test('Adds A- modifier with Command', () => {\n        simulateKeyDown({ key: '∆', code: 'KeyJ', altKey: true, metaKey: true });\n        expect(nvim.input).toHaveBeenCalledWith('<D-A-∆>');\n        expect(nvim.input).toHaveBeenCalledTimes(1);\n      });\n    });\n  });\n\n  describe('focus input', () => {\n    let input: HTMLInputElement;\n    let focusSpy: jest.SpyInstance;\n    let blurSpy: jest.SpyInstance;\n\n    beforeEach(() => {\n      input = document.getElementsByTagName('input')[0];\n      focusSpy = jest.spyOn(input, 'focus');\n      blurSpy = jest.spyOn(input, 'blur');\n    });\n\n    test('focus input on insert mode', () => {\n      nvimOn.mock.calls[0][1]([['mode_change', ['insert']]]);\n      expect(focusSpy).toHaveBeenCalled();\n    });\n\n    test('focus input on cmdline_normal mode', () => {\n      nvimOn.mock.calls[0][1]([['mode_change', ['cmdline_normal']]]);\n      expect(focusSpy).toHaveBeenCalled();\n    });\n\n    test('blurs input on other modes', () => {\n      nvimOn.mock.calls[0][1]([['mode_change', ['normal']]]);\n      expect(blurSpy).toHaveBeenCalled();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/browser-renderer/src/input/keyboard.ts",
    "content": "import type { Nvim } from '@vvim/nvim';\nimport { Screen } from 'src/screen';\n\n// :help keyCode\nconst specialKey = ({ key, code }: KeyboardEvent): string =>\n  (({\n    Insert: 'Insert',\n    Numpad0: 'k0',\n    Numpad1: 'k1',\n    Numpad2: 'k2',\n    Numpad3: 'k3',\n    Numpad4: 'k4',\n    Numpad5: 'k5',\n    Numpad6: 'k6',\n    Numpad7: 'k7',\n    Numpad8: 'k8',\n    Numpad9: 'k9',\n    NumpadAdd: 'kPlus',\n    NumpadSubtract: 'kMinus',\n    NumpadMultiply: 'kMultiply',\n    NumpadDivide: 'kDivide',\n    NumpadEnter: 'kEnter',\n    NumpadDecimal: 'kPoint',\n    Escape: 'Esc',\n    Backspace: 'BS',\n    Delete: 'Del',\n    Enter: 'CR',\n    Tab: 'Tab',\n    ArrowUp: 'Up',\n    ArrowDown: 'Down',\n    ArrowLeft: 'Left',\n    ArrowRight: 'Right',\n    PageUp: 'PageUp',\n    PageDown: 'PageDown',\n    Home: 'Home',\n    End: 'End',\n    F1: 'F1',\n    F2: 'F2',\n    F3: 'F3',\n    F4: 'F4',\n    F5: 'F5',\n    F6: 'F6',\n    F7: 'F7',\n    F8: 'F8',\n    F9: 'F9',\n    F10: 'F10',\n    F11: 'F11',\n    F12: 'F12',\n  } as Record<string, string>)[code] ||\n  ({\n    '<': 'lt',\n    '\\\\': 'Bslash',\n    '|': 'Bar',\n  } as Record<string, string>)[key]);\n\nconst skip = (key: string) =>\n  (({\n    Shift: true,\n    Control: true,\n    Alt: true,\n    Meta: true,\n    CapsLock: true,\n  } as Record<string, boolean>)[key]);\n\nexport const modifierPrefix = (\n  { metaKey, altKey, ctrlKey }: KeyboardEvent | MouseEvent,\n  insertMode?: boolean,\n): string => {\n  if (insertMode && altKey && !ctrlKey && !metaKey) {\n    return '';\n  }\n  return `${metaKey ? 'D-' : ''}${altKey ? 'A-' : ''}${ctrlKey ? 'C-' : ''}`;\n};\n\nexport const shiftPrefix = ({ shiftKey, key }: KeyboardEvent): string =>\n  shiftKey && key !== '<' ? 'S-' : '';\n\n/**\n * Filter hotkeys from menu.\n * TODO: Make it customizable and make it work differently in browser and electron app.\n */\nconst filterResult = (result: string) =>\n  !({\n    '<D-c>': true, // Cmd+C\n    '<D-v>': true, // Cmd+V\n    '<D-a>': true, // Cmd+A: \"Select all\" menu item\n    '<D-=>': true, // Cmd+Plus: \"Zoom In\" menu item\n    '<D-->': true, // Cmd+-: \"Zoom Out\" menu item\n    '<D-0>': true, // Cmd+0: \"Actual Size\" menu item\n    '<D-C-f>': true, // Cmd+Ctrl+F: \"Toggle Full Screen\" menu item\n    '<D-m>': true, // Cmd+M: \"Minimize\" menu item\n    '<D-h>': true, // Cmd+H: Hide window\n    '<D-q>': true, // Cmd+Q: Quit\n    '<D-o>': true, // Cmd+O: Open file\n    '<D-n>': true, // Cmd+N: New window\n    '<D-w>': true, // Cmd+W: Close window\n  } as Record<string, boolean>)[result] && result;\n\n// https://github.com/rhysd/NyaoVim/issues/87\nconst replaceResult = (result: string) =>\n  (({\n    '<C-6>': '<C-^>',\n    '<C-->': '<C-_>',\n    '<C-2>': '<C-@>',\n  } as Record<string, string>)[result] || result);\n\nconst eventKeyCode = (event: KeyboardEvent, insertMode?: boolean): string | null => {\n  const { key } = event;\n\n  if (skip(key)) return null;\n\n  // Handle Alt + modifier key input (for example Alt + i)\n  let deadKey;\n  if (key === 'Dead') {\n    if (!insertMode && event.altKey && event.code.match(/^Key[A-Z]$/)) {\n      deadKey = event.code[3].toLowerCase();\n    } else {\n      return null;\n    }\n  }\n\n  const modifier = modifierPrefix(event, insertMode);\n  const shift = shiftPrefix(event);\n  const special = specialKey(event);\n\n  const keyCode = deadKey || special || key;\n\n  const result = modifier || special ? `<${modifier}${shift}${keyCode}>` : keyCode;\n\n  const filteredResult = filterResult(result);\n  if (!filteredResult) {\n    return null;\n  }\n\n  return replaceResult(filteredResult);\n};\n\nconst initKeyboard = ({ nvim, screen }: { nvim: Nvim; screen: Screen }): void => {\n  const { getCursorElement } = screen;\n\n  let disableNextInput = false;\n  let inputKey: string | null = null;\n  let isComposing = false;\n  let compositionValue = null;\n  let insertMode = false;\n\n  const input = document.createElement('input');\n\n  input.style.position = 'absolute';\n  input.style.opacity = '0';\n  input.style.left = '0';\n  input.style.top = '0';\n  input.style.width = '0';\n  input.style.height = '0';\n\n  (getCursorElement() || document.getElementsByTagName('body')[0]).appendChild(input);\n\n  const handleKeydown = async (event: KeyboardEvent) => {\n    disableNextInput = true;\n    if (!isComposing) {\n      inputKey = eventKeyCode(event, insertMode);\n      if (inputKey) {\n        nvim.input(inputKey);\n      }\n    }\n  };\n\n  // Non-keyboard input. For example insert emoji.\n  const handleInput = (event: InputEvent) => {\n    if (disableNextInput || isComposing) {\n      disableNextInput = false;\n      return;\n    }\n    if (event.data) {\n      nvim.input(event.data);\n    }\n  };\n\n  // Composition input for logograms or diacritical signs. Also works for speech input.\n  const handleCompositionStart = () => {\n    isComposing = true;\n    compositionValue = inputKey || '';\n  };\n\n  const handleCompositionEnd = () => {\n    isComposing = false;\n  };\n\n  const handleCompositionUpdate = (event: CompositionEvent) => {\n    nvim.input(`${'<BS>'.repeat(compositionValue.length)}${event.data}`);\n    compositionValue = event.data;\n  };\n\n  document.addEventListener('keydown', handleKeydown);\n\n  // @ts-expect-error input event type is incorrect\n  input.addEventListener('input', handleInput);\n  input.addEventListener('compositionstart', handleCompositionStart);\n  input.addEventListener('compositionupdate', handleCompositionUpdate);\n  input.addEventListener('compositionend', handleCompositionEnd);\n\n  // Enable composition input only for insert and command-line modes. Enabling if for other modes\n  // is tricky. `preventDefault` does not work for compositionstart, so we need to blur/focus input\n  // element for this.\n  nvim.on('redraw', (args) => {\n    args.forEach((arg) => {\n      if (arg[0] === 'mode_change') {\n        const [mode] = arg[1];\n        // https://github.com/neovim/neovim/blob/master/src/nvim/cursor_shape.c#L18\n        if (['insert', 'cmdline_normal'].includes(mode)) {\n          insertMode = true;\n          input.focus();\n        } else {\n          insertMode = false;\n          input.blur();\n        }\n      }\n    });\n  });\n};\n\nexport default initKeyboard;\n"
  },
  {
    "path": "packages/browser-renderer/src/input/mouse.ts",
    "content": "import throttle from 'lodash/throttle';\n\nimport { modifierPrefix } from 'src/input/keyboard';\nimport { Screen } from 'src/screen';\nimport type Nvim from '@vvim/nvim';\n\nconst GRID = 0;\n\nconst SCROLL_STEP_X = 6;\nconst SCROLL_STEP_Y = 3;\nconst MOUSE_BUTTON = {\n  0: 'left',\n  1: 'middle',\n  2: 'right',\n  WHEEL: 'wheel',\n};\n\nconst ACTION = {\n  UP: 'up',\n  DOWN: 'down',\n  LEFT: 'left',\n  RIGHT: 'right',\n  PRESS: 'press',\n  DRAG: 'drag',\n  RELEASE: 'release',\n} as const;\n\ntype Action = typeof ACTION[keyof typeof ACTION];\n\n// const initMouse = ({ screenCoords }: Screen, nvim: Nvim): void => {\nconst initMouse = ({ screen, nvim }: { screen: Screen; nvim: Nvim }): void => {\n  const { screenCoords } = screen;\n  let scrollDeltaX = 0;\n  let scrollDeltaY = 0;\n\n  let mouseCoords: [number, number] = [0, 0];\n  let mouseButtonDown: boolean;\n\n  const mouseCoordsChanged = (event: MouseEvent) => {\n    const newCoords = screenCoords(event.clientX, event.clientY);\n    if (newCoords[0] !== mouseCoords[0] || newCoords[1] !== mouseCoords[1]) {\n      mouseCoords = newCoords;\n      return true;\n    }\n    return false;\n  };\n\n  const buttonName = (event: MouseEvent) =>\n    // @ts-expect-error TODO\n    event.type === 'wheel' ? MOUSE_BUTTON.WHEEL : MOUSE_BUTTON[event.button];\n\n  const mouseInput = (event: MouseEvent, action: Action) => {\n    mouseCoordsChanged(event);\n    const [col, row] = screenCoords(event.clientX, event.clientY);\n    const button = buttonName(event);\n    const modifier = modifierPrefix(event);\n    nvim.inputMouse(button, action, modifier, GRID, row, col);\n  };\n\n  const calculateScroll = (event: MouseEvent) => {\n    let [scrollX, scrollY] = screenCoords(Math.abs(scrollDeltaX), Math.abs(scrollDeltaY));\n    scrollX = Math.floor(scrollX / SCROLL_STEP_X);\n    scrollY = Math.floor(scrollY / SCROLL_STEP_Y);\n\n    if (scrollY === 0 && scrollX === 0) return;\n\n    if (scrollY !== 0) {\n      mouseInput(event, scrollDeltaY > 0 ? ACTION.DOWN : ACTION.UP);\n      scrollDeltaY = 0;\n    }\n\n    if (scrollX !== 0) {\n      mouseInput(event, scrollDeltaX > 0 ? ACTION.RIGHT : ACTION.LEFT);\n      scrollDeltaX = 0;\n    }\n  };\n\n  const handleMousewheel = (event: WheelEvent) => {\n    const { deltaX, deltaY } = event;\n    if (scrollDeltaY * deltaY < 0) scrollDeltaY = 0;\n    scrollDeltaX += deltaX;\n    scrollDeltaY += deltaY;\n    calculateScroll(event);\n  };\n\n  const handleMousedown = (event: MouseEvent) => {\n    event.preventDefault();\n    event.stopPropagation();\n    mouseButtonDown = true;\n    mouseInput(event, ACTION.PRESS);\n  };\n\n  const handleMouseup = (event: MouseEvent) => {\n    event.preventDefault();\n    event.stopPropagation();\n    mouseButtonDown = false;\n    mouseInput(event, ACTION.RELEASE);\n  };\n\n  const handleMousemove = (event: MouseEvent) => {\n    if (mouseButtonDown) {\n      event.preventDefault();\n      event.stopPropagation();\n      if (mouseCoordsChanged(event)) mouseInput(event, ACTION.DRAG);\n    }\n  };\n  nvim.command('set mouse=a'); // Enable mouse events\n\n  document.addEventListener('mousedown', handleMousedown);\n  document.addEventListener('mouseup', handleMouseup);\n  document.addEventListener('mousemove', throttle(handleMousemove, 50));\n  document.addEventListener('wheel', handleMousewheel);\n};\n\nexport default initMouse;\n"
  },
  {
    "path": "packages/browser-renderer/src/lib/__tests__/getColor.test.ts",
    "content": "import { getColor, getColorNum } from 'src/lib/getColor';\n\ndescribe('getColor', () => {\n  test('0 is black', () => {\n    expect(getColor(0)).toBe('rgb(0,0,0)');\n  });\n\n  test('0xffffff is white', () => {\n    expect(getColor(0xffffff)).toBe('rgb(255,255,255)');\n  });\n\n  test('0x333333 is gray', () => {\n    expect(getColor(0x333333)).toBe('rgb(51,51,51)');\n  });\n\n  test('0x003300 is rgb(0,51,0)', () => {\n    expect(getColor(0x003300)).toBe('rgb(0,51,0)');\n  });\n});\n\ndescribe('getColorNum', () => {\n  test('rgb(0, 0, 0) is 0', () => {\n    expect(getColorNum('rgb(0,0,0)')).toBe(0);\n  });\n\n  test('rgb(255,255,255) is 0xffffff', () => {\n    expect(getColorNum('rgb(255,255,255)')).toBe(0xffffff);\n  });\n\n  test('rgb(51,51,51) is 0x333333', () => {\n    expect(getColorNum('rgb(51,51,51)')).toBe(0x333333);\n  });\n\n  test('rgb(0,51,0) is 0x00ff00', () => {\n    expect(getColorNum('rgb(0,51,0)')).toBe(0x003300);\n  });\n\n  test('returns undefined for undefined param', () => {\n    expect(getColorNum()).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "packages/browser-renderer/src/lib/getColor.ts",
    "content": "/* eslint-disable no-bitwise */\n\nimport memoize from 'lodash/memoize';\n\n/**\n * Get color by number, for example hex number `0xFF0000` becomes `rgb(255,0,0)`\n * @param color Color in number\n * @param defaultColor Use default color if color is undefined or -1\n */\nexport const getColor = (color: number | undefined, defaultColor?: string): string | undefined => {\n  if (typeof color !== 'number' || color === -1) return defaultColor;\n  return `rgb(${(color >> 16) & 0xff},${(color >> 8) & 0xff},${color & 0xff})`;\n};\n\n/**\n * Get color number from string, for example `rgb(255,0,0)` becomes `0xFF0000`\n * @param color Color in rgb string\n */\nexport const getColorNum = memoize((color?: string): number | undefined => {\n  if (color) {\n    const [r, g, b] = color\n      .replace(/([^0-9,])/g, '')\n      .split(',')\n      .map((s) => parseInt(s, 10));\n    return (r << 16) + (g << 8) + b;\n  }\n  return undefined;\n});\n"
  },
  {
    "path": "packages/browser-renderer/src/lib/isWeb.ts",
    "content": "const isWeb = (): boolean =>\n  window.location.protocol === 'http:' || window.location.protocol === 'https:';\n\nexport default isWeb;\n"
  },
  {
    "path": "packages/browser-renderer/src/preloaded/electron.ts",
    "content": "export interface PreloadedIpcRenderer {\n  /**\n   * Listens to `channel`, when a new message arrives `listener` would be called with\n   * `listener(args...)`.\n   */\n  on(channel: string, listener: (...args: any[]) => void): this;\n  /**\n   * Adds a one time `listener` function for the event. This `listener` is invoked\n   * only the next time a message is sent to `channel`, after which it is removed.\n   */\n  removeListener(channel: string, listener: (...args: any[]) => void): this;\n  /**\n   * Send an asynchronous message to the main process via `channel`, along with\n   * arguments. Arguments will be serialized with the Structured Clone Algorithm,\n   * just like `window.postMessage`, so prototype chains will not be included.\n   * Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an\n   * exception.\n   *\n   * > **NOTE**: Sending non-standard JavaScript types such as DOM objects or special\n   * Electron objects is deprecated, and will begin throwing an exception starting\n   * with Electron 9.\n   *\n   * The main process handles it by listening for `channel` with the `ipcMain`\n   * module.\n   */\n  send(channel: string, ...args: any[]): void;\n}\n\ndeclare global {\n  interface Window {\n    electron: {\n      ipcRenderer: PreloadedIpcRenderer;\n    };\n  }\n}\n\nexport const { ipcRenderer } = window.electron || {};\n"
  },
  {
    "path": "packages/browser-renderer/src/renderer.ts",
    "content": "import Nvim from '@vvim/nvim';\n\nimport { Settings } from 'src/types';\n\nimport Transport from 'src/transport/transport';\nimport initScreen from 'src/screen';\nimport initKeyboard from 'src/input/keyboard';\nimport initMouse from 'src/input/mouse';\nimport hideMouseCursor from 'src/features/hideMouseCursor';\n\n/**\n * Browser renderer\n */\nconst renderer = (): void => {\n  const transport = new Transport();\n\n  const initRenderer = (settings: Settings) => {\n    const nvim = new Nvim(transport, true);\n    const screen = initScreen({ nvim, settings, transport });\n    initKeyboard({ nvim, screen });\n    initMouse({ nvim, screen });\n    hideMouseCursor();\n  };\n\n  transport.on('initRenderer', initRenderer);\n};\n\nexport default renderer;\n"
  },
  {
    "path": "packages/browser-renderer/src/screen.ts",
    "content": "import isEqual from 'lodash/isEqual';\n\nimport emojiRegex from 'emoji-regex';\n\nimport { getColor } from 'src/lib/getColor';\n\nimport type { Settings } from 'src/types';\nimport type {\n  Nvim,\n  Transport,\n  UiEventsHandlers,\n  UiEventsArgs,\n  ModeInfo,\n  HighlightAttrs,\n} from '@vvim/nvim';\n\nexport type Screen = {\n  screenCoords: (width: number, height: number) => [number, number];\n  getCursorElement: () => HTMLDivElement;\n};\n\ntype CalculatedProps = {\n  bgColor: string;\n  fgColor: string;\n  spColor?: string;\n  hiItalic: boolean;\n  hiBold: boolean;\n  hiUnderline: boolean;\n  hiUndercurl: boolean;\n  hiStrikethrough: boolean;\n};\n\ntype HighlightProps = {\n  calculated?: CalculatedProps;\n  value?: HighlightAttrs;\n};\n\ntype HighlightTable = Record<number, HighlightProps>;\n\ntype Char = {\n  bitmap?: HTMLCanvasElement;\n  char?: string | null;\n  hlId?: number;\n};\n\nconst DEFAULT_FONT_FAMILY = 'Courier New';\n\nconst DEFAULT_FG_COLOR = 'rgb(255,255,255)';\nconst DEFAULT_BG_COLOR = 'rgb(0,0,0)';\nconst DEFAULT_SP_COLOR = 'rgb(255,255,255)';\nconst DEFAULT_FONT_SIZE = 12;\nconst DEFAULT_LINE_HEIGHT = 1.25;\nconst DEFAULT_LETTER_SPACING = 0;\n\nlet cursorAnimation: Animation;\n\nconst isEmoji = (char: string): boolean => {\n  const regex = emojiRegex();\n  return !!char.match(regex);\n};\n\nconst screen = ({\n  settings,\n  transport,\n  nvim,\n}: {\n  settings: Settings;\n  transport: Transport;\n  nvim: Nvim;\n}): Screen => {\n  let screenContainer: HTMLDivElement;\n  let cursorEl: HTMLDivElement;\n\n  let canvasEl: HTMLCanvasElement;\n  let context: CanvasRenderingContext2D;\n\n  let cursorCanvasEl: HTMLCanvasElement;\n  let cursorContext: CanvasRenderingContext2D;\n\n  let cursorPosition: [number, number] = [0, 0];\n  let cursorChar: string;\n\n  let scale: number;\n  let charWidth: number;\n  let charHeight: number;\n\n  let fontFamily = DEFAULT_FONT_FAMILY;\n  let fontSize = DEFAULT_FONT_SIZE;\n  let lineHeight = DEFAULT_LINE_HEIGHT;\n  let letterSpacing = DEFAULT_LETTER_SPACING;\n\n  let cols: number;\n  let rows: number;\n\n  let modeInfoSet: Record<string, ModeInfo>;\n  let mode: string;\n\n  let showBold = true;\n  let showItalic = true;\n  let showUnderline = true;\n  let showUndercurl = true;\n  let showStrikethrough = true;\n\n  let chars: Char[][] = [];\n\n  const highlightTable: HighlightTable = {\n    '0': {\n      calculated: {\n        bgColor: DEFAULT_BG_COLOR,\n        fgColor: DEFAULT_FG_COLOR,\n        spColor: DEFAULT_SP_COLOR,\n        hiItalic: false,\n        hiBold: false,\n        hiUnderline: false,\n        hiUndercurl: false,\n        hiStrikethrough: false,\n      },\n    },\n    // Inverted default color for cursor\n    '-1': {\n      calculated: {\n        bgColor: DEFAULT_FG_COLOR,\n        fgColor: DEFAULT_BG_COLOR,\n        spColor: DEFAULT_SP_COLOR,\n        hiItalic: false,\n        hiBold: false,\n        hiUnderline: false,\n        hiUndercurl: false,\n        hiStrikethrough: false,\n      },\n    },\n  };\n\n  let isResizing = false;\n  let isResizingTimeout: NodeJS.Timeout | undefined;\n\n  const setResizing = () => {\n    isResizing = true;\n    if (isResizingTimeout) {\n      clearTimeout(isResizingTimeout);\n      isResizingTimeout = undefined;\n    }\n    isResizingTimeout = setTimeout(() => {\n      isResizing = false;\n    }, 300);\n  };\n\n  const getCursorElement = (): HTMLDivElement => cursorEl;\n\n  const initCursor = () => {\n    cursorEl = document.createElement('div');\n    cursorEl.style.position = 'absolute';\n    cursorEl.style.zIndex = '100';\n    cursorEl.style.top = '0';\n    cursorEl.style.left = '0';\n\n    screenContainer.appendChild(cursorEl);\n\n    cursorCanvasEl = document.createElement('canvas');\n\n    cursorCanvasEl.style.position = 'absolute';\n    cursorCanvasEl.style.top = '0px';\n    cursorCanvasEl.style.left = '0px';\n\n    cursorContext = cursorCanvasEl.getContext('2d', { alpha: true }) as CanvasRenderingContext2D;\n\n    cursorEl.appendChild(cursorCanvasEl);\n  };\n\n  const initScreen = () => {\n    screenContainer = document.createElement('div');\n    document.body.appendChild(screenContainer);\n\n    screenContainer.style.position = 'absolute';\n    screenContainer.style.left = '0';\n    screenContainer.style.top = '0';\n    screenContainer.style.transformOrigin = '0 0';\n\n    canvasEl = document.createElement('canvas');\n\n    canvasEl.style.position = 'absolute';\n    canvasEl.style.top = '0px';\n    canvasEl.style.left = '0px';\n\n    context = canvasEl.getContext('2d', { alpha: false }) as CanvasRenderingContext2D;\n\n    screenContainer.appendChild(canvasEl);\n  };\n\n  const RETINA_SCALE = 2;\n\n  const charsCache: Map<string, HTMLCanvasElement> = new Map();\n\n  const isRetina = () => window.devicePixelRatio === RETINA_SCALE;\n\n  const scaledLetterSpacing = () => {\n    if (isRetina() || letterSpacing === 0) {\n      return letterSpacing;\n    }\n    return letterSpacing > 0\n      ? Math.floor(letterSpacing / RETINA_SCALE)\n      : Math.ceil(letterSpacing / RETINA_SCALE);\n  };\n\n  const scaledFontSize = () => fontSize * scale;\n\n  const measureCharSize = () => {\n    const char = document.createElement('span');\n    char.innerHTML = '0';\n    char.style.fontFamily = fontFamily;\n    char.style.fontSize = `${scaledFontSize()}px`;\n    char.style.lineHeight = `${Math.round(scaledFontSize() * lineHeight)}px`;\n    char.style.position = 'absolute';\n    char.style.left = '-1000px';\n    char.style.top = '0';\n    screenContainer.appendChild(char);\n\n    const oldCharWidth = charWidth;\n    const oldCharHeight = charHeight;\n    charWidth = Math.max(char.offsetWidth + scaledLetterSpacing(), 1);\n    charHeight = char.offsetHeight;\n    if (oldCharWidth !== charWidth || oldCharHeight !== charHeight) {\n      cursorEl.style.width = `${charWidth}px`;\n      cursorEl.style.height = `${charHeight}px`;\n      cursorCanvasEl.width = charWidth;\n      cursorCanvasEl.height = charHeight;\n\n      charsCache.clear();\n    }\n    screenContainer.removeChild(char);\n  };\n\n  const font = (p: CalculatedProps, isEmojiFont?: boolean) =>\n    [\n      p.hiItalic ? 'italic' : '',\n      p.hiBold ? 'bold' : '',\n      `${scaledFontSize()}px`,\n      isEmojiFont ? 'Apple Color Emoji' : fontFamily,\n    ].join(' ');\n\n  const getCharBitmap = (char: string, hlId: number) => {\n    // eslint-disable-next-line\n    const p = highlightTable[hlId].calculated!;\n    const key = `${char}:${p.bgColor}:${p.fgColor}:${\n      p.hiUndercurl || p.hiUnderline ? p.spColor : '-'\n    }:${p.hiItalic}:${p.hiBold}:${p.hiUndercurl}:${p.hiStrikethrough}`;\n    if (!charsCache.has(key)) {\n      // TODO: worker maybe?\n      // const charCanvas = new OffscreenCanvas(charWidth * 3, charHeight);\n      const charCanvas = document.createElement('canvas');\n      charCanvas.width = charWidth * 3;\n      charCanvas.height = charHeight;\n\n      // eslint-disable-next-line\n      const charCtx = charCanvas.getContext('2d', {\n        alpha: true,\n      }) as CanvasRenderingContext2D;\n\n      if (p.hiUndercurl) {\n        charCtx.strokeStyle = p.spColor as string;\n        charCtx.lineWidth = scaledFontSize() * 0.08;\n        const x = charWidth;\n        const y = charHeight - (scaledFontSize() * 0.08) / 2;\n        const h = charHeight * 0.2; // Height of the wave\n        charCtx.beginPath();\n        charCtx.moveTo(x, y);\n        charCtx.bezierCurveTo(x + x / 4, y, x + x / 4, y - h / 2, x + x / 2, y - h / 2);\n        charCtx.bezierCurveTo(x + (x / 4) * 3, y - h / 2, x + (x / 4) * 3, y, x + x, y);\n        charCtx.stroke();\n      }\n\n      charCtx.fillStyle = p.fgColor;\n      charCtx.font = font(p, isEmoji(char));\n      charCtx.textAlign = 'left';\n      charCtx.textBaseline = 'middle';\n      if (char) {\n        charCtx.fillText(\n          char,\n          Math.round(scaledLetterSpacing() / 2) + charWidth,\n          Math.round(charHeight / 2),\n        );\n      }\n\n      if (p.hiUnderline) {\n        charCtx.strokeStyle = p.fgColor;\n        charCtx.lineWidth = scale;\n        charCtx.beginPath();\n        charCtx.moveTo(charWidth, charHeight - scale);\n        charCtx.lineTo(charWidth * 2, charHeight - scale);\n        charCtx.stroke();\n      }\n\n      if (p.hiStrikethrough) {\n        charCtx.strokeStyle = p.fgColor;\n        charCtx.lineWidth = scale;\n        charCtx.beginPath();\n        charCtx.moveTo(charWidth, charHeight * 0.5);\n        charCtx.lineTo(charWidth * 2, charHeight * 0.5);\n        charCtx.stroke();\n      }\n      charsCache.set(key, charCanvas);\n    }\n    // eslint-disable-next-line\n    return charsCache.get(key)!;\n  };\n\n  const initChar = (i: number, j: number) => {\n    if (!chars[i]) chars[i] = [];\n    if (!chars[i][j]) chars[i][j] = {};\n  };\n\n  const printBackground = (hlId: number, i: number, j: number, length: number) => {\n    const fillStyle = highlightTable[hlId]?.calculated?.bgColor;\n    if (fillStyle) {\n      context.fillStyle = fillStyle;\n      // Add an extra BG if this is the edge of the screen to make it look nicer\n      const isEndOfLine = j + length === cols;\n      const bgWidth = isEndOfLine ? (length + 1) * charWidth : length * charWidth;\n      context.fillRect(j * charWidth, i * charHeight, bgWidth, charHeight);\n    }\n  };\n\n  const printChar = (i: number, j: number, char: string, hlId: number) => {\n    initChar(i, j);\n    chars[i][j].char = char;\n    chars[i][j].hlId = hlId;\n    chars[i][j].bitmap = getCharBitmap(char, hlId);\n\n    context.drawImage(\n      chars[i][j].bitmap as HTMLCanvasElement,\n      0,\n      0,\n      charWidth * 3,\n      charHeight,\n      (j - 1) * charWidth,\n      i * charHeight,\n      charWidth * 3,\n      charHeight,\n    );\n  };\n\n  // https://github.com/neovim/neovim/blob/5a11e55/runtime/doc/ui.txt#L237\n  const redrawDefaultColors = () => {\n    context.fillStyle = highlightTable[0]?.calculated?.bgColor || DEFAULT_BG_COLOR;\n    context.fillRect(0, 0, canvasEl.width, canvasEl.height);\n    for (let i = 0; i <= rows; i += 1) {\n      if (chars[i]) {\n        for (let j = 0; j <= cols; j += 1) {\n          const redrawChar = chars[i][j];\n          if (redrawChar) {\n            const { hlId } = redrawChar;\n            if (hlId !== undefined && hlId > 0) {\n              const { foreground, background, special } = highlightTable[hlId].value || {};\n              if (!foreground || !background || !special) {\n                printBackground(hlId, i, j, 1);\n              }\n            }\n          }\n        }\n      }\n    }\n    for (let i = 0; i <= rows; i += 1) {\n      if (chars[i]) {\n        for (let j = 0; j <= cols; j += 1) {\n          const redrawChar = chars[i][j];\n          if (redrawChar) {\n            const { hlId, char } = redrawChar;\n            if (hlId !== undefined && typeof char === 'string' && char !== ' ') {\n              const { foreground, background, special } = highlightTable[hlId].value || {};\n              if (!foreground || !background || !special) {\n                chars[i][j].bitmap = undefined;\n                printChar(i, j, char, hlId);\n              }\n            }\n          }\n        }\n      }\n    }\n  };\n\n  const redrawCursor = () => {\n    const m = modeInfoSet && modeInfoSet[mode];\n\n    // TODO: check if cursor changed (char, hlId, etc)\n    if (!m) return;\n\n    const hlId = m.attr_id === 0 ? -1 : m.attr_id;\n\n    const { width, height } = cursorCanvasEl;\n    const fillStyle = highlightTable[hlId]?.calculated?.bgColor;\n    if (fillStyle) {\n      cursorContext.fillStyle = fillStyle;\n    }\n\n    if (m.cursor_shape === 'block') {\n      cursorChar = chars?.[cursorPosition[0]]?.[cursorPosition[1]]?.char || ' ';\n      cursorContext.fillRect(0, 0, charWidth, charHeight);\n      const cursorBitmap = getCharBitmap(cursorChar, hlId);\n      cursorContext.drawImage(cursorBitmap, -charWidth, 0);\n    } else if (m.cursor_shape === 'vertical') {\n      const curWidth = m.cell_percentage\n        ? Math.max(scale, Math.round((charWidth / 100) * m.cell_percentage))\n        : scale;\n      cursorContext.clearRect(0, 0, width, height);\n      cursorContext.fillRect(0, 0, curWidth, charHeight);\n    } else if (m.cursor_shape === 'horizontal') {\n      const curHeight = m.cell_percentage\n        ? Math.max(scale, Math.round((charHeight / 100) * m.cell_percentage))\n        : scale;\n\n      // TODO: test\n      cursorContext.clearRect(0, 0, width, height);\n      cursorContext.fillRect(0, charHeight - curHeight, charWidth, curHeight);\n    }\n\n    // Cursor blink\n    if (cursorAnimation) {\n      cursorAnimation.cancel();\n    }\n    if (m.blinkoff && m.blinkon) {\n      const offset = m.blinkon / (m.blinkon + m.blinkoff);\n      cursorAnimation = cursorEl.animate(\n        [\n          { opacity: 1, offset: 0 },\n          { opacity: 1, offset },\n          { opacity: 0, offset },\n          { opacity: 0, offset: 1 },\n          { opacity: 1, offset: 1 },\n        ],\n        {\n          duration: m.blinkoff + m.blinkon,\n          iterations: Infinity,\n          delay: m.blinkwait || 0,\n        },\n      );\n    }\n  };\n\n  const repositionCursor = (newCursor: [number, number]): void => {\n    if (newCursor) cursorPosition = newCursor;\n    const left = cursorPosition[1] * charWidth;\n    const top = cursorPosition[0] * charHeight;\n    cursorEl.style.transform = `translate(${left}px, ${top}px)`;\n  };\n\n  const optionSet = {\n    guifont: (newFont: string) => {\n      const [newFontFamily, newFontSize] = newFont.trim().split(':h');\n      if (newFontFamily && newFontFamily !== '') {\n        nvim.command(`VVset fontfamily=${newFontFamily.replace(/_/g, '\\\\ ')}`);\n        if (newFontSize && newFontFamily !== '') {\n          nvim.command(`VVset fontsize=${newFontSize}`);\n        }\n      }\n    },\n  };\n\n  const recalculateHighlightTable = () => {\n    (Object.keys(highlightTable) as unknown as number[]).forEach((id) => {\n      if (id > 0) {\n        const {\n          foreground,\n          background,\n          special,\n          reverse,\n          standout,\n          italic,\n          bold,\n          underline,\n          undercurl,\n          strikethrough,\n        } = highlightTable[id].value || {};\n        const r = reverse || standout;\n        const fg = getColor(foreground, highlightTable[0]?.calculated?.fgColor) as string;\n        const bg = getColor(background, highlightTable[0]?.calculated?.bgColor) as string;\n        const sp = getColor(special, highlightTable[0]?.calculated?.spColor) as string;\n\n        highlightTable[id as unknown as number].calculated = {\n          fgColor: r ? bg : fg,\n          bgColor: r ? fg : bg,\n          spColor: sp,\n          hiItalic: showItalic && !!italic,\n          hiBold: showBold && !!bold,\n          hiUnderline: showUnderline && !!underline,\n          hiUndercurl: showUndercurl && !!undercurl,\n          hiStrikethrough: showStrikethrough && !!strikethrough,\n        };\n      }\n    });\n  };\n\n  /**\n   * If char previous to the current cell is wider that char width, we need to draw that part\n   * of it that overlaps the current cell when we redraw it.\n   */\n  const overlapPrev = (i: number, j: number) => {\n    if (chars[i] && chars[i][j - 1] && chars[i][j - 1].bitmap) {\n      context.drawImage(\n        chars[i][j - 1].bitmap as HTMLCanvasElement,\n        charWidth * 2,\n        0,\n        charWidth,\n        charHeight,\n        j * charWidth,\n        i * charHeight,\n        charWidth,\n        charHeight,\n      );\n    }\n  };\n\n  /**\n   * If char next to the cell is wider that char width, we need to draw that part\n   * of it that overlaps the current cell when we redraw it.\n   */\n  const overlapNext = (i: number, j: number) => {\n    if (chars[i] && chars[i][j + 1] && chars[i][j + 1].bitmap) {\n      context.drawImage(\n        chars[i][j + 1].bitmap as HTMLCanvasElement,\n        0,\n        0,\n        charWidth,\n        charHeight,\n        j * charWidth,\n        i * charHeight,\n        charWidth,\n        charHeight,\n      );\n    }\n  };\n\n  /** Clean char from previous overlapping left and right symbols. */\n  const cleanOverlap = (i: number, j: number) => {\n    if (chars[i] && chars[i][j]) {\n      const { hlId } = chars[i][j];\n      if (hlId !== undefined) {\n        const fillStyle = highlightTable[hlId]?.calculated?.bgColor;\n        if (fillStyle) {\n          context.fillStyle = fillStyle;\n          context.fillRect(j * charWidth, i * charHeight, charWidth, charHeight);\n          context.drawImage(\n            chars[i][j].bitmap as HTMLCanvasElement,\n            charWidth,\n            0,\n            charWidth,\n            charHeight,\n            j * charWidth,\n            i * charHeight,\n            charWidth,\n            charHeight,\n          );\n          overlapPrev(i, j);\n          overlapNext(i, j);\n        }\n      }\n    }\n  };\n\n  // https://github.com/neovim/neovim/blob/master/runtime/doc/ui.txt\n  const redrawCmd: Partial<UiEventsHandlers> = {\n    set_title: () => {\n      /* empty */\n    },\n    set_icon: () => {\n      /* empty */\n    },\n\n    win_viewport: () => {\n      /* empty */\n    },\n\n    mode_info_set: (props) => {\n      modeInfoSet = props[0][1].reduce((r, modeInfo) => ({ ...r, [modeInfo.name]: modeInfo }), {});\n    },\n\n    option_set: (options) => {\n      options.forEach(([option, value]) => {\n        // @ts-expect-error TODO\n        if (optionSet[option]) {\n          // @ts-expect-error TODO\n          optionSet[option](value);\n        } else {\n          // console.warn('Unknown option', option, value); // eslint-disable-line no-console\n        }\n      });\n    },\n\n    mode_change: (modes) => {\n      mode = modes[modes.length - 1][0];\n    },\n\n    mouse_on: () => {\n      /* empty */\n    },\n    mouse_off: () => {\n      /* empty */\n    },\n\n    busy_start: () => {\n      /* empty */\n    },\n    busy_stop: () => {\n      /* empty */\n    },\n\n    suspend: () => {\n      /* empty */\n    },\n\n    update_menu: () => {\n      /* empty */\n    },\n\n    bell: () => {\n      /* empty */\n    },\n    visual_bell: () => {\n      /* empty */\n    },\n\n    hl_group_set: () => {\n      /* empty */\n    },\n\n    flush: () => {\n      redrawCursor();\n    },\n\n    grid_resize: ([[, newCols, newRows]]) => {\n      const oldCols = cols;\n      const oldRows = rows;\n\n      cols = newCols;\n      rows = newRows;\n\n      // Add extra column on the right to fill it with adjacent color to have a nice right border\n      if ((cols + 1) * charWidth > canvasEl.width || rows * charHeight > canvasEl.height) {\n        const width = (cols + 1) * charWidth;\n        const height = rows * charHeight;\n\n        screenContainer.style.width = `${width}px`;\n        screenContainer.style.height = `${height}px`;\n\n        canvasEl.width = (cols + 1) * charWidth;\n        canvasEl.height = rows * charHeight;\n        context.fillStyle = highlightTable[0]?.calculated?.bgColor || DEFAULT_BG_COLOR;\n        context.fillRect(0, 0, canvasEl.width, canvasEl.height);\n      }\n\n      // If we are not resizing the window, then we triggered resize from vim using `:set columns` or `:set lines`.\n      // We need to send message to the main to resize the window.\n      if (!isResizing) {\n        if (oldCols !== cols) {\n          const width = Math.ceil((cols * charWidth) / scale);\n          transport.send('set-screen-width', width);\n        }\n        if (oldRows !== rows) {\n          const height = Math.ceil((rows * charHeight) / scale);\n          transport.send('set-screen-height', height);\n        }\n      }\n    },\n\n    default_colors_set: (props) => {\n      const [foreground, background, special] = props[props.length - 1];\n\n      const calculated = {\n        bgColor: getColor(background, DEFAULT_BG_COLOR) as string,\n        fgColor: getColor(foreground, DEFAULT_FG_COLOR) as string,\n        spColor: getColor(special, DEFAULT_SP_COLOR),\n        hiItalic: false,\n        hiBold: false,\n        hiUnderline: false,\n        hiUndercurl: false,\n        hiStrikethrough: false,\n      };\n      if (!highlightTable[0] || !isEqual(highlightTable[0].calculated, calculated)) {\n        highlightTable[0] = { calculated };\n        highlightTable[-1] = {\n          calculated: {\n            ...calculated,\n            bgColor: getColor(foreground, DEFAULT_FG_COLOR) as string,\n            fgColor: getColor(background, DEFAULT_BG_COLOR) as string,\n          },\n        };\n        recalculateHighlightTable();\n        if (highlightTable[0]?.calculated?.bgColor) {\n          document.body.style.background = highlightTable[0].calculated.bgColor;\n          transport.send('set-background-color', highlightTable[0].calculated.bgColor);\n        }\n        redrawDefaultColors();\n      }\n    },\n\n    hl_attr_define: (props) => {\n      props.forEach(([id, value]) => {\n        highlightTable[id] = {\n          value,\n        };\n      });\n      recalculateHighlightTable();\n    },\n\n    grid_line: (props) => {\n      // eslint-disable-next-line\n      for (const [, row, col, cells] of props) {\n        let lineLength = 0;\n        let currentHlId = 0;\n\n        // eslint-disable-next-line\n        for (const [_char, hlId, length = 1] of cells) {\n          if (hlId !== undefined) {\n            currentHlId = hlId;\n          }\n\n          if (length > 0) {\n            printBackground(currentHlId, row, col + lineLength, length);\n\n            lineLength += length;\n          }\n        }\n\n        currentHlId = 0;\n        lineLength = 0;\n        // eslint-disable-next-line\n        for (const [char, hlId, length = 1] of cells) {\n          if (hlId !== undefined) {\n            currentHlId = hlId;\n          }\n          for (let j = 0; j < length; j += 1) {\n            printChar(row, col + lineLength + j, char, currentHlId);\n          }\n          lineLength += length;\n        }\n        cleanOverlap(row, col - 1);\n        cleanOverlap(row, col + lineLength);\n        overlapPrev(row, col);\n        overlapNext(row, col + lineLength - 1);\n      }\n    },\n\n    grid_clear: () => {\n      cursorPosition = [0, 0];\n      context.fillStyle = highlightTable[0]?.calculated?.bgColor || DEFAULT_BG_COLOR;\n      context.fillRect(0, 0, canvasEl.width, canvasEl.height);\n      chars = [];\n    },\n\n    grid_destroy: () => {\n      /* empty */\n    },\n\n    grid_cursor_goto: ([[_, ...newCursor]]) => {\n      repositionCursor(newCursor);\n\n      // Temporary workaround to fix cursor position in terminal mode. Nvim API does not send the very last cursor\n      // position in terminal on redraw, but when you send any command to nvim, it redraws it correctly. Need to\n      // investigate it and find a better permanent fix. Maybe this is a bug in nvim and then\n      // TODO: file a ticket to nvim.\n      nvim.getMode();\n    },\n\n    grid_scroll: ([[_grid, top, bottom, left, right, scrollCount]]) => {\n      const x = left * charWidth; // region left\n      let y; // region top\n      let w = (right - left) * charWidth; // clipped part width\n      const h = (bottom - top - Math.abs(scrollCount)) * charHeight; // clipped part height\n      const X = x; // destination left\n      let Y; // destination top\n\n      if (right === cols) {\n        // Add extra char if it is far right rect\n        w += charWidth;\n      }\n\n      if (scrollCount > 0) {\n        // scroll down\n        y = (top + scrollCount) * charHeight;\n        Y = top * charHeight;\n      } else {\n        // scroll up\n        y = top * charHeight;\n        Y = (top - scrollCount) * charHeight;\n      }\n\n      // Copy scrolled lines\n      // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage\n      context.drawImage(canvasEl, x, y, w, h, X, Y, w, h);\n\n      for (\n        let i = scrollCount > 0 ? top : bottom - 1;\n        scrollCount > 0 ? i <= bottom - scrollCount - 1 : i >= top - scrollCount;\n        i += scrollCount > 0 ? 1 : -1\n      ) {\n        for (let j = left; j <= right - 1; j += 1) {\n          const sourceI = i + scrollCount;\n\n          initChar(i, j);\n          initChar(sourceI, j);\n\n          // Swap char to scroll to destination\n          [chars[i][j], chars[sourceI][j]] = [chars[sourceI][j], chars[i][j]];\n        }\n      }\n\n      for (let i = top; i <= bottom; i += 1) {\n        cleanOverlap(i, left - 1);\n        cleanOverlap(i, right);\n      }\n    },\n  };\n\n  const handleSet = {\n    fontfamily: (newFontFamily: string) => {\n      fontFamily = `${newFontFamily}, ${DEFAULT_FONT_FAMILY}`;\n    },\n\n    fontsize: (newFontSize: string) => {\n      fontSize = parseInt(newFontSize, 10);\n    },\n\n    letterspacing: (newLetterSpacing: string) => {\n      letterSpacing = parseInt(newLetterSpacing, 10);\n    },\n\n    lineheight: (newLineHeight: string) => {\n      lineHeight = parseFloat(newLineHeight);\n    },\n\n    bold: (value: boolean) => {\n      showBold = value;\n    },\n\n    italic: (value: boolean) => {\n      showItalic = value;\n    },\n\n    underline: (value: boolean) => {\n      showUnderline = value;\n    },\n\n    undercurl: (value: boolean) => {\n      showUndercurl = value;\n    },\n\n    strikethrough: (value: boolean) => {\n      showStrikethrough = value;\n    },\n  };\n\n  const redraw = (args: UiEventsArgs) => {\n    try {\n      args.forEach(([cmd, ...props]) => {\n        const command = redrawCmd[cmd];\n        if (command) {\n          // console.log('hey', cmd, props);\n          // @ts-expect-error TODO: find the way to type it without errors\n          command(props);\n        } else {\n          console.warn('Unknown redraw command', cmd, props); // eslint-disable-line no-console\n        }\n      });\n    } catch (e) {\n      // eslint-disable-next-line\n      console.error(e);\n    }\n  };\n\n  const setScale = () => {\n    scale = isRetina() ? RETINA_SCALE : 1;\n    screenContainer.style.transform = `scale(${1 / scale})`;\n    screenContainer.style.width = `${scale * 100}%`;\n    screenContainer.style.height = `${scale * 100}%`;\n\n    // Detect when you drag between retina/non-retina displays\n    window.matchMedia('screen and (min-resolution: 2dppx)').addListener(() => {\n      setScale();\n      measureCharSize();\n      setResizing();\n      nvim.uiTryResize(cols, rows);\n    });\n  };\n\n  /**\n   * Return grid [col, row] coordinates by pixel coordinates.\n   */\n  const screenCoords = (width: number, height: number): [number, number] => {\n    return [Math.floor((width * scale) / charWidth), Math.floor((height * scale) / charHeight)];\n  };\n\n  const resize = (forceRedraw = false) => {\n    const [newCols, newRows] = screenCoords(window.innerWidth, window.innerHeight);\n    if (newCols !== cols || newRows !== rows || forceRedraw) {\n      cols = newCols;\n      rows = newRows;\n      setResizing();\n      nvim.uiTryResize(cols, rows);\n    }\n  };\n\n  const uiAttach = () => {\n    [cols, rows] = screenCoords(window.innerWidth, window.innerHeight);\n    nvim.uiAttach(cols, rows, { ext_linegrid: true });\n    window.addEventListener('resize', () => resize());\n  };\n\n  const updateSettings = (newSettings: Settings, isInitial = false) => {\n    let requireRedraw = isInitial;\n    let requireRecalculateHighlight = false;\n    const requireRedrawProps = [\n      'fontfamily',\n      'fontsize',\n      'letterspacing',\n      'lineheight',\n      'bold',\n      'italic',\n      'underline',\n      'undercurl',\n      'strikethrough',\n    ];\n\n    const requireRecalculateHighlightProps = [\n      'bold',\n      'italic',\n      'underline',\n      'undercurl',\n      'strikethrough',\n    ];\n\n    Object.keys(newSettings).forEach((key) => {\n      // @ts-expect-error TODO\n      if (handleSet[key]) {\n        requireRedraw = requireRedraw || requireRedrawProps.includes(key);\n        requireRecalculateHighlight =\n          requireRecalculateHighlight || requireRecalculateHighlightProps.includes(key);\n        // @ts-expect-error TODO\n        handleSet[key](newSettings[key]);\n      }\n    });\n\n    if (requireRecalculateHighlight && !isInitial) {\n      recalculateHighlightTable();\n    }\n\n    if (requireRedraw) {\n      measureCharSize();\n      charsCache.clear();\n      if (!isInitial) {\n        resize(true);\n      }\n    }\n  };\n\n  initScreen();\n  initCursor();\n  setScale();\n\n  nvim.on('redraw', redraw);\n\n  transport.on('updateSettings', (s) => updateSettings(s));\n  updateSettings(settings, true);\n\n  transport.on('force-resize', () => {\n    resize();\n  });\n\n  uiAttach();\n\n  return {\n    screenCoords,\n    getCursorElement,\n  };\n};\n\nexport default screen;\n"
  },
  {
    "path": "packages/browser-renderer/src/transport/__tests__/ipc.test.ts",
    "content": "import { EventEmitter } from 'events';\nimport { ipcRenderer } from 'src/preloaded/electron';\nimport type { PreloadedIpcRenderer } from 'src/preloaded/electron';\n\nimport IpcRendererTransport from 'src/transport/ipc';\n\njest.mock('src/preloaded/electron', () => ({\n  ipcRenderer: {\n    on: jest.fn(),\n    send: jest.fn(),\n  },\n}));\n\ndescribe('main transport', () => {\n  let transport: IpcRendererTransport;\n  let ipcRendererMock: NodeJS.EventEmitter;\n  const send = jest.fn();\n\n  beforeEach(() => {\n    ipcRendererMock = Object.assign(new EventEmitter(), {\n      send,\n    });\n    transport = new IpcRendererTransport((ipcRendererMock as unknown) as PreloadedIpcRenderer);\n  });\n\n  describe('on', () => {\n    const listener = jest.fn();\n\n    test('calls listener', () => {\n      transport.on('test-event', listener);\n      ipcRendererMock.emit('test-event', 'arg1', 'arg2');\n      expect(listener).toHaveBeenCalledWith('arg1', 'arg2');\n    });\n\n    test('does not call listener twice listener', () => {\n      const anotherListener = jest.fn();\n      transport.on('test-event', listener);\n      transport.on('test-event', anotherListener);\n      ipcRendererMock.emit('test-event', new Event('test-event'), 'arg1', 'arg2');\n      expect(listener).toHaveBeenCalledTimes(1);\n      expect(anotherListener).toHaveBeenCalledTimes(1);\n    });\n\n    test('listener with no args', () => {\n      transport.on('test-event', listener);\n      ipcRendererMock.emit('test-event');\n      expect(listener).toHaveBeenCalledWith();\n    });\n\n    test('use preloaded ipcRenderer if it is not passed', () => {\n      transport = new IpcRendererTransport();\n      transport.on('test-event', listener);\n      expect(ipcRenderer.on).toHaveBeenCalledWith('test-event', expect.any(Function));\n    });\n  });\n\n  describe('once', () => {\n    const listener = jest.fn();\n\n    test('calls listener once', () => {\n      transport.once('test-event', listener);\n      ipcRendererMock.emit('test-event', new Event('test-event'), 'arg1', 'arg2');\n      ipcRendererMock.emit('test-event', new Event('test-event'), 'arg1', 'arg2');\n      expect(listener).toHaveBeenCalledTimes(1);\n    });\n  });\n\n  describe('removeListener', () => {\n    const listener = jest.fn();\n\n    test('does not call listener after off', () => {\n      transport.on('test-event', listener);\n      transport.removeListener('test-event', listener);\n      ipcRendererMock.emit('test-event', new Event('test-event'), 'arg1', 'arg2');\n      expect(listener).not.toHaveBeenCalled();\n    });\n\n    test('other subscribed events work', () => {\n      const anotherListener = jest.fn();\n      transport.on('test-event', listener);\n      transport.on('test-event', anotherListener);\n      transport.removeListener('test-event', anotherListener);\n      ipcRendererMock.emit('test-event', new Event('test-event'), 'arg1', 'arg2');\n      expect(listener).toHaveBeenCalled();\n    });\n\n    test('unsubscribes from ipc event if there are not subscriptions left', () => {\n      const addListenerSpy = jest.spyOn(ipcRendererMock, 'on');\n      const removeListenerSpy = jest.spyOn(ipcRendererMock, 'removeListener');\n      transport.on('test-event', listener);\n      transport.off('test-event', listener);\n\n      expect(removeListenerSpy).toHaveBeenCalledWith('test-event', addListenerSpy.mock.calls[0][1]);\n    });\n  });\n\n  describe('send', () => {\n    test('pass args to win.webContents', () => {\n      transport.send('test-event', 'arg1', 'arg2');\n      expect(send).toHaveBeenCalledWith('test-event', 'arg1', 'arg2');\n    });\n\n    test('with no args', () => {\n      transport.send('test-event');\n      expect(send).toHaveBeenCalledWith('test-event');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/browser-renderer/src/transport/__tests__/websocket.test.ts",
    "content": "import WebSocketTransport from 'src/transport/websocket';\n\ndescribe('websocket transport', () => {\n  const OriginalWebSocket = WebSocket;\n\n  const constructor = jest.fn();\n  const send = jest.fn();\n  let onmessage: (x: { data: string }) => void;\n  const listener = jest.fn();\n\n  class MockWebSocket {\n    constructor(...args: any[]) {\n      constructor(...args);\n    }\n\n    // eslint-disable-next-line class-methods-use-this\n    send(...args: any[]) {\n      send(...args);\n    }\n\n    // eslint-disable-next-line class-methods-use-this\n    set onmessage(value: (x: { data: string }) => void) {\n      onmessage = value;\n    }\n  }\n\n  let transport: WebSocketTransport;\n\n  beforeEach(() => {\n    // @ts-expect-error Mocking WebSocket\n    global.WebSocket = MockWebSocket;\n\n    transport = new WebSocketTransport();\n  });\n\n  afterEach(() => {\n    global.WebSocket = OriginalWebSocket;\n  });\n\n  test('connects to websocket', () => {\n    expect(constructor).toHaveBeenCalledWith('ws://localhost');\n  });\n\n  test('send method sends channel and args to websocket', () => {\n    transport.send('channel', 'arg1', 'arg2');\n    expect(send).toHaveBeenCalledWith('[\"channel\",\"arg1\",\"arg2\"]');\n  });\n\n  test('sent message is JSON stringified', () => {\n    transport.send('channel', { complex: { object: true } });\n    expect(send).toHaveBeenCalledWith('[\"channel\",{\"complex\":{\"object\":true}}]');\n  });\n\n  test('receive message if you subscribe to chanel', () => {\n    transport.on('channel', listener);\n\n    onmessage({ data: JSON.stringify(['channel', ['arg1', 'arg2']]) });\n    expect(listener).toHaveBeenCalledWith(['arg1', 'arg2']);\n\n    onmessage({ data: JSON.stringify(['channel', ['arg3']]) });\n    expect(listener).toHaveBeenCalledWith(['arg3']);\n  });\n\n  test(\"don't receive messages for channels you did not subscribe\", () => {\n    transport.on('channel', listener);\n    onmessage({ data: JSON.stringify(['other-channel', ['arg1', 'arg2']]) });\n    expect(listener).not.toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "packages/browser-renderer/src/transport/ipc.ts",
    "content": "import { EventEmitter } from 'events';\nimport memoize from 'lodash/memoize';\n\nimport { ipcRenderer } from 'src/preloaded/electron';\nimport type { PreloadedIpcRenderer } from 'src/preloaded/electron';\n\nimport type { Transport, Args } from '@vvim/nvim';\n\n/**\n * Init transport between main and renderer via Electron ipcRenderer.\n */\nclass IpcRendererTransport extends EventEmitter implements Transport {\n  private ipc: PreloadedIpcRenderer;\n\n  constructor(ipc = ipcRenderer) {\n    super();\n\n    this.ipc = ipc;\n\n    this.on('newListener', (eventName: string) => {\n      if (\n        !this.listenerCount(eventName) &&\n        !['newListener', 'removeListener'].includes(eventName)\n      ) {\n        this.ipc.on(eventName, this.handleEvent(eventName));\n      }\n    });\n\n    this.on('removeListener', (eventName: string) => {\n      if (\n        !this.listenerCount(eventName) &&\n        !['newListener', 'removeListener'].includes(eventName)\n      ) {\n        this.ipc.removeListener(eventName, this.handleEvent(eventName));\n      }\n    });\n  }\n\n  handleEvent = memoize((eventName: string) => (...args: Args): void => {\n    this.emit(eventName, ...args);\n  });\n\n  send(channel: string, ...params: Args): void {\n    this.ipc.send(channel, ...params);\n  }\n}\n\nexport default IpcRendererTransport;\n"
  },
  {
    "path": "packages/browser-renderer/src/transport/transport.ts",
    "content": "import IpcRendererTransport from 'src/transport/ipc';\nimport WebSocketTransport from 'src/transport/websocket';\nimport isWeb from 'src/lib/isWeb';\n\nconst Transport = isWeb() ? WebSocketTransport : IpcRendererTransport;\n\nexport default Transport;\n"
  },
  {
    "path": "packages/browser-renderer/src/transport/websocket.ts",
    "content": "import { EventEmitter } from 'events';\nimport type { Transport, Args } from '@vvim/nvim';\n\n/**\n * Init transport between main and renderer via WebSocket.\n */\nclass WebSocketTransport extends EventEmitter implements Transport {\n  socket: WebSocket;\n\n  constructor() {\n    super();\n\n    this.socket = new WebSocket(`ws://${window.location.host}`);\n\n    this.socket.onmessage = ({ data }) => {\n      const [channel, args] = JSON.parse(data);\n      this.emit(channel, args);\n    };\n  }\n\n  send(channel: string, ...args: Args): void {\n    this.socket.send(JSON.stringify([channel, ...args]));\n  }\n}\n\nexport default WebSocketTransport;\n"
  },
  {
    "path": "packages/browser-renderer/src/types.ts",
    "content": "type BooleanSetting = 0 | 1;\n\nexport type Settings = {\n  fullscreen: BooleanSetting;\n  simplefullscreen: BooleanSetting;\n  bold: BooleanSetting;\n  italic: BooleanSetting;\n  underline: BooleanSetting;\n  undercurl: BooleanSetting;\n  strikethrough: BooleanSetting;\n  fontfamily: string;\n  fontsize: string; // TODO: number\n  lineheight: string; // TODO: number\n  letterspacing: string; // TODO: number\n  reloadchanged: BooleanSetting;\n  quitoncloselastwindow: BooleanSetting;\n  autoupdateinterval: string; // TODO: number\n  openInProject: BooleanSetting;\n};\n"
  },
  {
    "path": "packages/browser-renderer/tsconfig.declaration.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"emitDeclarationOnly\": true,\n    \"declarationMap\": true,\n    \"noEmit\": false,\n    \"outDir\": \"dist\"\n  },\n  \"include\": [\"src/index.ts\", \"@types\"]\n}\n"
  },
  {
    "path": "packages/browser-renderer/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\"\n  },\n  \"include\": [\"src\", \"@types\"]\n}\n"
  },
  {
    "path": "packages/electron/@types/html2plaintext.d.ts",
    "content": "declare module 'html2plaintext' {\n  const html2plaintext: (x: string) => string;\n  export default html2plaintext;\n}\n"
  },
  {
    "path": "packages/electron/README.md",
    "content": "# VV\n\nVV is a Neovim client for macOS. A pure, fast, minimalistic Vim experience with good macOS integration. Optimized for speed and nice font rendering.\n\nPlease check main readme file for details: [README.md](../../README.md).\n"
  },
  {
    "path": "packages/electron/babel.config.json",
    "content": "{\n  \"extends\": \"../../babel.config.json\"\n}\n"
  },
  {
    "path": "packages/electron/bin/openInProject.vim",
    "content": "\" Opens file respecting switchbuf setting.\nfunction! VVopenInProject(filename, ...)\n  \" Take switch override from second parameter or from VV settings.\n  let l:switchbuf_override = get(a:, 1, VVsettingValue('openInProject'))\n\n  silent call VVopenInProjectLoud(a:filename, l:switchbuf_override)\nendfunction\n\nfunction! VVopenInProjectLoud(fileName, switchbuf_override)\n  \" Temporary override switchbuf if we have custom openInProject setting.\n  if type(a:switchbuf_override) == v:t_string && a:switchbuf_override != '0' && a:switchbuf_override != '1'\n    let l:original_switchbuf = &switchbuf\n    let &switchbuf = a:switchbuf_override\n  endif\n\n  \" Create temporary quickfix list with file we want to open\n  if (!exists('g:vvOpenInProjectQfId') || getqflist({ 'id': 0 }).id != g:vvOpenInProjectQfId)\n    call setqflist([], ' ', { 'title': 'VV Temporary Quickfix' })\n    let g:vvOpenInProjectQfId = getqflist({ 'id': 0 }).id\n  end\n\n  \" Add file to list and open it. It will be opened according to current switchbuf\n  \" setting.\n  call setqflist([], 'r', { 'id': g:vvOpenInProjectQfId, 'items': [{ 'filename': a:fileName }] })\n  cc! 1\n\n  \" Switch to previous quickfix list if there are other lists.\n  if getqflist({'nr' : 0 }).nr > 1\n    colder\n  end\n\n  \" Rollback to original switchbuf option if needed.\n  if exists('l:original_switchbuf')\n    let &switchbuf = l:original_switchbuf\n  endif\nendfunction\n\nfunction! VVprojectRoot()\n  return getcwd()\nendfunction\n"
  },
  {
    "path": "packages/electron/bin/reloadChanged.vim",
    "content": "\" TODO: Remove on the next major version\n\" Iterate on buffers and reload them from disk. No questions asked.\n\" Do it in temporary tab to keep the same windows layout.\nfunction! VVrefresh(...)\n  -tabnew\n  for bufnr in a:000\n    execute \"buffer\" bufnr\n    execute \"e!\"\n  endfor\n  tabclose!\nendfunction\n\nfunction! VVenableReloadChanged(enabled)\n  if a:enabled\n    augroup ReloadChanged\n      autocmd!\n      autocmd FileChangedShell * call rpcnotify(get(g:, \"vv_channel\", 1), \"vv:file_changed\", { \"name\": expand(\"<afile>\"), \"bufnr\": expand(\"<abuf>\") })\n      autocmd CursorHold * checktime\n    augroup END\n  else\n    autocmd! ReloadChanged *\n  endif\nendfunction\n"
  },
  {
    "path": "packages/electron/bin/vv",
    "content": "#!/bin/sh\n\nif [ \"$1\" == \"--help\" ] || [ \"$1\" == \"-h\" ]; then\n  cat << END \nVV - NeoVim GUI Client\n\nUsage:\n  vv [options] [file ...]\n\nOptions:\n  --debug               Debug mode. Keep process attached to terminal and\n                        output errors.\n\nAll other options will be passed to nvim. You can check available options\nby running: nvim --help\nEND\n\nelse\n  SOURCE=\"${BASH_SOURCE[0]}\"\n  while [ -h \"$SOURCE\" ]; do\n    DIR=\"$( cd -P \"$( dirname \"$SOURCE\" )\" && pwd )\"\n    SOURCE=\"$(readlink \"$SOURCE\")\"\n    [[ $SOURCE != /* ]] && SOURCE=\"$DIR/$SOURCE\"\n  done\n  DIR=\"$( cd -P \"$( dirname \"$SOURCE\" )\" && pwd )\"\n  BIN=\"$DIR/../../MacOS/VV\"\n\n  if [[ \"${@#--debug}\" = \"$@\" ]]; then\n    exec \"$BIN\" \"$@\" &>/dev/null & disown\n  else\n    exec \"$BIN\" \"${@#--debug}\"\n  fi\nfi\n"
  },
  {
    "path": "packages/electron/bin/vv.vim",
    "content": "let g:vv = 1\n\nlet s:dir = expand('<sfile>:p:h')\n\nexecute 'source ' . fnameescape(s:dir . '/vvset.vim')\nexecute 'source ' . fnameescape(s:dir . '/reloadChanged.vim')\nexecute 'source ' . fnameescape(s:dir . '/openInProject.vim')\n\nset termguicolors\n\nautocmd VimEnter * call rpcnotify(get(g:, 'vv_channel', 1), \"vv:vim_enter\")\n\n\" Send unsaved buffers to client\nfunction! VVunsavedBuffers()\n  let l:buffers = getbufinfo()\n  call filter(l:buffers, \"v:val['changed'] == 1\")\n  let l:buffers = map(l:buffers , \"{ 'name': v:val['name'] }\" )\n  return l:buffers\nendfunction\n"
  },
  {
    "path": "packages/electron/bin/vvset.vim",
    "content": "let g:vv_settings_synonims = {\n\\  'fu': 'fullscreen',\n\\  'sfu': 'simplefullscreen',\n\\  'width': 'windowwidth',\n\\  'height': 'windowheight',\n\\  'top': 'windowtop',\n\\  'left': 'windowleft',\n\\  'openinproject': 'openInProject'\n\\}\n\nlet g:vv_default_settings = {\n\\  'fullscreen': 0,\n\\  'simplefullscreen': 1,\n\\  'bold': 1,\n\\  'italic': 1,\n\\  'underline': 1,\n\\  'undercurl': 1,\n\\  'strikethrough': 1,\n\\  'fontfamily': 'monospace',\n\\  'fontsize': 12,\n\\  'lineheight': 1.25,\n\\  'letterspacing': 0,\n\\  'reloadchanged': 0,\n\\  'windowwidth': v:null,\n\\  'windowheight': v:null,\n\\  'windowleft': v:null,\n\\  'windowtop': v:null,\n\\  'quitoncloselastwindow': 0,\n\\  'autoupdateinterval': 1440,\n\\  'openInProject': 1\n\\}\n\nlet g:vv_settings = deepcopy(g:vv_default_settings)\n\n\" Custom VVset command, mimic default set command (:help set) with\n\" settings specified in g:vv_default_settings\nfunction! VVset(...)\n  for arg in a:000\n    call VVsetItem(arg)\n  endfor\nendfunction\n\nfunction! VVsettingValue(name)\n  let l:name = VVsettingName(a:name)\n  if has_key(g:vv_settings, l:name)\n    return g:vv_settings[l:name]\n  else\n    echoerr \"Unknown option: \".a:name\n  endif\nendfunction\n\nfunction! VVsettingName(name)\n  if has_key(g:vv_settings_synonims, a:name)\n    return g:vv_settings_synonims[a:name]\n  else\n    return a:name\n  endif\nendfunction\n\nfunction! VVsetItem(name)\n  if a:name == 'all'\n    echo g:vv_settings\n    return\n  elseif a:name =~ '?'\n    let l:name = VVsettingName(split(a:name, '?')[0])\n    echo VVsettingValue(l:name)\n    return\n  elseif a:name =~ '&'\n    let l:name = VVsettingName(split(a:name, '&')[0])\n    if l:name == 'all'\n      let g:vv_settings = deepcopy(g:vv_default_settings)\n      call VVsettings()\n      return\n    elseif has_key(g:vv_default_settings, l:name)\n      let l:value = g:vv_default_settings[l:name]\n    else\n      echoerr \"Unknown option: \".l:name\n      return\n    endif\n  elseif a:name =~ '+='\n    let l:split = split(a:name, '+=')\n    let l:name = VVsettingName(l:split[0])\n    let l:value = VVsettingValue(l:name) + l:split[1]\n  elseif a:name =~ '-='\n    let l:split = split(a:name, '-=')\n    let l:name = VVsettingName(l:split[0])\n    let l:value = VVsettingValue(l:name) - l:split[1]\n  elseif a:name =~ '\\^='\n    let l:split = split(a:name, '\\^=')\n    let l:name = VVsettingName(l:split[0])\n    let l:value = VVsettingValue(l:name) * l:split[1]\n  elseif a:name =~ '='\n    let l:split = split(a:name, '=')\n    let l:name = l:split[0]\n    let l:value = l:split[1]\n  elseif a:name =~ ':'\n    let l:split = split(a:name, ':')\n    let l:name = l:split[0]\n    let l:value = l:split[1]\n  elseif a:name =~ '!'\n    let l:name = VVsettingName(split(a:name, '!')[0])\n    if VVsettingValue(l:name) == 0\n      let l:value = 1\n    else\n      let l:value = 0\n    endif\n  elseif a:name =~ '^inv'\n    let l:name = VVsettingName(strpart(a:name, 3))\n    if VVsettingValue(l:name) == 0\n      let l:value = 1\n    else\n      let l:value = 0\n    endif\n  elseif a:name =~ '^no'\n    let l:name = strpart(a:name, 2)\n    let l:value = 0\n  else\n    let l:name = a:name\n    let l:value = 1\n  endif\n\n  let l:name = VVsettingName(l:name)\n\n  if has_key(g:vv_settings, l:name)\n    let g:vv_settings[l:name] = l:value\n    call rpcnotify(get(g:, \"vv_channel\", 1), \"vv:set\", l:name, l:value)\n  else\n    echoerr \"Unknown option: \".l:name\n  endif\nendfunction\n\nfunction! VVsettings()\n  for key in keys(g:vv_settings)\n    call rpcnotify(get(g:, \"vv_channel\", 1), \"vv:set\", key, g:vv_settings[key])\n  endfor\nendfunction\n\ncommand! -nargs=* VVset :call VVset(<f-args>)\ncommand! -nargs=* VVse :call VVset(<f-args>)\ncommand! -nargs=0 VVsettings :call VVsettings() \" Send all settings to client\n"
  },
  {
    "path": "packages/electron/config/electron-builder/build.js",
    "content": "require('dotenv').config();\n\nconst { join } = require('path');\nconst { readFileSync } = require('fs');\n\nconst fileAssociations = require('./fileAssociations.json');\n\nconst path = require.resolve('electron');\nconst data = readFileSync(join(path, '..', 'package.json'), { encoding: 'utf-8' });\nconst electronVersion = JSON.parse(data).version;\n\nconst build = {\n  appId: process.env.APPID || 'app.vvim.vv',\n  productName: 'VV',\n  extraMetadata: {\n    name: 'VV',\n  },\n  files: ['build/**/*'],\n  extraResources: ['bin/**/*', 'src/main/preload.js'],\n  electronVersion,\n  directories: {\n    buildResources: 'assets',\n  },\n  fileAssociations: [\n    ...fileAssociations,\n    {\n      name: 'Document',\n      role: 'Editor',\n      ext: '*',\n      icon: 'generic.icns',\n    },\n  ],\n  mac: {\n    category: 'public.app-category.developer-tools',\n    target: {\n      target: 'dir',\n      arch: 'universal',\n    },\n  },\n};\n\nmodule.exports = build;\n"
  },
  {
    "path": "packages/electron/config/electron-builder/fileAssociations.json",
    "content": "[\n  {\n    \"name\": \"1C Enterprise\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"bsl\", \"os\"]\n  },\n  {\n    \"name\": \"ABAP\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"abap\"]\n  },\n  {\n    \"name\": \"ABNF\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"abnf\"]\n  },\n  {\n    \"name\": \"AGS Script\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"asc\", \"ash\"]\n  },\n  {\n    \"name\": \"AMPL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ampl\", \"mod\"]\n  },\n  {\n    \"name\": \"ANTLR\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"g4\"]\n  },\n  {\n    \"name\": \"API Blueprint\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"apib\"]\n  },\n  {\n    \"name\": \"APL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"apl\", \"dyalog\"]\n  },\n  {\n    \"name\": \"ASN.1\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"asn\", \"asn1\"]\n  },\n  {\n    \"name\": \"ASP\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"asp\", \"asax\", \"ascx\", \"ashx\", \"asmx\", \"aspx\", \"axd\"]\n  },\n  {\n    \"name\": \"ATS\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"dats\", \"hats\", \"sats\"]\n  },\n  {\n    \"name\": \"ActionScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"as\"]\n  },\n  {\n    \"name\": \"Ada\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"adb\", \"ada\", \"ads\"]\n  },\n  {\n    \"name\": \"Adobe Font Metrics\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"afm\"]\n  },\n  {\n    \"name\": \"Agda\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"agda\"]\n  },\n  {\n    \"name\": \"Alloy\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"als\"]\n  },\n  {\n    \"name\": \"Altium Designer\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"OutJob\", \"PcbDoc\", \"PrjPCB\", \"SchDoc\"]\n  },\n  {\n    \"name\": \"AngelScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"as\", \"angelscript\"]\n  },\n  {\n    \"name\": \"ApacheConf\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"apacheconf\", \"vhost\"]\n  },\n  {\n    \"name\": \"Apex\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cls\"]\n  },\n  {\n    \"name\": \"Apollo Guidance Computer\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"agc\"]\n  },\n  {\n    \"name\": \"AppleScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"applescript\", \"scpt\"]\n  },\n  {\n    \"name\": \"Arc\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"arc\"]\n  },\n  {\n    \"name\": \"AsciiDoc\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"asciidoc\", \"adoc\", \"asc\"]\n  },\n  {\n    \"name\": \"AspectJ\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"aj\"]\n  },\n  {\n    \"name\": \"Assembly\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"asm\", \"a51\", \"inc\", \"nasm\"]\n  },\n  {\n    \"name\": \"Asymptote\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"asy\"]\n  },\n  {\n    \"name\": \"Augeas\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"aug\"]\n  },\n  {\n    \"name\": \"AutoHotkey\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ahk\", \"ahkl\"]\n  },\n  {\n    \"name\": \"AutoIt\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"au3\"]\n  },\n  {\n    \"name\": \"Awk\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"awk\", \"auk\", \"gawk\", \"mawk\", \"nawk\"]\n  },\n  {\n    \"name\": \"Ballerina\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"bal\"]\n  },\n  {\n    \"name\": \"Batchfile\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"bat\", \"cmd\"]\n  },\n  {\n    \"name\": \"Befunge\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"befunge\"]\n  },\n  {\n    \"name\": \"BibTeX\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"bib\"]\n  },\n  {\n    \"name\": \"Bison\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"bison\"]\n  },\n  {\n    \"name\": \"BitBake\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"bb\"]\n  },\n  {\n    \"name\": \"Blade\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"blade\", \"blade.php\"]\n  },\n  {\n    \"name\": \"BlitzBasic\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"bb\", \"decls\"]\n  },\n  {\n    \"name\": \"BlitzMax\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"bmx\"]\n  },\n  {\n    \"name\": \"Bluespec\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"bsv\"]\n  },\n  {\n    \"name\": \"Boo\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"boo\"]\n  },\n  {\n    \"name\": \"Brainfuck\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"b\", \"bf\"]\n  },\n  {\n    \"name\": \"Brightscript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"brs\"]\n  },\n  {\n    \"name\": \"C\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"c\", \"cats\", \"h\", \"idc\"]\n  },\n  {\n    \"name\": \"C#\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cs\", \"cake\", \"csx\"]\n  },\n  {\n    \"name\": \"C++\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\n      \"cpp\",\n      \"c++\",\n      \"cc\",\n      \"cp\",\n      \"cxx\",\n      \"h\",\n      \"h++\",\n      \"hh\",\n      \"hpp\",\n      \"hxx\",\n      \"inc\",\n      \"inl\",\n      \"ino\",\n      \"ipp\",\n      \"re\",\n      \"tcc\",\n      \"tpp\"\n    ]\n  },\n  {\n    \"name\": \"C-ObjDump\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"c-objdump\"]\n  },\n  {\n    \"name\": \"C2hs Haskell\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"chs\"]\n  },\n  {\n    \"name\": \"CLIPS\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"clp\"]\n  },\n  {\n    \"name\": \"CMake\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cmake\", \"cmake.in\"]\n  },\n  {\n    \"name\": \"COBOL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cob\", \"cbl\", \"ccp\", \"cobol\", \"cpy\"]\n  },\n  {\n    \"name\": \"COLLADA\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"dae\"]\n  },\n  {\n    \"name\": \"CSON\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cson\"]\n  },\n  {\n    \"name\": \"CSS\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"css\"]\n  },\n  {\n    \"name\": \"CSV\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"csv\"]\n  },\n  {\n    \"name\": \"CWeb\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"w\"]\n  },\n  {\n    \"name\": \"Cabal Config\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cabal\"]\n  },\n  {\n    \"name\": \"Cap'n Proto\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"capnp\"]\n  },\n  {\n    \"name\": \"CartoCSS\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mss\"]\n  },\n  {\n    \"name\": \"Ceylon\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ceylon\"]\n  },\n  {\n    \"name\": \"Chapel\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"chpl\"]\n  },\n  {\n    \"name\": \"Charity\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ch\"]\n  },\n  {\n    \"name\": \"ChucK\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ck\"]\n  },\n  {\n    \"name\": \"Cirru\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cirru\"]\n  },\n  {\n    \"name\": \"Clarion\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"clw\"]\n  },\n  {\n    \"name\": \"Clean\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"icl\", \"dcl\"]\n  },\n  {\n    \"name\": \"Click\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"click\"]\n  },\n  {\n    \"name\": \"Clojure\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"clj\", \"boot\", \"cl2\", \"cljc\", \"cljs\", \"cljs.hl\", \"cljscm\", \"cljx\", \"hic\"]\n  },\n  {\n    \"name\": \"Closure Templates\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"soy\"]\n  },\n  {\n    \"name\": \"CoNLL-U\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"conllu\", \"conll\"]\n  },\n  {\n    \"name\": \"CoffeeScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"coffee\", \"_coffee\", \"cake\", \"cjsx\", \"iced\"]\n  },\n  {\n    \"name\": \"ColdFusion\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cfm\", \"cfml\"]\n  },\n  {\n    \"name\": \"ColdFusion CFC\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cfc\"]\n  },\n  {\n    \"name\": \"Common Lisp\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"lisp\", \"asd\", \"cl\", \"l\", \"lsp\", \"ny\", \"podsl\", \"sexp\"]\n  },\n  {\n    \"name\": \"Common Workflow Language\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cwl\"]\n  },\n  {\n    \"name\": \"Component Pascal\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cp\", \"cps\"]\n  },\n  {\n    \"name\": \"Cool\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cl\"]\n  },\n  {\n    \"name\": \"Coq\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"coq\", \"v\"]\n  },\n  {\n    \"name\": \"Cpp-ObjDump\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cppobjdump\", \"c++-objdump\", \"c++objdump\", \"cpp-objdump\", \"cxx-objdump\"]\n  },\n  {\n    \"name\": \"Creole\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"creole\"]\n  },\n  {\n    \"name\": \"Crystal\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cr\"]\n  },\n  {\n    \"name\": \"Csound\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"orc\", \"udo\"]\n  },\n  {\n    \"name\": \"Csound Document\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"csd\"]\n  },\n  {\n    \"name\": \"Csound Score\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sco\"]\n  },\n  {\n    \"name\": \"Cuda\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cu\", \"cuh\"]\n  },\n  {\n    \"name\": \"Cycript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cy\"]\n  },\n  {\n    \"name\": \"Cython\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pyx\", \"pxd\", \"pxi\"]\n  },\n  {\n    \"name\": \"D\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"d\", \"di\"]\n  },\n  {\n    \"name\": \"D-ObjDump\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"d-objdump\"]\n  },\n  {\n    \"name\": \"DIGITAL Command Language\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"com\"]\n  },\n  {\n    \"name\": \"DM\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"dm\"]\n  },\n  {\n    \"name\": \"DNS Zone\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"zone\", \"arpa\"]\n  },\n  {\n    \"name\": \"DTrace\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"d\"]\n  },\n  {\n    \"name\": \"Darcs Patch\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"darcspatch\", \"dpatch\"]\n  },\n  {\n    \"name\": \"Dart\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"dart\"]\n  },\n  {\n    \"name\": \"DataWeave\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"dwl\"]\n  },\n  {\n    \"name\": \"Dhall\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"dhall\"]\n  },\n  {\n    \"name\": \"Diff\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"diff\", \"patch\"]\n  },\n  {\n    \"name\": \"Dockerfile\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"dockerfile\"]\n  },\n  {\n    \"name\": \"Dogescript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"djs\"]\n  },\n  {\n    \"name\": \"Dylan\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"dylan\", \"dyl\", \"intr\", \"lid\"]\n  },\n  {\n    \"name\": \"E\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"E\"]\n  },\n  {\n    \"name\": \"EBNF\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ebnf\"]\n  },\n  {\n    \"name\": \"ECL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ecl\", \"eclxml\"]\n  },\n  {\n    \"name\": \"ECLiPSe\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ecl\"]\n  },\n  {\n    \"name\": \"EJS\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ejs\"]\n  },\n  {\n    \"name\": \"EML\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"eml\", \"mbox\"]\n  },\n  {\n    \"name\": \"EQ\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"eq\"]\n  },\n  {\n    \"name\": \"Eagle\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sch\", \"brd\"]\n  },\n  {\n    \"name\": \"Easybuild\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"eb\"]\n  },\n  {\n    \"name\": \"Ecere Projects\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"epj\"]\n  },\n  {\n    \"name\": \"Edje Data Collection\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"edc\"]\n  },\n  {\n    \"name\": \"Eiffel\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"e\"]\n  },\n  {\n    \"name\": \"Elixir\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ex\", \"exs\"]\n  },\n  {\n    \"name\": \"Elm\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"elm\"]\n  },\n  {\n    \"name\": \"Emacs Lisp\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"el\", \"emacs\", \"emacs.desktop\"]\n  },\n  {\n    \"name\": \"EmberScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"em\", \"emberscript\"]\n  },\n  {\n    \"name\": \"Erlang\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"erl\", \"app.src\", \"es\", \"escript\", \"hrl\", \"xrl\", \"yrl\"]\n  },\n  {\n    \"name\": \"F#\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"fs\", \"fsi\", \"fsx\"]\n  },\n  {\n    \"name\": \"F*\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"fst\"]\n  },\n  {\n    \"name\": \"FIGlet Font\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"flf\"]\n  },\n  {\n    \"name\": \"FLUX\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"fx\", \"flux\"]\n  },\n  {\n    \"name\": \"Factor\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"factor\"]\n  },\n  {\n    \"name\": \"Fancy\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"fy\", \"fancypack\"]\n  },\n  {\n    \"name\": \"Fantom\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"fan\"]\n  },\n  {\n    \"name\": \"Filebench WML\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"f\"]\n  },\n  {\n    \"name\": \"Filterscript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"fs\"]\n  },\n  {\n    \"name\": \"Formatted\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"for\", \"eam.fs\"]\n  },\n  {\n    \"name\": \"Forth\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"fth\", \"4th\", \"f\", \"for\", \"forth\", \"fr\", \"frt\", \"fs\"]\n  },\n  {\n    \"name\": \"Fortran\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"f90\", \"f\", \"f03\", \"f08\", \"f77\", \"f95\", \"for\", \"fpp\"]\n  },\n  {\n    \"name\": \"FreeMarker\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ftl\"]\n  },\n  {\n    \"name\": \"Frege\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"fr\"]\n  },\n  {\n    \"name\": \"G-code\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"g\", \"cnc\", \"gco\", \"gcode\"]\n  },\n  {\n    \"name\": \"GAML\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gaml\"]\n  },\n  {\n    \"name\": \"GAMS\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gms\"]\n  },\n  {\n    \"name\": \"GAP\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"g\", \"gap\", \"gd\", \"gi\", \"tst\"]\n  },\n  {\n    \"name\": \"GCC Machine Description\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"md\"]\n  },\n  {\n    \"name\": \"GDB\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gdb\", \"gdbinit\"]\n  },\n  {\n    \"name\": \"GDScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gd\"]\n  },\n  {\n    \"name\": \"GLSL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\n      \"glsl\",\n      \"fp\",\n      \"frag\",\n      \"frg\",\n      \"fs\",\n      \"fsh\",\n      \"fshader\",\n      \"geo\",\n      \"geom\",\n      \"glslv\",\n      \"gshader\",\n      \"shader\",\n      \"tesc\",\n      \"tese\",\n      \"vert\",\n      \"vrx\",\n      \"vsh\",\n      \"vshader\"\n    ]\n  },\n  {\n    \"name\": \"GN\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gn\", \"gni\"]\n  },\n  {\n    \"name\": \"Game Maker Language\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gml\"]\n  },\n  {\n    \"name\": \"Genie\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gs\"]\n  },\n  {\n    \"name\": \"Genshi\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"kid\"]\n  },\n  {\n    \"name\": \"Gentoo Ebuild\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ebuild\"]\n  },\n  {\n    \"name\": \"Gentoo Eclass\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"eclass\"]\n  },\n  {\n    \"name\": \"Gerber Image\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\n      \"gbr\",\n      \"gbl\",\n      \"gbo\",\n      \"gbp\",\n      \"gbs\",\n      \"gko\",\n      \"gml\",\n      \"gpb\",\n      \"gpt\",\n      \"gtl\",\n      \"gto\",\n      \"gtp\",\n      \"gts\"\n    ]\n  },\n  {\n    \"name\": \"Gettext Catalog\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"po\", \"pot\"]\n  },\n  {\n    \"name\": \"Gherkin\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"feature\"]\n  },\n  {\n    \"name\": \"Git Config\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gitconfig\"]\n  },\n  {\n    \"name\": \"Glyph\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"glf\"]\n  },\n  {\n    \"name\": \"Glyph Bitmap Distribution Format\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"bdf\"]\n  },\n  {\n    \"name\": \"Gnuplot\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gp\", \"gnu\", \"gnuplot\", \"plot\", \"plt\"]\n  },\n  {\n    \"name\": \"Go\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"go\"]\n  },\n  {\n    \"name\": \"Golo\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"golo\"]\n  },\n  {\n    \"name\": \"Gosu\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gs\", \"gst\", \"gsx\", \"vark\"]\n  },\n  {\n    \"name\": \"Grace\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"grace\"]\n  },\n  {\n    \"name\": \"Gradle\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gradle\"]\n  },\n  {\n    \"name\": \"Grammatical Framework\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gf\"]\n  },\n  {\n    \"name\": \"Graph Modeling Language\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gml\"]\n  },\n  {\n    \"name\": \"GraphQL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"graphql\", \"gql\", \"graphqls\"]\n  },\n  {\n    \"name\": \"Graphviz (DOT)\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"dot\", \"gv\"]\n  },\n  {\n    \"name\": \"Groovy\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"groovy\", \"grt\", \"gtpl\", \"gvy\"]\n  },\n  {\n    \"name\": \"Groovy Server Pages\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gsp\"]\n  },\n  {\n    \"name\": \"HAProxy\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cfg\"]\n  },\n  {\n    \"name\": \"HCL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"hcl\", \"tf\", \"tfvars\", \"workflow\"]\n  },\n  {\n    \"name\": \"HLSL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"hlsl\", \"cginc\", \"fx\", \"fxh\", \"hlsli\"]\n  },\n  {\n    \"name\": \"HTML\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"html\", \"htm\", \"html.hl\", \"inc\", \"st\", \"xht\", \"xhtml\"]\n  },\n  {\n    \"name\": \"HTML+Django\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"jinja\", \"jinja2\", \"mustache\", \"njk\"]\n  },\n  {\n    \"name\": \"HTML+ECR\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ecr\"]\n  },\n  {\n    \"name\": \"HTML+EEX\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"eex\"]\n  },\n  {\n    \"name\": \"HTML+ERB\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"erb\", \"erb.deface\"]\n  },\n  {\n    \"name\": \"HTML+PHP\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"phtml\"]\n  },\n  {\n    \"name\": \"HTML+Razor\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cshtml\", \"razor\"]\n  },\n  {\n    \"name\": \"HTTP\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"http\"]\n  },\n  {\n    \"name\": \"HXML\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"hxml\"]\n  },\n  {\n    \"name\": \"Hack\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"hack\", \"hh\", \"php\"]\n  },\n  {\n    \"name\": \"Haml\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"haml\", \"haml.deface\"]\n  },\n  {\n    \"name\": \"Handlebars\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"handlebars\", \"hbs\"]\n  },\n  {\n    \"name\": \"Harbour\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"hb\"]\n  },\n  {\n    \"name\": \"Haskell\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"hs\", \"hs-boot\", \"hsc\"]\n  },\n  {\n    \"name\": \"Haxe\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"hx\", \"hxsl\"]\n  },\n  {\n    \"name\": \"HiveQL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"q\"]\n  },\n  {\n    \"name\": \"HolyC\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"hc\"]\n  },\n  {\n    \"name\": \"Hy\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"hy\"]\n  },\n  {\n    \"name\": \"HyPhy\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"bf\"]\n  },\n  {\n    \"name\": \"IDL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pro\", \"dlm\"]\n  },\n  {\n    \"name\": \"IGOR Pro\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ipf\"]\n  },\n  {\n    \"name\": \"INI\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ini\", \"cfg\", \"lektorproject\", \"prefs\", \"pro\", \"properties\"]\n  },\n  {\n    \"name\": \"IRC log\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"irclog\", \"weechatlog\"]\n  },\n  {\n    \"name\": \"Idris\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"idr\", \"lidr\"]\n  },\n  {\n    \"name\": \"Ignore List\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"gitignore\"]\n  },\n  {\n    \"name\": \"Inform 7\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ni\", \"i7x\"]\n  },\n  {\n    \"name\": \"Inno Setup\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"iss\"]\n  },\n  {\n    \"name\": \"Io\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"io\"]\n  },\n  {\n    \"name\": \"Ioke\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ik\"]\n  },\n  {\n    \"name\": \"Isabelle\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"thy\"]\n  },\n  {\n    \"name\": \"J\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ijs\"]\n  },\n  {\n    \"name\": \"JFlex\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"flex\", \"jflex\"]\n  },\n  {\n    \"name\": \"JSON\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\n      \"json\",\n      \"avsc\",\n      \"geojson\",\n      \"gltf\",\n      \"har\",\n      \"ice\",\n      \"JSON-tmLanguage\",\n      \"jsonl\",\n      \"mcmeta\",\n      \"tfstate\",\n      \"tfstate.backup\",\n      \"topojson\",\n      \"webapp\",\n      \"webmanifest\",\n      \"yy\",\n      \"yyp\"\n    ]\n  },\n  {\n    \"name\": \"JSON with Comments\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\n      \"sublime-build\",\n      \"sublime-commands\",\n      \"sublime-completions\",\n      \"sublime-keymap\",\n      \"sublime-macro\",\n      \"sublime-menu\",\n      \"sublime-mousemap\",\n      \"sublime-project\",\n      \"sublime-settings\",\n      \"sublime-theme\",\n      \"sublime-workspace\",\n      \"sublime_metrics\",\n      \"sublime_session\"\n    ]\n  },\n  {\n    \"name\": \"JSON5\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"json5\"]\n  },\n  {\n    \"name\": \"JSONLD\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"jsonld\"]\n  },\n  {\n    \"name\": \"JSONiq\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"jq\"]\n  },\n  {\n    \"name\": \"JSX\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"jsx\"]\n  },\n  {\n    \"name\": \"Jasmin\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"j\"]\n  },\n  {\n    \"name\": \"Java\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"java\"]\n  },\n  {\n    \"name\": \"Java Properties\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"properties\"]\n  },\n  {\n    \"name\": \"Java Server Pages\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"jsp\"]\n  },\n  {\n    \"name\": \"JavaScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\n      \"js\",\n      \"_js\",\n      \"bones\",\n      \"es\",\n      \"es6\",\n      \"frag\",\n      \"gs\",\n      \"jake\",\n      \"jsb\",\n      \"jscad\",\n      \"jsfl\",\n      \"jsm\",\n      \"jss\",\n      \"mjs\",\n      \"njs\",\n      \"pac\",\n      \"sjs\",\n      \"ssjs\",\n      \"xsjs\",\n      \"xsjslib\"\n    ]\n  },\n  {\n    \"name\": \"JavaScript+ERB\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"js.erb\"]\n  },\n  {\n    \"name\": \"Jison\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"jison\"]\n  },\n  {\n    \"name\": \"Jison Lex\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"jisonlex\"]\n  },\n  {\n    \"name\": \"Jolie\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ol\", \"iol\"]\n  },\n  {\n    \"name\": \"Jsonnet\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"jsonnet\", \"libsonnet\"]\n  },\n  {\n    \"name\": \"Julia\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"jl\"]\n  },\n  {\n    \"name\": \"Jupyter Notebook\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ipynb\"]\n  },\n  {\n    \"name\": \"KRL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"krl\"]\n  },\n  {\n    \"name\": \"KiCad Layout\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"kicad_pcb\", \"kicad_mod\", \"kicad_wks\"]\n  },\n  {\n    \"name\": \"KiCad Legacy Layout\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"brd\"]\n  },\n  {\n    \"name\": \"KiCad Schematic\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sch\"]\n  },\n  {\n    \"name\": \"Kit\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"kit\"]\n  },\n  {\n    \"name\": \"Kotlin\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"kt\", \"ktm\", \"kts\"]\n  },\n  {\n    \"name\": \"LFE\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"lfe\"]\n  },\n  {\n    \"name\": \"LLVM\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ll\"]\n  },\n  {\n    \"name\": \"LOLCODE\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"lol\"]\n  },\n  {\n    \"name\": \"LSL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"lsl\", \"lslp\"]\n  },\n  {\n    \"name\": \"LTspice Symbol\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"asy\"]\n  },\n  {\n    \"name\": \"LabVIEW\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"lvproj\"]\n  },\n  {\n    \"name\": \"Lasso\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"lasso\", \"las\", \"lasso8\", \"lasso9\"]\n  },\n  {\n    \"name\": \"Latte\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"latte\"]\n  },\n  {\n    \"name\": \"Lean\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"lean\", \"hlean\"]\n  },\n  {\n    \"name\": \"Less\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"less\"]\n  },\n  {\n    \"name\": \"Lex\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"l\", \"lex\"]\n  },\n  {\n    \"name\": \"LilyPond\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ly\", \"ily\"]\n  },\n  {\n    \"name\": \"Limbo\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"b\", \"m\"]\n  },\n  {\n    \"name\": \"Linker Script\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ld\", \"lds\", \"x\"]\n  },\n  {\n    \"name\": \"Linux Kernel Module\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mod\"]\n  },\n  {\n    \"name\": \"Liquid\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"liquid\"]\n  },\n  {\n    \"name\": \"Literate Agda\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"lagda\"]\n  },\n  {\n    \"name\": \"Literate CoffeeScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"litcoffee\"]\n  },\n  {\n    \"name\": \"Literate Haskell\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"lhs\"]\n  },\n  {\n    \"name\": \"LiveScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ls\", \"_ls\"]\n  },\n  {\n    \"name\": \"Logos\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"xm\", \"x\", \"xi\"]\n  },\n  {\n    \"name\": \"Logtalk\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"lgt\", \"logtalk\"]\n  },\n  {\n    \"name\": \"LookML\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"lookml\", \"model.lkml\", \"view.lkml\"]\n  },\n  {\n    \"name\": \"LoomScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ls\"]\n  },\n  {\n    \"name\": \"Lua\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"lua\", \"fcgi\", \"nse\", \"p8\", \"pd_lua\", \"rbxs\", \"wlua\"]\n  },\n  {\n    \"name\": \"M\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mumps\", \"m\"]\n  },\n  {\n    \"name\": \"M4\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"m4\"]\n  },\n  {\n    \"name\": \"M4Sugar\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"m4\"]\n  },\n  {\n    \"name\": \"MATLAB\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"matlab\", \"m\"]\n  },\n  {\n    \"name\": \"MAXScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ms\", \"mcr\"]\n  },\n  {\n    \"name\": \"MLIR\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mlir\"]\n  },\n  {\n    \"name\": \"MQL4\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mq4\", \"mqh\"]\n  },\n  {\n    \"name\": \"MQL5\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mq5\", \"mqh\"]\n  },\n  {\n    \"name\": \"MTML\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mtml\"]\n  },\n  {\n    \"name\": \"MUF\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"muf\", \"m\"]\n  },\n  {\n    \"name\": \"Makefile\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mak\", \"d\", \"make\", \"mk\", \"mkfile\"]\n  },\n  {\n    \"name\": \"Mako\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mako\", \"mao\"]\n  },\n  {\n    \"name\": \"Markdown\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"md\", \"markdown\", \"mdown\", \"mdwn\", \"mdx\", \"mkd\", \"mkdn\", \"mkdown\", \"ronn\", \"workbook\"]\n  },\n  {\n    \"name\": \"Marko\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"marko\"]\n  },\n  {\n    \"name\": \"Mask\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mask\"]\n  },\n  {\n    \"name\": \"Mathematica\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mathematica\", \"cdf\", \"m\", \"ma\", \"mt\", \"nb\", \"nbp\", \"wl\", \"wlt\"]\n  },\n  {\n    \"name\": \"Max\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"maxpat\", \"maxhelp\", \"maxproj\", \"mxt\", \"pat\"]\n  },\n  {\n    \"name\": \"MediaWiki\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mediawiki\", \"wiki\"]\n  },\n  {\n    \"name\": \"Mercury\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"m\", \"moo\"]\n  },\n  {\n    \"name\": \"Metal\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"metal\"]\n  },\n  {\n    \"name\": \"MiniD\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"minid\"]\n  },\n  {\n    \"name\": \"Mirah\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"druby\", \"duby\", \"mirah\"]\n  },\n  {\n    \"name\": \"Modelica\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mo\"]\n  },\n  {\n    \"name\": \"Modula-2\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mod\"]\n  },\n  {\n    \"name\": \"Modula-3\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"i3\", \"ig\", \"m3\", \"mg\"]\n  },\n  {\n    \"name\": \"Module Management System\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mms\", \"mmk\"]\n  },\n  {\n    \"name\": \"Monkey\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"monkey\", \"monkey2\"]\n  },\n  {\n    \"name\": \"Moocode\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"moo\"]\n  },\n  {\n    \"name\": \"MoonScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"moon\"]\n  },\n  {\n    \"name\": \"Motorola 68K Assembly\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"X68\"]\n  },\n  {\n    \"name\": \"Muse\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"muse\"]\n  },\n  {\n    \"name\": \"Myghty\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"myt\"]\n  },\n  {\n    \"name\": \"NCL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ncl\"]\n  },\n  {\n    \"name\": \"NL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"nl\"]\n  },\n  {\n    \"name\": \"NSIS\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"nsi\", \"nsh\"]\n  },\n  {\n    \"name\": \"Nearley\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ne\", \"nearley\"]\n  },\n  {\n    \"name\": \"Nemerle\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"n\"]\n  },\n  {\n    \"name\": \"NetLinx\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"axs\", \"axi\"]\n  },\n  {\n    \"name\": \"NetLinx+ERB\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"axs.erb\", \"axi.erb\"]\n  },\n  {\n    \"name\": \"NetLogo\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"nlogo\"]\n  },\n  {\n    \"name\": \"NewLisp\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"nl\", \"lisp\", \"lsp\"]\n  },\n  {\n    \"name\": \"Nextflow\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"nf\"]\n  },\n  {\n    \"name\": \"Nginx\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"nginxconf\", \"vhost\"]\n  },\n  {\n    \"name\": \"Nim\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"nim\", \"nim.cfg\", \"nimble\", \"nimrod\", \"nims\"]\n  },\n  {\n    \"name\": \"Ninja\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ninja\"]\n  },\n  {\n    \"name\": \"Nit\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"nit\"]\n  },\n  {\n    \"name\": \"Nix\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"nix\"]\n  },\n  {\n    \"name\": \"Nu\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"nu\"]\n  },\n  {\n    \"name\": \"NumPy\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"numpy\", \"numpyw\", \"numsc\"]\n  },\n  {\n    \"name\": \"OCaml\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ml\", \"eliom\", \"eliomi\", \"ml4\", \"mli\", \"mll\", \"mly\"]\n  },\n  {\n    \"name\": \"ObjDump\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"objdump\"]\n  },\n  {\n    \"name\": \"ObjectScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cls\"]\n  },\n  {\n    \"name\": \"Objective-C\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"m\", \"h\"]\n  },\n  {\n    \"name\": \"Objective-C++\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mm\"]\n  },\n  {\n    \"name\": \"Objective-J\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"j\", \"sj\"]\n  },\n  {\n    \"name\": \"Omgrofl\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"omgrofl\"]\n  },\n  {\n    \"name\": \"Opa\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"opa\"]\n  },\n  {\n    \"name\": \"Opal\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"opal\"]\n  },\n  {\n    \"name\": \"Open Policy Agent\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rego\"]\n  },\n  {\n    \"name\": \"OpenCL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cl\", \"opencl\"]\n  },\n  {\n    \"name\": \"OpenEdge ABL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"p\", \"cls\", \"w\"]\n  },\n  {\n    \"name\": \"OpenSCAD\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"scad\"]\n  },\n  {\n    \"name\": \"OpenStep Property List\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"plist\"]\n  },\n  {\n    \"name\": \"OpenType Feature File\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"fea\"]\n  },\n  {\n    \"name\": \"Org\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"org\"]\n  },\n  {\n    \"name\": \"Ox\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ox\", \"oxh\", \"oxo\"]\n  },\n  {\n    \"name\": \"Oxygene\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"oxygene\"]\n  },\n  {\n    \"name\": \"Oz\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"oz\"]\n  },\n  {\n    \"name\": \"P4\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"p4\"]\n  },\n  {\n    \"name\": \"PHP\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"php\", \"aw\", \"ctp\", \"fcgi\", \"inc\", \"php3\", \"php4\", \"php5\", \"phps\", \"phpt\"]\n  },\n  {\n    \"name\": \"PLSQL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\n      \"pls\",\n      \"bdy\",\n      \"ddl\",\n      \"fnc\",\n      \"pck\",\n      \"pkb\",\n      \"pks\",\n      \"plb\",\n      \"plsql\",\n      \"prc\",\n      \"spc\",\n      \"sql\",\n      \"tpb\",\n      \"tps\",\n      \"trg\",\n      \"vw\"\n    ]\n  },\n  {\n    \"name\": \"PLpgSQL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pgsql\", \"sql\"]\n  },\n  {\n    \"name\": \"POV-Ray SDL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pov\", \"inc\"]\n  },\n  {\n    \"name\": \"Pan\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pan\"]\n  },\n  {\n    \"name\": \"Papyrus\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"psc\"]\n  },\n  {\n    \"name\": \"Parrot\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"parrot\"]\n  },\n  {\n    \"name\": \"Parrot Assembly\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pasm\"]\n  },\n  {\n    \"name\": \"Parrot Internal Representation\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pir\"]\n  },\n  {\n    \"name\": \"Pascal\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pas\", \"dfm\", \"dpr\", \"inc\", \"lpr\", \"pascal\", \"pp\"]\n  },\n  {\n    \"name\": \"Pawn\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pwn\", \"inc\", \"sma\"]\n  },\n  {\n    \"name\": \"Pep8\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pep\"]\n  },\n  {\n    \"name\": \"Perl\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pl\", \"al\", \"cgi\", \"fcgi\", \"perl\", \"ph\", \"plx\", \"pm\", \"psgi\", \"t\"]\n  },\n  {\n    \"name\": \"Perl 6\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"6pl\", \"6pm\", \"nqp\", \"p6\", \"p6l\", \"p6m\", \"pl\", \"pl6\", \"pm\", \"pm6\", \"t\"]\n  },\n  {\n    \"name\": \"Pic\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pic\", \"chem\"]\n  },\n  {\n    \"name\": \"Pickle\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pkl\"]\n  },\n  {\n    \"name\": \"PicoLisp\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"l\"]\n  },\n  {\n    \"name\": \"PigLatin\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pig\"]\n  },\n  {\n    \"name\": \"Pike\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pike\", \"pmod\"]\n  },\n  {\n    \"name\": \"Pod\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pod\"]\n  },\n  {\n    \"name\": \"Pod 6\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pod\", \"pod6\"]\n  },\n  {\n    \"name\": \"PogoScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pogo\"]\n  },\n  {\n    \"name\": \"Pony\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pony\"]\n  },\n  {\n    \"name\": \"PostCSS\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pcss\"]\n  },\n  {\n    \"name\": \"PostScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ps\", \"eps\", \"pfa\"]\n  },\n  {\n    \"name\": \"PowerBuilder\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pbt\", \"sra\", \"sru\", \"srw\"]\n  },\n  {\n    \"name\": \"PowerShell\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ps1\", \"psd1\", \"psm1\"]\n  },\n  {\n    \"name\": \"Prisma\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"prisma\"]\n  },\n  {\n    \"name\": \"Processing\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pde\"]\n  },\n  {\n    \"name\": \"Prolog\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pl\", \"pro\", \"prolog\", \"yap\"]\n  },\n  {\n    \"name\": \"Propeller Spin\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"spin\"]\n  },\n  {\n    \"name\": \"Protocol Buffer\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"proto\"]\n  },\n  {\n    \"name\": \"Public Key\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"asc\", \"pub\"]\n  },\n  {\n    \"name\": \"Pug\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"jade\", \"pug\"]\n  },\n  {\n    \"name\": \"Puppet\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pp\"]\n  },\n  {\n    \"name\": \"Pure Data\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pd\"]\n  },\n  {\n    \"name\": \"PureBasic\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pb\", \"pbi\"]\n  },\n  {\n    \"name\": \"PureScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"purs\"]\n  },\n  {\n    \"name\": \"Python\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\n      \"py\",\n      \"bzl\",\n      \"cgi\",\n      \"fcgi\",\n      \"gyp\",\n      \"gypi\",\n      \"lmi\",\n      \"py3\",\n      \"pyde\",\n      \"pyi\",\n      \"pyp\",\n      \"pyt\",\n      \"pyw\",\n      \"rpy\",\n      \"spec\",\n      \"tac\",\n      \"wsgi\",\n      \"xpy\"\n    ]\n  },\n  {\n    \"name\": \"Python traceback\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pytb\"]\n  },\n  {\n    \"name\": \"QML\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"qml\", \"qbs\"]\n  },\n  {\n    \"name\": \"QMake\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"pro\", \"pri\"]\n  },\n  {\n    \"name\": \"R\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"r\", \"rd\", \"rsx\"]\n  },\n  {\n    \"name\": \"RAML\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"raml\"]\n  },\n  {\n    \"name\": \"RDoc\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rdoc\"]\n  },\n  {\n    \"name\": \"REALbasic\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rbbas\", \"rbfrm\", \"rbmnu\", \"rbres\", \"rbtbar\", \"rbuistate\"]\n  },\n  {\n    \"name\": \"REXX\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rexx\", \"pprx\", \"rex\"]\n  },\n  {\n    \"name\": \"RHTML\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rhtml\"]\n  },\n  {\n    \"name\": \"RMarkdown\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rmd\"]\n  },\n  {\n    \"name\": \"RPC\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"x\"]\n  },\n  {\n    \"name\": \"RPM Spec\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"spec\"]\n  },\n  {\n    \"name\": \"RUNOFF\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rnh\", \"rno\"]\n  },\n  {\n    \"name\": \"Racket\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rkt\", \"rktd\", \"rktl\", \"scrbl\"]\n  },\n  {\n    \"name\": \"Ragel\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rl\"]\n  },\n  {\n    \"name\": \"Rascal\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rsc\"]\n  },\n  {\n    \"name\": \"Raw token data\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"raw\"]\n  },\n  {\n    \"name\": \"Reason\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"re\", \"rei\"]\n  },\n  {\n    \"name\": \"Rebol\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"reb\", \"r\", \"r2\", \"r3\", \"rebol\"]\n  },\n  {\n    \"name\": \"Red\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"red\", \"reds\"]\n  },\n  {\n    \"name\": \"Redcode\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cw\"]\n  },\n  {\n    \"name\": \"Regular Expression\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"regexp\", \"regex\"]\n  },\n  {\n    \"name\": \"Ren'Py\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rpy\"]\n  },\n  {\n    \"name\": \"RenderScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rs\", \"rsh\"]\n  },\n  {\n    \"name\": \"Rich Text Format\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rtf\"]\n  },\n  {\n    \"name\": \"Ring\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ring\"]\n  },\n  {\n    \"name\": \"Riot\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"riot\"]\n  },\n  {\n    \"name\": \"RobotFramework\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"robot\"]\n  },\n  {\n    \"name\": \"Roff\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\n      \"roff\",\n      \"1\",\n      \"1in\",\n      \"1m\",\n      \"1x\",\n      \"2\",\n      \"3\",\n      \"3in\",\n      \"3m\",\n      \"3p\",\n      \"3pm\",\n      \"3qt\",\n      \"3x\",\n      \"4\",\n      \"5\",\n      \"6\",\n      \"7\",\n      \"8\",\n      \"9\",\n      \"l\",\n      \"man\",\n      \"mdoc\",\n      \"me\",\n      \"ms\",\n      \"n\",\n      \"nr\",\n      \"rno\",\n      \"tmac\"\n    ]\n  },\n  {\n    \"name\": \"Roff Manpage\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\n      \"1\",\n      \"1in\",\n      \"1m\",\n      \"1x\",\n      \"2\",\n      \"3\",\n      \"3in\",\n      \"3m\",\n      \"3p\",\n      \"3pm\",\n      \"3qt\",\n      \"3x\",\n      \"4\",\n      \"5\",\n      \"6\",\n      \"7\",\n      \"8\",\n      \"9\",\n      \"man\",\n      \"mdoc\"\n    ]\n  },\n  {\n    \"name\": \"Rouge\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rg\"]\n  },\n  {\n    \"name\": \"Ruby\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\n      \"rb\",\n      \"builder\",\n      \"eye\",\n      \"fcgi\",\n      \"gemspec\",\n      \"god\",\n      \"jbuilder\",\n      \"mspec\",\n      \"pluginspec\",\n      \"podspec\",\n      \"rabl\",\n      \"rake\",\n      \"rbuild\",\n      \"rbw\",\n      \"rbx\",\n      \"ru\",\n      \"ruby\",\n      \"spec\",\n      \"thor\",\n      \"watchr\"\n    ]\n  },\n  {\n    \"name\": \"Rust\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rs\", \"rs.in\"]\n  },\n  {\n    \"name\": \"SAS\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sas\"]\n  },\n  {\n    \"name\": \"SCSS\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"scss\"]\n  },\n  {\n    \"name\": \"SMT\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"smt2\", \"smt\"]\n  },\n  {\n    \"name\": \"SPARQL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sparql\", \"rq\"]\n  },\n  {\n    \"name\": \"SQF\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sqf\", \"hqf\"]\n  },\n  {\n    \"name\": \"SQL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sql\", \"cql\", \"ddl\", \"inc\", \"mysql\", \"prc\", \"tab\", \"udf\", \"viw\"]\n  },\n  {\n    \"name\": \"SQLPL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sql\", \"db2\"]\n  },\n  {\n    \"name\": \"SRecode Template\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"srt\"]\n  },\n  {\n    \"name\": \"STON\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ston\"]\n  },\n  {\n    \"name\": \"SVG\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"svg\"]\n  },\n  {\n    \"name\": \"Sage\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sage\", \"sagews\"]\n  },\n  {\n    \"name\": \"SaltStack\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sls\"]\n  },\n  {\n    \"name\": \"Sass\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sass\"]\n  },\n  {\n    \"name\": \"Scala\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"scala\", \"kojo\", \"sbt\", \"sc\"]\n  },\n  {\n    \"name\": \"Scaml\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"scaml\"]\n  },\n  {\n    \"name\": \"Scheme\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"scm\", \"sch\", \"sld\", \"sls\", \"sps\", \"ss\"]\n  },\n  {\n    \"name\": \"Scilab\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sci\", \"sce\", \"tst\"]\n  },\n  {\n    \"name\": \"Self\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"self\"]\n  },\n  {\n    \"name\": \"ShaderLab\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"shader\"]\n  },\n  {\n    \"name\": \"Shell\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sh\", \"bash\", \"bats\", \"cgi\", \"command\", \"fcgi\", \"ksh\", \"sh.in\", \"tmux\", \"tool\", \"zsh\"]\n  },\n  {\n    \"name\": \"ShellSession\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sh-session\"]\n  },\n  {\n    \"name\": \"Shen\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"shen\"]\n  },\n  {\n    \"name\": \"Slash\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sl\"]\n  },\n  {\n    \"name\": \"Slice\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ice\"]\n  },\n  {\n    \"name\": \"Slim\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"slim\"]\n  },\n  {\n    \"name\": \"SmPL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"cocci\"]\n  },\n  {\n    \"name\": \"Smali\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"smali\"]\n  },\n  {\n    \"name\": \"Smalltalk\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"st\", \"cs\"]\n  },\n  {\n    \"name\": \"Smarty\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"tpl\"]\n  },\n  {\n    \"name\": \"SourcePawn\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sp\", \"inc\"]\n  },\n  {\n    \"name\": \"Spline Font Database\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sfd\"]\n  },\n  {\n    \"name\": \"Squirrel\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"nut\"]\n  },\n  {\n    \"name\": \"Stan\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"stan\"]\n  },\n  {\n    \"name\": \"Standard ML\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ML\", \"fun\", \"sig\", \"sml\"]\n  },\n  {\n    \"name\": \"Stata\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"do\", \"ado\", \"doh\", \"ihlp\", \"mata\", \"matah\", \"sthlp\"]\n  },\n  {\n    \"name\": \"Stylus\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"styl\"]\n  },\n  {\n    \"name\": \"SubRip Text\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"srt\"]\n  },\n  {\n    \"name\": \"SugarSS\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sss\"]\n  },\n  {\n    \"name\": \"SuperCollider\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sc\", \"scd\"]\n  },\n  {\n    \"name\": \"Svelte\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"svelte\"]\n  },\n  {\n    \"name\": \"Swift\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"swift\"]\n  },\n  {\n    \"name\": \"SystemVerilog\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sv\", \"svh\", \"vh\"]\n  },\n  {\n    \"name\": \"TI Program\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"8xp\", \"8xk\", \"8xk.txt\", \"8xp.txt\"]\n  },\n  {\n    \"name\": \"TLA\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"tla\"]\n  },\n  {\n    \"name\": \"TOML\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"toml\"]\n  },\n  {\n    \"name\": \"TSQL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sql\"]\n  },\n  {\n    \"name\": \"TSX\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"tsx\"]\n  },\n  {\n    \"name\": \"TXL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"txl\"]\n  },\n  {\n    \"name\": \"Tcl\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"tcl\", \"adp\", \"tm\"]\n  },\n  {\n    \"name\": \"Tcsh\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"tcsh\", \"csh\"]\n  },\n  {\n    \"name\": \"TeX\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\n      \"tex\",\n      \"aux\",\n      \"bbx\",\n      \"cbx\",\n      \"cls\",\n      \"dtx\",\n      \"ins\",\n      \"lbx\",\n      \"ltx\",\n      \"mkii\",\n      \"mkiv\",\n      \"mkvi\",\n      \"sty\",\n      \"toc\"\n    ]\n  },\n  {\n    \"name\": \"Tea\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"tea\"]\n  },\n  {\n    \"name\": \"Terra\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"t\"]\n  },\n  {\n    \"name\": \"Texinfo\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"texinfo\", \"texi\", \"txi\"]\n  },\n  {\n    \"name\": \"Text\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"txt\", \"fr\", \"nb\", \"ncl\", \"no\"]\n  },\n  {\n    \"name\": \"Textile\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"textile\"]\n  },\n  {\n    \"name\": \"Thrift\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"thrift\"]\n  },\n  {\n    \"name\": \"Turing\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"t\", \"tu\"]\n  },\n  {\n    \"name\": \"Turtle\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ttl\"]\n  },\n  {\n    \"name\": \"Twig\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"twig\"]\n  },\n  {\n    \"name\": \"Type Language\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"tl\"]\n  },\n  {\n    \"name\": \"TypeScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ts\"]\n  },\n  {\n    \"name\": \"Unified Parallel C\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"upc\"]\n  },\n  {\n    \"name\": \"Unity3D Asset\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"anim\", \"asset\", \"mat\", \"meta\", \"prefab\", \"unity\"]\n  },\n  {\n    \"name\": \"Unix Assembly\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"s\", \"ms\"]\n  },\n  {\n    \"name\": \"Uno\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"uno\"]\n  },\n  {\n    \"name\": \"UnrealScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"uc\"]\n  },\n  {\n    \"name\": \"UrWeb\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ur\", \"urs\"]\n  },\n  {\n    \"name\": \"V\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"v\"]\n  },\n  {\n    \"name\": \"VCL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"vcl\"]\n  },\n  {\n    \"name\": \"VHDL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"vhdl\", \"vhd\", \"vhf\", \"vhi\", \"vho\", \"vhs\", \"vht\", \"vhw\"]\n  },\n  {\n    \"name\": \"Vala\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"vala\", \"vapi\"]\n  },\n  {\n    \"name\": \"Verilog\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"v\", \"veo\"]\n  },\n  {\n    \"name\": \"Vim Snippet\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"snip\", \"snippet\", \"snippets\"]\n  },\n  {\n    \"name\": \"Vim script\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"vim\", \"vba\", \"vmb\"]\n  },\n  {\n    \"name\": \"Visual Basic\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"vb\", \"bas\", \"cls\", \"frm\", \"frx\", \"vba\", \"vbhtml\", \"vbs\"]\n  },\n  {\n    \"name\": \"Volt\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"volt\"]\n  },\n  {\n    \"name\": \"Vue\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"vue\"]\n  },\n  {\n    \"name\": \"Wavefront Material\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mtl\"]\n  },\n  {\n    \"name\": \"Wavefront Object\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"obj\"]\n  },\n  {\n    \"name\": \"Web Ontology Language\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"owl\"]\n  },\n  {\n    \"name\": \"WebAssembly\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"wast\", \"wat\"]\n  },\n  {\n    \"name\": \"WebIDL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"webidl\"]\n  },\n  {\n    \"name\": \"WebVTT\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"vtt\"]\n  },\n  {\n    \"name\": \"Windows Registry Entries\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"reg\"]\n  },\n  {\n    \"name\": \"Wollok\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"wlk\"]\n  },\n  {\n    \"name\": \"World of Warcraft Addon Data\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"toc\"]\n  },\n  {\n    \"name\": \"X BitMap\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"xbm\"]\n  },\n  {\n    \"name\": \"X PixMap\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"xpm\", \"pm\"]\n  },\n  {\n    \"name\": \"X10\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"x10\"]\n  },\n  {\n    \"name\": \"XC\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"xc\"]\n  },\n  {\n    \"name\": \"XML\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\n      \"xml\",\n      \"adml\",\n      \"admx\",\n      \"ant\",\n      \"axml\",\n      \"builds\",\n      \"ccproj\",\n      \"ccxml\",\n      \"clixml\",\n      \"cproject\",\n      \"cscfg\",\n      \"csdef\",\n      \"csl\",\n      \"csproj\",\n      \"ct\",\n      \"depproj\",\n      \"dita\",\n      \"ditamap\",\n      \"ditaval\",\n      \"dll.config\",\n      \"dotsettings\",\n      \"filters\",\n      \"fsproj\",\n      \"fxml\",\n      \"glade\",\n      \"gml\",\n      \"gmx\",\n      \"grxml\",\n      \"iml\",\n      \"ivy\",\n      \"jelly\",\n      \"jsproj\",\n      \"kml\",\n      \"launch\",\n      \"mdpolicy\",\n      \"mjml\",\n      \"mm\",\n      \"mod\",\n      \"mxml\",\n      \"natvis\",\n      \"ncl\",\n      \"ndproj\",\n      \"nproj\",\n      \"nuspec\",\n      \"odd\",\n      \"osm\",\n      \"pkgproj\",\n      \"pluginspec\",\n      \"proj\",\n      \"props\",\n      \"ps1xml\",\n      \"psc1\",\n      \"pt\",\n      \"rdf\",\n      \"resx\",\n      \"rss\",\n      \"sch\",\n      \"scxml\",\n      \"sfproj\",\n      \"shproj\",\n      \"srdf\",\n      \"storyboard\",\n      \"sublime-snippet\",\n      \"targets\",\n      \"tml\",\n      \"ts\",\n      \"tsx\",\n      \"ui\",\n      \"urdf\",\n      \"ux\",\n      \"vbproj\",\n      \"vcxproj\",\n      \"vsixmanifest\",\n      \"vssettings\",\n      \"vstemplate\",\n      \"vxml\",\n      \"wixproj\",\n      \"workflow\",\n      \"wsdl\",\n      \"wsf\",\n      \"wxi\",\n      \"wxl\",\n      \"wxs\",\n      \"x3d\",\n      \"xacro\",\n      \"xaml\",\n      \"xib\",\n      \"xlf\",\n      \"xliff\",\n      \"xmi\",\n      \"xml.dist\",\n      \"xproj\",\n      \"xsd\",\n      \"xspec\",\n      \"xul\",\n      \"zcml\"\n    ]\n  },\n  {\n    \"name\": \"XML Property List\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"plist\", \"stTheme\", \"tmCommand\", \"tmLanguage\", \"tmPreferences\", \"tmSnippet\", \"tmTheme\"]\n  },\n  {\n    \"name\": \"XPages\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"xsp-config\", \"xsp.metadata\"]\n  },\n  {\n    \"name\": \"XProc\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"xpl\", \"xproc\"]\n  },\n  {\n    \"name\": \"XQuery\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"xquery\", \"xq\", \"xql\", \"xqm\", \"xqy\"]\n  },\n  {\n    \"name\": \"XS\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"xs\"]\n  },\n  {\n    \"name\": \"XSLT\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"xslt\", \"xsl\"]\n  },\n  {\n    \"name\": \"Xojo\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"xojo_code\", \"xojo_menu\", \"xojo_report\", \"xojo_script\", \"xojo_toolbar\", \"xojo_window\"]\n  },\n  {\n    \"name\": \"Xtend\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"xtend\"]\n  },\n  {\n    \"name\": \"YAML\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\n      \"yml\",\n      \"mir\",\n      \"reek\",\n      \"rviz\",\n      \"sublime-syntax\",\n      \"syntax\",\n      \"yaml\",\n      \"yaml-tmlanguage\",\n      \"yml.mysql\"\n    ]\n  },\n  {\n    \"name\": \"YANG\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"yang\"]\n  },\n  {\n    \"name\": \"YARA\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"yar\", \"yara\"]\n  },\n  {\n    \"name\": \"YASnippet\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"yasnippet\"]\n  },\n  {\n    \"name\": \"Yacc\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"y\", \"yacc\", \"yy\"]\n  },\n  {\n    \"name\": \"ZAP\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"zap\", \"xzap\"]\n  },\n  {\n    \"name\": \"ZIL\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"zil\", \"mud\"]\n  },\n  {\n    \"name\": \"Zeek\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"zeek\", \"bro\"]\n  },\n  {\n    \"name\": \"ZenScript\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"zs\"]\n  },\n  {\n    \"name\": \"Zephir\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"zep\"]\n  },\n  {\n    \"name\": \"Zig\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"zig\"]\n  },\n  {\n    \"name\": \"Zimpl\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"zimpl\", \"zmpl\", \"zpl\"]\n  },\n  {\n    \"name\": \"desktop\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"desktop\", \"desktop.in\"]\n  },\n  {\n    \"name\": \"eC\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ec\", \"eh\"]\n  },\n  {\n    \"name\": \"edn\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"edn\"]\n  },\n  {\n    \"name\": \"fish\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"fish\"]\n  },\n  {\n    \"name\": \"mIRC Script\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mrc\"]\n  },\n  {\n    \"name\": \"mcfunction\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mcfunction\"]\n  },\n  {\n    \"name\": \"mupad\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"mu\"]\n  },\n  {\n    \"name\": \"nanorc\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"nanorc\"]\n  },\n  {\n    \"name\": \"nesC\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"nc\"]\n  },\n  {\n    \"name\": \"ooc\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"ooc\"]\n  },\n  {\n    \"name\": \"q\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"q\"]\n  },\n  {\n    \"name\": \"reStructuredText\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"rst\", \"rest\", \"rest.txt\", \"rst.txt\"]\n  },\n  {\n    \"name\": \"sed\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"sed\"]\n  },\n  {\n    \"name\": \"wdl\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"wdl\"]\n  },\n  {\n    \"name\": \"wisp\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"wisp\"]\n  },\n  {\n    \"name\": \"xBase\",\n    \"role\": \"Editor\",\n    \"icon\": \"generic.icns\",\n    \"ext\": [\"prg\", \"ch\", \"prw\"]\n  }\n]\n"
  },
  {
    "path": "packages/electron/config/electron-builder/release.js",
    "content": "// Notarize needs APP_ID, APPLE_ID, APPLE_APP_SPECIFIC_PASSWORD, TEAM_ID env variables.\n// Github repo to release is automatically detected from package.json.\n// GH_TOKEN env variable is required to upload release.\n\n// eslint-disable-next-line import/extensions\nconst build = require('./build.js');\n\nconst publish = {\n  ...build,\n  mac: {\n    category: 'public.app-category.developer-tools',\n    target: {\n      target: 'default',\n      arch: 'universal',\n    },\n    notarize: {\n      teamId: process.env.TEAM_ID,\n    },\n  },\n};\n\nmodule.exports = publish;\n"
  },
  {
    "path": "packages/electron/config/webpack.common.config.js",
    "content": "const path = require('path');\n\nconst buildPath = path.resolve(__dirname, './../build');\n\nmodule.exports = {\n  mode: 'development',\n  output: {\n    path: buildPath,\n  },\n  resolve: {\n    extensions: ['.ts', '.js'],\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts)$/,\n        exclude: /node_modules/,\n        loader: 'babel-loader',\n      },\n    ],\n  },\n};\n"
  },
  {
    "path": "packages/electron/config/webpack.config.js",
    "content": "const rendererConfig = require('./webpack.renderer.config');\nconst mainConfig = require('./webpack.main.config');\n\nmodule.exports = [rendererConfig, mainConfig];\n"
  },
  {
    "path": "packages/electron/config/webpack.main.config.js",
    "content": "const { merge } = require('webpack-merge');\nconst common = require('./webpack.common.config');\n\nconst config = merge(common, {\n  entry: './src/main/index.ts',\n  output: {\n    filename: 'main.js',\n  },\n  target: 'electron-main',\n  node: {\n    __dirname: false,\n    __filename: false,\n  },\n});\n\nmodule.exports = config;\n"
  },
  {
    "path": "packages/electron/config/webpack.prod.config.js",
    "content": "const { merge } = require('webpack-merge');\n\nconst rendererConfig = require('./webpack.renderer.config');\nconst mainConfig = require('./webpack.main.config');\n\nconst prod = {\n  mode: 'production',\n};\n\nconst rendererConfigProd = merge(rendererConfig, prod);\nconst mainConfigProd = merge(mainConfig, prod);\n\nmodule.exports = [rendererConfigProd, mainConfigProd];\n"
  },
  {
    "path": "packages/electron/config/webpack.renderer.config.js",
    "content": "const { merge } = require('webpack-merge');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst common = require('./webpack.common.config');\n\nconst config = merge(common, {\n  entry: './src/renderer/index.ts',\n  output: {\n    filename: 'renderer.js',\n  },\n  plugins: [\n    new HtmlWebpackPlugin({\n      template: './src/renderer/index.html',\n    }),\n  ],\n  target: 'web',\n  devtool: 'eval-cheap-source-map',\n});\n\nmodule.exports = config;\n"
  },
  {
    "path": "packages/electron/jest.config.js",
    "content": "module.exports = {\n  clearMocks: true,\n  moduleNameMapper: {\n    'src/(.*)': ['<rootDir>/src/$1'],\n  },\n};\n"
  },
  {
    "path": "packages/electron/package.json",
    "content": "{\n  \"name\": \"@vvim/electron\",\n  \"description\": \"Neovim GUI Client\",\n  \"author\": \"Igor Gladkoborodov <igor.gladkoborodov@gmail.com>\",\n  \"version\": \"2.6.2\",\n  \"private\": true,\n  \"keywords\": [\n    \"vim\",\n    \"neovim\",\n    \"client\",\n    \"gui\",\n    \"electron\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com:vv-vim/vv.git\"\n  },\n  \"license\": \"MIT\",\n  \"main\": \"./build/main.js\",\n  \"sideEffects\": false,\n  \"scripts\": {\n    \"test\": \"jest\",\n    \"clean\": \"rm -rf dist/*\",\n    \"webpack:dev\": \"webpack --watch --config ./config/webpack.config.js\",\n    \"webpack:prod\": \"webpack --config ./config/webpack.prod.config.js\",\n    \"build:local\": \"yarn webpack:prod; electron-builder -c.mac.identity=null -c.extraMetadata.main=build/main.js --config config/electron-builder/build.js\",\n    \"build:release\": \"electron-builder -c.extraMetadata.main=build/main.js --config config/electron-builder/release.js --publish always\",\n    \"build:link\": \"rm -rf /Applications/VV.app; cp -R dist/mac-universal/VV.app /Applications; ln -s -f /Applications/VV.app/Contents/Resources/bin/vv /usr/local/bin/vv\",\n    \"build\": \"npm-run-all clean webpack:prod build:local build:link\",\n    \"release:open-github\": \"open https://github.com/vv-vim/vv/releases\",\n    \"release\": \"npm-run-all clean webpack:prod build:release release:open-github\",\n    \"filetypes\": \"node scripts/filetypes.js\",\n    \"dev\": \"yarn webpack:dev\",\n    \"start\": \"electron .\"\n  },\n  \"browserslist\": [\n    \"chrome 122\",\n    \"node 20\"\n  ],\n  \"devDependencies\": {\n    \"@types/jest\": \"^26.0.20\",\n    \"@types/lodash\": \"^4.14.168\",\n    \"@types/node\": \"^16.0.0\",\n    \"chalk\": \"^4.1.0\",\n    \"dotenv\": \"^8.2.0\",\n    \"electron\": \"^29\",\n    \"electron-builder\": \"^24.13.3\",\n    \"html-webpack-plugin\": \"^5.6.0\",\n    \"js-yaml\": \"^3.14.0\",\n    \"node-fetch\": \"^2.6.7\"\n  },\n  \"dependencies\": {\n    \"@vvim/browser-renderer\": \"0.0.1\",\n    \"@vvim/nvim\": \"0.0.1\",\n    \"electron-store\": \"^7.0.2\",\n    \"electron-updater\": \"^4.3.5\",\n    \"emoji-regex\": \"^10.3.0\",\n    \"html2plaintext\": \"^2.1.2\",\n    \"lodash\": \"^4.17.21\",\n    \"semver\": \"^7.5.2\"\n  }\n}\n"
  },
  {
    "path": "packages/electron/scripts/filetypes.js",
    "content": "// Generate fileAssociations for electron-builder.\n// File types are generated from [Github Linguist](https://github.com/github/linguist)\n// languates list.\n\nconst fetch = require('node-fetch');\nconst yaml = require('js-yaml');\nconst fs = require('fs');\nconst path = require('path');\n\nconst SOURCE_YAML =\n  'https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml';\n\nconst SAVE_TO = path.join(__dirname, '../config/electron-builder/fileAssociations.json');\n\nconst filetypes = async () => {\n  const yamlDoc = await fetch(SOURCE_YAML).then((res) => res.text());\n\n  const parsed = yaml.safeLoad(yamlDoc);\n\n  const fileAssociations = Object.keys(parsed)\n    .filter((key) => parsed[key].extensions)\n    .map((key) => ({\n      name: key,\n      role: 'Editor',\n      icon: 'generic.icns',\n      ext: parsed[key].extensions.map((e) => e.replace('.', '')),\n    }));\n\n  fs.writeFileSync(SAVE_TO, JSON.stringify(fileAssociations, null, 2), { encoding: 'utf-8' });\n};\n\nfiletypes();\n"
  },
  {
    "path": "packages/electron/src/lib/isDev.ts",
    "content": "type IsDevFunction = {\n  <T, F>(dev: T, notDev: F): T | F;\n  (): boolean;\n};\n\nconst isDev: IsDevFunction = (dev = true, notDev = false) =>\n  process.env.NODE_ENV === 'development' ? dev : notDev;\n\nexport default isDev;\n"
  },
  {
    "path": "packages/electron/src/lib/log.ts",
    "content": "const initNow = Date.now();\nlet lastNow = initNow;\nconst log = (...text: string[]): void => {\n  // eslint-disable-next-line no-console\n  console.log(...text, Date.now() - lastNow, Date.now() - initNow, initNow, Date.now());\n  lastNow = Date.now();\n};\n\nlog('Init log');\n\nexport default log;\n"
  },
  {
    "path": "packages/electron/src/main/autoUpdate.ts",
    "content": "import { dialog, BrowserWindow } from 'electron';\nimport { autoUpdater } from 'electron-updater';\nimport html2plaintext from 'html2plaintext';\n\nimport { getSettings, onChangeSettings, SettingsCallback } from 'src/main/nvim/settings';\n\nimport store from 'src/main/lib/store';\n\nlet interval = 0;\nlet updaterIntervalId: NodeJS.Timeout;\n\nconst LAST_CHECKED = 'autoUpdate.lastCheckedForUpdate';\n\nconst MINUTE = 60 * 1000;\n\nconst needToCheck = () => {\n  if (interval === 0) {\n    return false;\n  }\n  const lastChecked = store.get(LAST_CHECKED);\n  if (!lastChecked) {\n    return true;\n  }\n  return Date.now() - lastChecked > interval * MINUTE;\n};\n\nconst updater = () => {\n  if (needToCheck()) {\n    store.set(LAST_CHECKED, Date.now());\n    autoUpdater.checkForUpdates();\n  }\n};\n\nconst startUpdater = () => {\n  if (!updaterIntervalId) {\n    updaterIntervalId = setInterval(updater, MINUTE);\n  }\n};\n\nconst updateInterval = (newInterval: string) => {\n  if (interval !== parseInt(newInterval, 10)) {\n    interval = parseInt(newInterval, 10);\n    startUpdater();\n  }\n};\n\nconst handleChangeSettings: SettingsCallback = (_newSettings, allSettings) => {\n  const { autoupdateinterval } = allSettings;\n  if (autoupdateinterval !== undefined) {\n    updateInterval(autoupdateinterval);\n  }\n};\n\nconst handleUpdateAvailable = ({\n  version,\n  releaseNotes,\n}: {\n  version: string;\n  releaseNotes: string;\n}) => {\n  const response = dialog.showMessageBoxSync({\n    type: 'question',\n    buttons: ['Update', 'Ignore'],\n    defaultId: 0,\n    message: `Version ${version} is available, do you want to install it now?`,\n    detail: html2plaintext(releaseNotes),\n    title: 'Update available',\n  });\n  if (response === 0) {\n    autoUpdater.downloadUpdate();\n  }\n};\n\nconst handleUpdateDownloaded = () => {\n  dialog.showMessageBox({\n    type: 'question',\n    buttons: ['OK'],\n    defaultId: 0,\n    message: `Update Downloaded`,\n    detail: 'Please restart app to install update.',\n    title: 'Update Downloaded',\n  });\n};\n\nconst initAutoUpdate = ({ win }: { win: BrowserWindow }): void => {\n  updateInterval(getSettings().autoupdateinterval);\n  onChangeSettings(win, handleChangeSettings);\n\n  autoUpdater.autoDownload = false;\n\n  autoUpdater.on('update-available', handleUpdateAvailable);\n  autoUpdater.on('update-downloaded', handleUpdateDownloaded);\n};\n\nexport default initAutoUpdate;\n"
  },
  {
    "path": "packages/electron/src/main/checkNeovim.ts",
    "content": "import { app, dialog, shell } from 'electron';\nimport semver from 'semver';\n\nimport { nvimVersion } from '@vvim/nvim';\n\nconst REQUIRED_VERSION = '0.4.0';\n\nconst checkNeovim = (): void => {\n  const version = nvimVersion();\n  if (!version) {\n    const result = dialog.showMessageBoxSync({\n      message: 'Neovim is not installed',\n      detail: `VV requires Neovim. You can install it via Homebrew:\nbrew install neovim\n\nOr you can find Neovim installation instructions here:\nhttps://github.com/neovim/neovim/wiki/Installing-Neovim\n  `,\n      defaultId: 0,\n      buttons: ['Open Installation Instructions', 'Close'],\n    });\n    if (result === 0) {\n      shell.openExternal('https://github.com/neovim/neovim/wiki/Installing-Neovim');\n    }\n    app.exit();\n  } else if (semver.lt(version, REQUIRED_VERSION)) {\n    const result = dialog.showMessageBoxSync({\n      message: 'Neovim is outdated',\n      detail: `VV requires Neovim version ${REQUIRED_VERSION} and later.\nYou have ${version}.\n\nIf you installed Neovim via Homebrew, please run:\nbrew upgrade neovim\n\nOtherwise please check installation instructions here:\nhttps://github.com/neovim/neovim/wiki/Installing-Neovim\n  `,\n      defaultId: 0,\n      buttons: ['Open Installation Instructions', 'Close'],\n    });\n    if (result === 0) {\n      shell.openExternal('https://github.com/neovim/neovim/wiki/Installing-Neovim');\n    }\n    app.exit();\n  }\n};\n\nexport default checkNeovim;\n"
  },
  {
    "path": "packages/electron/src/main/index.ts",
    "content": "import { app, BrowserWindow, dialog } from 'electron';\nimport { statSync, existsSync } from 'fs';\nimport { join, resolve } from 'path';\n\nimport isDev from 'src/lib/isDev';\n\nimport menu from 'src/main/menu';\nimport installCli from 'src/main/installCli';\nimport checkNeovim from 'src/main/checkNeovim';\n\nimport { setShouldQuit } from 'src/main/nvim/features/quit';\nimport { getSettings } from 'src/main/nvim/settings';\nimport { getNvimByWindow } from 'src/main/nvim/nvimByWindow';\n\nimport initAutoUpdate from 'src/main/autoUpdate';\n\nimport initNvim from 'src/main/nvim/nvim';\nimport { parseArgs, joinArgs, filterArgs, cliArgs, argValue } from 'src/main/lib/args';\n\nimport IpcTransport from 'src/main/transport/ipc';\n\nlet currentWindow: BrowserWindow | undefined | null;\n\nconst windows: BrowserWindow[] = [];\n\n/** Empty windows created in advance to make windows creation faster */\nconst emptyWindows: BrowserWindow[] = [];\n\napp.commandLine.appendSwitch('force_high_performance_gpu');\n\nconst openDeveloperTools = (win: BrowserWindow) => {\n  win.webContents.openDevTools({ mode: 'detach' });\n  win.webContents.on('devtools-opened', () => {\n    win.webContents.focus();\n  });\n};\n\nconst handleAllClosed = () => {\n  const { quitoncloselastwindow } = getSettings();\n  if (quitoncloselastwindow || process.platform !== 'darwin') {\n    app.quit();\n  }\n};\n\nconst createEmptyWindow = (isDebug = false) => {\n  const options = {\n    width: 800,\n    height: 600,\n    show: isDebug,\n    fullscreenable: false,\n    // frame: false,\n    // roundedCorners: false,\n    webPreferences: {\n      preload: join(app.getAppPath(), isDev('./', '../'), 'src/main/preload.js'),\n    },\n  };\n  let win = new BrowserWindow(options);\n  // @ts-expect-error TODO\n  win.zoomLevel = 0;\n\n  win.on('closed', async () => {\n    if (currentWindow === win) currentWindow = null;\n\n    const i = windows.indexOf(win);\n    if (i !== -1) windows.splice(i, 1);\n    // @ts-expect-error TODO\n    win = null;\n\n    if (windows.length === 0) handleAllClosed();\n  });\n\n  win.on('focus', () => {\n    currentWindow = win;\n  });\n\n  win.loadURL(\n    process.env.DEV_SERVER ? 'http://localhost:3000' : `file://${join(__dirname, './index.html')}`,\n  );\n\n  return win;\n};\n\nconst getEmptyWindow = (isDebug = false): BrowserWindow => {\n  if (emptyWindows.length > 0) {\n    return emptyWindows.pop() as BrowserWindow;\n  }\n  return createEmptyWindow(isDebug);\n};\n\nconst createWindow = async (originalArgs: string[] = [], newCwd?: string) => {\n  const settings = getSettings();\n  const cwd = newCwd || process.cwd();\n\n  const isDebug = originalArgs.includes('--debug') || originalArgs.includes('--inspect');\n  // TODO: Use yargs maybe.\n  const { args, files } = parseArgs(filterArgs(originalArgs));\n  let unopenedFiles = files;\n\n  let { openInProject } = settings;\n  let openInProjectArg = argValue(originalArgs, '--open-in-project');\n  if (openInProjectArg === '0' || openInProjectArg === 'false') {\n    openInProjectArg = undefined;\n    openInProject = 0;\n  }\n  if (openInProjectArg === 'true') {\n    openInProjectArg = '1';\n  }\n\n  // TODO: Rafactor this somewhere to a separate file or function.\n  if (openInProject || openInProjectArg) {\n    await Promise.all(\n      windows.map(async (win) => {\n        const nvim = getNvimByWindow(win);\n        if (nvim) {\n          // @ts-expect-error TODO: don't add custom props to win\n          win.cwd = await nvim.callFunction<string>('VVprojectRoot', []); // eslint-disable-line\n        }\n        return Promise.resolve();\n      }),\n    );\n    unopenedFiles = files.reduce<string[]>((result, fileName) => {\n      const resolvedFileName = resolve(cwd, fileName);\n      const openInWindow = windows.find(\n        // @ts-expect-error TODO: don't add custom props to win\n        (w) => resolvedFileName.startsWith(w.cwd) && !w.isMinimized(),\n      );\n      if (openInWindow) {\n        const nvim = getNvimByWindow(openInWindow);\n        if (nvim) {\n          // @ts-expect-error TODO: don't add custom props to win\n          const relativeFileName = resolvedFileName.substring(openInWindow.cwd.length + 1);\n          nvim.callFunction(\n            'VVopenInProject',\n            openInProjectArg ? [relativeFileName, openInProjectArg] : [relativeFileName],\n          );\n          openInWindow.focus();\n          app.focus({ steal: true });\n          return result;\n        }\n      }\n      return [...result, fileName];\n    }, []);\n  }\n\n  if (files.length === 0 || unopenedFiles.length > 0) {\n    const win = getEmptyWindow(isDebug);\n\n    // @ts-expect-error TODO: don't add custom props to win\n    win.cwd = cwd;\n\n    if (currentWindow && !currentWindow.isFullScreen() && !currentWindow.isSimpleFullScreen()) {\n      const [x, y] = currentWindow.getPosition();\n      const [width, height] = currentWindow.getSize();\n      win.setBounds({ x: x + 20, y: y + 20, width, height }, false);\n    }\n\n    const transport = new IpcTransport(win);\n\n    initNvim({\n      args: joinArgs({ args, files: unopenedFiles }),\n      cwd,\n      win,\n      transport,\n    });\n\n    const initRenderer = () => transport.send('initRenderer', settings);\n\n    if (win.webContents.isLoading()) {\n      win.webContents.on('did-finish-load', initRenderer);\n    } else {\n      initRenderer();\n    }\n\n    win.focus();\n    windows.push(win);\n\n    if (isDebug) {\n      openDeveloperTools(win);\n    } else {\n      setTimeout(() => emptyWindows.push(createEmptyWindow()), 1000);\n    }\n\n    initAutoUpdate({ win });\n  }\n};\n\nconst openFileOrDir = (fileName: string) => {\n  app.addRecentDocument(fileName);\n  if (existsSync(fileName) && statSync(fileName).isDirectory()) {\n    createWindow([fileName], fileName);\n  } else {\n    createWindow([fileName]);\n  }\n};\n\nconst openFile = () => {\n  const fileNames = dialog.showOpenDialogSync({\n    properties: ['openFile', 'openDirectory', 'createDirectory', 'multiSelections'],\n  });\n  if (fileNames) {\n    fileNames.forEach(openFileOrDir);\n  }\n};\n\nconst gotTheLock = isDev() || app.requestSingleInstanceLock();\n\nif (!gotTheLock) {\n  app.quit();\n} else {\n  let fileToOpen: string | undefined | null;\n  app.on('will-finish-launching', () => {\n    app.on('open-file', (_e, file) => {\n      fileToOpen = file;\n    });\n  });\n\n  app.on('ready', () => {\n    checkNeovim();\n    if (fileToOpen) {\n      openFileOrDir(fileToOpen);\n      fileToOpen = null;\n    } else {\n      createWindow(cliArgs());\n    }\n    menu({\n      createWindow,\n      openFile,\n      installCli: installCli(join(app.getAppPath(), '../bin/vv')),\n    });\n    app.on('open-file', (_e, file) => openFileOrDir(file));\n\n    app.focus();\n  });\n\n  app.on('second-instance', (_e, args, cwd) => {\n    createWindow(cliArgs(args), cwd);\n  });\n\n  app.on('before-quit', (e) => {\n    setShouldQuit(true);\n    const visibleWindows = windows.filter((w) => w.isVisible());\n    if (visibleWindows.length > 0) {\n      e.preventDefault();\n      (currentWindow || visibleWindows[0]).close();\n    }\n  });\n\n  app.on('window-all-closed', handleAllClosed);\n\n  app.on('activate', (_e, hasVisibleWindows) => {\n    if (!hasVisibleWindows) {\n      createWindow();\n    }\n  });\n}\n"
  },
  {
    "path": "packages/electron/src/main/installCli.ts",
    "content": "import { dialog } from 'electron';\nimport { execSync } from 'child_process';\n\nimport which from 'src/main/lib/which';\n\nconst showInstallCliDialog = () =>\n  dialog.showMessageBoxSync({\n    message: 'Command line launcher',\n    detail: `With command line launcher you can run VV from terminal:\n$ vv [filename]\n\nDo you wish to install it? It will be placed\nto /usr/local/bin.\n`,\n    cancelId: 1,\n    defaultId: 0,\n    buttons: ['Install', 'Cancel'],\n  });\n\nconst showCliInstalledDialog = (message: string, path: string) =>\n  dialog.showMessageBox({\n    message,\n    detail: `Command line launcher installed at ${path}. You can run VV from terminal by typing:\n$ vv [filename]\n`,\n    defaultId: 0,\n    buttons: ['Ok'],\n  });\n\nconst showErrorDialog = (error: Error) => {\n  dialog.showMessageBox({\n    message: 'Error',\n    detail: error.message,\n    defaultId: 0,\n    buttons: ['Ok'],\n  });\n};\n\nconst installCli = (binPath: string) => (): void => {\n  let path = which('vv');\n  if (path && path.indexOf('VV.app/Contents/MacOS/vv') === -1) {\n    path = path.replace('\\n', '');\n    showCliInstalledDialog('Command Line Launcher', path);\n  } else {\n    const response = showInstallCliDialog();\n    if (response === 0) {\n      try {\n        execSync(`ln -sf ${binPath} /usr/local/bin/`);\n      } catch (error) {\n        showErrorDialog(error);\n        return;\n      }\n      showCliInstalledDialog('Done', '/usr/local/bin/vv');\n    }\n  }\n};\n\nexport default installCli;\n"
  },
  {
    "path": "packages/electron/src/main/lib/__tests__/args.test.ts",
    "content": "import { parseArgs, joinArgs, filterArgs, argValue } from 'src/main/lib/args';\n\ndescribe('parseArgs', () => {\n  test('return empty array if input is empty', () => {\n    expect(parseArgs([])).toEqual({ args: [], files: [] });\n    expect(parseArgs()).toEqual({ args: [], files: [] });\n  });\n\n  test('returns everything if there ar no params', () => {\n    expect(parseArgs(['file1', 'file2'])).toEqual({\n      args: [],\n      files: ['file1', 'file2'],\n    });\n  });\n\n  test('returns everything after --', () => {\n    expect(parseArgs(['before1', 'before2', '--', 'after1', 'after2'])).toEqual({\n      args: ['before1', 'before2'],\n      files: ['after1', 'after2'],\n    });\n  });\n\n  test('skip params started with - or +', () => {\n    ['-param1', '--param2', '+cmd1'].forEach((param) => {\n      expect(parseArgs([param, 'file1', 'file2'])).toEqual({\n        args: [param],\n        files: ['file1', 'file2'],\n      });\n    });\n  });\n\n  test('skip params with argument', () => {\n    ['--cmd', '-c', '-i', '-r', '-s', '-S', '-u', '--listen', '--startuptime'].forEach((param) => {\n      expect(parseArgs([param, 'arg', 'file1', 'file2'])).toEqual({\n        args: [param, 'arg'],\n        files: ['file1', 'file2'],\n      });\n    });\n  });\n\n  test('does not mutate arguments', () => {\n    const args = ['arg1', 'arg2'];\n    parseArgs(args);\n    expect(args).toEqual(['arg1', 'arg2']);\n  });\n});\n\ndescribe('joinArgs', () => {\n  test('joins args and files arrays and put -- between them', () => {\n    expect(joinArgs({ args: ['arg1', 'arg2'], files: ['file1', 'file2'] })).toEqual([\n      'arg1',\n      'arg2',\n      '--',\n      'file1',\n      'file2',\n    ]);\n  });\n\n  test(\"don't add -- if args is empty\", () => {\n    expect(joinArgs({ args: [], files: ['file1', 'file2'] })).toEqual(['file1', 'file2']);\n  });\n\n  test(\"don't add -- if files is empty\", () => {\n    expect(joinArgs({ args: ['arg1'], files: [] })).toEqual(['arg1']);\n  });\n});\n\ndescribe('filterArgs', () => {\n  test('returns all args if none of them are VV-specific', () => {\n    expect(filterArgs(['arg1', 'arg2'])).toEqual(['arg1', 'arg2']);\n  });\n\n  test('filters out --inspect', () => {\n    expect(filterArgs(['arg1', '--inspect', 'arg2'])).toEqual(['arg1', 'arg2']);\n    expect(filterArgs(['--inspect', 'arg1', 'arg2'])).toEqual(['arg1', 'arg2']);\n    expect(filterArgs(['arg1', 'arg2', '--inspect'])).toEqual(['arg1', 'arg2']);\n    expect(filterArgs(['--inspect'])).toEqual([]);\n  });\n\n  test('filters out --open-in-project with value', () => {\n    expect(filterArgs(['arg1', '--open-in-project', 'value', 'arg2'])).toEqual(['arg1', 'arg2']);\n    expect(filterArgs(['--open-in-project', 'value', 'arg1', 'arg2'])).toEqual(['arg1', 'arg2']);\n    expect(filterArgs(['arg1', 'arg2', '--open-in-project'])).toEqual(['arg1', 'arg2']);\n    expect(filterArgs(['--open-in-project'])).toEqual([]);\n    expect(filterArgs(['--open-in-project', 'value'])).toEqual([]);\n  });\n\n  test('filters out chromium flags', () => {\n    expect(\n      filterArgs(['arg1', '--allow-file-access-from-files', '--enable-avfoundation', 'arg2']),\n    ).toEqual(['arg1', 'arg2']);\n  });\n});\n\ndescribe('argValue', () => {\n  test('returns true if argument is present', () => {\n    expect(argValue(['--arg1', '--arg2'], '--arg1')).toBe(true);\n    expect(argValue(['--arg1', '--arg2', 'file1'], '--arg1')).toBe(true);\n    expect(argValue(['--arg1', '--arg2', '--', 'file1'], '--arg1')).toBe(true);\n  });\n\n  test('returns undefined if argument is not present', () => {\n    expect(argValue(['--arg1', '--arg2'], '--arg3')).toBeUndefined();\n    expect(argValue(['--arg1', '--', '--arg2'], '--arg2')).toBeUndefined();\n  });\n\n  test('returns value for argument with param', () => {\n    expect(argValue(['--arg1', '--cmd', 'cmdValue', '--arg2'], '--cmd')).toBe('cmdValue');\n    expect(argValue(['--cmd', 'cmdValue'], '--cmd')).toBe('cmdValue');\n    expect(argValue(['--cmd', 'cmdValue', 'file1'], '--cmd')).toBe('cmdValue');\n    expect(argValue(['--cmd', 'cmdValue', '--', '--cmd', 'invalid'], '--cmd')).toBe('cmdValue');\n  });\n\n  test('returns undefined invalid argument with param', () => {\n    expect(argValue(['--arg1', '--cmd'], '--cmd')).toBeUndefined();\n    expect(argValue(['--', '--cmd', 'cmdValue'], '--cmd')).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "packages/electron/src/main/lib/args.ts",
    "content": "// TODO: Use commander or yargs\n\nimport isDev from 'src/lib/isDev';\n\nconst ARGS_WITH_PARAM = [\n  '--cmd',\n  '-c',\n  '-i',\n  '-r',\n  '-s',\n  '-S',\n  '-u',\n  '--listen',\n  '--startuptime',\n  '--open-in-project',\n];\n\n/**\n * Args specific to VV.\n */\nconst VV_ARGS = ['--debug', '--inspect', '--open-in-project'];\n\n/**\n * Chromium args added by electron.\n * TODO: find more reliable way to filter them.\n */\nconst CHROMIUM_ARGS = [\n  '--allow-file-access-from-files',\n  '--enable-avfoundation',\n  '--force_high_performance_gpu',\n];\n\n/**\n * Parse CLI args and return the list of files and arguments.\n */\nexport const parseArgs = (\n  originalArgs: string[] = [],\n): {\n  args: string[];\n  files: string[];\n} => {\n  const args = [...originalArgs];\n\n  const filesSeparator = args.indexOf('--');\n  if (filesSeparator !== -1) {\n    return {\n      args: args.slice(0, filesSeparator),\n      files: args.slice(filesSeparator + 1),\n    };\n  }\n\n  const files: string[] = [];\n  for (let i = args.length - 1; i >= 0; i -= 1) {\n    if (['-', '+'].includes(args[i][0]) || (args[i - 1] && ARGS_WITH_PARAM.includes(args[i - 1]))) {\n      break;\n    }\n    files.unshift(args.pop() as string);\n  }\n  return { args, files };\n};\n\n/**\n * Join previously parsed args.\n */\nexport const joinArgs = ({ args, files }: { args: string[]; files: string[] }): string[] => {\n  if (args.length === 0) {\n    return files;\n  }\n  if (files.length === 0) {\n    return args;\n  }\n  return [...args, '--', ...files];\n};\n\n/**\n * Argument value.\n * Returns true for argument that does not require argument if it is present.\n * Returns argument param for argumenst with params (for example --cmd).\n * Undefined if param is not present.\n */\nexport const argValue = (originalArgs: string[], argName: string): string | true | undefined => {\n  const { args } = parseArgs(originalArgs);\n  const index = args.indexOf(argName);\n  if (index === -1) {\n    return undefined;\n  }\n  if (ARGS_WITH_PARAM.includes(argName)) {\n    return args[index + 1];\n  }\n  return true;\n};\n\n/**\n * Remove VV specific arguments not supported by nvim\n */\nexport const filterArgs = (args: string[]): string[] =>\n  args.reduce<string[]>((result, a, i) => {\n    if (VV_ARGS.includes(a) || CHROMIUM_ARGS.includes(a)) {\n      return result;\n    }\n    if (args[i - 1] && VV_ARGS.includes(args[i - 1]) && ARGS_WITH_PARAM.includes(args[i - 1])) {\n      return result;\n    }\n    return [...result, a];\n  }, []);\n\n/**\n * Get CLI arguments\n */\nexport const cliArgs = (args?: string[]): string[] | undefined =>\n  (args || process.argv).slice(isDev(2, 1));\n"
  },
  {
    "path": "packages/electron/src/main/lib/store.ts",
    "content": "import Store from 'electron-store';\n\ntype BooleanSetting = 0 | 1;\n\nexport type Settings = {\n  fullscreen: BooleanSetting;\n  simplefullscreen: BooleanSetting;\n  bold: BooleanSetting;\n  italic: BooleanSetting;\n  underline: BooleanSetting;\n  undercurl: BooleanSetting;\n  strikethrough: BooleanSetting;\n  fontfamily: string;\n  fontsize: string; // TODO: number\n  lineheight: string; // TODO: number\n  letterspacing: string; // TODO: number\n  reloadchanged: BooleanSetting;\n  quitoncloselastwindow: BooleanSetting;\n  autoupdateinterval: string; // TODO: number\n  openInProject: BooleanSetting;\n};\n\ntype StoreData = {\n  lastSettings: Settings;\n  autoUpdate: {\n    lastCheckedForUpdate: number;\n  };\n  'autoUpdate.lastCheckedForUpdate': number;\n};\n\nconst store = new Store<StoreData>();\n\nexport default store;\n"
  },
  {
    "path": "packages/electron/src/main/lib/which.ts",
    "content": "import { execSync } from 'child_process';\n\nimport { shellEnv } from '@vvim/nvim';\n\n/**\n * Checks if command exists in shell.\n */\nconst which = (command: string): string | null => {\n  let result: string | null | undefined;\n  try {\n    result = execSync(`which ${command}`, {\n      encoding: 'utf-8',\n      env: shellEnv(),\n    });\n  } catch (e) {\n    result = null;\n  }\n  return result;\n};\n\nexport default which;\n"
  },
  {
    "path": "packages/electron/src/main/menu.ts",
    "content": "import { Menu, MenuItemConstructorOptions } from 'electron';\n\n// import { handleCloseWindow } from 'src/main/nvim/features/closeWindow';\n\nimport { copyMenuItem, pasteMenuItem, selectAllMenuItem } from 'src/main/nvim/features/copyPaste';\nimport { zoomInMenuItem, zoomOutMenuItem, actualSizeMenuItem } from 'src/main/nvim/features/zoom';\nimport { closeWindowMenuItem } from 'src/main/nvim/features/closeWindow';\nimport { toggleFullScreenMenuItem } from 'src/main/nvim/features/windowSize';\n\nlet menu: Menu;\n\nconst createMenu = ({\n  createWindow,\n  openFile,\n  installCli,\n}: {\n  createWindow: () => void;\n  openFile: MenuItemConstructorOptions['click'];\n  installCli: MenuItemConstructorOptions['click'];\n}): void => {\n  const menuTemplate: MenuItemConstructorOptions[] = [\n    {\n      label: 'VV',\n      submenu: [\n        { role: 'about' },\n        {\n          label: 'Command Line Launcher...',\n          click: installCli,\n        },\n        { type: 'separator' },\n        { role: 'services', submenu: [] },\n        { type: 'separator' },\n        { role: 'hide' },\n        { role: 'hideOthers' },\n        { role: 'unhide' },\n        { type: 'separator' },\n        { role: 'quit' },\n      ],\n    },\n    {\n      label: 'File',\n      submenu: [\n        {\n          label: 'New Window',\n          accelerator: 'CmdOrCtrl+N',\n          click: () => createWindow(),\n        },\n        {\n          label: 'Open...',\n          accelerator: 'CmdOrCtrl+O',\n          click: openFile,\n        },\n        {\n          role: 'recentDocuments',\n          submenu: [\n            {\n              role: 'clearRecentDocuments',\n            },\n          ],\n        },\n        { type: 'separator' },\n        {\n          label: 'Close',\n          accelerator: 'CmdOrCtrl+W',\n          click: closeWindowMenuItem,\n        },\n      ],\n    },\n    {\n      label: 'Edit',\n      submenu: [\n        {\n          label: 'Copy',\n          accelerator: 'CmdOrCtrl+C',\n          click: copyMenuItem,\n        },\n        {\n          label: 'Paste',\n          accelerator: 'CmdOrCtrl+V',\n          click: pasteMenuItem,\n        },\n        {\n          label: 'Select All',\n          accelerator: 'CmdOrCtrl+A',\n          click: selectAllMenuItem,\n        },\n      ],\n    },\n    {\n      label: 'View',\n      submenu: [\n        {\n          label: 'Toggle Full Screen',\n          accelerator: 'Cmd+Ctrl+F',\n          click: toggleFullScreenMenuItem,\n        },\n        {\n          label: 'Actual Size',\n          id: 'actualSize',\n          accelerator: 'CmdOrCtrl+0',\n          click: actualSizeMenuItem,\n          enabled: false,\n        },\n        {\n          label: 'Zoom In',\n          accelerator: 'CmdOrCtrl+=',\n          click: zoomInMenuItem,\n        },\n        {\n          label: 'Zoom Out',\n          accelerator: 'CmdOrCtrl+-',\n          click: zoomOutMenuItem,\n        },\n        { type: 'separator' },\n        {\n          label: 'Developer',\n          submenu: [{ role: 'toggleDevTools' }],\n        },\n      ],\n    },\n    {\n      role: 'window',\n      submenu: [{ role: 'minimize' }, { role: 'zoom' }, { type: 'separator' }, { role: 'front' }],\n    },\n  ];\n  menu = Menu.buildFromTemplate(menuTemplate);\n  Menu.setApplicationMenu(menu);\n};\n\nexport default createMenu;\n"
  },
  {
    "path": "packages/electron/src/main/nvim/__tests__/nvim.test.ts",
    "content": "// eslint-disable-next-line\nimport initNvim from 'src/main/nvim/nvim';\n\ndescribe('initNvim', () => {\n  test.todo('TODO');\n});\n"
  },
  {
    "path": "packages/electron/src/main/nvim/features/__tests__/backrdoundColor.test.ts",
    "content": "import backgroundColor from 'src/main/nvim/features/backrdoundColor';\n\nimport type { Transport } from '@vvim/nvim';\nimport type { BrowserWindow } from 'electron';\n\ndescribe('backrdoundColor', () => {\n  const setBackgroundColor = jest.fn();\n  let emitSetBackgroundColor: (color: string) => void;\n\n  const transport = ({\n    on: (event: string, callback: (...args: any[]) => void) => {\n      if (event === 'set-background-color') {\n        emitSetBackgroundColor = callback;\n      }\n    },\n  } as unknown) as Transport;\n\n  const win = ({\n    setBackgroundColor,\n  } as unknown) as BrowserWindow;\n\n  test('set window background color on `set-backround-color` event', () => {\n    backgroundColor({ transport, win });\n    emitSetBackgroundColor('red');\n    expect(setBackgroundColor).toHaveBeenCalledWith('red');\n  });\n});\n"
  },
  {
    "path": "packages/electron/src/main/nvim/features/__tests__/windowSize.test.ts",
    "content": "import initWindowSize from 'src/main/nvim/features/windowSize';\n\nimport { EventEmitter } from 'events';\nimport type { Transport } from '@vvim/nvim';\nimport type { BrowserWindow } from 'electron';\n\ndescribe('initWindowSize', () => {\n  describe('set-screen-width', () => {\n    const setContentSize = jest.fn();\n    const getContentSize = jest.fn();\n    const send = jest.fn();\n\n    // TODO: Come up with the better way to mock BrowserWindow\n    const win = ({\n      setContentSize,\n      getContentSize,\n      getBounds: () => {\n        /* empty */\n      },\n      setBounds: () => {\n        /* empty */\n      },\n      isFullScreen: () => false,\n      setSimpleFullScreen: () => {\n        /* empty */\n      },\n      webContents: {\n        focus: () => {\n          /* empty */\n        },\n      },\n    } as unknown) as BrowserWindow;\n\n    let transport: Transport;\n\n    beforeEach(() => {\n      jest.clearAllMocks();\n      getContentSize.mockReturnValue([100, 200]);\n      transport = Object.assign(new EventEmitter(), {\n        send,\n      });\n    });\n\n    test('set window size on set-screen-width', () => {\n      initWindowSize({ transport, win });\n      transport.emit('set-screen-width', 150);\n      expect(setContentSize).toHaveBeenCalledWith(150, 200);\n    });\n\n    test('set window size on set-screen-height', () => {\n      initWindowSize({ transport, win });\n      getContentSize.mockReturnValueOnce([100, 200]).mockReturnValueOnce([100, 250]);\n      transport.emit('set-screen-height', 250);\n      expect(setContentSize).toHaveBeenCalledWith(100, 250);\n      expect(send).not.toHaveBeenCalledWith('force-resize');\n    });\n\n    test('send force-resize if window height is the same after resize', () => {\n      initWindowSize({ transport, win });\n      getContentSize.mockReturnValueOnce([100, 200]).mockReturnValueOnce([100, 200]);\n      transport.emit('set-screen-height', 250);\n      expect(send).toHaveBeenCalledWith('force-resize');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/electron/src/main/nvim/features/backrdoundColor.ts",
    "content": "import type { BrowserWindow } from 'electron';\nimport type { Transport } from '@vvim/nvim';\n\n/**\n * Change Electron window background color depending when renderer ask for it.\n */\nconst backroundColor = ({ transport, win }: { transport: Transport; win: BrowserWindow }): void => {\n  transport.on('set-background-color', (bgColor: string) => {\n    win.setBackgroundColor(bgColor);\n  });\n};\n\nexport default backroundColor;\n"
  },
  {
    "path": "packages/electron/src/main/nvim/features/closeWindow.ts",
    "content": "import { MenuItemConstructorOptions } from 'electron';\n\nimport { getNvimByWindow } from 'src/main/nvim/nvimByWindow';\n\nexport const closeWindowMenuItem: MenuItemConstructorOptions['click'] = async (_item, win) => {\n  if (win) {\n    const nvim = getNvimByWindow(win);\n    if (nvim) {\n      const isNotLastWindow = await nvim.eval<boolean>('tabpagenr(\"$\") > 1 || winnr(\"$\") > 1');\n      if (isNotLastWindow) {\n        nvim.command(`q`);\n      } else {\n        win.close();\n      }\n    }\n  }\n};\n"
  },
  {
    "path": "packages/electron/src/main/nvim/features/copyPaste.ts",
    "content": "import { clipboard, MenuItemConstructorOptions } from 'electron';\n\nimport { getNvimByWindow } from 'src/main/nvim/nvimByWindow';\n\nexport const pasteMenuItem: MenuItemConstructorOptions['click'] = async (_item, win) => {\n  const nvim = getNvimByWindow(win);\n  if (nvim) {\n    const clipboardText = clipboard.readText();\n    nvim.paste(clipboardText, true, -1);\n  }\n};\n\nexport const copyMenuItem: MenuItemConstructorOptions['click'] = async (_item, win) => {\n  const nvim = getNvimByWindow(win);\n  if (nvim) {\n    const mode = await nvim.getShortMode();\n    if (mode === 'v' || mode === 'V') {\n      nvim.input('\"*y');\n    }\n  }\n};\n\nexport const selectAllMenuItem: MenuItemConstructorOptions['click'] = (_item, win) => {\n  const nvim = getNvimByWindow(win);\n  if (nvim) {\n    nvim.input('ggVG');\n  }\n};\n"
  },
  {
    "path": "packages/electron/src/main/nvim/features/focusAutocmd.ts",
    "content": "import { BrowserWindow } from 'electron';\n\nimport type Nvim from '@vvim/nvim';\n\n/**\n * Emit FocusGained or FocusLost autocmd when app window get or loose focus.\n * https://neovim.io/doc/user/autocmd.html#FocusGained\n */\nconst focusAutocmd = ({ win, nvim }: { win: BrowserWindow; nvim: Nvim }): void => {\n  win.on('focus', () => {\n    nvim.command('doautocmd FocusGained');\n  });\n\n  win.on('blur', () => {\n    nvim.command('doautocmd FocusLost');\n  });\n};\n\nexport default focusAutocmd;\n"
  },
  {
    "path": "packages/electron/src/main/nvim/features/quit.ts",
    "content": "/**\n * Handle close window routine\n */\n\nimport { dialog, app, BrowserWindow } from 'electron';\nimport { deleteNvimByWindow } from 'src/main/nvim/nvimByWindow';\n\nimport type Nvim from '@vvim/nvim';\n\n/**\n * If we want to quit app after closing window, shouldQuit is true.\n * This function is used in 'before-quit' event to switch to close app mode.\n */\nlet shouldQuit = false;\n\nexport const setShouldQuit = (newShouldQuit: boolean): void => {\n  shouldQuit = newShouldQuit;\n};\n\n/**\n * Show Save All dialog if there are any unsaved buffers.\n * Cancel quit on cancel.\n */\nconst showCloseDialog = async ({ nvim, win }: { nvim: Nvim; win: BrowserWindow }) => {\n  const unsavedBuffers = await nvim.callFunction<Array<{ name: string }>>('VVunsavedBuffers', []);\n  if (unsavedBuffers.length === 0) {\n    nvim.command('qa');\n  } else {\n    win.focus();\n    const { response } = await dialog.showMessageBox(win, {\n      message: `You have ${unsavedBuffers.length} unsaved buffers. Do you want to save them?`,\n      detail: `${unsavedBuffers.map((b) => b.name).join('\\n')}\\n`,\n      cancelId: 2,\n      defaultId: 0,\n      buttons: ['Save All', 'Discard All', 'Cancel'],\n    });\n    if (response === 0) {\n      await nvim.command('xa'); // Save All\n    } else if (response === 1) {\n      await nvim.command('qa!'); // Discard All\n    }\n    setShouldQuit(false);\n  }\n};\n\nconst initQuit = ({ win, nvim }: { nvim: Nvim; win: BrowserWindow }): void => {\n  let isConnected = true;\n\n  // Close window if nvim process is closed.\n  nvim.on('close', () => {\n    // Disable fullscreen before close, otherwise it it will keep menu bar hidden after window\n    // is closed.\n    win.hide();\n    win.setSimpleFullScreen(false);\n\n    isConnected = false;\n    deleteNvimByWindow(win);\n    win.close();\n  });\n\n  // If nvim process is not closed, show Save All dialog.\n  win.on('close', (e: Electron.Event) => {\n    if (isConnected) {\n      e.preventDefault();\n      showCloseDialog({ win, nvim });\n    }\n  });\n\n  // After window is closed, continue quit app if this is a part of quit app routine\n  win.on('closed', () => {\n    if (shouldQuit) {\n      app.quit();\n    }\n  });\n};\n\nexport default initQuit;\n"
  },
  {
    "path": "packages/electron/src/main/nvim/features/reloadChanged.ts",
    "content": "import { dialog, BrowserWindow } from 'electron';\n\nimport type Nvim from '@vvim/nvim';\n\n/**\n * Show dialog when opened files are changed externally. For example, when you switch git branches. It\n * will prompt you to keep your changes or reload the file.\n * Controlled by `:VVset reloadchanged` setting, off by default.\n *\n * Deprecated because this could be done via plugins and it was buggy anyway.\n * If you miss this feature, you can use https://github.com/igorgladkoborodov/load-all.vim plugin, that\n * does the same.\n *\n * TODO: remove on the next major version\n *\n * @deprecated\n */\nconst initReloadChanged = ({ nvim, win }: { nvim: Nvim; win: BrowserWindow }): void => {\n  type Buffer = {\n    bufnr: string;\n    name: string;\n  };\n\n  let changedBuffers: Record<string, Buffer> = {};\n  let enabled = false;\n  let checking = false;\n\n  const showChangedDialog = async () => {\n    if (win.isFocused() && Object.keys(changedBuffers).length > 0) {\n      const message =\n        Object.keys(changedBuffers).length > 1\n          ? `${\n              Object.keys(changedBuffers).length\n            } opened files were changed outside. Do you want to reload them or keep your version?`\n          : 'File was changed outside. Do you want to reload it or keep your version?';\n\n      const buttons =\n        Object.keys(changedBuffers).length > 1 ? ['Reload All', 'Keep All'] : ['Reload', 'Keep'];\n\n      const { response } = await dialog.showMessageBox(win, {\n        message,\n        detail: `${Object.keys(changedBuffers)\n          .map((k) => changedBuffers[k].name)\n          .join('\\n')}\\n`,\n        cancelId: 1,\n        defaultId: 0,\n        buttons,\n      });\n      if (response === 0) {\n        nvim.callFunction(\n          'VVrefresh',\n          Object.keys(changedBuffers).map((k) => changedBuffers[k].bufnr),\n        );\n        changedBuffers = {};\n      }\n    }\n  };\n\n  const checktime = async () => {\n    if (!checking) {\n      checking = true;\n      await nvim.command('checktime');\n      checking = false;\n      showChangedDialog();\n    }\n  };\n\n  const enable = (newEnabled = true) => {\n    if (enabled !== !!newEnabled) {\n      enabled = !!newEnabled;\n      nvim.callFunction('VVenableReloadChanged', [enabled ? 1 : 0]);\n    }\n  };\n\n  nvim.on('vv:file_changed', ([buffer]: [Buffer]) => {\n    if (enabled) {\n      if (!changedBuffers[buffer.bufnr]) {\n        changedBuffers[buffer.bufnr] = buffer;\n      }\n      checktime();\n    }\n  });\n\n  nvim.on('vv:set', ([option, isEnabled]: [string, boolean]) => {\n    if (option === 'reloadchanged') {\n      enable(isEnabled);\n    }\n  });\n\n  win.on('focus', () => {\n    if (enabled) {\n      // The page will be blank on focus without timeout.\n      setTimeout(() => checktime(), 10);\n    }\n  });\n};\n\nexport default initReloadChanged;\n"
  },
  {
    "path": "packages/electron/src/main/nvim/features/windowSize.ts",
    "content": "import { screen, MenuItemConstructorOptions, BrowserWindow } from 'electron';\nimport type { Transport } from '@vvim/nvim';\n\nimport { getSettings, onChangeSettings, SettingsCallback } from 'src/main/nvim/settings';\nimport { getNvimByWindow } from 'src/main/nvim/nvimByWindow';\n\nexport const toggleFullScreenMenuItem: MenuItemConstructorOptions['click'] = (_item, win) => {\n  const nvim = getNvimByWindow(win);\n  if (nvim) {\n    nvim.command('VVset fullscreen!');\n  }\n};\n\nconst initWindowSize = ({ transport, win }: { transport: Transport; win: BrowserWindow }): void => {\n  const initialBounds = win.getBounds();\n  let bounds = win.getBounds();\n  let simpleFullScreen = false;\n  let fullScreen = false;\n  let isInitial = false;\n\n  const set = {\n    windowwidth: (w?: string) => {\n      if (w !== undefined) {\n        let width = parseInt(w, 10);\n        if (w.toString().indexOf('%') !== -1) {\n          width = Math.round((screen.getPrimaryDisplay().workAreaSize.width * width) / 100);\n        }\n        bounds.width = width;\n      }\n    },\n    windowheight: (h?: string) => {\n      if (h !== undefined) {\n        let height = parseInt(h, 10);\n        if (h.toString().indexOf('%') !== -1) {\n          height = Math.round((screen.getPrimaryDisplay().workAreaSize.height * height) / 100);\n        }\n        bounds.height = height;\n      }\n    },\n    windowleft: (l?: string) => {\n      if (l !== undefined) {\n        let left = parseInt(l, 10);\n        if (l.toString().indexOf('%') !== -1) {\n          const displayWidth = screen.getPrimaryDisplay().workAreaSize.width;\n          const winWidth = bounds.width;\n          left = Math.round(((displayWidth - winWidth) * left) / 100);\n        }\n        bounds.x = left;\n      }\n    },\n    windowtop: (t?: string) => {\n      if (t !== undefined) {\n        let top = parseInt(t, 10);\n        if (t.toString().indexOf('%') !== -1) {\n          const displayHeight = screen.getPrimaryDisplay().workAreaSize.height;\n          const winHeight = bounds.height;\n          top = Math.round(((displayHeight - winHeight) * top) / 100);\n        }\n        bounds.y = top;\n      }\n    },\n    fullscreen: (value: string) => {\n      fullScreen = !!parseInt(value, 10);\n      if (fullScreen) bounds = win.getBounds();\n      if (simpleFullScreen) {\n        win.setSimpleFullScreen(fullScreen);\n      } else {\n        win.setFullScreen(fullScreen);\n      }\n      win.webContents.focus();\n    },\n    simplefullscreen: (value: string) => {\n      simpleFullScreen = !!parseInt(value, 10);\n      if (simpleFullScreen && win.isFullScreen()) {\n        win.setFullScreen(false);\n        setTimeout(() => {\n          win.setSimpleFullScreen(true);\n          win.webContents.focus();\n        }, 1);\n      } else if (!simpleFullScreen && win.isSimpleFullScreen()) {\n        win.setSimpleFullScreen(false);\n        setTimeout(() => {\n          win.setFullScreen(true);\n          win.webContents.focus();\n        }, 1);\n      }\n      win.fullScreenable = !simpleFullScreen; // eslint-disable-line no-param-reassign\n    },\n  };\n\n  const updateWindowSize: SettingsCallback = (newSettings, allSettings) => {\n    let settings = newSettings;\n    if (!fullScreen) {\n      bounds = win.getBounds();\n    }\n    if (isInitial && allSettings.fullscreen === 0) {\n      settings = allSettings;\n      bounds = initialBounds;\n      isInitial = false;\n    }\n    // Order is iportant.\n    [\n      'simplefullscreen',\n      'fullscreen',\n      'windowwidth',\n      'windowheight',\n      'windowleft',\n      'windowtop',\n      // @ts-expect-error FIXME\n    ].forEach((key) => settings[key] !== undefined && set[key](settings[key]));\n    if (!fullScreen) {\n      win.setBounds(bounds);\n    }\n  };\n\n  updateWindowSize(getSettings(), getSettings());\n  isInitial = true;\n\n  onChangeSettings(win, updateWindowSize);\n\n  transport.on('set-screen-width', (width: number) => {\n    const height = win.getContentSize()[1];\n    win.setContentSize(width, height);\n  });\n\n  transport.on('set-screen-height', (height: number) => {\n    const [width, oldHeight] = win.getContentSize();\n    win.setContentSize(width, height);\n    // The new height is more than screen height.\n    if (win.getContentSize()[1] === oldHeight) {\n      transport.send('force-resize');\n    }\n  });\n};\n\nexport default initWindowSize;\n"
  },
  {
    "path": "packages/electron/src/main/nvim/features/windowTitle.ts",
    "content": "import fs from 'fs';\nimport { BrowserWindow } from 'electron';\n\nimport type { Nvim } from '@vvim/nvim';\n\nconst initWindowTitle = ({ nvim, win }: { win: BrowserWindow; nvim: Nvim }): void => {\n  nvim.on('redraw', (args) => {\n    args.forEach((arg) => {\n      if (arg[0] === 'set_title') {\n        win.setTitle(arg[1][0]);\n      }\n    });\n  });\n\n  nvim.on('vv:filename', ([filename]: [string]) => {\n    if (fs.existsSync(filename)) {\n      win.setRepresentedFilename(filename);\n    }\n  });\n\n  nvim.command('set title'); // Enable title\n  nvim.command('set titlestring&'); // Set default titlestring\n\n  // Send current file name to client on buffer enter\n  nvim.command(\n    'autocmd BufEnter * call rpcnotify(get(g:, \"vv_channel\", 1), \"vv:filename\", expand(\"%:p\"))',\n  );\n\n  // Filename don't fire on startup, doing it manually\n  nvim.command('call rpcnotify(get(g:, \"vv_channel\", 1), \"vv:filename\", expand(\"%:p\"))');\n};\n\nexport default initWindowTitle;\n"
  },
  {
    "path": "packages/electron/src/main/nvim/features/zoom.ts",
    "content": "import { app, MenuItemConstructorOptions, BrowserWindow } from 'electron';\nimport { getNvimByWindow } from 'src/main/nvim/nvimByWindow';\n\nconst nvimChangeZoom = (win: BrowserWindow, level: number) => {\n  const nvim = getNvimByWindow(win);\n  if (nvim) {\n    nvim.command(`VVset fontsize${level > 0 ? '+' : '-'}=${Math.abs(level)}`);\n  }\n};\n\nconst disableActualSizeItem = (win: BrowserWindow) => {\n  const actualSize = app.applicationMenu?.getMenuItemById('actualSize');\n  if (actualSize) {\n    // @ts-expect-error TODO: window custom params\n    actualSize.enabled = win.zoomLevel !== 0;\n  }\n};\n\nexport const zoomInMenuItem: MenuItemConstructorOptions['click'] = (_item, win) => {\n  if (win) {\n    // @ts-expect-error TODO: window custom params\n    win.zoomLevel += 1; // eslint-disable-line no-param-reassign\n    nvimChangeZoom(win, 1);\n    disableActualSizeItem(win);\n  }\n};\n\nexport const zoomOutMenuItem: MenuItemConstructorOptions['click'] = (_item, win) => {\n  if (win) {\n    // @ts-expect-error TODO: window custom params\n    win.zoomLevel -= 1; // eslint-disable-line no-param-reassign\n    nvimChangeZoom(win, -1);\n    disableActualSizeItem(win);\n  }\n};\n\nexport const actualSizeMenuItem: MenuItemConstructorOptions['click'] = (_item, win) => {\n  if (win) {\n    // @ts-expect-error TODO: window custom params\n    nvimChangeZoom(win, -win.zoomLevel);\n    // @ts-expect-error TODO: window custom params\n    win.zoomLevel = 0; // eslint-disable-line no-param-reassign\n    disableActualSizeItem(win);\n  }\n};\n\nconst initZoom = ({ win }: { win: BrowserWindow }): void => {\n  win.on('focus', () => {\n    disableActualSizeItem(win);\n  });\n};\n\nexport default initZoom;\n"
  },
  {
    "path": "packages/electron/src/main/nvim/nvim.ts",
    "content": "import { app } from 'electron';\n\nimport Nvim, { startNvimProcess, ProcNvimTransport, Transport } from '@vvim/nvim';\n\nimport { setNvimByWindow } from 'src/main/nvim/nvimByWindow';\n\nimport quit from 'src/main/nvim/features/quit';\nimport windowTitle from 'src/main/nvim/features/windowTitle';\nimport zoom from 'src/main/nvim/features/zoom';\nimport reloadChanged from 'src/main/nvim/features/reloadChanged';\nimport windowSize from 'src/main/nvim/features/windowSize';\nimport focusAutocmd from 'src/main/nvim/features/focusAutocmd';\nimport backgroundColor from 'src/main/nvim/features/backrdoundColor';\n\nimport initSettings from 'src/main/nvim/settings';\n\nimport type { BrowserWindow } from 'electron';\n\nconst initNvim = ({\n  args,\n  cwd,\n  win,\n  transport,\n}: {\n  args: string[];\n  cwd: string;\n  win: BrowserWindow;\n  transport: Transport;\n}): void => {\n  const proc = startNvimProcess({ args, cwd, appPath: app.getAppPath() });\n  const nvimTransport = new ProcNvimTransport(proc, transport);\n  const nvim = new Nvim(nvimTransport);\n\n  setNvimByWindow(win, nvim);\n\n  initSettings({ win, nvim, args, transport });\n  windowSize({ win, transport });\n  quit({ win, nvim });\n  windowTitle({ win, nvim });\n  zoom({ win });\n  reloadChanged({ win, nvim });\n  focusAutocmd({ win, nvim });\n  backgroundColor({ win, transport });\n\n  nvim\n    .request('nvim_get_api_info')\n    .then(([channelId]: [string]) => nvim.setVar('vv_channel', channelId));\n\n  nvim.on('vv:vim_enter', () => {\n    win.show();\n  });\n};\n\nexport default initNvim;\n"
  },
  {
    "path": "packages/electron/src/main/nvim/nvimByWindow.ts",
    "content": "import type { BrowserWindow } from 'electron';\nimport type Nvim from '@vvim/nvim';\n\nconst nvimByWindowId: Record<number, Nvim> = [];\n\nexport const getNvimByWindow = (winOrId?: number | BrowserWindow): Nvim | null => {\n  if (!winOrId) {\n    return null;\n  }\n  if (typeof winOrId === 'number') {\n    return nvimByWindowId[winOrId];\n  }\n  if (winOrId.webContents) {\n    return nvimByWindowId[winOrId.id];\n  }\n  return null;\n};\n\nexport const setNvimByWindow = (win: BrowserWindow, nvim: Nvim): void => {\n  if (win.webContents) {\n    nvimByWindowId[win.id] = nvim;\n  }\n};\n\nexport const deleteNvimByWindow = (win: BrowserWindow): void => {\n  if (win.webContents) {\n    delete nvimByWindowId[win.id];\n  }\n};\n"
  },
  {
    "path": "packages/electron/src/main/nvim/settings.ts",
    "content": "import debounce from 'lodash/debounce';\n\nimport type { BrowserWindow } from 'electron';\nimport type { Nvim, Transport } from '@vvim/nvim';\n\nimport store, { Settings } from 'src/main/lib/store';\n\nexport type SettingsCallback = (newSettings: Partial<Settings>, allSettings: Settings) => void;\n\nconst getDefaultSettings = (): Settings => ({\n  fullscreen: 0,\n  simplefullscreen: 1,\n  bold: 1,\n  italic: 1,\n  underline: 1,\n  undercurl: 1,\n  strikethrough: 1,\n  fontfamily: 'monospace',\n  fontsize: '12',\n  lineheight: '1.25',\n  letterspacing: '0',\n  reloadchanged: 0,\n  quitoncloselastwindow: 0,\n  autoupdateinterval: '1440', // One day, 60*24 minutes\n  openInProject: 0,\n});\n\nlet hasCustomConfig = false;\n\n/**\n * Get saved settings if we have them, default settings otherwise.\n * If you run app with -u flag, return default settings.\n */\nexport const getSettings = (): Settings => {\n  if (hasCustomConfig) {\n    return getDefaultSettings();\n  }\n  return {\n    ...getDefaultSettings(),\n    ...store.get('lastSettings'),\n  };\n};\n\nconst onChangeSettingsCallbacks: Record<string, SettingsCallback[]> = {};\n\nexport const onChangeSettings = (win: BrowserWindow, callback: SettingsCallback): void => {\n  if (!onChangeSettingsCallbacks[win.id]) {\n    onChangeSettingsCallbacks[win.id] = [];\n  }\n  onChangeSettingsCallbacks[win.id].push(callback);\n};\n\nconst initSettings = ({\n  win,\n  nvim,\n  args,\n  transport,\n}: {\n  win: BrowserWindow;\n  nvim: Nvim;\n  args: string[];\n  transport: Transport;\n}): void => {\n  hasCustomConfig = args.indexOf('-u') !== -1;\n  let initialSettings: Settings | null = getSettings();\n  let settings = getDefaultSettings();\n\n  let newSettings: Partial<Settings> = {};\n\n  const applyAllSettings = async () => {\n    settings = {\n      ...settings,\n      ...newSettings,\n    };\n\n    // If we have initial settings newSetting will be only those that different from initialSettings. We\n    // aleady applied initialSettings when we created a window.\n    // Also store default colors to settings to avoid blinks on init.\n    if (initialSettings && !hasCustomConfig) {\n      newSettings = Object.keys(settings).reduce<Partial<Settings>>((result, key) => {\n        // @ts-expect-error TODO FIXME\n        if (initialSettings[key] !== settings[key]) {\n          return {\n            ...result,\n            // @ts-expect-error TODO FIXME\n            [key]: settings[key],\n          };\n        }\n        return result;\n      }, {});\n      initialSettings = null;\n    }\n    store.set('lastSettings', settings);\n\n    transport.send('updateSettings', newSettings, settings);\n    if (onChangeSettingsCallbacks[win.id]) {\n      onChangeSettingsCallbacks[win.id].forEach((c) => c(newSettings, settings));\n    }\n\n    newSettings = {};\n  };\n\n  const debouncedApplyAllSettings = debounce(applyAllSettings, 10);\n\n  const applySetting = <K extends keyof Settings>([option, props]: [K, Settings[K]]) => {\n    if (props !== null) {\n      newSettings[option] = props;\n      debouncedApplyAllSettings();\n    }\n  };\n\n  nvim.on('vv:set', applySetting);\n};\n\nexport default initSettings;\n"
  },
  {
    "path": "packages/electron/src/main/preload.js",
    "content": "const { contextBridge, ipcRenderer } = require('electron');\n\ncontextBridge.exposeInMainWorld('electron', {\n  ipcRenderer: {\n    send: (channel, ...params) => {\n      ipcRenderer.send(channel, ...params);\n    },\n    on: (channel, callback) => {\n      ipcRenderer.on(channel, (_event, ...args) => callback(...args));\n    },\n    removeListener: ipcRenderer.removeListener,\n  },\n});\n"
  },
  {
    "path": "packages/electron/src/main/transport/__tests__/ipc.test.ts",
    "content": "import { BrowserWindow, IpcMain } from 'electron';\nimport { EventEmitter } from 'events';\n\nimport IpcTransport from 'src/main/transport/ipc';\n\ndescribe('main transport', () => {\n  let ipcMain: IpcMain;\n\n  const win = (Object.assign(new EventEmitter(), {\n    id: 'winId',\n    webContents: {\n      send: jest.fn(),\n    },\n  }) as unknown) as BrowserWindow;\n\n  let transport: IpcTransport;\n\n  beforeEach(() => {\n    win.removeAllListeners();\n    ipcMain = new EventEmitter() as IpcMain;\n    transport = new IpcTransport(win, ipcMain);\n  });\n\n  describe('on', () => {\n    const listener = jest.fn();\n\n    test('calls listener if sender.id matches window id', () => {\n      transport.on('test-event', listener);\n      ipcMain.emit('test-event', { type: 'test-event', sender: { id: 'winId' } }, 'arg1', 'arg2');\n      expect(listener).toHaveBeenCalledWith('arg1', 'arg2');\n    });\n\n    test('does not call listener if sender.id does not match window id', () => {\n      transport.on('test-event', listener);\n      ipcMain.emit(\n        'test-event',\n        { type: 'test-event', sender: { id: 'otherWinId' } },\n        'arg1',\n        'arg2',\n      );\n      expect(listener).not.toHaveBeenCalled();\n    });\n\n    test('listener with not args', () => {\n      transport.on('test-event', listener);\n      ipcMain.emit('test-event', { type: 'test-event', sender: { id: 'winId' } });\n      expect(listener).toHaveBeenCalledWith();\n    });\n\n    test('removes event listener on win `closed` event', () => {\n      transport.on('test-event', listener);\n      jest.spyOn(ipcMain, 'removeListener');\n      win.emit('closed');\n      expect(ipcMain.removeListener).toHaveBeenCalledWith('test-event', expect.any(Function));\n    });\n  });\n\n  test('unsubscribes from ipc event if there are not subscriptions left', () => {\n    const listener = jest.fn();\n    const addListenerSpy = jest.spyOn(ipcMain, 'on');\n    const removeListenerSpy = jest.spyOn(ipcMain, 'removeListener');\n    transport.on('test-event', listener);\n    transport.off('test-event', listener);\n\n    expect(removeListenerSpy).toHaveBeenCalledWith('test-event', addListenerSpy.mock.calls[0][1]);\n  });\n\n  describe('send', () => {\n    test('pass args to win.webContents', () => {\n      transport.send('test-event', 'arg1', 'arg2');\n      expect(win.webContents.send).toHaveBeenCalledWith('test-event', 'arg1', 'arg2');\n    });\n\n    test('with no args', () => {\n      transport.send('test-event');\n      expect(win.webContents.send).toHaveBeenCalledWith('test-event');\n    });\n\n    test('does not send anything if window is closed', () => {\n      win.emit('closed');\n      transport.send('test-event');\n      expect(win.webContents.send).not.toHaveBeenCalled();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/electron/src/main/transport/ipc.ts",
    "content": "import { ipcMain } from 'electron';\nimport { EventEmitter } from 'events';\nimport memoize from 'lodash/memoize';\n\nimport { Transport, Args } from '@vvim/nvim';\n\n/**\n * Init transport between main and renderer to be used for main side.\n */\nclass IpcTransport extends EventEmitter implements Transport {\n  win: Electron.BrowserWindow;\n\n  closed = false;\n\n  ipc: Electron.IpcMain;\n\n  constructor(win: Electron.BrowserWindow, ipc = ipcMain) {\n    super();\n\n    this.win = win;\n\n    this.ipc = ipc;\n\n    win.on('closed', () => {\n      this.closed = true;\n    });\n\n    this.on('newListener', (eventName: string) => {\n      if (\n        !this.listenerCount(eventName) &&\n        !['newListener', 'removeListener'].includes(eventName)\n      ) {\n        this.ipc.on(eventName, this.handleEvent(eventName));\n        this.win.on('closed', () => {\n          this.ipc.removeListener(eventName, this.handleEvent(eventName));\n        });\n      }\n    });\n\n    this.on('removeListener', (eventName: string) => {\n      if (\n        !this.listenerCount(eventName) &&\n        !['newListener', 'removeListener'].includes(eventName)\n      ) {\n        this.ipc.removeListener(eventName, this.handleEvent(eventName));\n      }\n    });\n  }\n\n  handleEvent = memoize(\n    (eventName: string) => (event: Electron.IpcMainEvent, ...args: Args): void => {\n      const {\n        sender: { id },\n      } = event;\n      if (id === this.win.id) {\n        this.emit(eventName, ...args);\n      }\n    },\n  );\n\n  send(channel: string, ...args: any[]): void {\n    if (!this.closed) {\n      this.win.webContents.send(channel, ...args);\n    }\n  }\n}\n\nexport default IpcTransport;\n"
  },
  {
    "path": "packages/electron/src/renderer/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\" />\n    <title>VV</title>\n  </head>\n  <body>\n    <style>\n      html,\n      body {\n        width: 100%;\n        height: 100%;\n        overflow: hidden;\n        margin: 0;\n        padding: 0;\n      }\n    </style>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/electron/src/renderer/index.ts",
    "content": "import renderer from '@vvim/browser-renderer';\n\nrenderer();\n"
  },
  {
    "path": "packages/electron/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\"\n  },\n  \"include\": [\"src\", \"@types\"]\n}\n"
  },
  {
    "path": "packages/nvim/README.md",
    "content": "# @vvim/nvim\n\nLightweight transport agnostic Neovim API client to be used in other @vvim packages.\n"
  },
  {
    "path": "packages/nvim/babel.config.json",
    "content": "{\n  \"extends\": \"../../babel.config.json\",\n  \"plugins\": [\n    [\n      \"module-resolver\",\n      {\n        \"root\": [\".\"],\n        \"alias\": {\n          \"src\": \"./src\"\n        }\n      }\n    ]\n  ]\n}\n"
  },
  {
    "path": "packages/nvim/config/webpack.config.js",
    "content": "const path = require('path');\nconst { merge } = require('webpack-merge');\n\nconst buildPath = path.resolve(__dirname, './../dist');\n\nconst commonConfig = {\n  mode: 'development',\n  output: {\n    path: buildPath,\n    filename: '[name].js',\n    libraryTarget: 'umd',\n    globalObject: 'this',\n  },\n  devtool: 'eval-cheap-source-map',\n  resolve: {\n    extensions: ['.ts', '.js'],\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts)$/,\n        exclude: /node_modules/,\n        loader: 'babel-loader',\n      },\n    ],\n  },\n};\n\nconst browserConfig = merge(commonConfig, {\n  target: 'web',\n  entry: {\n    browser: './src/browser.ts',\n  },\n});\n\nconst nodeConfig = merge(commonConfig, {\n  target: 'node',\n  entry: {\n    index: './src/index.ts',\n  },\n});\n\nmodule.exports = [browserConfig, nodeConfig];\n"
  },
  {
    "path": "packages/nvim/config/webpack.prod.config.js",
    "content": "const { merge } = require('webpack-merge');\n\nconst webpackConfig = require('./webpack.config');\n\nconst prod = {\n  mode: 'production',\n  devtool: 'source-map',\n};\n\nconst webpackConfigProd = webpackConfig.map((config) => merge(config, prod));\n\nmodule.exports = webpackConfigProd;\n"
  },
  {
    "path": "packages/nvim/jest.config.js",
    "content": "module.exports = {\n  clearMocks: true,\n  moduleNameMapper: {\n    'src/(.*)': ['<rootDir>/src/$1'],\n  },\n  testPathIgnorePatterns: ['/node_modules/', '/dist/'],\n};\n"
  },
  {
    "path": "packages/nvim/package.json",
    "content": "{\n  \"name\": \"@vvim/nvim\",\n  \"version\": \"0.0.1\",\n  \"description\": \"Lightweight transport agnostic Neovim API client to be used in other @vvim packages\",\n  \"author\": \"Igor Gladkoborodov <igor.gladkoborodov@gmail.com>\",\n  \"keywords\": [\n    \"vim\",\n    \"neovim\",\n    \"client\",\n    \"api\"\n  ],\n  \"homepage\": \"https://github.com/vv-vim/vv#readme\",\n  \"license\": \"MIT\",\n  \"main\": \"dist/index.js\",\n  \"browser\": \"dist/browser.js\",\n  \"sideEffects\": false,\n  \"scripts\": {\n    \"test\": \"jest\",\n    \"clean\": \"rm -rf dist/*\",\n    \"build:types\": \"tsc -p tsconfig.declaration.json\",\n    \"build:dev\": \"webpack --config ./config/webpack.config.js\",\n    \"build:prod\": \"webpack --config ./config/webpack.prod.config.js\",\n    \"build\": \"npm-run-all clean build:types build:prod\",\n    \"dev\": \"npm-run-all --parallel \\\"build:types --watch\\\" \\\"build:dev --watch\\\"\"\n  },\n  \"publishConfig\": {\n    \"registry\": \"https://registry.yarnpkg.com\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/vv-vim/vv.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/vv-vim/vv/issues\"\n  },\n  \"browserslist\": [\n    \"defaults\",\n    \"last 2 electron versions\",\n    \"maintained node versions\"\n  ],\n  \"devDependencies\": {\n    \"@types/express\": \"^4.17.11\",\n    \"@types/lodash\": \"^4.14.168\",\n    \"@types/msgpack-lite\": \"^0.1.7\",\n    \"@types/node\": \"^14.14.31\",\n    \"@types/ws\": \"^7.4.0\",\n    \"strict-event-emitter-types\": \"^2.0.0\"\n  },\n  \"dependencies\": {\n    \"lodash\": \"^4.17.21\",\n    \"msgpack-lite\": \"^0.1.26\",\n    \"ws\": \"^7.4.6\"\n  }\n}\n"
  },
  {
    "path": "packages/nvim/src/Nvim.ts",
    "content": "import { EventEmitter } from 'events';\n\nimport { nvimCommandNames } from 'src/__generated__/constants';\n\nimport type { Transport, MessageType, NvimInterface } from './types';\n\nconst NvimEventEmitter = (EventEmitter as unknown) as { new (): NvimInterface };\n\n/**\n * Lightweight transport agnostic Neovim API client to be used in other @vvim packages.\n */\nclass Nvim extends NvimEventEmitter {\n  private requestId = 0;\n\n  private transport: Transport;\n\n  private requestPromises: Record<\n    string,\n    { resolve: (result: any) => void; reject: (error: any) => void }\n  > = {};\n\n  private isRenderer: boolean;\n\n  constructor(transport: Transport, isRenderer = false) {\n    super();\n\n    this.transport = transport;\n    this.isRenderer = isRenderer;\n\n    this.transport.on('nvim:data', (params: MessageType) => {\n      if (params[0] === 0) {\n        // eslint-disable-next-line no-console\n        console.error('Unsupported request type', ...params);\n      } else if (params[0] === 1) {\n        this.handleResponse(params[1], params[2], params[3]);\n      } else if (params[0] === 2) {\n        this.emit(params[1], params[2]);\n      }\n    });\n\n    this.transport.on('nvim:close', () => {\n      this.emit('close');\n    });\n\n    (Object.keys(nvimCommandNames) as Array<keyof typeof nvimCommandNames>).forEach(\n      (commandName) => {\n        (this as any)[commandName] = (...params: any[]) =>\n          this.request(nvimCommandNames[commandName], params);\n      },\n    );\n\n    this.on('newListener', (eventName: string) => {\n      if (\n        !this.listenerCount(eventName) &&\n        !['close', 'newListener', 'removeListener'].includes(eventName) &&\n        !eventName.startsWith('nvim:')\n      ) {\n        this.subscribe(eventName);\n      }\n    });\n\n    this.on('removeListener', (eventName: string) => {\n      if (\n        !this.listenerCount(eventName) &&\n        !['close', 'newListener', 'removeListener'].includes(eventName) &&\n        !eventName.startsWith('nvim:')\n      ) {\n        this.unsubscribe(eventName);\n      }\n    });\n  }\n\n  request<R = void>(command: string, params: any[] = []): Promise<R> {\n    this.requestId += 1;\n    // Workaround to avoid request ids conflict vetween main and renderer. Renderer ids are even, main ids are odd.\n    // TODO: sync request id between all instances.\n    const id = this.requestId * 2 + (this.isRenderer ? 0 : 1);\n    this.transport.send('nvim:write', id, command, params);\n    return new Promise((resolve, reject) => {\n      this.requestPromises[id] = {\n        resolve,\n        reject,\n      };\n    });\n  }\n\n  private handleResponse(id: number, error: Error, result?: any): void {\n    if (this.requestPromises[id]) {\n      if (error) {\n        this.requestPromises[id].reject(error);\n      } else {\n        this.requestPromises[id].resolve(result);\n      }\n      delete this.requestPromises[id];\n    }\n  }\n\n  /**\n   * Fetch current mode from nvim, leaves only first letter to match groups of modes.\n   * https://neovim.io/doc/user/eval.html#mode()\n   */\n  getShortMode = async (): Promise<string> => {\n    const { mode } = await this.getMode();\n    return mode.replace('CTRL-', '')[0];\n  };\n}\n\nexport default Nvim;\n"
  },
  {
    "path": "packages/nvim/src/ProcNvimTransport.ts",
    "content": "import { createDecodeStream, encode } from 'msgpack-lite';\nimport { EventEmitter } from 'events';\n\nimport type { ChildProcessWithoutNullStreams } from 'child_process';\nimport type { DecodeStream } from 'msgpack-lite';\nimport type { Transport, MessageType } from 'src/types';\n\n/**\n * Transport that communicates directly with nvim process.\n * It also used as to communicate nvim api with remote transport.\n */\nclass ProcNvimTransport extends EventEmitter implements Transport {\n  private msgpackIn: DecodeStream;\n\n  private proc: ChildProcessWithoutNullStreams;\n\n  constructor(proc: ChildProcessWithoutNullStreams, remoteTransport?: Transport) {\n    super();\n\n    this.proc = proc;\n\n    const decodeStream = createDecodeStream();\n    this.msgpackIn = this.proc.stdout.pipe(decodeStream);\n\n    this.proc.on('close', () => this.emit('nvim:close'));\n    this.msgpackIn.on('data', (message: MessageType) => this.emit('nvim:data', message));\n\n    if (remoteTransport) {\n      this.attachRemoteTransport(remoteTransport);\n    }\n  }\n\n  attachRemoteTransport(remoteTransport: Transport): void {\n    remoteTransport.on('nvim:write', (...args: [number, string, string[]]) => this.write(...args));\n    this.on('nvim:close', () => remoteTransport.send('nvim:close'));\n    this.on('nvim:data', (data: MessageType) => remoteTransport.send('nvim:data', data));\n  }\n\n  private write(id: number, command: string, params: string[]): void {\n    if (this.proc.stdin.writable) {\n      this.proc.stdin.write(encode([0, id, command, params]));\n    }\n  }\n\n  send(channel: string, id: number, command: string, params: string[]): void {\n    if (channel === 'nvim:write') {\n      this.write(id, command, params);\n    }\n  }\n}\n\nexport default ProcNvimTransport;\n"
  },
  {
    "path": "packages/nvim/src/__generated__/constants.ts",
    "content": "/* eslint-disable camelcase */\n/**\n * Constants generated by `yarn generate-types`. Do not edit manually.\n *\n * Version: 0.5.0\n * Api Level: 7\n * Api Compatible: 0\n * Api Prerelease: false\n */\n\nexport const nvimCommandNames = {\n  bufLineCount: 'nvim_buf_line_count',\n  bufAttach: 'nvim_buf_attach',\n  bufDetach: 'nvim_buf_detach',\n  bufGetLines: 'nvim_buf_get_lines',\n  bufSetLines: 'nvim_buf_set_lines',\n  bufSetText: 'nvim_buf_set_text',\n  bufGetOffset: 'nvim_buf_get_offset',\n  bufGetVar: 'nvim_buf_get_var',\n  bufGetChangedtick: 'nvim_buf_get_changedtick',\n  bufGetKeymap: 'nvim_buf_get_keymap',\n  bufSetKeymap: 'nvim_buf_set_keymap',\n  bufDelKeymap: 'nvim_buf_del_keymap',\n  bufGetCommands: 'nvim_buf_get_commands',\n  bufSetVar: 'nvim_buf_set_var',\n  bufDelVar: 'nvim_buf_del_var',\n  bufGetOption: 'nvim_buf_get_option',\n  bufSetOption: 'nvim_buf_set_option',\n  bufGetName: 'nvim_buf_get_name',\n  bufSetName: 'nvim_buf_set_name',\n  bufIsLoaded: 'nvim_buf_is_loaded',\n  bufDelete: 'nvim_buf_delete',\n  bufIsValid: 'nvim_buf_is_valid',\n  bufGetMark: 'nvim_buf_get_mark',\n  bufGetExtmarkById: 'nvim_buf_get_extmark_by_id',\n  bufGetExtmarks: 'nvim_buf_get_extmarks',\n  bufSetExtmark: 'nvim_buf_set_extmark',\n  bufDelExtmark: 'nvim_buf_del_extmark',\n  bufAddHighlight: 'nvim_buf_add_highlight',\n  bufClearNamespace: 'nvim_buf_clear_namespace',\n  bufSetVirtualText: 'nvim_buf_set_virtual_text',\n  bufCall: 'nvim_buf_call',\n  tabpageListWins: 'nvim_tabpage_list_wins',\n  tabpageGetVar: 'nvim_tabpage_get_var',\n  tabpageSetVar: 'nvim_tabpage_set_var',\n  tabpageDelVar: 'nvim_tabpage_del_var',\n  tabpageGetWin: 'nvim_tabpage_get_win',\n  tabpageGetNumber: 'nvim_tabpage_get_number',\n  tabpageIsValid: 'nvim_tabpage_is_valid',\n  uiAttach: 'nvim_ui_attach',\n  uiDetach: 'nvim_ui_detach',\n  uiTryResize: 'nvim_ui_try_resize',\n  uiSetOption: 'nvim_ui_set_option',\n  uiTryResizeGrid: 'nvim_ui_try_resize_grid',\n  uiPumSetHeight: 'nvim_ui_pum_set_height',\n  uiPumSetBounds: 'nvim_ui_pum_set_bounds',\n  exec: 'nvim_exec',\n  command: 'nvim_command',\n  getHlByName: 'nvim_get_hl_by_name',\n  getHlById: 'nvim_get_hl_by_id',\n  getHlIdByName: 'nvim_get_hl_id_by_name',\n  setHl: 'nvim_set_hl',\n  feedkeys: 'nvim_feedkeys',\n  input: 'nvim_input',\n  inputMouse: 'nvim_input_mouse',\n  replaceTermcodes: 'nvim_replace_termcodes',\n  eval: 'nvim_eval',\n  execLua: 'nvim_exec_lua',\n  notify: 'nvim_notify',\n  callFunction: 'nvim_call_function',\n  callDictFunction: 'nvim_call_dict_function',\n  strwidth: 'nvim_strwidth',\n  listRuntimePaths: 'nvim_list_runtime_paths',\n  getRuntimeFile: 'nvim_get_runtime_file',\n  setCurrentDir: 'nvim_set_current_dir',\n  getCurrentLine: 'nvim_get_current_line',\n  setCurrentLine: 'nvim_set_current_line',\n  delCurrentLine: 'nvim_del_current_line',\n  getVar: 'nvim_get_var',\n  setVar: 'nvim_set_var',\n  delVar: 'nvim_del_var',\n  getVvar: 'nvim_get_vvar',\n  setVvar: 'nvim_set_vvar',\n  getOption: 'nvim_get_option',\n  getAllOptionsInfo: 'nvim_get_all_options_info',\n  getOptionInfo: 'nvim_get_option_info',\n  setOption: 'nvim_set_option',\n  echo: 'nvim_echo',\n  outWrite: 'nvim_out_write',\n  errWrite: 'nvim_err_write',\n  errWriteln: 'nvim_err_writeln',\n  listBufs: 'nvim_list_bufs',\n  getCurrentBuf: 'nvim_get_current_buf',\n  setCurrentBuf: 'nvim_set_current_buf',\n  listWins: 'nvim_list_wins',\n  getCurrentWin: 'nvim_get_current_win',\n  setCurrentWin: 'nvim_set_current_win',\n  createBuf: 'nvim_create_buf',\n  openTerm: 'nvim_open_term',\n  chanSend: 'nvim_chan_send',\n  openWin: 'nvim_open_win',\n  listTabpages: 'nvim_list_tabpages',\n  getCurrentTabpage: 'nvim_get_current_tabpage',\n  setCurrentTabpage: 'nvim_set_current_tabpage',\n  createNamespace: 'nvim_create_namespace',\n  getNamespaces: 'nvim_get_namespaces',\n  paste: 'nvim_paste',\n  put: 'nvim_put',\n  subscribe: 'nvim_subscribe',\n  unsubscribe: 'nvim_unsubscribe',\n  getColorByName: 'nvim_get_color_by_name',\n  getColorMap: 'nvim_get_color_map',\n  getContext: 'nvim_get_context',\n  loadContext: 'nvim_load_context',\n  getMode: 'nvim_get_mode',\n  getKeymap: 'nvim_get_keymap',\n  setKeymap: 'nvim_set_keymap',\n  delKeymap: 'nvim_del_keymap',\n  getCommands: 'nvim_get_commands',\n  getApiInfo: 'nvim_get_api_info',\n  setClientInfo: 'nvim_set_client_info',\n  getChanInfo: 'nvim_get_chan_info',\n  listChans: 'nvim_list_chans',\n  callAtomic: 'nvim_call_atomic',\n  parseExpression: 'nvim_parse_expression',\n  listUis: 'nvim_list_uis',\n  getProcChildren: 'nvim_get_proc_children',\n  getProc: 'nvim_get_proc',\n  selectPopupmenuItem: 'nvim_select_popupmenu_item',\n  setDecorationProvider: 'nvim_set_decoration_provider',\n  winGetBuf: 'nvim_win_get_buf',\n  winSetBuf: 'nvim_win_set_buf',\n  winGetCursor: 'nvim_win_get_cursor',\n  winSetCursor: 'nvim_win_set_cursor',\n  winGetHeight: 'nvim_win_get_height',\n  winSetHeight: 'nvim_win_set_height',\n  winGetWidth: 'nvim_win_get_width',\n  winSetWidth: 'nvim_win_set_width',\n  winGetVar: 'nvim_win_get_var',\n  winSetVar: 'nvim_win_set_var',\n  winDelVar: 'nvim_win_del_var',\n  winGetOption: 'nvim_win_get_option',\n  winSetOption: 'nvim_win_set_option',\n  winGetPosition: 'nvim_win_get_position',\n  winGetTabpage: 'nvim_win_get_tabpage',\n  winGetNumber: 'nvim_win_get_number',\n  winIsValid: 'nvim_win_is_valid',\n  winSetConfig: 'nvim_win_set_config',\n  winGetConfig: 'nvim_win_get_config',\n  winHide: 'nvim_win_hide',\n  winClose: 'nvim_win_close',\n  winCall: 'nvim_win_call',\n} as const;\n"
  },
  {
    "path": "packages/nvim/src/__generated__/types.ts",
    "content": "/* eslint-disable camelcase */\n/**\n * Types generated by `yarn generate-types`. Do not edit manually.\n *\n * Version: 0.5.0\n * Api Level: 7\n * Api Compatible: 0\n * Api Prerelease: false\n */\n\n/**\n * UI events types emitted by `redraw` event. Do not edit manually.\n * More info: https://neovim.io/doc/user/ui.html\n */\nexport type UiEvents = {\n  mode_info_set: [enabled: boolean, cursor_styles: Array<any>];\n\n  update_menu: [];\n\n  busy_start: [];\n\n  busy_stop: [];\n\n  mouse_on: [];\n\n  mouse_off: [];\n\n  mode_change: [mode: string, mode_idx: number];\n\n  bell: [];\n\n  visual_bell: [];\n\n  flush: [];\n\n  suspend: [];\n\n  set_title: [title: string];\n\n  set_icon: [icon: string];\n\n  screenshot: [path: string];\n\n  option_set: [name: string, value: any];\n\n  update_fg: [fg: number];\n\n  update_bg: [bg: number];\n\n  update_sp: [sp: number];\n\n  resize: [width: number, height: number];\n\n  clear: [];\n\n  eol_clear: [];\n\n  cursor_goto: [row: number, col: number];\n\n  highlight_set: [attrs: Record<string, any>];\n\n  put: [str: string];\n\n  set_scroll_region: [top: number, bot: number, left: number, right: number];\n\n  scroll: [count: number];\n\n  default_colors_set: [\n    rgb_fg: number,\n    rgb_bg: number,\n    rgb_sp: number,\n    cterm_fg: number,\n    cterm_bg: number,\n  ];\n\n  hl_attr_define: [\n    id: number,\n    rgb_attrs: Record<string, any>,\n    cterm_attrs: Record<string, any>,\n    info: Array<any>,\n  ];\n\n  hl_group_set: [name: string, id: number];\n\n  grid_resize: [grid: number, width: number, height: number];\n\n  grid_clear: [grid: number];\n\n  grid_cursor_goto: [grid: number, row: number, col: number];\n\n  grid_line: [grid: number, row: number, col_start: number, data: Array<any>];\n\n  grid_scroll: [\n    grid: number,\n    top: number,\n    bot: number,\n    left: number,\n    right: number,\n    rows: number,\n    cols: number,\n  ];\n\n  grid_destroy: [grid: number];\n\n  win_pos: [\n    grid: number,\n    win: number,\n    startrow: number,\n    startcol: number,\n    width: number,\n    height: number,\n  ];\n\n  win_float_pos: [\n    grid: number,\n    win: number,\n    anchor: string,\n    anchor_grid: number,\n    anchor_row: number,\n    anchor_col: number,\n    focusable: boolean,\n    zindex: number,\n  ];\n\n  win_external_pos: [grid: number, win: number];\n\n  win_hide: [grid: number];\n\n  win_close: [grid: number];\n\n  msg_set_pos: [grid: number, row: number, scrolled: boolean, sep_char: string];\n\n  win_viewport: [\n    grid: number,\n    win: number,\n    topline: number,\n    botline: number,\n    curline: number,\n    curcol: number,\n  ];\n\n  popupmenu_show: [items: Array<any>, selected: number, row: number, col: number, grid: number];\n\n  popupmenu_hide: [];\n\n  popupmenu_select: [selected: number];\n\n  tabline_update: [current: number, tabs: Array<any>, current_buffer: number, buffers: Array<any>];\n\n  cmdline_show: [\n    content: Array<any>,\n    pos: number,\n    firstc: string,\n    prompt: string,\n    indent: number,\n    level: number,\n  ];\n\n  cmdline_pos: [pos: number, level: number];\n\n  cmdline_special_char: [c: string, shift: boolean, level: number];\n\n  cmdline_hide: [level: number];\n\n  cmdline_block_show: [lines: Array<any>];\n\n  cmdline_block_append: [lines: Array<any>];\n\n  cmdline_block_hide: [];\n\n  wildmenu_show: [items: Array<any>];\n\n  wildmenu_select: [selected: number];\n\n  wildmenu_hide: [];\n\n  msg_show: [kind: string, content: Array<any>, replace_last: boolean];\n\n  msg_clear: [];\n\n  msg_showcmd: [content: Array<any>];\n\n  msg_showmode: [content: Array<any>];\n\n  msg_ruler: [content: Array<any>];\n\n  msg_history_show: [entries: Array<any>];\n};\n\n/**\n * Nvim commands.\n * More info: https://neovim.io/doc/user/api.html\n */\nexport type NvimCommands = {\n  nvim_buf_line_count: (buffer: number) => number;\n\n  nvim_buf_attach: (buffer: number, send_buffer: boolean, opts: Record<string, any>) => boolean;\n\n  nvim_buf_detach: (buffer: number) => boolean;\n\n  nvim_buf_get_lines: (\n    buffer: number,\n    start: number,\n    end: number,\n    strict_indexing: boolean,\n  ) => string[];\n\n  nvim_buf_set_lines: (\n    buffer: number,\n    start: number,\n    end: number,\n    strict_indexing: boolean,\n    replacement: string[],\n  ) => void;\n\n  nvim_buf_set_text: (\n    buffer: number,\n    start_row: number,\n    start_col: number,\n    end_row: number,\n    end_col: number,\n    replacement: string[],\n  ) => void;\n\n  nvim_buf_get_offset: (buffer: number, index: number) => number;\n\n  nvim_buf_get_var: (buffer: number, name: string) => any;\n\n  nvim_buf_get_changedtick: (buffer: number) => number;\n\n  nvim_buf_get_keymap: (buffer: number, mode: string) => Record<string, any>[];\n\n  nvim_buf_set_keymap: (\n    buffer: number,\n    mode: string,\n    lhs: string,\n    rhs: string,\n    opts: Record<string, any>,\n  ) => void;\n\n  nvim_buf_del_keymap: (buffer: number, mode: string, lhs: string) => void;\n\n  nvim_buf_get_commands: (buffer: number, opts: Record<string, any>) => Record<string, any>;\n\n  nvim_buf_set_var: (buffer: number, name: string, value: any) => void;\n\n  nvim_buf_del_var: (buffer: number, name: string) => void;\n\n  nvim_buf_get_option: (buffer: number, name: string) => any;\n\n  nvim_buf_set_option: (buffer: number, name: string, value: any) => void;\n\n  nvim_buf_get_name: (buffer: number) => string;\n\n  nvim_buf_set_name: (buffer: number, name: string) => void;\n\n  nvim_buf_is_loaded: (buffer: number) => boolean;\n\n  nvim_buf_delete: (buffer: number, opts: Record<string, any>) => void;\n\n  nvim_buf_is_valid: (buffer: number) => boolean;\n\n  nvim_buf_get_mark: (buffer: number, name: string) => [number, number];\n\n  nvim_buf_get_extmark_by_id: (\n    buffer: number,\n    ns_id: number,\n    id: number,\n    opts: Record<string, any>,\n  ) => number[];\n\n  nvim_buf_get_extmarks: (\n    buffer: number,\n    ns_id: number,\n    start: any,\n    end: any,\n    opts: Record<string, any>,\n  ) => Array<any>;\n\n  nvim_buf_set_extmark: (\n    buffer: number,\n    ns_id: number,\n    line: number,\n    col: number,\n    opts: Record<string, any>,\n  ) => number;\n\n  nvim_buf_del_extmark: (buffer: number, ns_id: number, id: number) => boolean;\n\n  nvim_buf_add_highlight: (\n    buffer: number,\n    ns_id: number,\n    hl_group: string,\n    line: number,\n    col_start: number,\n    col_end: number,\n  ) => number;\n\n  nvim_buf_clear_namespace: (\n    buffer: number,\n    ns_id: number,\n    line_start: number,\n    line_end: number,\n  ) => void;\n\n  nvim_buf_set_virtual_text: (\n    buffer: number,\n    src_id: number,\n    line: number,\n    chunks: Array<any>,\n    opts: Record<string, any>,\n  ) => number;\n\n  nvim_buf_call: (buffer: number, fun: any) => any;\n\n  nvim_tabpage_list_wins: (tabpage: number) => number[];\n\n  nvim_tabpage_get_var: (tabpage: number, name: string) => any;\n\n  nvim_tabpage_set_var: (tabpage: number, name: string, value: any) => void;\n\n  nvim_tabpage_del_var: (tabpage: number, name: string) => void;\n\n  nvim_tabpage_get_win: (tabpage: number) => number;\n\n  nvim_tabpage_get_number: (tabpage: number) => number;\n\n  nvim_tabpage_is_valid: (tabpage: number) => boolean;\n\n  nvim_ui_attach: (width: number, height: number, options: Record<string, any>) => void;\n\n  nvim_ui_detach: () => void;\n\n  nvim_ui_try_resize: (width: number, height: number) => void;\n\n  nvim_ui_set_option: (name: string, value: any) => void;\n\n  nvim_ui_try_resize_grid: (grid: number, width: number, height: number) => void;\n\n  nvim_ui_pum_set_height: (height: number) => void;\n\n  nvim_ui_pum_set_bounds: (width: number, height: number, row: number, col: number) => void;\n\n  nvim_exec: (src: string, output: boolean) => string;\n\n  nvim_command: (command: string) => void;\n\n  nvim_get_hl_by_name: (name: string, rgb: boolean) => Record<string, any>;\n\n  nvim_get_hl_by_id: (hl_id: number, rgb: boolean) => Record<string, any>;\n\n  nvim_get_hl_id_by_name: (name: string) => number;\n\n  nvim_set_hl: (ns_id: number, name: string, val: Record<string, any>) => void;\n\n  nvim_feedkeys: (keys: string, mode: string, escape_csi: boolean) => void;\n\n  nvim_input: (keys: string) => number;\n\n  nvim_input_mouse: (\n    button: string,\n    action: string,\n    modifier: string,\n    grid: number,\n    row: number,\n    col: number,\n  ) => void;\n\n  nvim_replace_termcodes: (\n    str: string,\n    from_part: boolean,\n    do_lt: boolean,\n    special: boolean,\n  ) => string;\n\n  nvim_eval: (expr: string) => any;\n\n  nvim_exec_lua: (code: string, args: Array<any>) => any;\n\n  nvim_notify: (msg: string, log_level: number, opts: Record<string, any>) => any;\n\n  nvim_call_function: (fn: string, args: Array<any>) => any;\n\n  nvim_call_dict_function: (dict: any, fn: string, args: Array<any>) => any;\n\n  nvim_strwidth: (text: string) => number;\n\n  nvim_list_runtime_paths: () => string[];\n\n  nvim_get_runtime_file: (name: string, all: boolean) => string[];\n\n  nvim_set_current_dir: (dir: string) => void;\n\n  nvim_get_current_line: () => string;\n\n  nvim_set_current_line: (line: string) => void;\n\n  nvim_del_current_line: () => void;\n\n  nvim_get_var: (name: string) => any;\n\n  nvim_set_var: (name: string, value: any) => void;\n\n  nvim_del_var: (name: string) => void;\n\n  nvim_get_vvar: (name: string) => any;\n\n  nvim_set_vvar: (name: string, value: any) => void;\n\n  nvim_get_option: (name: string) => any;\n\n  nvim_get_all_options_info: () => Record<string, any>;\n\n  nvim_get_option_info: (name: string) => Record<string, any>;\n\n  nvim_set_option: (name: string, value: any) => void;\n\n  nvim_echo: (chunks: Array<any>, history: boolean, opts: Record<string, any>) => void;\n\n  nvim_out_write: (str: string) => void;\n\n  nvim_err_write: (str: string) => void;\n\n  nvim_err_writeln: (str: string) => void;\n\n  nvim_list_bufs: () => number[];\n\n  nvim_get_current_buf: () => number;\n\n  nvim_set_current_buf: (buffer: number) => void;\n\n  nvim_list_wins: () => number[];\n\n  nvim_get_current_win: () => number;\n\n  nvim_set_current_win: (win: number) => void;\n\n  nvim_create_buf: (listed: boolean, scratch: boolean) => number;\n\n  nvim_open_term: (buffer: number, opts: Record<string, any>) => number;\n\n  nvim_chan_send: (chan: number, data: string) => void;\n\n  nvim_open_win: (buffer: number, enter: boolean, config: Record<string, any>) => number;\n\n  nvim_list_tabpages: () => number[];\n\n  nvim_get_current_tabpage: () => number;\n\n  nvim_set_current_tabpage: (tabpage: number) => void;\n\n  nvim_create_namespace: (name: string) => number;\n\n  nvim_get_namespaces: () => Record<string, any>;\n\n  nvim_paste: (data: string, crlf: boolean, phase: number) => boolean;\n\n  nvim_put: (lines: string[], type: string, after: boolean, follow: boolean) => void;\n\n  nvim_subscribe: (event: string) => void;\n\n  nvim_unsubscribe: (event: string) => void;\n\n  nvim_get_color_by_name: (name: string) => number;\n\n  nvim_get_color_map: () => Record<string, any>;\n\n  nvim_get_context: (opts: Record<string, any>) => Record<string, any>;\n\n  nvim_load_context: (dict: Record<string, any>) => any;\n\n  nvim_get_mode: () => Record<string, any>;\n\n  nvim_get_keymap: (mode: string) => Record<string, any>[];\n\n  nvim_set_keymap: (mode: string, lhs: string, rhs: string, opts: Record<string, any>) => void;\n\n  nvim_del_keymap: (mode: string, lhs: string) => void;\n\n  nvim_get_commands: (opts: Record<string, any>) => Record<string, any>;\n\n  nvim_get_api_info: () => Array<any>;\n\n  nvim_set_client_info: (\n    name: string,\n    version: Record<string, any>,\n    type: string,\n    methods: Record<string, any>,\n    attributes: Record<string, any>,\n  ) => void;\n\n  nvim_get_chan_info: (chan: number) => Record<string, any>;\n\n  nvim_list_chans: () => Array<any>;\n\n  nvim_call_atomic: (calls: Array<any>) => Array<any>;\n\n  nvim_parse_expression: (expr: string, flags: string, highlight: boolean) => Record<string, any>;\n\n  nvim_list_uis: () => Array<any>;\n\n  nvim_get_proc_children: (pid: number) => Array<any>;\n\n  nvim_get_proc: (pid: number) => any;\n\n  nvim_select_popupmenu_item: (\n    item: number,\n    insert: boolean,\n    finish: boolean,\n    opts: Record<string, any>,\n  ) => void;\n\n  nvim_set_decoration_provider: (ns_id: number, opts: Record<string, any>) => void;\n\n  nvim_win_get_buf: (win: number) => number;\n\n  nvim_win_set_buf: (win: number, buffer: number) => void;\n\n  nvim_win_get_cursor: (win: number) => [number, number];\n\n  nvim_win_set_cursor: (win: number, pos: [number, number]) => void;\n\n  nvim_win_get_height: (win: number) => number;\n\n  nvim_win_set_height: (win: number, height: number) => void;\n\n  nvim_win_get_width: (win: number) => number;\n\n  nvim_win_set_width: (win: number, width: number) => void;\n\n  nvim_win_get_var: (win: number, name: string) => any;\n\n  nvim_win_set_var: (win: number, name: string, value: any) => void;\n\n  nvim_win_del_var: (win: number, name: string) => void;\n\n  nvim_win_get_option: (win: number, name: string) => any;\n\n  nvim_win_set_option: (win: number, name: string, value: any) => void;\n\n  nvim_win_get_position: (win: number) => [number, number];\n\n  nvim_win_get_tabpage: (win: number) => number;\n\n  nvim_win_get_number: (win: number) => number;\n\n  nvim_win_is_valid: (win: number) => boolean;\n\n  nvim_win_set_config: (win: number, config: Record<string, any>) => void;\n\n  nvim_win_get_config: (win: number) => Record<string, any>;\n\n  nvim_win_hide: (win: number) => void;\n\n  nvim_win_close: (win: number, force: boolean) => void;\n\n  nvim_win_call: (win: number, fun: any) => any;\n};\n"
  },
  {
    "path": "packages/nvim/src/__tests__/Nvim.test.ts",
    "content": "import { EventEmitter } from 'events';\nimport Nvim from 'src/nvim';\n\nimport type { Transport } from 'src/types';\n\ndescribe('Nvim', () => {\n  const send = jest.fn();\n\n  const transportMock: Transport = Object.assign(new EventEmitter(), {\n    send,\n  });\n\n  let nvim: Nvim;\n\n  beforeEach(() => {\n    transportMock.removeAllListeners();\n    nvim = new Nvim(transportMock);\n  });\n\n  describe('request', () => {\n    test('call send with `nvim:write` on request', () => {\n      nvim.request('nvim_command', ['param1', 'param2']);\n      expect(send).toHaveBeenCalledWith('nvim:write', 3, 'nvim_command', ['param1', 'param2']);\n    });\n\n    test('increment request id on second call and it is always odd', () => {\n      nvim.request('nvim_command1');\n      expect(send).toHaveBeenCalledWith('nvim:write', 3, 'nvim_command1', []);\n      nvim.request('nvim_command2');\n      expect(send).toHaveBeenCalledWith('nvim:write', 5, 'nvim_command2', []);\n    });\n\n    test('in renderer mode request id is always even', () => {\n      nvim = new Nvim(transportMock, true);\n      nvim.request('nvim_command1');\n      expect(send).toHaveBeenCalledWith('nvim:write', 2, 'nvim_command1', []);\n      nvim.request('nvim_command2');\n      expect(send).toHaveBeenCalledWith('nvim:write', 4, 'nvim_command2', []);\n    });\n\n    test('receives result of request', async () => {\n      const resultPromise = nvim.request('nvim_command', ['param1', 'param2']);\n      transportMock.emit('nvim:data', [1, 3, null, 'result']);\n      expect(await resultPromise).toEqual('result');\n    });\n\n    test('reject on error returned', async () => {\n      const resultPromise = nvim.request('nvim_command', ['param1', 'param2']);\n      transportMock.emit('nvim:data', [1, 3, 'error']);\n      await expect(resultPromise).rejects.toEqual('error');\n    });\n  });\n\n  describe('notification', () => {\n    test('send `nvim_subscribe` when you subscribe', () => {\n      nvim.on('onSomething', () => null);\n      expect(send).toHaveBeenCalledWith('nvim:write', 3, 'nvim_subscribe', ['onSomething']);\n    });\n\n    test('does not subscribe twice on the same event', () => {\n      nvim.on('onSomething', () => null);\n      nvim.on('onSomething', () => null);\n      expect(send).toHaveBeenCalledWith('nvim:write', 3, 'nvim_subscribe', ['onSomething']);\n      expect(send).toHaveBeenCalledTimes(1);\n    });\n\n    test('send `nvim_unsubscribe` when you subscribe', () => {\n      const listener = () => null;\n      nvim.on('onSomething', listener);\n      nvim.removeListener('onSomething', listener);\n      expect(send).toHaveBeenCalledWith('nvim:write', 5, 'nvim_unsubscribe', ['onSomething']);\n    });\n\n    test('does not unsubscribe if you have events with that name', () => {\n      const listener = () => null;\n      const anotherListener = () => null;\n      nvim.on('onSomething', listener);\n      nvim.on('onSomething', anotherListener);\n      nvim.removeListener('onSomething', listener);\n      expect(send).not.toHaveBeenCalledWith('nvim:write', 5, 'nvim_unsubscribe', ['onSomething']);\n    });\n\n    test('receives notification for subscription', () => {\n      const callback = jest.fn();\n      nvim.on('onSomething', callback);\n      transportMock.emit('nvim:data', [2, 'onSomething', 'params1']);\n      expect(callback).toHaveBeenCalledWith('params1');\n      transportMock.emit('nvim:data', [2, 'onSomething', 'params2']);\n      expect(callback).toHaveBeenCalledWith('params2');\n    });\n\n    test('does not receives notifications that are not subscribed', () => {\n      const callback = jest.fn();\n      nvim.on('onSomething', callback);\n      transportMock.emit('nvim:data', [2, 'onSomethingElse', 'params1']);\n      expect(callback).not.toHaveBeenCalled();\n    });\n  });\n\n  describe('request message type', () => {\n    test('receives result of request', async () => {\n      const errorSpy = jest.spyOn(console, 'error').mockImplementationOnce(() => {\n        /* empty */\n      });\n      transportMock.emit('nvim:data', [0]);\n      expect(errorSpy).toHaveBeenCalled();\n    });\n  });\n\n  describe('predefined commands', () => {\n    const commands = [\n      ['subscribe', 'subscribe'],\n      ['unsubscribe', 'unsubscribe'],\n      ['callFunction', 'call_function'],\n      ['command', 'command'],\n      ['input', 'input'],\n      ['inputMouse', 'input_mouse'],\n      ['getMode', 'get_mode'],\n      ['uiTryResize', 'ui_try_resize'],\n      ['uiAttach', 'ui_attach'],\n      ['getHlByName', 'get_hl_by_name'],\n      ['paste', 'paste'],\n    ] as const;\n    commands.forEach(([command, request]) => {\n      test(`${command}`, () => {\n        nvim = new Nvim(transportMock);\n        nvim[command]('param1', 'param2');\n        expect(send).toHaveBeenCalledWith('nvim:write', 3, `nvim_${request}`, ['param1', 'param2']);\n      });\n    });\n\n    test('eval', () => {\n      nvim = new Nvim(transportMock);\n      nvim.eval('param1');\n      expect(send).toHaveBeenCalledWith('nvim:write', 3, `nvim_eval`, ['param1']);\n    });\n\n    test('getShortMode returns mode', async () => {\n      const resultPromise = nvim.getShortMode();\n      transportMock.emit('nvim:data', [1, 3, null, { mode: 'n' }]);\n      expect(await resultPromise).toBe('n');\n    });\n\n    test('getShortMode cut CTRL- from mode', async () => {\n      const resultPromise = nvim.getShortMode();\n      transportMock.emit('nvim:data', [1, 3, null, { mode: 'CTRL-n' }]);\n      expect(await resultPromise).toBe('n');\n    });\n  });\n\n  test('emit `close` when transport emits `nvim:close`', () => {\n    const callback1 = jest.fn();\n    const callback2 = jest.fn();\n\n    nvim.on('close', callback1);\n    nvim.on('close', callback2);\n\n    transportMock.emit('nvim:close');\n\n    expect(callback1).toHaveBeenCalled();\n    expect(callback2).toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "packages/nvim/src/__tests__/ProcNvimTransport.test.ts",
    "content": "import { EventEmitter } from 'events';\nimport { PassThrough } from 'stream';\nimport { encode } from 'msgpack-lite';\n\nimport type { ChildProcessWithoutNullStreams } from 'child_process';\n\nimport ProcNvimTransport from 'src/ProcNvimTransport';\n\ndescribe('ProcNvimTransport', () => {\n  let proc: ChildProcessWithoutNullStreams;\n  let transport: ProcNvimTransport;\n  const onData = jest.fn();\n\n  const remoteTransport = Object.assign(new EventEmitter(), {\n    send: jest.fn(),\n  });\n\n  beforeEach(() => {\n    proc = Object.assign(new EventEmitter(), {\n      stdout: new PassThrough(),\n      stdin: new PassThrough(),\n    } as unknown) as ChildProcessWithoutNullStreams;\n    proc.stdin.on('data', onData);\n\n    transport = new ProcNvimTransport(proc, remoteTransport);\n  });\n\n  test('transport receives `nvim:data` event with msgpack-encoded data from proc.stdout', () => {\n    const readCallback = jest.fn();\n    transport.on('nvim:data', readCallback);\n    proc.stdout.push(encode('hello'));\n    expect(readCallback).toHaveBeenCalledWith('hello');\n  });\n\n  test('transport emits nvim:close when proc is closed', () => {\n    const handleClose = jest.fn();\n    transport.on('nvim:close', handleClose);\n    proc.emit('close');\n    expect(handleClose).toHaveBeenCalled();\n  });\n\n  test('send to `nvim:write` writes msgpack-encoded data to stdin', async () => {\n    transport.send('nvim:write', 10, 'command', ['param1', 'param2']);\n    expect(onData).toHaveBeenCalledWith(encode([0, 10, 'command', ['param1', 'param2']]));\n  });\n\n  test(\"don't write to stdin if it is not writable\", async () => {\n    proc.stdin.end();\n    transport.send('nvim:write', 10, 'command', ['param1', 'param2']);\n    expect(onData).not.toHaveBeenCalled();\n  });\n\n  describe('remoteTransport', () => {\n    test('receives and relays to proc.stin `nvim-send` event from remoteTransport', () => {\n      remoteTransport.emit('nvim:write', 1, 'command', ['params']);\n      expect(onData).toHaveBeenCalledWith(encode([0, 1, 'command', ['params']]));\n    });\n\n    test('send `nvim:close` event to remoteTransport on close', () => {\n      proc.emit('close');\n      expect(remoteTransport.send).toHaveBeenCalledWith('nvim:close');\n    });\n\n    test('translate nvim proc stdout data to remoteTransport', () => {\n      proc.stdout.push(encode('hello'));\n      expect(remoteTransport.send).toHaveBeenCalledWith('nvim:data', 'hello');\n    });\n\n    test('has attachRemoteTransport method', () => {\n      transport = new ProcNvimTransport(proc);\n      transport.attachRemoteTransport(remoteTransport);\n      proc.stdout.push(encode('hello'));\n      expect(remoteTransport.send).toHaveBeenCalledWith('nvim:data', 'hello');\n    });\n  });\n});\n"
  },
  {
    "path": "packages/nvim/src/__tests__/process.test.ts",
    "content": "import { PassThrough } from 'stream';\nimport { spawn } from 'child_process';\nimport type { ChildProcessWithoutNullStreams } from 'child_process';\n\nimport startNvimProcess from 'src/process';\n\njest.mock('child_process');\n\nconst mockedSpawn = jest.mocked(spawn);\n\nmockedSpawn.mockImplementation(\n  () =>\n    (({\n      stderr: new PassThrough(),\n      stdout: new PassThrough(),\n      stdin: new PassThrough(),\n    } as unknown) as ChildProcessWithoutNullStreams),\n);\n\ndescribe('startNvimProcess', () => {\n  test('init nvim process with spawn', () => {\n    startNvimProcess();\n    expect(mockedSpawn).toHaveBeenCalledWith(\n      'nvim',\n      ['--embed', '--cmd', 'source bin/vv.vim'],\n      expect.anything(),\n    );\n  });\n\n  test.todo('TODO: test vvSourceCommand');\n  test.todo('TODO: test nvimCommand');\n  test.todo('TODO: test env');\n  test.todo('TODO: test cwd');\n});\n"
  },
  {
    "path": "packages/nvim/src/__tests__/utils.test.ts",
    "content": "import { execSync } from 'child_process';\n\nimport { shellEnv, nvimVersion, resetCache } from 'src/utils';\n\njest.mock('child_process');\n\nconst mockedExecSync = jest.mocked((execSync as unknown) as () => string);\n\ndescribe('utils', () => {\n  beforeEach(() => {\n    resetCache();\n  });\n\n  describe('shellEnv', () => {\n    const fakeProc = (env = {}) =>\n      ({\n        env,\n      } as NodeJS.Process);\n\n    test('returns env from bash', () => {\n      mockedExecSync.mockReturnValue(`key1=val1\\nkey2=val2`);\n      expect(shellEnv(fakeProc())).toEqual({ key1: 'val1', key2: 'val2' });\n      expect(mockedExecSync).toHaveBeenCalledWith('/bin/bash -ilc env', { encoding: 'utf-8' });\n    });\n\n    test('returns original env if it has SHLVL', () => {\n      mockedExecSync.mockReturnValue(`key1=val1\\nkey2=val2`);\n      expect(shellEnv(fakeProc({ SHLVL: true, key: 'val' }))).toEqual({\n        SHLVL: true,\n        key: 'val',\n      });\n    });\n\n    test('add default path if something happens', () => {\n      mockedExecSync.mockImplementationOnce(() => {\n        throw new Error();\n      });\n      expect(shellEnv(fakeProc({ PATH: 'some/path', key: 'val' }))).toEqual({\n        PATH: '/usr/local/bin:/opt/homebrew/bin:some/path',\n        key: 'val',\n      });\n    });\n  });\n\n  describe('nvimVersion', () => {\n    test('find version string from `nvim --version`', () => {\n      mockedExecSync.mockReturnValue(`Something\nNVIM v1.2.3\nSomething else`);\n      expect(nvimVersion()).toBe('1.2.3');\n      expect(mockedExecSync).toHaveBeenCalledWith('nvim --version', expect.any(Object));\n    });\n  });\n});\n"
  },
  {
    "path": "packages/nvim/src/browser.ts",
    "content": "// Only use relative imports here because https://github.com/microsoft/TypeScript/issues/32999#issuecomment-523558695\n\nimport Nvim from './Nvim';\n\nexport * from './types';\n\nexport default Nvim;\n"
  },
  {
    "path": "packages/nvim/src/index.ts",
    "content": "// Only use relative imports here because https://github.com/microsoft/TypeScript/issues/32999#issuecomment-523558695\n// TODO: Bundle .d.ts or something\n\nimport Nvim from './Nvim';\n\nexport { default as startNvimProcess } from './process';\n\nexport { default as ProcNvimTransport } from './ProcNvimTransport';\n\nexport * from './types';\n\nexport { Nvim };\n\nexport { shellEnv, nvimCommand, nvimVersion } from './utils';\n\nexport default Nvim;\n"
  },
  {
    "path": "packages/nvim/src/process.ts",
    "content": "import { spawn } from 'child_process';\nimport path from 'path';\n\nimport debounce from 'lodash/debounce';\n\nimport { shellEnv, isDev, nvimCommand } from 'src/utils';\n\nimport type { ChildProcessWithoutNullStreams } from 'child_process';\n\nconst vvSourceCommand = (appPath?: string) =>\n  appPath ? `source ${path.join(appPath, isDev('./', '../'), 'bin/vv.vim')}` : 'source bin/vv.vim';\n\nlet nvimProcess;\n\nconst startNvimProcess = ({\n  args = [],\n  cwd,\n  appPath,\n}: {\n  args?: string[];\n  cwd?: string;\n  appPath?: string;\n} = {}): ChildProcessWithoutNullStreams => {\n  const env = shellEnv();\n\n  const nvimArgs = ['--embed', '--cmd', vvSourceCommand(appPath), ...args];\n\n  nvimProcess = spawn(nvimCommand(env), nvimArgs, { cwd, env });\n\n  // Pipe errors to std output and also send it in console as error.\n  let errorStr = '';\n  nvimProcess.stderr.pipe(process.stdout);\n  nvimProcess.stderr.on('data', (data) => {\n    errorStr += data.toString();\n    debounce(() => {\n      if (errorStr) console.error(errorStr); // eslint-disable-line no-console\n      errorStr = '';\n    }, 10)();\n  });\n\n  // nvimProcess.stdout.on('data', (data) => {\n  //   console.log(data.toString());\n  // });\n\n  return nvimProcess;\n};\n\nexport default startNvimProcess;\n"
  },
  {
    "path": "packages/nvim/src/types.ts",
    "content": "/* eslint-disable camelcase */\n\nimport type { EventEmitter } from 'events';\nimport type TypedEventEmitter from 'strict-event-emitter-types';\n\n// Only use relative imports here because https://github.com/microsoft/TypeScript/issues/32999#issuecomment-523558695\n// TODO: Bundle .d.ts or something\nimport type {\n  UiEvents as UiEventsOriginal,\n  NvimCommands as NvimCommandsOriginal,\n} from './__generated__/types';\nimport { nvimCommandNames } from './__generated__/constants';\n\nexport type RequestMessage = [0, number, string, any[]];\nexport type ResponseMessage = [1, number, any, any];\nexport type NotificationMessage = [2, string, any[]];\n\nexport type MessageType = RequestMessage | ResponseMessage | NotificationMessage;\nexport type ReadCallback = (message: MessageType) => void;\nexport type OnCloseCallback = () => void;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type Args = any[];\n\nexport type Listener = (...args: Args) => void;\n\n/**\n * Remote transport between server or main and renderer.\n * Use emitter events (`on`, `once` etc) for receiving message, and `send` to send message to other side.\n */\nexport type Transport = EventEmitter & {\n  /**\n   * Send message to remote\n   */\n  send: (channel: string, ...args: Args) => void;\n};\n\n// Manual refine of the auto-generated UiEvents\n// More info: https://neovim.io/doc/user/ui.html\n\nexport type ModeInfo = {\n  cursor_shape: 'block' | 'horizontal' | 'vertical';\n  cell_percentage: number;\n  blinkwait: number;\n  blinkon: number;\n  blinkoff: number;\n  attr_id: number;\n  attr_id_lm: number;\n  short_name: string; // TODO: union\n  name: string; // TODO: union\n  mouse_shape: number;\n};\n\n// TODO: refine this type as a union of `[option, value]` with the correct value type for each option.\nexport type OptionSet = [\n  option:\n    | 'arabicshape'\n    | 'ambiwidth'\n    | 'emoji'\n    | 'guifont'\n    | 'guifontwide'\n    | 'linespace'\n    | 'mousefocus'\n    | 'pumblend'\n    | 'showtabline'\n    | 'termguicolors'\n    | 'rgb'\n    | 'ext_cmdline'\n    | 'ext_popupmenu'\n    | 'ext_tabline'\n    | 'ext_wildmenu'\n    | 'ext_messages'\n    | 'ext_linegrid'\n    | 'ext_multigrid'\n    | 'ext_hlstate'\n    | 'ext_termcolors',\n  value: boolean | string,\n];\n\nexport type HighlightAttrs = {\n  foreground?: number;\n  background?: number;\n  special?: number;\n  reverse?: boolean;\n  standout?: boolean;\n  italic?: boolean;\n  bold?: boolean;\n  underline?: boolean;\n  undercurl?: boolean;\n  strikethrough?: boolean;\n  blend?: number;\n};\n\nexport type Cell = [text: string, hl_id?: number, repeat?: number];\n\ntype UiEventsPatch = {\n  mode_info_set: [enabled: boolean, cursor_styles: ModeInfo[]];\n  option_set: OptionSet;\n  hl_attr_define: [id: number, rgb_attrs: HighlightAttrs, cterm_attrs: HighlightAttrs, info: []];\n  grid_line: [grid: number, row: number, col_start: number, cells: Cell[]];\n};\n\nexport type UiEvents = Omit<UiEventsOriginal, keyof UiEventsPatch> & UiEventsPatch;\n\nexport type UiEventsHandlers = {\n  [Key in keyof UiEvents]: (params: Array<UiEvents[Key]>) => void;\n};\n\ntype UiEventsArgsByKey = {\n  [Key in keyof UiEvents]: [Key, ...Array<UiEvents[Key]>];\n};\n\nexport type UiEventsArgs = Array<UiEventsArgsByKey[keyof UiEventsArgsByKey]>;\n\nexport interface NvimEvents {\n  redraw: (args: UiEventsArgs) => void;\n\n  close: () => void;\n\n  [x: string]: (...args: any[]) => void;\n}\n\ntype NvimCommandsPatch = {\n  nvim_get_mode: () => { mode: string };\n};\n\nexport type NvimCommands = Omit<NvimCommandsOriginal, keyof NvimCommandsPatch> & NvimCommandsPatch;\n\ntype NvimCommandsMethods = {\n  [K in keyof typeof nvimCommandNames]: <\n    Return = ReturnType<NvimCommands[typeof nvimCommandNames[K]]>\n  >(\n    ...args: Parameters<NvimCommands[typeof nvimCommandNames[K]]>\n  ) => Promise<Return>;\n};\nexport type NvimInterface = TypedEventEmitter<EventEmitter, NvimEvents> & NvimCommandsMethods;\n"
  },
  {
    "path": "packages/nvim/src/utils.ts",
    "content": "import { execSync } from 'child_process';\n\ntype IsDevFunction = {\n  <T, F>(dev: T, notDev: F): T | F;\n  (): boolean;\n};\n\nexport const isDev: IsDevFunction = (dev = true, notDev = false) =>\n  process.env.NODE_ENV === 'development' ? dev : notDev;\n\nexport const nvimCommand = (env: NodeJS.ProcessEnv = {}): string =>\n  env.VV_NVIM_COMMAND || process.env.VV_NVIM_COMMAND || 'nvim';\n\n/**\n * Cached patched `process.env` used in `shellEnv` function.\n */\nlet env: NodeJS.ProcessEnv | undefined;\n\n/**\n * Find env variables if the app is started from Finder. We need a correct PATH variable to\n * start nvim.\n */\nexport const shellEnv = (proc = process): NodeJS.ProcessEnv => {\n  if (!env) {\n    env = proc.env;\n    // If we start app from terminal, it will have SHLVL variable. Then we already have correct\n    // env variables and can skip this.\n    if (!env.SHLVL) {\n      try {\n        // Try to get user's default shell and get env from it.\n        const envString = execSync(`${env.SHELL || '/bin/bash'} -ilc env`, { encoding: 'utf-8' });\n        env = envString\n          .split('\\n')\n          .filter(Boolean)\n          .reduce((result, line) => {\n            const [key, ...vals] = line.split('=');\n            return {\n              ...result,\n              [key]: vals.join('='),\n            };\n          }, {});\n      } catch (e) {\n        // Most likely nvim is here:\n        // * `/usr/local/bin` Homebrew default bin path\n        // * `/opt/homebrew/bin` Homebrew bin path for Apple Silicon (https://docs.brew.sh/Installation)\n        env.PATH = `/usr/local/bin:/opt/homebrew/bin:${env.PATH}`;\n      }\n    }\n  }\n  return env;\n};\n\n/**\n * Cached Neovim version used in `nvimVersion` function.\n */\nlet version: string | undefined | null;\n\n/**\n * Get Neovim version string.\n */\nexport const nvimVersion = (): string | undefined | null => {\n  if (version !== undefined) return version;\n\n  const shEnv = shellEnv();\n  try {\n    const execResult = execSync(`${nvimCommand(shEnv)} --version`, {\n      encoding: 'utf-8',\n      env: shEnv,\n    });\n    if (execResult) {\n      const match = execResult.match(/NVIM v(\\d+)\\.(\\d+).(\\d+)(.*)/);\n      if (match) {\n        version = `${match[1]}.${match[2]}.${match[3]}${match[4]}`;\n      }\n    }\n  } catch (e) {\n    version = null;\n  }\n  return version;\n};\n\n/** @deprecated helper function for tests */\nexport const resetCache = (): void => {\n  version = undefined;\n  env = undefined;\n};\n"
  },
  {
    "path": "packages/nvim/tsconfig.declaration.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"emitDeclarationOnly\": true,\n    \"declarationMap\": true,\n    \"noEmit\": false,\n    \"outDir\": \"dist\"\n  },\n  \"include\": [\"src/index.ts\", \"src/browser.ts\", \"@types\"]\n}\n"
  },
  {
    "path": "packages/nvim/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\"\n  },\n  \"include\": [\"src\", \"@types\"]\n}\n"
  },
  {
    "path": "packages/server/README.md",
    "content": "# VV Server\n\nRun Neovim remotely in browser via VV Server:\n\n```\nyarn start\n```\n\nThen open [http://localhost:3000](http://localhost:3000)\n\nYou can run in in dev mode in watch mode:\n\n```\nyarn dev\n```\n\nServer is in a very early stage of development. Please check this milestone for development status: [https://github.com/vv-vim/vv/milestone/1](https://github.com/vv-vim/vv/milestone/1).\n"
  },
  {
    "path": "packages/server/babel.config.json",
    "content": "{\n  \"extends\": \"../../babel.config.json\"\n}\n"
  },
  {
    "path": "packages/server/bin/vv.vim",
    "content": "let g:vv = 1\n\nsource <sfile>:h/vvset.vim\n\nset termguicolors\n\nautocmd VimEnter * call rpcnotify(get(g:, \"vv_channel\", 1), \"vv:vim_enter\")\n\n\" Send unsaved buffers to client\nfunction! VVunsavedBuffers()\n  let l:buffers = getbufinfo()\n  call filter(l:buffers, \"v:val['changed'] == 1\")\n  let l:buffers = map(l:buffers , \"{ 'name': v:val['name'] }\" )\n  return l:buffers\nendfunction\n"
  },
  {
    "path": "packages/server/bin/vvset.vim",
    "content": "let g:vv_settings_synonims = {\n\\  'fu': 'fullscreen',\n\\  'sfu': 'simplefullscreen',\n\\  'width': 'windowwidth',\n\\  'height': 'windowheight',\n\\  'top': 'windowtop',\n\\  'left': 'windowleft',\n\\  'openinproject': 'openInProject'\n\\}\n\nlet g:vv_default_settings = {\n\\  'fullscreen': 0,\n\\  'simplefullscreen': 1,\n\\  'bold': 1,\n\\  'italic': 1,\n\\  'underline': 1,\n\\  'undercurl': 1,\n\\  'strikethrough': 1,\n\\  'fontfamily': 'monospace',\n\\  'fontsize': 12,\n\\  'lineheight': 1.25,\n\\  'letterspacing': 0,\n\\  'reloadchanged': 0,\n\\  'windowwidth': v:null,\n\\  'windowheight': v:null,\n\\  'windowleft': v:null,\n\\  'windowtop': v:null,\n\\  'quitoncloselastwindow': 0,\n\\  'autoupdateinterval': 1440,\n\\  'openInProject': 1\n\\}\n\nlet g:vv_settings = deepcopy(g:vv_default_settings)\n\n\" Custom VVset command, mimic default set command (:help set) with\n\" settings specified in g:vv_default_settings\nfunction! VVset(...)\n  for arg in a:000\n    call VVsetItem(arg)\n  endfor\nendfunction\n\nfunction! VVsettingValue(name)\n  let l:name = VVsettingName(a:name)\n  if has_key(g:vv_settings, l:name)\n    return g:vv_settings[l:name]\n  else\n    echoerr \"Unknown option: \".a:name\n  endif\nendfunction\n\nfunction! VVsettingName(name)\n  if has_key(g:vv_settings_synonims, a:name)\n    return g:vv_settings_synonims[a:name]\n  else\n    return a:name\n  endif\nendfunction\n\nfunction! VVsetItem(name)\n  if a:name == 'all'\n    echo g:vv_settings\n    return\n  elseif a:name =~ '?'\n    let l:name = VVsettingName(split(a:name, '?')[0])\n    echo VVsettingValue(l:name)\n    return\n  elseif a:name =~ '&'\n    let l:name = VVsettingName(split(a:name, '&')[0])\n    if l:name == 'all'\n      let g:vv_settings = deepcopy(g:vv_default_settings)\n      call VVsettings()\n      return\n    elseif has_key(g:vv_default_settings, l:name)\n      let l:value = g:vv_default_settings[l:name]\n    else\n      echoerr \"Unknown option: \".l:name\n      return\n    endif\n  elseif a:name =~ '+='\n    let l:split = split(a:name, '+=')\n    let l:name = VVsettingName(l:split[0])\n    let l:value = VVsettingValue(l:name) + l:split[1]\n  elseif a:name =~ '-='\n    let l:split = split(a:name, '-=')\n    let l:name = VVsettingName(l:split[0])\n    let l:value = VVsettingValue(l:name) - l:split[1]\n  elseif a:name =~ '\\^='\n    let l:split = split(a:name, '\\^=')\n    let l:name = VVsettingName(l:split[0])\n    let l:value = VVsettingValue(l:name) * l:split[1]\n  elseif a:name =~ '='\n    let l:split = split(a:name, '=')\n    let l:name = l:split[0]\n    let l:value = l:split[1]\n  elseif a:name =~ ':'\n    let l:split = split(a:name, ':')\n    let l:name = l:split[0]\n    let l:value = l:split[1]\n  elseif a:name =~ '!'\n    let l:name = VVsettingName(split(a:name, '!')[0])\n    if VVsettingValue(l:name) == 0\n      let l:value = 1\n    else\n      let l:value = 0\n    endif\n  elseif a:name =~ '^inv'\n    let l:name = VVsettingName(strpart(a:name, 3))\n    if VVsettingValue(l:name) == 0\n      let l:value = 1\n    else\n      let l:value = 0\n    endif\n  elseif a:name =~ '^no'\n    let l:name = strpart(a:name, 2)\n    let l:value = 0\n  else\n    let l:name = a:name\n    let l:value = 1\n  endif\n\n  let l:name = VVsettingName(l:name)\n\n  if has_key(g:vv_settings, l:name)\n    let g:vv_settings[l:name] = l:value\n    call rpcnotify(get(g:, \"vv_channel\", 1), \"vv:set\", l:name, l:value)\n  else\n    echoerr \"Unknown option: \".l:name\n  endif\nendfunction\n\nfunction! VVsettings()\n  for key in keys(g:vv_settings)\n    call rpcnotify(get(g:, \"vv_channel\", 1), \"vv:set\", key, g:vv_settings[key])\n  endfor\nendfunction\n\ncommand! -nargs=* VVset :call VVset(<f-args>)\ncommand! -nargs=* VVse :call VVset(<f-args>)\ncommand! -nargs=0 VVsettings :call VVsettings() \" Send all settings to client\n"
  },
  {
    "path": "packages/server/config/webpack.common.config.js",
    "content": "const path = require('path');\n\nconst buildPath = path.resolve(__dirname, './../build');\n\nmodule.exports = {\n  mode: 'development',\n  output: {\n    path: buildPath,\n  },\n  resolve: {\n    extensions: ['.ts', '.js'],\n  },\n  module: {\n    rules: [\n      {\n        test: /\\.(js|ts)$/,\n        exclude: /node_modules/,\n        loader: 'babel-loader',\n      },\n    ],\n  },\n};\n"
  },
  {
    "path": "packages/server/config/webpack.config.js",
    "content": "const rendererConfig = require('./webpack.renderer.config');\nconst serverConfig = require('./webpack.server.config');\n\nmodule.exports = [rendererConfig, serverConfig];\n"
  },
  {
    "path": "packages/server/config/webpack.prod.config.js",
    "content": "const { merge } = require('webpack-merge');\n\nconst rendererConfig = require('./webpack.renderer.config');\nconst serverConfig = require('./webpack.server.config');\n\nconst prod = {\n  mode: 'production',\n  devtool: 'source-map',\n};\n\nconst rendererConfigProd = merge(rendererConfig, prod);\nconst mainConfigProd = merge(serverConfig, prod);\n\nmodule.exports = [rendererConfigProd, mainConfigProd];\n"
  },
  {
    "path": "packages/server/config/webpack.renderer.config.js",
    "content": "const { merge } = require('webpack-merge');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst common = require('./webpack.common.config');\n\nconst config = merge(common, {\n  entry: './src/renderer/index.ts',\n  output: {\n    filename: 'renderer.js',\n  },\n  plugins: [\n    new HtmlWebpackPlugin({\n      template: './src/renderer/index.html',\n    }),\n  ],\n  target: 'web',\n  devtool: 'eval-cheap-source-map',\n});\n\nmodule.exports = config;\n"
  },
  {
    "path": "packages/server/config/webpack.server.config.js",
    "content": "const { merge } = require('webpack-merge');\nconst common = require('./webpack.common.config');\n\nconst config = merge(common, {\n  entry: './src/server/index.ts',\n  target: 'node',\n  output: {\n    filename: 'server.js',\n  },\n  resolve: {\n    extensions: ['.ts', '.js'],\n  },\n});\n\nmodule.exports = config;\n"
  },
  {
    "path": "packages/server/jest.config.js",
    "content": "module.exports = {\n  clearMocks: true,\n  moduleNameMapper: {\n    'src/(.*)': ['<rootDir>/src/$1'],\n  },\n};\n"
  },
  {
    "path": "packages/server/package.json",
    "content": "{\n  \"name\": \"@vvim/server\",\n  \"version\": \"0.0.1\",\n  \"description\": \"VV Server: Run Neovim remotely in browser\",\n  \"author\": \"Igor Gladkoborodov <igor.gladkoborodov@gmail.com>\",\n  \"keywords\": [\n    \"vim\",\n    \"neovim\",\n    \"client\",\n    \"gui\",\n    \"electron\"\n  ],\n  \"license\": \"MIT\",\n  \"main\": \"./build/main.js\",\n  \"sideEffects\": false,\n  \"scripts\": {\n    \"test\": \"jest\",\n    \"clean\": \"rm -rf dist/*\",\n    \"webpack:dev\": \"webpack --watch --config ./config/webpack.config.js\",\n    \"webpack:prod\": \"webpack --config ./config/webpack.prod.config.js\",\n    \"server:dev\": \"nodemon build/server.js\",\n    \"server\": \"node build/server.js\",\n    \"dev\": \"npm-run-all --parallel webpack:dev server:dev\",\n    \"build\": \"npm-run-all clean webpack:prod\",\n    \"start\": \"yarn server\"\n  },\n  \"browserslist\": [\n    \"maintained node versions\"\n  ],\n  \"devDependencies\": {\n    \"@types/express\": \"^4.17.11\",\n    \"@types/lodash\": \"^4.14.168\",\n    \"@types/node\": \"^14.14.31\",\n    \"@types/ws\": \"^7.4.0\",\n    \"html-webpack-plugin\": \"^5.6.0\",\n    \"node-fetch\": \"^2.6.7\",\n    \"nodemon\": \"^2.0.7\"\n  },\n  \"dependencies\": {\n    \"@vvim/browser-renderer\": \"0.0.1\",\n    \"@vvim/nvim\": \"0.0.1\",\n    \"express\": \"^4.17.1\",\n    \"lodash\": \"^4.17.21\",\n    \"semver\": \"^7.5.2\",\n    \"ws\": \"^7.4.6\"\n  }\n}\n"
  },
  {
    "path": "packages/server/src/lib/isDev.ts",
    "content": "type IsDevFunction = {\n  <T, F>(dev: T, notDev: F): T | F;\n  (): boolean;\n};\n\nconst isDev: IsDevFunction = (dev = true, notDev = false) =>\n  process.env.NODE_ENV === 'development' ? dev : notDev;\n\nexport default isDev;\n"
  },
  {
    "path": "packages/server/src/renderer/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\" />\n    <title>VV</title>\n  </head>\n  <body>\n    <style>\n      html,\n      body {\n        width: 100%;\n        height: 100%;\n        overflow: hidden;\n        margin: 0;\n        padding: 0;\n      }\n    </style>\n  </body>\n</html>\n"
  },
  {
    "path": "packages/server/src/renderer/index.ts",
    "content": "import renderer from '@vvim/browser-renderer';\n\nrenderer();\n"
  },
  {
    "path": "packages/server/src/server/index.ts",
    "content": "import express from 'express';\nimport http from 'http';\n\nimport initNvim from 'src/server/nvim/nvim';\nimport { getDefaultSettings } from 'src/server/nvim/settings';\nimport websocketTransport from 'src/server/transport/websocket';\n\nimport { Transport } from '@vvim/nvim';\n\nconst { PORT = 3000 } = process.env;\n\nconst app = express();\nconst server = http.createServer(app);\n\napp.use(express.static('build'));\n\nconst onConnect = (transport: Transport) => {\n  const args = process.argv.slice(2);\n\n  initNvim({\n    transport,\n    args,\n  });\n  transport.send('initRenderer', getDefaultSettings());\n};\n\nwebsocketTransport({ server, onConnect });\n\nserver.listen(PORT, () => {\n  // eslint-disable-next-line no-console\n  console.log(`Server started at http://localhost:${PORT}`);\n});\n"
  },
  {
    "path": "packages/server/src/server/nvim/nvim.ts",
    "content": "// TODO\n// import quit from '@main/nvim/features/quit';\n// import windowTitle from '@main/nvim/features/windowTitle';\n// import zoom from '@main/nvim/features/zoom';\n// import windowSize from '@main/nvim/features/windowSize';\n// import focusAutocmd from '@main/nvim/features/focusAutocmd';\n\nimport initSettings from 'src/server/nvim/settings';\n\nimport Nvim, { startNvimProcess, ProcNvimTransport, Transport } from '@vvim/nvim';\n\nconst initNvim = ({\n  args,\n  cwd,\n  transport,\n}: {\n  args?: string[];\n  cwd?: string;\n  transport: Transport;\n}): void => {\n  const proc = startNvimProcess({ args, cwd });\n  const nvimTransport = new ProcNvimTransport(proc, transport);\n  const nvim = new Nvim(nvimTransport);\n\n  initSettings({ nvim, args, transport });\n\n  // TODO\n  // nvim.on('disconnect', () => {});\n};\n\nexport default initNvim;\n"
  },
  {
    "path": "packages/server/src/server/nvim/settings.ts",
    "content": "import debounce from 'lodash/debounce';\n\nimport type { Nvim, Transport } from '@vvim/nvim';\n\ntype BooleanSetting = 0 | 1;\n\nexport type Settings = {\n  fullscreen: BooleanSetting;\n  simplefullscreen: BooleanSetting;\n  bold: BooleanSetting;\n  italic: BooleanSetting;\n  underline: BooleanSetting;\n  undercurl: BooleanSetting;\n  strikethrough: BooleanSetting;\n  fontfamily: string;\n  fontsize: string; // TODO: number\n  lineheight: string; // TODO: number\n  letterspacing: string; // TODO: number\n  reloadchanged: BooleanSetting;\n  quitoncloselastwindow: BooleanSetting;\n  autoupdateinterval: string; // TODO: number\n  openInProject: BooleanSetting;\n};\n\nexport type SettingsCallback = (newSettings: Partial<Settings>, allSettings: Settings) => void;\n\nexport const getDefaultSettings = (): Settings => ({\n  fullscreen: 0,\n  simplefullscreen: 1,\n  bold: 1,\n  italic: 1,\n  underline: 1,\n  undercurl: 1,\n  strikethrough: 1,\n  fontfamily: 'monospace',\n  fontsize: '12',\n  lineheight: '1.25',\n  letterspacing: '0',\n  reloadchanged: 0,\n  quitoncloselastwindow: 0,\n  autoupdateinterval: '1440', // One day, 60*24 minutes\n  openInProject: 0,\n});\n\nlet hasCustomConfig = false;\n\nconst initSettings = ({\n  nvim,\n  args,\n  transport,\n}: {\n  nvim: Nvim;\n  args?: string[];\n  transport: Transport;\n}): void => {\n  hasCustomConfig = args?.indexOf('-u') !== -1;\n  let initialSettings: Settings | null = getDefaultSettings();\n  let settings = getDefaultSettings();\n\n  let newSettings: Partial<Settings> = {};\n\n  const applyAllSettings = async () => {\n    settings = {\n      ...settings,\n      ...newSettings,\n    };\n\n    // If we have initial settings newSetting will be only those that different from initialSettings. We\n    // aleady applied initialSettings when we created a window.\n    if (initialSettings && !hasCustomConfig) {\n      newSettings = Object.keys(settings).reduce<Partial<Settings>>((result, key) => {\n        // @ts-expect-error TODO FIXME\n        if (initialSettings[key] !== settings[key]) {\n          return {\n            ...result,\n            // @ts-expect-error TODO FIXME\n            [key]: settings[key],\n          };\n        }\n        return result;\n      }, {});\n      initialSettings = null;\n    }\n\n    transport.send('updateSettings', newSettings, settings);\n\n    newSettings = {};\n  };\n\n  const debouncedApplyAllSettings = debounce(applyAllSettings, 10);\n\n  const applySetting = <K extends keyof Settings>([option, props]: [K, Settings[K]]) => {\n    if (props !== null) {\n      newSettings[option] = props;\n      debouncedApplyAllSettings();\n    }\n  };\n\n  nvim.on('vv:set', applySetting);\n};\n\nexport default initSettings;\n"
  },
  {
    "path": "packages/server/src/server/transport/websocket.ts",
    "content": "import WebSocket from 'ws';\nimport { Server } from 'http';\n\nimport { Transport, Args } from '@vvim/nvim';\n\nimport { EventEmitter } from 'events';\n\nclass WsTransport extends EventEmitter implements Transport {\n  ws: WebSocket;\n\n  constructor(ws: WebSocket) {\n    super();\n\n    this.ws = ws;\n\n    this.ws.on('message', (data: string) => {\n      try {\n        const [channel, ...args] = JSON.parse(data);\n        this.emit(channel, ...args);\n      } catch (e) {\n        /* empty */\n      }\n    });\n  }\n\n  send(channel: string, ...args: Args) {\n    this.ws.send(JSON.stringify([channel, ...args]));\n  }\n}\n\n/**\n * Init transport between main and renderer via websocket on server side.\n */\nconst transport = ({\n  server,\n  onConnect,\n}: {\n  server: Server;\n  onConnect: (t: Transport) => void;\n}): void => {\n  const wss = new WebSocket.Server({ server });\n\n  // TODO: handle disconnect\n  wss.on('connection', (ws) => {\n    onConnect(new WsTransport(ws));\n  });\n};\n\nexport default transport;\n"
  },
  {
    "path": "packages/server/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\"\n  },\n  \"include\": [\"src\", \"@types\"]\n}\n"
  },
  {
    "path": "scripts/codegen.ts",
    "content": "/* eslint-disable camelcase */\n\nimport { spawn } from 'child_process';\nimport { createDecodeStream, encode } from 'msgpack-lite';\nimport { writeFileSync } from 'fs';\nimport prettier from 'prettier';\nimport camelCase from 'lodash/camelCase';\n\nconst TYPES_FILE_NAME = 'packages/nvim/src/__generated__/types.ts';\nconst CONST_FILE_NAME = 'packages/nvim/src/__generated__/constants.ts';\n\nconst nvimProcess = spawn('nvim', ['--embed', '-u', 'NONE']);\n\nnvimProcess.stderr.pipe(process.stdout);\n\nconst decodeStream = createDecodeStream();\nconst msgpackIn = nvimProcess.stdout.pipe(decodeStream);\n\nconst replaceType = (originalType: string) => {\n  const replacements = {\n    Array: 'Array<any>',\n    String: 'string',\n    Integer: 'number',\n    Boolean: 'boolean',\n    Float: 'number',\n    Dictionary: 'Record<string, any>',\n    Object: 'any',\n    Window: 'number',\n    Buffer: 'number',\n    Tabpage: 'number',\n    LuaRef: 'any',\n    'ArrayOf(String)': 'string[]',\n    'ArrayOf(Integer)': 'number[]',\n    'ArrayOf(Integer, 2)': '[number, number]',\n    'ArrayOf(Dictionary)': 'Record<string, any>[]',\n    'ArrayOf(Window)': 'number[]',\n    'ArrayOf(Buffer)': 'number[]',\n    'ArrayOf(Tabpage)': 'number[]',\n  } as Record<string, string>;\n  return replacements[originalType] || originalType;\n};\n\nconst replaceName = (originalName: string) => {\n  const replacements = {\n    window: 'win',\n  } as Record<string, string>;\n\n  return replacements[originalName] || originalName;\n};\n\nmsgpackIn.on('data', (data) => {\n  const apiInfo = data[3][1];\n  writeFileSync('tmp/apiInfo.json', JSON.stringify(apiInfo, null, 2), { encoding: 'utf8' });\n  const { ui_events, functions } = apiInfo;\n\n  let result: string[] = [];\n\n  const version = [apiInfo.version.major, apiInfo.version.minor, apiInfo.version.patch].join('.');\n\n  result.push('/* eslint-disable camelcase */');\n  result.push('/**');\n  result.push(' * Types generated by `yarn generate-types`. Do not edit manually.');\n  result.push(' * ');\n  result.push(` * Version: ${version}`);\n  result.push(` * Api Level: ${apiInfo.version.api_level}`);\n  result.push(` * Api Compatible: ${apiInfo.version.api_compatible}`);\n  result.push(` * Api Prerelease: ${apiInfo.version.api_prerelease}`);\n  result.push(' */');\n  result.push('');\n\n  result.push('/**');\n  result.push(' * UI events types emitted by `redraw` event. Do not edit manually.');\n  result.push(' * More info: https://neovim.io/doc/user/ui.html');\n  result.push(' */');\n\n  result.push('export type UiEvents = {');\n  ui_events.forEach(({ name, parameters }: { name: string; parameters: string[][] }) => {\n    const parametersType = parameters.map(([type, typeName]) => {\n      return `${typeName}: ${replaceType(type)}`;\n    });\n    result.push(`  ${name}: [${parametersType.join(', ')}];\\n`);\n  });\n  result.push('}\\n');\n\n  result.push('/**');\n  result.push(' * Nvim commands.');\n  result.push(' * More info: https://neovim.io/doc/user/api.html');\n  result.push(' */');\n\n  result.push('export type NvimCommands = {');\n  functions\n    .filter((f) => !f.deprecated_since)\n    .forEach(\n      ({\n        name,\n        parameters,\n        return_type,\n      }: {\n        name: string;\n        parameters: string[][];\n        return_type: string;\n      }) => {\n        const parametersType = parameters.map(([type, typeName]) => {\n          return `${replaceName(typeName)}: ${replaceType(type)}`;\n        });\n        result.push(`  ${name}: (${parametersType.join(', ')}) => ${replaceType(return_type)};\\n`);\n      },\n    );\n  result.push('}\\n');\n\n  const prettifiedTypes = prettier.format(result.join('\\n'), { parser: 'typescript' });\n\n  writeFileSync(TYPES_FILE_NAME, prettifiedTypes, {\n    encoding: 'utf8',\n  });\n\n  result = [];\n  result.push('/* eslint-disable camelcase */');\n  result.push('/**');\n  result.push(' * Constants generated by `yarn generate-types`. Do not edit manually.');\n  result.push(' * ');\n  result.push(` * Version: ${version}`);\n  result.push(` * Api Level: ${apiInfo.version.api_level}`);\n  result.push(` * Api Compatible: ${apiInfo.version.api_compatible}`);\n  result.push(` * Api Prerelease: ${apiInfo.version.api_prerelease}`);\n  result.push(' */');\n  result.push('');\n\n  result.push('export const nvimCommandNames = {');\n  functions\n    .filter((f) => !f.deprecated_since)\n    .forEach(({ name }: { name: string }) => {\n      result.push(`  ${camelCase(name.replace('nvim_', ''))}: '${name}',`);\n    });\n  result.push('} as const;\\n');\n\n  const prettifiedConst = prettier.format(result.join('\\n'), { parser: 'typescript' });\n\n  writeFileSync(CONST_FILE_NAME, prettifiedConst, {\n    encoding: 'utf8',\n  });\n});\n\nnvimProcess.stdin.write(encode([0, 1, 'nvim_get_api_info', []]));\n\nsetTimeout(() => {\n  nvimProcess.stdin.write(encode([0, 1, 'nvim_command', ['q']]));\n}, 100);\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"allowJs\": true,\n    \"noEmit\": true,\n    \"strict\": true,\n    \"moduleResolution\": \"node\",\n    \"sourceMap\": true,\n    \"declaration\": true,\n    \"skipLibCheck\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"isolatedModules\": true\n  }\n}\n"
  }
]