[
  {
    "path": ".gitignore",
    "content": "/bower_components\nnode_modules\n/build\nnpm-debug.log\n/src/**/*.js\n/src/**/*.js.map\n/test/e2e/**/*.js\n/test/e2e/**/*.js.map\n\n# for now\n/package-lock.json\n"
  },
  {
    "path": ".npmignore",
    "content": "/Guardfile\n/bower_components\n/test\n/scripts\nnode_modules\nnpm-debug.log\nGuardfile\ntslint.json\n/build/test\n"
  },
  {
    "path": ".prettierrc.json",
    "content": "{\n  \"tabWidth\": 4,\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\",\n  \"printWidth\": 120,\n  \"overrides\": [\n    {\n      \"files\": \"*.css\",\n      \"options\": {\n        \"tabWidth\": 2,\n        \"printWidth\": -1\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\n\nos:\n    - linux\n    - osx\n\nosx_image: xcode10.2\n\ndist: xenial\n\nnode_js:\n    - stable\n\ninstall:\n    - ./scripts/travis-ci-install.sh\n\nscript:\n    - ./scripts/travis-ci-run.sh\n\nnotifications:\n    email:\n        on_success: never\n        on_failure: change\n\naddons:\n    apt:\n        sources:\n            - sourceline: 'ppa:neovim-ppa/unstable'\n        packages:\n            - neovim\n            - g++\n            - build-essential\n            - libcairo2-dev\n            - libpango1.0-dev\n            - libjpeg-dev\n            - libgif-dev\n            - librsvg2-dev\n        update: true\n    homebrew:\n        packages:\n            - neovim\n            - pkg-config\n            - cairo\n            - pango\n            - libpng\n            - giflib\n            - jpeg\n            - librsvg\n        update: true\n"
  },
  {
    "path": "Guardfile",
    "content": "ignore /^node_modules/, /^build/, /^bower_components/\n\ndef npm_run(sub, file)\n  puts \"\\033[93m#{Time.now}: #{File.basename file}\\033[0m\"\n  success = system \"npm run #{sub}\"\n  if success\n    puts \"\\033[92mOK\\033[0m\\n\\n\"\n  else\n    puts \"\\033[91mFAIL\\033[0m\\n\\n\"\n  end\nend\n\ndef exec(cmdline)\n  success = system cmdline\n  if success\n    puts \"\\033[92mOK\\033[0m\\n\\n\"\n  else\n    puts \"\\033[91mFAIL\\033[0m\\n\\n\"\n  end\nend\n\nguard :shell do\n  watch %r[^(:?src|test)/.+\\.ts$] do |m|\n    puts \"\\033[93m#{Time.now}: #{File.basename m[0]}\\033[0m\"\n    exec 'npm run build'\n  end\n\n  watch %r[^test/.+\\.js$] do |m|\n    puts \"\\033[93m#{Time.now}: Test #{File.basename m[0]}\\033[0m\"\n    exec \"./node_modules/.bin/mocha #{m[0]}\"\n  end\nend\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Copyright (c) 2015 rhysd\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 copies\nof the Software, and to permit persons to whom the Software is furnished to do so,\nsubject 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 IMPLIED,\nINCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\nPURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\nTORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR\nTHE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n"
  },
  {
    "path": "README.md",
    "content": "`<neovim-editor>` Web Component\n===============================\n[![Build Status](https://travis-ci.org/rhysd/neovim-component.svg?branch=master)](https://travis-ci.org/rhysd/neovim-component)\n\n\nThis component provides `<neovim-editor>`, an HTML custom element built on [Polymer v2](https://github.com/Polymer/polymer)\nand [flux](https://github.com/facebook/flux).  It provides a frontend for the [Neovim editor](https://github.com/neovim/neovim)\nusing Neovim's MessagePack API. It allows you to easily embed a Neovim-backed editor into your application.\n\n**This component assumes to be used in Node.js environment. (i.e. Electron)**\n\nYou can use this component for modern desktop application frameworks such as [Electron](https://github.com/atom/electron)\nor [NW.js](https://github.com/nwjs/nw.js).  You can even use it in Electron-based editors such as [Atom](http://atom.io/)\nor [VS Code](https://github.com/Microsoft/vscode).\n\nThis component is designed around the [Flux architecture](https://facebook.github.io/flux/docs/overview.html).\nYou can access the UI event notifications and can call Neovim APIs directly via `<neovim-editor>`'s\nAPIs.\n\nYou can install this component as an [npm package](https://www.npmjs.com/package/neovim-component).\n\n```\n$ npm install neovim-component\n```\n\nCurrent supported `nvim` version is v0.1.6 or later.\n\n\n## Examples\n\nEach example only takes 100~300 lines.\n\n### [Minimal Example](/example/minimal)\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <script src=\"/path/to/webcomponents-lite.js\"></script>\n    <link rel=\"import\" href=\"/path/to/polymer.html\" />\n    <link rel=\"import\" href=\"/path/to/neovim-editor.html\" />\n  </head>\n  <body>\n    <neovim-editor></neovim-editor>\n  </body>\n</html>\n```\n\nMinimal [Electron](https://github.com/atom/electron) app can be found in the [example directory](/example/minimal).\nThis is a good start point to use this package and it shows how the component works.\n\n![main screenshot](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/main.gif)\n\nHow to run minimal example is:\n\n```sh\n$ git clone https://github.com/rhysd/neovim-component.git\n$ cd neovim-component\n$ npm start\n```\n\n### [Markdown Editor Example](/example/markdown)\n\nFor a more complicated and realistic example, see the [markdown editor example](/example/markdown).\nThe markdown previewer is integrated with the Neovim GUI using the `<neovim-editor>` component.\n\n![markdown example screenshot](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/markdown-example.gif)\n\n### [Image Popup Example](/example/image-popup)\n\nThis is an image popup widget example [here](/example/image-popup).  The `gi` mapping is defined\nto show an image under the cursor in a tooltip.\n\n![image popup example screenshot](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/popup-image-example.gif)\n\n### [Mini Browser Example](/example/mini-browser)\n\nThis example shows how to include a mini web-browser using the\n[`<webview>` tag from Electron](https://github.com/atom/electron/blob/master/docs/api/web-view-tag.md).\n\n![mini browser example screenshot](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/mini-browser.gif)\n\n\n## Why Did You Create This?\n\nVim has very powerful editing features, but Vim is an editor (see `:help design-not`) and unfortunately\nlacks support for many graphical tools that writers and programmers like.  NyaoVim adds support for\ngraphical features without losing Vim's powerful text editing abilities.\n[Neovim's msgpack APIs](https://neovim.io/doc/user/msgpack_rpc.html) provide a perfect way to add\na GUI layer using HTML and CSS.  [NyaoVim](https://github.com/rhysd/NyaoVim) is a GUI frontend as\na proof of concept.\n\n\n## Architecture\n\n![data flow](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/flow.png)\n\n`<neovim-editor>` has an `editor` property to access the internal APIs of the component.\n\n- `editor.screen` is a view of the component (using canvas).  It receives user input and dispatches\n  input actions to the data store.\n- `editor.process` is a process handler to interact with the backing Neovim process via msgpack-rpc\n  APIs.  You can call Neovim's APIs via the Neovim client (`editor.getClient()` helper).\n- `editor.store` is the state of this component.  You can access the current state of the editor through\n  this object.\n\n\n## `<neovim-editor>` Properties\n\nYou can customize `<neovim-editor>` with the following properties:\n\n| Name                | Description                                | Default       |\n| ------------------- | -------------------------------------------| ------------- |\n| `width`             | Width of the editor in pixels.             | `null`        |\n| `height`            | Height of the editor in pixels.            | `null`        |\n| `font`              | Name of the editor's monospace font.       | `\"monospace\"` |\n| `font-size`         | Font-size in pixels.                       | `12`          |\n| `line-height`       | Line height rate relative to font size.    | `1.3`         |\n| `nvim-cmd`          | Command used to start Neovim.              | `\"nvim\"`      |\n| `argv`              | Arguments passed with the Neovim command.  | `[]`          |\n| `on-quit`           | Callback function to run when Neovim quits.| `null`        |\n| `on-error`          | Callback function for Neovim errors.       | `null`        |\n| `disable-alt-key`   | Do not send alt key input to Neovim.       | `false`       |\n| `disable-meta-key`  | Do not send meta key input to Neovim.      | `false`       |\n| `cursor-draw-delay` | Delay in millisec before drawing cursor.   | `10`          |\n| `no-blink-cursor`   | Blink cursor or not.                       | `false`       |\n| `window-title`      | Specify first window title.                | `\"Neovim\"`    |\n\n\n## `<neovim-editor>` APIs\n\n### Receive internal various events\n\nYou can receive various events (including UI redraw notifications) from the **store**.\nThe `store` is a part of flux architecture. It's a global instance of [EventEmitter](https://nodejs.org/api/events.html).\n\nYou can also access the state of editor via the `store`. Note that all values are read only.\nDo not change the values of the `store` directly, it will break the internal state of the component.\n\n```javascript\nconst neovim_element = document.getElementById('neovim');\nconst Store = neovim_element.editor.store;\n\n// Handle cursor movements\nStore.on('cursor', () => console.log('Cursor is moved to ', Store.cursor));\n\n// Handle mode changes\nStore.on('mode', () => console.log('Mode is changed to ', Store.mode));\n\n// Handle text redraws\nStore.on('put', () => console.log('UI was redrawn'));\n\n// Accessing the state of the editor.\nconst bounds = [ Store.size.lines, Store.size.cols ];\nconst cursor_pos = [ Store.cursor.line, Store.cursor.col ];\n```\n\n\n### Call Neovim APIs\n\nYou can call [Neovim APIs](https://neovim.io/doc/user/msgpack_rpc.html#msgpack-rpc-api) via the **client**.\nWhen you call APIs via the client, it sends the call to the underlying Neovim process via MessagePack\nRPC and will return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)\nwhich resolves to the returned value.\n\n\n`<neovim-component>` uses [promised-neovim-client](https://github.com/rhysd/promised-neovim-client) package.\nYou can see the all API definitions [here](https://github.com/rhysd/promised-neovim-client/blob/promisified/index.d.ts).\nIf you know further about Neovim APIs, [python client implementation](https://github.com/neovim/python-client)\nmay be helpful.\n\n```javascript\nconst neovim_element = document.getElementById('neovim');\nconst client = neovim_element.editor.getClient();\n\n// Send a command\nclient.command('vsplit');\n\n// Send input\nclient.input('<C-w><C-l>');\n\n// Evaluate a Vim script expression\nclient.eval('\"aaa\" . \"bbb\"').then(result => console.log(result));\n\n// Get the 'b:foo' variable\nclient.getCurrentBuffer()\n    .then(buf => buf.getVar('foo'))\n    .then(v => console.log(v));\n\n// Query something (windows, buffers, etc.)\n// Move to the neighbor window and show its information.\nclient.getWindows()\n    .then(windows => client.secCurrentWindow(windows[1]))\n    .then(() => client.getCurrentWindow())\n    .then(win => console.log(win));\n\n// Receive an RPC request from Neovim\nclient.on('request', (n, args, res) => console.log(`Name: ${n}, Args: ${JSON.stringify(args)}, Response: ${res}`));\n```\n\n\n### Editor lifecycle\n\nYou can receive notifications related to lifecycle of the editor.\n\n```javascript\nconst neovim_element = document.getElementById('neovim');\n\n// Called when the Neovim background process attaches\nneovim_element.editor.on('process-attached', () => console.log('Neovim process is ready'));\n\n// Called when the Neovim process is disconnected (usually by :quit)\nneovim_element.editor.on('quit', () => console.log('Neovim process died'));\n\n// Called when the <neovim-component> detaches\nneovim_element.editor.on('detach', () => console.log('Element does not exist in DOM.'));\n\n// Called upon experiencing an error in the internal process \nneovim_element.editor.on('error', err => alert(err.message));\n```\n\n\n### View APIs\n\n- Resize screen\n\n```javascript\nconst editor = document.getElementById('neovim').editor;\neditor.screen.resize(80, 100); // Resize screen to 80 lines and 100 columns\neditor.screen.resizeWithPixels(1920, 1080); // Resize screen to 1920px x 1080px\n```\n\n- Change font size\n\n```javascript\nconst editor = document.getElementById('neovim').editor;\neditor.screen.changeFontSize(18); // Change font size to 18px\n```\n\n- Convert pixels to lines/cols.\n\n```javascript\nconst editor = document.getElementById('neovim').editor;\n\nconst loc = editor.screen.convertPositionToLocation(80, 24);\nconsole.log(loc.x, loc.y);  // Coordinates in pixels of (line, col) = (80, 24)\n\nconst pos = editor.screen.convertLocationToPosition(400, 300);\nconst.log(pos.col, pos.line);  // line/col of location (400px, 300px)\n```\n\n- Notify of screen-size changes:\n\nWhen some process has changed the screen-size **you must notify the `screen`**. The internal `<canvas>`\nelement has a fixed size and must update itself if there are size changes.  Call `screen.checkShouldResize()`\nif the screen size may have changed.  Note that you don't need to care about `resize` event of `<body>`\nelement.  `<neovim-editor>` component automatically detects this particular resize event and updates\nautomatically. `screen.checkShouldResize()` will simply be ignored if nothing has actually changed.\n\n```javascript\nconst editor = document.getElementById('neovim').editor;\n\nfunction showUpSomeElementInNeovim() {\n    const e = document.getElementById('some-elem');\n\n    // New element shows up!  The screen may be resized by the change.\n    // 'none' -> 'block'\n    e.style.display = 'block';\n\n    // This call tells to editor to adjust itself in the case that it has been resized\n    editor.screen.checkShouldResize();\n}\n```\n\n### Other APIs\n\n- Setting arguments afterwards:\n\nIf your app doesn't use Polymer you can set arguments afterwards using JavaScript\nNote that it is better to use `argv` property of `<neovim-element>` if possible.\n\n```javascript\nconst editor = document.getElementById('neovim').editor;\neditor.setArgv(['README.md']);\n```\n\n- Focusing the editor\n\n`<neovim-editor>` is just a web-component, so it can be focused just like other elements.  \nIf it loses focus the editor won't receive any input events. \nThe `editor` instance has a method to re-focus the editor in JavaScript.\nThe `store` instance contains the current focus state.\n\n```javascript\nconst editor = document.getElementById('neovim').editor;\nconsole.log(editor.store.focused);\neditor.store.on('focus-changed', () => {\n    console.log('Focus was changed: ' + editor.store.focused);\n});\n\n// Refocus the editor to ensure it receives user input.\neditor.focus();\n```\n\n### Log Levels\n\n`<neovim-component>` prints logs in the browser console.  The log level is controlled by the `NODE_ENV`\nenvironment variable:\n\n- `NODE_ENV=debug` will log everything.\n- `NODE_ENV=production` ignores all logs except for warnings and errors.\n- Setting `NODE_ENV` to empty string or some other value enables logging for info, warnings, and errors.\n\n\n## TODOs\n\n- [ ] WebGL rendering (using [pixi.js](http://www.pixijs.com/) or [CreateJS](http://www.createjs.com/)).\n  [#2](https://github.com/rhysd/neovim-component/issues/2)\n- [ ] Follow dynamic device pixel ratio change. [#18](https://github.com/rhysd/neovim-component/issues/18)\n"
  },
  {
    "path": "bower.json",
    "content": "{\n  \"name\": \"neovim-component\",\n  \"description\": \"Polymer component of Neovim frontend\",\n  \"version\": \"0.0.2\",\n  \"keywords\": [\n    \"neovim\",\n    \"polymer\",\n    \"WebComponent\",\n    \"Electron\",\n    \"NW.js\",\n    \"editor\"\n  ],\n  \"homepage\": \"\",\n  \"authors\": [\n    \"rhysd <lin90162@yahoo.co.jp>\"\n  ],\n  \"main\": \"neovim-component.html\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"polymer\": \"^2.5.0\"\n  },\n  \"resolutions\": {\n    \"webcomponentsjs\": \"^v1.0.19\"\n  }\n}\n"
  },
  {
    "path": "example/image-popup/README.md",
    "content": "This is image tooltip example for `<neovim-editor>` component.\n`:ShowImage` command shows an image under the cursor.  I created `<popup-tooltip>` component and use it and `<neovim-editor>` together.  Please see `index.html`.\n\n![screenshot](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/popup-image-example.gif)\n\nHow to run this example:\n\n```sh\n$ cd /path/to/neovim-component\n$ npm run dep\n$ cd ./example/image-tooltip\n$ npm run app # No dependency.  App starts.\n```\n"
  },
  {
    "path": "example/image-popup/cli.js",
    "content": "#! /usr/bin/env node\n'use strict';\n\nvar argv = process.argv.slice(2);\nargv.unshift(__dirname);\nif (!process.env['NODE_ENV']) {\n    process.env['NODE_ENV'] = 'production';\n}\nrequire('child_process').spawn(require('electron'), argv, { stdio: 'inherit' });\n"
  },
  {
    "path": "example/image-popup/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes\" />\n    <title>Image Tooltip Example</title>\n\n    <script src=\"../../bower_components/webcomponentsjs/webcomponents-lite.js\"></script>\n    <link rel=\"import\" href=\"../../bower_components/polymer/polymer.html\" />\n    <link rel=\"import\" href=\"../../neovim-editor.html\" />\n    <link rel=\"import\" href=\"./popup-tooltip.html\" />\n\n    <style>\n      html,body {\n        height: 100%;\n        width: 100%;\n        margin: 0px;\n        padding: 0px;\n        overflow: hidden;\n      }\n      #neovim {\n        width: 100%;\n        height: 100%;\n      }\n    </style>\n  </head>\n\n  <body>\n    <neovim-editor id=\"neovim\" font=\"Monaco,Consolas,monospace\" font-size=\"14\"></neovim-editor>\n    <popup-tooltip id=\"tooltip\"></popup-tooltip>\n  </body>\n\n  <script>\n    const editor = document.getElementById('neovim').editor;\n    const remote = require('electron').remote;\n    const tooltip = document.getElementById('tooltip');\n\n    editor.on('error', (err) => alert(err.message));\n\n    // Note: Callback on Neovim process attached\n    editor.on('process-attached', function() {\n      if (remote.process.argv.length > 2) {\n        editor.setArgv(remote.process.argv.slice(2)); // It is better to use 'argv' property.\n      }\n      const c = editor.getClient();\n\n      // Note:\n      // Define mapping 'gi' from JavaScript.\n      // Of course you can execute these commands in Vim plugin.\n      c.command('nnoremap <silent>gi :<C-u>call rpcnotify(0, \"image-tooltip:open\", \\'<C-r><C-p>\\', line(\".\") - line(\"w0\") + 1, virtcol(\".\"))<CR>');\n\n      // Note:\n      // Subscribe notification sent by rpcnotify() from Neovim process\n      c.subscribe('image-tooltip:open');\n      c.on('notification', function(method, args) {\n          if (method === 'image-tooltip:open' && args.length === 3 && args[0]) {\n            // Toggle tooltip with image\n            if (tooltip.shown) {\n              tooltip.dismiss();\n            } else {\n              // Note: col('.') is 1-based\n              const loc = editor.screen.convertPositionToLocation(args[1], args[2] - 1);\n              const img = document.createElement('img');\n              img.src = args[0];\n              tooltip.setContent(img);\n              tooltip.show(loc.x, loc.y);\n            }\n          }\n      });\n    });\n    editor.on('quit', function() {\n      remote.require('app').quit();\n    });\n  </script>\n</html>\n"
  },
  {
    "path": "example/image-popup/main.js",
    "content": "var path = require('path');\nvar electron = require('electron');\nvar app = electron.app;\nvar BrowserWindow = electron.BrowserWindow;\n\nvar index_html = 'file://' + path.join(__dirname, 'index.html');\n\napp.on('ready', function() {\n    var win = new BrowserWindow({\n        width: 800,\n        height: 600,\n        useContentSize: true,\n        webPreferences: {\n            blinkFeatures: 'KeyboardEventKey,Accelerated2dCanvas,Canvas2dFixedRenderingMode',\n            nodeIntegration: true,\n        },\n    });\n\n    win.on('closed', function() {\n        win = null;\n        app.quit();\n    });\n\n    win.loadURL(index_html);\n    if (process.env['NODE_ENV'] !== 'production') {\n        win.webContents.openDevTools({ mode: 'detach' });\n    }\n});\n"
  },
  {
    "path": "example/image-popup/package.json",
    "content": "{\n  \"name\": \"neovim-component-image-tooltip-example\",\n  \"version\": \"0.0.1\",\n  \"main\": \"main.js\",\n  \"scripts\": {\n    \"app\": \"../../node_modules/.bin/electron .\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  }\n}\n"
  },
  {
    "path": "example/image-popup/popup-tooltip.html",
    "content": "<link rel=\"import\" href=\"../../bower_components/polymer/polymer.html\" />\n\n<dom-module id=\"popup-tooltip\">\n  <template>\n    <style>\n      #popup {\n        position: absolute;\n        padding: 8px;\n        border-radius: 8px;\n        display: none;\n        background-color: #dddddd;\n      }\n      #proj {\n        width: 0px;\n        height: 0px;\n        position: absolute;\n        display: none;\n        border: 13px transparent solid;\n        border-top-width: 0;\n        border-bottom-color: #dddddd;\n      }\n      #btn {\n        display: block;\n        margin: auto;\n      }\n    </style>\n\n    <span id=\"proj\">\n    </span>\n    <div id=\"popup\">\n      <content></content>\n    </div>\n  </template>\n</dom-module>\n\n<script>\n  let popup, proj;\n\n  Polymer({\n    is: 'popup-tooltip',\n\n    properties: {\n      color: {\n        type: String,\n        value: ''\n      },\n\n      shown: {\n        type: Boolean,\n        value: false\n      },\n\n      x: {\n        type: Number,\n        value: 0\n      },\n\n      y: {\n        type: Number,\n        value: 0\n      }\n    },\n\n    ready: function() {\n      popup = this.$.popup;\n      proj = this.$.proj;\n\n      if (this.color) {\n        popup.style.backgroundColor = this.color;\n        proj.style.backgroundColor = this.color;\n      }\n      if (this.shown) {\n        this.show(this.x, this.y);\n      }\n    },\n\n    setContent(elem) {\n      const len = popup.childNodes.length;\n      if (len > 1) {\n        popup.replaceChild(elem, popup.lastChild);\n      } else {\n        popup.appendChild(elem);\n      }\n    },\n\n    show: function(x, y) {\n      popup.style.left = (x - 20 - 13) + 'px';\n      popup.style.top = (y + 13) + 'px';\n      popup.style.display = 'block';\n      proj.style.left = (x - 13) + 'px';\n      proj.style.top = y + 'px';\n      proj.style.display = 'inline'\n      this.shown = true;\n    },\n\n    dismiss: function() {\n      popup.style.display = 'none';\n      proj.style.display = 'none'\n      this.shown = false;\n    },\n\n    toggle: function(x, y) {\n      if (this.shown) {\n        this.dismiss();\n      } else {\n        this.show(x, y);\n      }\n    }\n  });\n</script>\n"
  },
  {
    "path": "example/markdown/README.md",
    "content": "Markdown Editor Example\n=======================\n\nThis is a markdown editor example of [<neovim-editor> component](https://github.com/rhysd/neovim-component).\n\n![screenshot](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/markdown-example.gif)\n\nPreview is updated at `TextChanged` and `TextChangedI` events.  Buffer content is sent from `<neovim-editor>` at the events and converted to HTML using [marked](https://github.com/chjj/marked) and displayed to viewer.  Math formula can also be rendered using [katex](https://github.com/Khan/KaTeX) and `katex` code block.\n\nYou can execute this by below commands.\n\n```sh\n$ cd /path/to/neovim-component\n$ npm run dep\n$ cd ./example/markdown\n$ npm run dep\n$ npm run app\n```\n\nWhen you change the buffer, `TextChanged` or `TextChangedI` is fired in Neovim and notifies it to the callback which is set in `index.html`.  The callback gets the buffer and sets it to `<markdown-viewer>` component.  Finally, `<markdown-viewer>` renders markdown preview.\n\nIt is important that `<markdown-viewer>` component doen't know about `<neovim-editor>` component at all but they work well together.  It means that now Neovim (= `<neovim-editor>` component) can work with so many WebComponents (e.g. [Polymer components](https://elements.polymer-project.org/)).\n"
  },
  {
    "path": "example/markdown/cli.js",
    "content": "#! /usr/bin/env node\n'use strict';\n\nvar argv = process.argv.slice(2);\nargv.unshift(__dirname);\nif (!process.env['NODE_ENV']) {\n    process.env['NODE_ENV'] = 'production';\n}\nrequire('child_process').spawn(require('electron'), argv, { stdio: 'inherit' });\n"
  },
  {
    "path": "example/markdown/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes\" />\n    <title>Neovim Embedded Markdown Editor</title>\n\n    <script src=\"../../bower_components/webcomponentsjs/webcomponents-lite.js\"></script>\n    <link rel=\"import\" href=\"../../bower_components/polymer/polymer.html\" />\n    <link rel=\"import\" href=\"../../neovim-editor.html\" />\n    <link rel=\"import\" href=\"./markdown-viewer.html\" />\n\n    <style>\n      html {\n        height: 100%;\n        width: 100%;\n      }\n      body {\n        height: 100%;\n        width: 100%;\n        margin: 0px;\n        padding: 0px;\n        overflow: hidden;\n        display: flex;\n      }\n      #neovim {\n        width: 50%;\n        height: 100%;\n      }\n      #mdviewer {\n        width: 50%;\n        height: 100%;\n        overflow: auto;\n      }\n    </style>\n  </head>\n\n  <body>\n    <neovim-editor id=\"neovim\" font=\"Monaco,Consolas,monospace\" width=\"500\" height=\"600\"></neovim-editor>\n    <markdown-viewer id=\"mdviewer\"></markdown-viewer>\n  </body>\n\n  <script>\n    const remote = require('electron').remote;\n    const editor = document.getElementById('neovim').editor;\n    const mdviewer = document.getElementById('mdviewer');\n\n    editor.on('error', function(err){ alert(err.message); });\n\n    // Note: Callback on Neovim process attached\n    editor.on('process-attached', function() {\n      if (remote.process.argv.length > 2) {\n        editor.setArgv(remote.process.argv.slice(2)); // It is better to use 'argv' property.\n      }\n      const c = editor.getClient();\n\n      // Note:\n      // Send commands from JavaScript.\n      // Of course you can execute these commands in Vim plugin.\n      c.command('setf markdown');\n      c.command('autocmd TextChanged,TextChangedI * if &ft ==# \"markdown\" | call rpcnotify(0, \"markdown-viewer:text-update\", join(getline(1, \"$\"), \"\\\\n\")) | endif');\n      c.command('autocmd FileType markdown call rpcnotify(0, \"markdown-viewer:text-update\", join(getline(1, \"$\"), \"\\\\n\"))');\n\n      // Note:\n      // Subscribe notification sent by rpcnotify() from Neovim process\n      c.subscribe('markdown-viewer:text-update');\n      c.on('notification', function(method, args) {\n        if (method === 'markdown-viewer:text-update' &&\n          args.length > 0 &&\n          typeof args[0] === 'string') {\n          mdviewer.contents = args[0];\n        }\n      });\n    });\n    editor.on('quit', function() {\n      remote.app.quit();\n    });\n  </script>\n</html>\n"
  },
  {
    "path": "example/markdown/main.js",
    "content": "var path = require('path');\nvar electron = require('electron');\nvar app = electron.app;\nvar BrowserWindow = electron.BrowserWindow;\n\nvar index_html = 'file://' + path.join(__dirname, 'index.html');\n\napp.on('ready', function() {\n    var win = new BrowserWindow({\n        width: 1000,\n        height: 600,\n        useContentSize: true,\n        webPreferences: {\n            blinkFeatures: 'KeyboardEventKey,Accelerated2dCanvas,Canvas2dFixedRenderingMode',\n            nodeIntegration: true,\n        },\n    });\n\n    win.on('closed', function() {\n        win = null;\n        app.quit();\n    });\n\n    win.loadURL(index_html);\n    if (process.env['NODE_ENV'] !== 'production') {\n        win.webContents.openDevTools({ mode: 'detach' });\n    }\n});\n"
  },
  {
    "path": "example/markdown/markdown-viewer.html",
    "content": "<link rel=\"import\" href=\"../../bower_components/polymer/polymer.html\" />\n<script src=\"./node_modules/marked/lib/marked.js\"></script>\n<script src=\"./node_modules/highlightjs/highlight.pack.min.js\"></script>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.js\"></script>\n\n<dom-module id=\"markdown-viewer\">\n  <template>\n    <link rel=\"stylesheet\" href=\"./node_modules/github-markdown-css/github-markdown.css\" />\n    <link rel=\"stylesheet\" href=\"./node_modules/highlightjs/styles/github.css\" />\n    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css\" />\n    <style>\n      .markdown-body {\n        box-sizing: border-box;\n        min-width: 200px;\n        max-width: 980px;\n        margin: 0 auto;\n        padding: 25px;\n        overflow: auto;\n      }\n    </style>\n\n    <div id=\"markdown-contents\" class=\"markdown-body\">\n    </div>\n  </template>\n</dom-module>\n\n<script>\n  marked.setOptions({\n    sanitize: true,\n    highlight: function(code, lang) {\n      if (lang === undefined) {\n        return code;\n      }\n      if (lang === 'katex') {\n        return katex.renderToString(code);\n      }\n      try {\n        return hljs.highlight(lang, code).value;\n      } catch (e) {\n        return code;\n      }\n    }\n  });\n\n  Polymer({\n    is: 'markdown-viewer',\n\n    properties: {\n      contents: {\n        type: String,\n        value: '',\n        observer: '_contentsChanged'\n      }\n    },\n\n    _contentsChanged: function(markdown_text) {\n      const v = this.$['markdown-contents'];\n      v.innerHTML = marked(markdown_text);\n    }\n  });\n</script>\n"
  },
  {
    "path": "example/markdown/package.json",
    "content": "{\n  \"name\": \"neovim-component-markdown-example\",\n  \"version\": \"0.0.1\",\n  \"main\": \"main.js\",\n  \"scripts\": {\n    \"dep\": \"npm install\",\n    \"app\": \"../../node_modules/.bin/electron .\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"dependencies\": {\n    \"github-markdown-css\": \"^2.1.1\",\n    \"highlightjs\": \"^8.7.0\",\n    \"katex\": \"^0.5.1\",\n    \"marked\": \"^0.3.5\"\n  }\n}\n"
  },
  {
    "path": "example/mini-browser/README.md",
    "content": "Mini Browser Example\n====================\n\nThis is an embedded mini browser example of [<neovim-editor> component](https://github.com/rhysd/neovim-component).\n\n![screenshot](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/mini-browser.gif)\n\n`:Browse {url}` command opens embedded mobile browser as screenshot.  You can see the document page, issue page, and so on quickly.  This embedded browser is implemented with [`<webview>` tag of Electron](https://github.com/atom/electron/blob/master/docs/api/web-view-tag.md).\n\nYou can execute this example by below commands.\n\n```sh\n$ cd /path/to/neovim-component\n$ npm run dep\n$ cd ./example/mini-browser\n$ npm run app\n```\n\nI created very simple webview wrapper as `<mini-browser>` component.  It works with `<neovim-editor>` component and Neovim process.  When user inputs command, the url is sent by `rpcnotify()` and `<neovim-editor>` forwards it to `<mini-browser>` component.  `<mini-browser>` component is separated and reusable for Electron apps.\n"
  },
  {
    "path": "example/mini-browser/cli.js",
    "content": "#! /usr/bin/env node\n'use strict';\n\nvar argv = process.argv.slice(2);\nargv.unshift(__dirname);\nif (!process.env['NODE_ENV']) {\n    process.env['NODE_ENV'] = 'production';\n}\nrequire('child_process').spawn(require('electron'), argv, { stdio: 'inherit' });\n"
  },
  {
    "path": "example/mini-browser/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes\" />\n    <title>Mini Browser Example</title>\n\n    <script src=\"../../bower_components/webcomponentsjs/webcomponents-lite.js\"></script>\n    <link rel=\"import\" href=\"../../bower_components/polymer/polymer.html\" />\n    <link rel=\"import\" href=\"../../neovim-editor.html\" />\n    <link rel=\"import\" href=\"./mini-browser.html\" />\n\n    <style>\n      html {\n        height: 100%;\n        width: 100%;\n      }\n      body {\n        height: 100%;\n        width: 100%;\n        margin: 0px;\n        padding: 0px;\n        overflow: hidden;\n        display: flex;\n      }\n      #neovim {\n        flex: auto;\n        height: 100%;\n      }\n      #browser {\n        flex: none;\n        height: 100%;\n      }\n    </style>\n  </head>\n\n  <body>\n    <neovim-editor id=\"neovim\" font=\"Monaco,Consolas,monospace\" font-size=\"14\"></neovim-editor>\n    <mini-browser id=\"browser\"></mini-browser>\n  </body>\n\n  <script>\n  (function() {\n    var editor = document.getElementById('neovim').editor;\n    var remote = require('electron').remote;\n    var browser = document.getElementById('browser');\n\n    function resize() {\n      editor.screen.checkShouldResize();\n    }\n\n    browser.didClose = function() {\n      resize();\n      editor.focus();\n    };\n\n    browser.didShow = resize;\n\n    editor.on('error', function(err){ alert(err.message); });\n\n    // Note: Callback on Neovim process attached\n    editor.on('process-attached', function() {\n      if (remote.process.argv.length > 2) {\n        editor.setArgv(remote.process.argv.slice(2)); // It is better to use 'argv' property.\n      }\n      var c = editor.getClient();\n\n      // Note:\n      // Define mapping 'gi' from JavaScript.\n      // Of course you can execute these commands in Vim plugin.\n      c.command('command! -nargs=1 Browse call rpcnotify(0, \"mini-browser:url\", <q-args>)');\n      c.command('command! -nargs=0 CloseBrowser call rpcnotify(0, \"mini-browser:close\")');\n\n      // Note:\n      // Subscribe notification sent by rpcnotify() from Neovim process\n      c.subscribe('mini-browser:url');\n      c.subscribe('mini-browser:close');\n      c.on('notification', function(method, args) {\n          if (method === 'mini-browser:url' && args[0]) {\n            browser.open(args[0]);\n          } else if (method === 'mini-browser:close') {\n            browser.close();\n          }\n      });\n    });\n    editor.on('quit', function() {\n      remote.app.quit();\n    });\n    window.addEventListener('resize', function() { resize(); });\n  })();\n  </script>\n</html>\n"
  },
  {
    "path": "example/mini-browser/main.js",
    "content": "var path = require('path');\nvar electron = require('electron');\nvar app = electron.app;\nvar BrowserWindow = electron.BrowserWindow;\n\nvar index_html = 'file://' + path.join(__dirname, 'index.html');\n\napp.on('ready', function() {\n    var win = new BrowserWindow({\n        width: 800,\n        height: 600,\n        useContentSize: true,\n        webPreferences: {\n            blinkFeatures: 'KeyboardEventKey,Accelerated2dCanvas,Canvas2dFixedRenderingMode',\n            nodeIntegration: true,\n        },\n    });\n\n    win.on('closed', function() {\n        win = null;\n        app.quit();\n    });\n\n    win.loadURL(index_html);\n    if (process.env['NODE_ENV'] !== 'production') {\n        win.webContents.openDevTools({ mode: 'detach' });\n    }\n});\n"
  },
  {
    "path": "example/mini-browser/mini-browser.html",
    "content": "<link rel=\"import\" href=\"../../bower_components/polymer/polymer.html\" />\n<link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css\">\n\n<dom-module id=\"mini-browser\">\n  <template>\n    <style>\n      #body {\n        display: none;\n        height: 100%;\n        width: 375px; /* iPhone6 width */\n        flex-direction: column;\n        font-family: Consolas,\"Liberation Mono\",Menlo,Courier,monospace;\n      }\n      #title-bar {\n        height: 24px;\n        flex: none;\n        display: flex;\n        flex-direction: row;\n        text-align: center;\n        border-bottom: solid 1px dimgray;\n        color: dimgray;\n        background-color: #dddddd;\n      }\n      #inner-browser {\n        flex: auto;\n        height: calc(100vh - 24px);\n        width: 375px;\n      }\n      .button {\n        border: solid 1px;\n        border-radius: 4px;\n        margin: 2px;\n        padding: 4px;\n        flex: none;\n        min-width: 20px;\n        font-size: 14px;\n        cursor: pointer;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n      }\n      #title {\n        flex: auto;\n        font-size: 16px;\n        overflow: hidden;\n        white-space: nowrap;\n        text-overflow: ellipsis;\n        margin: 0px 8px;\n        line-height: 24px;\n      }\n    </style>\n\n    <div id=\"body\">\n      <div id=\"title-bar\">\n        <div class=\"button\" id=\"back-btn\"><i class=\"fa fa-arrow-left\"></i></div>\n        <div class=\"button\" id=\"forward-btn\"><i class=\"fa fa-arrow-right\"></i></div>\n        <div id=\"title\"></div>\n        <div class=\"button\" id=\"close-btn\"><i class=\"fa fa-times\"></i></div>\n      </div>\n      <webview id=\"inner-browser\" src$=\"[[url]]\" autosize=\"on\" useragent=\"Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d Safari/600.1.4\"></webview>\n    </div>\n  </template>\n</dom-module>\n\n<script>\n(function() {\n  let browser, body, title;\n  Polymer({\n    is: 'mini-browser',\n\n    properties: {\n      url: {\n        type: String,\n        value: ''\n      },\n      visible: {\n        type: Boolean,\n        value: false\n      },\n      didClose: Object,\n      didShow: Object\n    },\n\n    ready: function() {\n      var self = this;\n      body = this.$.body;\n      browser = this.$['inner-browser'];\n      title = this.$.title;\n      this.$['back-btn'].onclick = function() {\n        browser.goBack();\n      };\n      this.$['forward-btn'].onclick = function() {\n        browser.goForward();\n      };\n      this.$['close-btn'].onclick = function() {\n        browser.src = '';\n        self.close();\n      };\n      browser.addEventListener('dom-ready', function() {\n          title.innerText = browser.getTitle();\n      });\n      if (this.visible) {\n        self.show();\n      }\n    },\n\n    show() {\n      body.style.display = 'flex';\n      this.visible = true;\n      if (this.didShow) {\n        this.didShow();\n      }\n    },\n\n    open(url) {\n      browser.src = url;\n      if (!this.visible) {\n        this.show();\n      }\n    },\n\n    close() {\n      body.style.display = 'none';\n      this.visible = false;\n      if (this.didClose) {\n        this.didClose();\n      }\n    }\n  });\n})();\n</script>\n"
  },
  {
    "path": "example/mini-browser/package.json",
    "content": "{\n  \"name\": \"neovim-component-mini-browser-example\",\n  \"version\": \"0.0.1\",\n  \"main\": \"main.js\",\n  \"scripts\": {\n    \"app\": \"../../node_modules/.bin/electron .\",\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  }\n}\n"
  },
  {
    "path": "example/minimal/README.md",
    "content": "This is minimal usage of `<neovim-editor>`.  You can easily try this using `npm run` command as below.\n\n```sh\n$ cd /path/to/neovim-component\n$ npm start # Install dependencies, build source and run this example\n```\n"
  },
  {
    "path": "example/minimal/cli.js",
    "content": "#! /usr/bin/env node\n'use strict';\n\nvar argv = process.argv.slice(2);\nargv.unshift(require('path').join(__dirname, '..', '..'));\nif (!process.env['NODE_ENV']) {\n    process.env['NODE_ENV'] = 'production';\n}\nrequire('child_process').spawn(require('electron'), argv, { stdio: 'inherit' });\n"
  },
  {
    "path": "example/minimal/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes\" />\n    <title>Neovim Web Component Example</title>\n\n    <script src=\"../../bower_components/webcomponentsjs/webcomponents-lite.js\"></script>\n    <link rel=\"import\" href=\"../../bower_components/polymer/polymer.html\" />\n    <link rel=\"import\" href=\"../../neovim-editor.html\" />\n\n    <style>\n      html, body {\n        height: 100%;\n        width: 100%;\n        margin: 0px;\n        padding: 0px;\n        overflow: hidden;\n      }\n    </style>\n  </head>\n  <body>\n    <neovim-editor id=\"neovim\" font=\"Monaco,Consolas,monospace\" font-size=\"14\"></neovim-editor>\n  </body>\n  <script>\n    'use strict';\n    const neovim_element = document.getElementById('neovim');\n    const editor = neovim_element.editor;\n    const electron = require('electron');\n    const remote = electron.remote;\n    const shell = electron.shell;\n    editor.on('error', function(err){ alert(err.message); });\n    editor.on('process-attached', function() {\n      if (remote.process.argv.length > 2) {\n        // It is better to use 'argv' property of <neovim-editor>.\n        editor.setArgv(remote.process.argv.slice(2));\n      }\n      neovim_element.addEventListener('drop', function(e) {\n        e.preventDefault();\n        const f = e.dataTransfer.files[0];\n        if (f) {\n          editor.getClient().command('e! ' + f.path);  // 'path' member is Electron extension\n        }\n      });\n    });\n    editor.on('quit', () => remote.app.quit());\n    editor.store.on('beep', () => shell.beep());\n    editor.store.on('title-changed', () => {\n      document.title = editor.store.title;\n    });\n    editor.store.on('icon-changed', () => {\n      var icon = editor.store.icon_path;\n      if (icon === '') {\n        return;\n      }\n      if (process.platform === 'darwin') {\n        remote.getCurrentWindow().setRepresentedFilename(icon);\n      }\n    });\n    neovim_element.addEventListener('dragover', e => e.preventDefault());\n  </script>\n</html>\n"
  },
  {
    "path": "example/minimal/main.js",
    "content": "var path = require('path');\nvar electron = require('electron');\nvar app = electron.app;\nvar BrowserWindow = electron.BrowserWindow;\n\nvar index_html = 'file://' + path.join(__dirname, 'index.html');\n\napp.on('ready', function() {\n    var win = new BrowserWindow({\n        width: 800,\n        height: 600,\n        useContentSize: true,\n        webPreferences: {\n            blinkFeatures: 'KeyboardEventKey,Accelerated2dCanvas,Canvas2dFixedRenderingMode',\n            nodeIntegration: true,\n        },\n    });\n\n    win.on('closed', function() {\n        win = null;\n        app.quit();\n    });\n\n    win.loadURL(index_html);\n    if (process.env['NODE_ENV'] !== 'production') {\n        win.webContents.openDevTools({ mode: 'detach' });\n    }\n});\n"
  },
  {
    "path": "index.d.ts",
    "content": "import { EventEmitter } from 'events';\nimport { Nvim } from 'promised-neovim-client';\nimport { Dispatcher } from 'flux';\nimport cp = require('child_process');\nimport NvimClient = require('promised-neovim-client');\nexport declare type RPCValue = NvimClient.Buffer | NvimClient.Window | NvimClient.Tabpage | number | boolean | string | any[] | {\n    [key: string]: any;\n};\n\nexport interface HighlightSet {\n    background?: number;\n    bg?: string;\n    bold?: boolean;\n    fg?: string;\n    special?: string;\n    foreground?: number;\n    italic?: boolean;\n    reverse?: boolean;\n    undercurl?: boolean;\n    underline?: boolean;\n}\nexport interface Region {\n    top: number;\n    left: number;\n    right: number;\n    bottom: number;\n}\nexport declare enum Kind {\n    Bell = 0,\n    BusyStart = 1,\n    BusyStop = 2,\n    ChangeCursorDrawDelay = 3,\n    ClearAll = 4,\n    ClearEOL = 5,\n    CompositionStart = 6,\n    CompositionEnd = 7,\n    Cursor = 8,\n    DisableMouse = 9,\n    DisableAltKey = 10,\n    DisableMetaKey = 11,\n    DragEnd = 12,\n    DragStart = 13,\n    DragUpdate = 14,\n    EnableMouse = 15,\n    Highlight = 16,\n    Input = 17,\n    Mode = 18,\n    PutText = 19,\n    Resize = 20,\n    ScrollScreen = 21,\n    SetIcon = 22,\n    SetScrollRegion = 23,\n    SetTitle = 24,\n    StartBlinkCursor = 25,\n    StopBlinkCursor = 26,\n    UpdateBG = 27,\n    UpdateFG = 28,\n    UpdateSP = 29,\n    UpdateFontFace = 30,\n    updateLineHeight = 31,\n    UpdateFontPx = 32,\n    UpdateFontSize = 33,\n    UpdateScreenBounds = 34,\n    UpdateScreenSize = 35,\n    WheelScroll = 36,\n    FocusChanged = 37,\n}\nexport interface ActionType {\n    type: Kind;\n    col?: number;\n    color?: number;\n    cols?: number;\n    delay?: number;\n    disabled?: boolean;\n    draw_width?: number;\n    draw_height?: number;\n    event?: MouseEvent | WheelEvent;\n    focused?: boolean;\n    font_face?: string;\n    font_px?: number;\n    height?: number;\n    highlight?: HighlightSet;\n    icon_path?: string;\n    input?: string;\n    line?: number;\n    lines?: number;\n    mode?: string;\n    region?: Region;\n    text?: string[][];\n    title?: string;\n    visual?: boolean;\n    width?: number;\n}\nexport declare function putText(text: string[][]): {\n    type: Kind;\n    text: string[][];\n};\nexport declare function cursor(line: number, col: number): {\n    type: Kind;\n    line: number;\n    col: number;\n};\nexport declare function highlight(highlight: HighlightSet): {\n    type: Kind;\n    highlight: HighlightSet;\n};\nexport declare function clearAll(): {\n    type: Kind;\n};\nexport declare function clearEndOfLine(): {\n    type: Kind;\n};\nexport declare function compositionStart(): {\n    type: Kind;\n};\nexport declare function compositionEnd(): {\n    type: Kind;\n};\nexport declare function resize(lines: number, cols: number): {\n    type: Kind;\n    lines: number;\n    cols: number;\n};\nexport declare function updateForeground(color: number): {\n    type: Kind;\n    color: number;\n};\nexport declare function updateBackground(color: number): {\n    type: Kind;\n    color: number;\n};\nexport declare function updateSpecialColor(color: number): {\n    type: Kind;\n    color: number;\n};\nexport declare function changeMode(mode: string): {\n    type: Kind;\n    mode: string;\n};\nexport declare function startBusy(): {\n    type: Kind;\n};\nexport declare function stopBusy(): {\n    type: Kind;\n};\nexport declare function updateFontSize(draw_width: number, draw_height: number, width: number, height: number): {\n    type: Kind;\n    draw_width: number;\n    draw_height: number;\n    width: number;\n    height: number;\n};\nexport declare function inputToNeovim(input: string): {\n    type: Kind;\n    input: string;\n};\nexport declare function updateFontPx(font_px: number): {\n    type: Kind;\n    font_px: number;\n};\nexport declare function updateFontFace(font_face: string): {\n    type: Kind;\n    font_face: string;\n};\nexport declare function updateScreenSize(width: number, height: number): {\n    type: Kind;\n    width: number;\n    height: number;\n};\nexport declare function updateScreenBounds(lines: number, cols: number): {\n    type: Kind;\n    lines: number;\n    cols: number;\n};\nexport declare function enableMouse(): {\n    type: Kind;\n};\nexport declare function disableMouse(): {\n    type: Kind;\n};\nexport declare function dragStart(event: MouseEvent): {\n    type: Kind;\n    event: MouseEvent;\n};\nexport declare function dragUpdate(event: MouseEvent): {\n    type: Kind;\n    event: MouseEvent;\n};\nexport declare function dragEnd(event: MouseEvent): {\n    type: Kind;\n    event: MouseEvent;\n};\nexport declare function bell(visual: boolean): {\n    type: Kind;\n    visual: boolean;\n};\nexport declare function setTitle(title: string): {\n    type: Kind;\n    title: string;\n};\nexport declare function setIcon(icon_path: string): {\n    type: Kind;\n    icon_path: string;\n};\nexport declare function wheelScroll(event: WheelEvent): {\n    type: Kind;\n    event: WheelEvent;\n};\nexport declare function scrollScreen(cols: number): {\n    type: Kind;\n    cols: number;\n};\nexport declare function setScrollRegion(region: Region): {\n    type: Kind;\n    region: Region;\n};\nexport declare function notifyFocusChanged(focused: boolean): {\n    type: Kind;\n    focused: boolean;\n};\nexport declare function disableAltKey(disabled: boolean): {\n    type: Kind;\n    disabled: boolean;\n};\nexport declare function disableMetaKey(disabled: boolean): {\n    type: Kind;\n    disabled: boolean;\n};\nexport declare function changeCursorDrawDelay(delay: number): {\n    type: Kind;\n    delay: number;\n};\nexport declare function startBlinkCursor(): {\n    type: Kind;\n};\nexport declare function stopBlinkCursor(): {\n    type: Kind;\n};\n\nexport class NeovimCursor {\n    constructor(store: NeovimStore, screen_ctx: CanvasRenderingContext2D);\n    updateSize(): void;\n    redraw(): void;\n    onModeChanged(): void;\n    updateCursorPos(): void;\n}\n\nexport class NeovimInput {\n    element: HTMLInputElement;\n    ime_running: boolean;\n    static shouldHandleModifier(event: KeyboardEvent): boolean;\n    static getVimSpecialChar(code: number, shift: boolean): string;\n    constructor(store: NeovimStore);\n    startComposition(event: Event): void;\n    endComposition(event: Event): void;\n    focus(): void;\n    onInsertControlChar(event: KeyboardEvent): void;\n    inputToNeovim(input: string, event: Event): void;\n    onInsertNormalChar(event: KeyboardEvent): void;\n}\n\nexport class NeovimProcess {\n    command: string;\n    argv: string[];\n    neovim_process: cp.ChildProcess;\n    client: NvimClient.Nvim;\n    started: boolean;\n    constructor(store: NeovimStore, command: string, argv: string[]);\n    attach(lines: number, columns: number): Promise<void>;\n    onRequested(method: string, args: RPCValue[], response: RPCValue): void;\n    onNotified(method: string, args: RPCValue[]): void;\n    onDisconnected(): void;\n    finalize(): Promise<void>;\n}\n\nexport class ScreenDrag {\n    line: number;\n    col: number;\n    static buildInputOf(e: MouseEvent, type: string, line: number, col: number): string;\n    constructor(store: NeovimStore);\n    start(down_event: MouseEvent): string;\n    drag(move_event: MouseEvent): string;\n    end(up_event: MouseEvent): string;\n}\n\nexport class ScreenWheel {\n    x: number;\n    y: number;\n    shift: boolean;\n    ctrl: boolean;\n    constructor(store: NeovimStore);\n    handleEvent(e: WheelEvent): string;\n}\n\nexport class NeovimScreen {\n    canvas: HTMLCanvasElement;\n    ctx: CanvasRenderingContext2D;\n    cursor: NeovimCursor;\n    input: NeovimInput;\n    constructor(store: NeovimStore, canvas: HTMLCanvasElement);\n    wheel(e: WheelEvent): void;\n    mouseDown(e: MouseEvent): void;\n    mouseUp(e: MouseEvent): void;\n    mouseMove(e: MouseEvent): void;\n    resizeWithPixels(width_px: number, height_px: number): void;\n    resize(lines: number, cols: number): void;\n    changeFontSize(specified_px: number): void;\n    changeLineHeight(new_value: number): void;\n    scroll(cols_delta: number): void;\n    focus(): void;\n    clearAll(): void;\n    clearEol(): void;\n    convertPositionToLocation(line: number, col: number): {\n        x: number;\n        y: number;\n    };\n    convertLocationToPosition(x: number, y: number): {\n        line: number;\n        col: number;\n    };\n    checkShouldResize(): void;\n}\n\nexport interface Size {\n    lines: number;\n    cols: number;\n    width: number;\n    height: number;\n}\nexport interface Cursor {\n    line: number;\n    col: number;\n}\nexport interface FontAttributes {\n    fg: string;\n    bg: string;\n    sp: string;\n    bold: boolean;\n    italic: boolean;\n    underline: boolean;\n    undercurl: boolean;\n    draw_width: number;\n    draw_height: number;\n    width: number;\n    height: number;\n    face: string;\n    specified_px: number;\n}\nexport declare type DispatcherType = Dispatcher<ActionType>;\nexport class NeovimStore extends EventEmitter {\n    blink_cursor: boolean;\n    cursor_blink_interval: number;\n    cursor_draw_delay: number;\n    dispatch_token: string;\n    size: Size;\n    focused: boolean;\n    font_attr: FontAttributes;\n    line_height: number;\n    fg_color: string;\n    bg_color: string;\n    cursor: Cursor;\n    mode: string;\n    busy: boolean;\n    mouse_enabled: boolean;\n    dragging: ScreenDrag;\n    title: string;\n    icon_path: string;\n    wheel_scrolling: ScreenWheel;\n    scroll_region: Region;\n    dispatcher: Dispatcher<ActionType>;\n}\n\nexport class Neovim extends EventEmitter {\n    process: NeovimProcess;\n    screen: NeovimScreen;\n    store: NeovimStore;\n    constructor(\n        command: string,\n        argv: string[],\n        font: string,\n        font_size: number,\n        line_height: number,\n        blink_cursor: boolean,\n        window_title: string,\n    );\n    attachCanvas(width: number, height: number, canvas: HTMLCanvasElement): void;\n    quit(): void;\n    getClient(): Nvim;\n    focus(): void;\n    setArgv(argv: string[]): Promise<void>;\n}\n\nexport class NeovimElement extends HTMLElement {\n    disableAltKey: boolean;\n    drawDelay: number;\n    editor: Neovim;\n    width: number;\n    height: number;\n    fontSize: number;\n    font: string;\n    lineHeight: number;\n    noBlinkCursor: boolean;\n    windowTitle: string;\n    nvimCmd: string;\n    argv: string[];\n    onProcessAttached: () => void;\n    onQuit: () => void;\n    onError: (err: Error) => void;\n}\n"
  },
  {
    "path": "neovim-editor.html",
    "content": "<dom-module id=\"neovim-editor\">\n  <template>\n    <style>\n      :host {\n        position: relative;\n        min-width: 0px;\n        min-height: 0px;\n      }\n      #container {\n        padding: 0px;\n        margin: 0px;\n        width: 100%;\n        height: 100%;\n      }\n      #cursor {\n        padding: 0px;\n        margin: 0px;\n        position: absolute;\n      }\n      #input {\n        width: 1px;\n        color: transparent;\n        background-color: transparent;\n        padding: 0px;\n        border: 0px;\n        outline: none;\n        vertical-align: middle;\n        position: absolute;\n        top: 0px;\n        left: 0px;\n      }\n      #preedit {\n        display: none;\n        visibility: hidden;\n        position: fixed;\n      }\n    </style>\n    <div id=\"container\">\n      <canvas id=\"screen\" width$=\"[[width]]\" height$=\"[[height]]\"></canvas>\n      <canvas id=\"cursor\"></canvas>\n      <input id=\"input\" autocomplete=\"off\" autofocus />\n      <span id=\"preedit\"></span>\n    </div>\n  </template>\n</dom-module>\n\n<script src=\"./build/index.js\"></script>\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"neovim-component\",\n  \"version\": \"0.10.1\",\n  \"description\": \"Polymer component for Neovim frontend\",\n  \"main\": \"example/minimal/main.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/rhysd/neovim-component.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/rhysd/neovim-component/issues\"\n  },\n  \"scripts\": {\n    \"start\": \"npm run dep && npm run build && npm run example\",\n    \"browserify\": \"browserify -o build/index.js build/src/index.js\",\n    \"tsc\": \"tsc --pretty --project .\",\n    \"build\": \"npm-run-all tsc browserify\",\n    \"debug\": \"cross-env ELECTRON_ENABLE_STACK_DUMPING=true NODE_ENV=debug electron .\",\n    \"dep\": \"npm install && bower install && mkdir build\",\n    \"example\": \"cross-env NODE_ENV=production electron .\",\n    \"tslint\": \"tslint --project .\",\n    \"nsp\": \"nsp check\",\n    \"lint\": \"npm-run-all -p tslint\",\n    \"format\": \"prettier --write 'src/**/*.ts' 'test/**/*.ts' 'example/*/*.js'\",\n    \"watch\": \"guard --watchdir src test\",\n    \"test\": \"mocha test/unit/ --exit\",\n    \"e2e\": \"mocha build/test/e2e/ --opts test/e2e/mocha.opts --exit\"\n  },\n  \"keywords\": [\n    \"neovim\",\n    \"polymer\",\n    \"WebComponent\",\n    \"Electron\",\n    \"NW.js\",\n    \"editor\"\n  ],\n  \"author\": \"rhysd <lin90162@yahoo.co.jp>\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"flux\": \"^3.1.3\",\n    \"loglevel\": \"^1.6.3\",\n    \"promised-neovim-client\": \"^2.0.2\"\n  },\n  \"devDependencies\": {\n    \"@types/chai\": \"^4.1.7\",\n    \"@types/fbemitter\": \"^2.0.32\",\n    \"@types/flux\": \"^3.1.9\",\n    \"@types/loglevel\": \"^1.5.4\",\n    \"@types/mocha\": \"^5.2.7\",\n    \"@types/node\": \"^12.0.8\",\n    \"@types/react\": \"^16.8.19\",\n    \"@types/webdriverio\": \"^4.10.4\",\n    \"bower\": \"^1.8.8\",\n    \"browserify\": \"^16.2.3\",\n    \"canvas\": \"^2.5.0\",\n    \"chai\": \"^4.2.0\",\n    \"cross-env\": \"^5.2.0\",\n    \"electron\": \"~5.0.3\",\n    \"jsdom\": \"^15.1.1\",\n    \"mocha\": \"^6.1.4\",\n    \"npm-run-all\": \"^4.1.5\",\n    \"nsp\": \"^3.2.1\",\n    \"prettier\": \"^1.18.2\",\n    \"spectron\": \"^5.0.0\",\n    \"tslint\": \"^5.17.0\",\n    \"typescript\": \"^3.5.1\"\n  }\n}\n"
  },
  {
    "path": "scripts/travis-ci-install.sh",
    "content": "#!/bin/bash\n\nset -e\n\necho Installing dependencies\nnpm run dep\n\necho 'uname:'\nuname -a\n\necho 'nvim:'\nnvim --version\n"
  },
  {
    "path": "scripts/travis-ci-run.sh",
    "content": "#!/bin/bash\n\nset -e\n\necho 'Building package'\nnpm run build\nnpm run lint\n\necho 'Running unit tests'\nnpm run test\n\necho 'Running E2E tests'\nif [[ \"$TRAVIS_OS_NAME\" == \"osx\" ]]; then\n    # E2E tests fail on Linux worker because of Hardware Acceleration.\n    # It may work with the environment HA is disabled. But it does not make sense to run tests\n    # without HA because most users enable HA usually.\n    npm run e2e\nfi\n"
  },
  {
    "path": "src/index.ts",
    "content": "import Neovim, { DOM } from './neovim';\n\nclass NeovimEditor extends Polymer.Element {\n    static get is() {\n        return 'neovim-editor';\n    }\n\n    static get properties() {\n        return {\n            width: Number,\n            height: Number,\n            fontSize: {\n                type: Number,\n                value: 12,\n            },\n            font: {\n                type: String,\n                value: 'monospace',\n            },\n            lineHeight: {\n                type: Number,\n                value: 1.3,\n            },\n            nvimCmd: {\n                type: String,\n                value: 'nvim',\n            },\n            argv: {\n                type: Array,\n                value: () => [] as string[],\n            },\n            disableAltKey: {\n                type: Boolean,\n                value: false,\n            },\n            disableMetaKey: {\n                type: Boolean,\n                value: false,\n            },\n            cursorDrawDelay: {\n                type: Number,\n                value: 10,\n            },\n            noBlinkCursor: {\n                type: Boolean,\n                value: false,\n            },\n            windowTitle: {\n                type: String,\n                value: 'Neovim',\n            },\n            onProcessAttached: Object,\n            onQuit: Object,\n            onError: Object,\n        };\n    }\n\n    width: number;\n    height: number;\n    fontSize: number;\n    font: string;\n    lineHeight: number;\n    nvimCmd: string;\n    argv: string[];\n    disableAltKey: boolean;\n    disableMetaKey: boolean;\n    cursorDrawDelay: number;\n    noBlinkCursor: boolean;\n    windowTitle: string;\n    editor: Neovim;\n    onProcessAttached: () => void;\n    onQuit: () => void;\n    onError: (err: Error) => void;\n    resizeHandler: NodeJS.Timer;\n    resizeListener: () => void;\n\n    ready() {\n        super.ready();\n        this.editor = new Neovim(\n            this.$ as DOM,\n            this.nvimCmd,\n            this.argv,\n            this.font,\n            this.fontSize,\n            this.lineHeight,\n            this.disableAltKey,\n            this.disableMetaKey,\n            this.cursorDrawDelay,\n            !this.noBlinkCursor,\n            this.windowTitle,\n        );\n        this.resizeHandler = null;\n\n        if (this.onError) {\n            this.editor.on('error', this.onError);\n        }\n\n        if (this.onQuit) {\n            this.editor.on('quit', this.onQuit);\n        }\n\n        if (this.onProcessAttached) {\n            this.editor.on('process-attached', this.onProcessAttached);\n        }\n    }\n\n    connectedCallback() {\n        super.connectedCallback();\n        Polymer.RenderStatus.afterNextRender(this, function() {\n            // measure size of element\n            const parent: HTMLCanvasElement = this.$.container;\n            const canvas: HTMLCanvasElement = this.$.screen;\n            const width = this.width || parent.offsetWidth;\n            const height = this.height || parent.offsetHeight;\n            this.editor.attachCanvas(width, height, canvas);\n            this.resizeListener = () => {\n                if (this.resizeHandler !== null) {\n                    clearTimeout(this.resizeHandler);\n                }\n                this.resizeHandler = setTimeout(() => {\n                    this.editor.screen.checkShouldResize();\n                    this.resizeHandler = null;\n                }, 100);\n            };\n            window.addEventListener('resize', this.resizeListener);\n        });\n    }\n\n    disconnectedCallback() {\n        super.disconnectedCallback();\n        this.editor.emit('detach');\n        if (this.resizeListener) {\n            window.removeEventListener('resize', this.resizeListener);\n        }\n    }\n\n    attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null) {\n        // @ts-ignore: https://github.com/Polymer/polymer/issues/5087\n        super.attributeChangedCallback(name, oldVal, newVal);\n        if (this.editor === undefined) {\n            return;\n        }\n        this.editor.emit('change-attribute', name, oldVal, newVal);\n    }\n}\n\ncustomElements.define(NeovimEditor.is, NeovimEditor);\n"
  },
  {
    "path": "src/lib.d.ts",
    "content": "/// <reference path=\"../bower_components/polymer/types/polymer.d.ts\"/>\ndeclare namespace NodeJS {\n    interface Global {\n        require(mod: string): any;\n    }\n}\n"
  },
  {
    "path": "src/log.ts",
    "content": "import log = require('loglevel');\n\nexport const NODE_ENV = (() => {\n    try {\n        return global.require('electron').remote.process.env.NODE_ENV || 'production';\n    } catch (e) {\n        return 'production';\n    }\n})();\n\nif (NODE_ENV === 'production') {\n    log.setLevel('WARN');\n} else if (NODE_ENV === 'debug') {\n    log.setLevel('DEBUG');\n} else {\n    log.setLevel('INFO');\n}\n\nexport default log;\n"
  },
  {
    "path": "src/neovim/actions.ts",
    "content": "export interface HighlightSet {\n    background?: number;\n    bg?: string;\n    bold?: boolean;\n    fg?: string;\n    foreground?: number;\n    italic?: boolean;\n    reverse?: boolean;\n    special?: number;\n    undercurl?: boolean;\n    underline?: boolean;\n}\n\nexport interface ModeInfoSet {\n    [key: string]: ModeInfo;\n}\n\nexport interface ModeInfo {\n    blinkoff?: number;\n    blinkon?: number;\n    blinkwait?: number;\n    cell_percentage?: number;\n    cursor_shape?: string;\n    hl_id?: number;\n    id_lm?: number;\n    mouse_shape: number;\n    name: string;\n    short_name: string;\n}\n\nexport interface Region {\n    top: number;\n    left: number;\n    right: number;\n    bottom: number;\n}\n\nexport enum Kind {\n    Bell,\n    BusyStart,\n    BusyStop,\n    ChangeCursorDrawDelay,\n    ClearAll,\n    ClearEOL,\n    CompositionStart,\n    CompositionEnd,\n    Cursor,\n    DisableMouse,\n    DisableAltKey,\n    DisableMetaKey,\n    DragEnd,\n    DragStart,\n    DragUpdate,\n    EnableMouse,\n    Highlight,\n    Input,\n    Mode,\n    ModeInfo,\n    PutText,\n    Resize,\n    ScrollScreen,\n    SetIcon,\n    SetScrollRegion,\n    SetTitle,\n    StartBlinkCursor,\n    StopBlinkCursor,\n    UpdateBG,\n    UpdateFG,\n    UpdateSP,\n    UpdateFontFace,\n    UpdateFontPx,\n    UpdateFontSize,\n    UpdateLineHeight,\n    UpdateScreenBounds,\n    UpdateScreenSize,\n    WheelScroll,\n    FocusChanged,\n}\n\nexport interface ActionType {\n    type: Kind;\n    col?: number;\n    color?: number;\n    cols?: number;\n    delay?: number;\n    disabled?: boolean;\n    draw_width?: number;\n    draw_height?: number;\n    event?: MouseEvent | WheelEvent;\n    focused?: boolean;\n    font_face?: string;\n    font_px?: number;\n    height?: number;\n    highlight?: HighlightSet;\n    icon_path?: string;\n    input?: string;\n    line?: number;\n    line_height?: number;\n    lines?: number;\n    modeInfo?: ModeInfoSet;\n    mode?: string;\n    region?: Region;\n    text?: string[][];\n    title?: string;\n    visual?: boolean;\n    width?: number;\n}\n\nexport function putText(text: string[][]) {\n    return {\n        type: Kind.PutText,\n        text,\n    };\n}\n\nexport function cursor(line: number, col: number) {\n    return {\n        type: Kind.Cursor,\n        line,\n        col,\n    };\n}\n\nexport function highlight(hl: HighlightSet) {\n    return {\n        type: Kind.Highlight,\n        highlight: hl,\n    };\n}\n\nexport function clearAll() {\n    return {\n        type: Kind.ClearAll,\n    };\n}\n\nexport function clearEndOfLine() {\n    return {\n        type: Kind.ClearEOL,\n    };\n}\n\nexport function compositionStart() {\n    return {\n        type: Kind.CompositionStart,\n    };\n}\n\nexport function compositionEnd() {\n    return {\n        type: Kind.CompositionEnd,\n    };\n}\n\nexport function resize(lines: number, cols: number) {\n    return {\n        type: Kind.Resize,\n        lines,\n        cols,\n    };\n}\n\nexport function updateForeground(color: number) {\n    return {\n        type: Kind.UpdateFG,\n        color,\n    };\n}\n\nexport function updateBackground(color: number) {\n    return {\n        type: Kind.UpdateBG,\n        color,\n    };\n}\n\nexport function updateSpecialColor(color: number) {\n    return {\n        type: Kind.UpdateSP,\n        color,\n    };\n}\n\nexport function modeInfo(info: ModeInfoSet) {\n    return {\n        type: Kind.ModeInfo,\n        modeInfo: info,\n    };\n}\n\nexport function changeMode(mode: string) {\n    return {\n        type: Kind.Mode,\n        mode,\n    };\n}\n\nexport function startBusy() {\n    return {\n        type: Kind.BusyStart,\n    };\n}\n\nexport function stopBusy() {\n    return {\n        type: Kind.BusyStop,\n    };\n}\n\nexport function updateFontSize(draw_width: number, draw_height: number, width: number, height: number) {\n    return {\n        type: Kind.UpdateFontSize,\n        draw_width,\n        draw_height,\n        width,\n        height,\n    };\n}\n\nexport function inputToNeovim(input: string) {\n    return {\n        type: Kind.Input,\n        input,\n    };\n}\n\nexport function updateFontPx(font_px: number) {\n    return {\n        type: Kind.UpdateFontPx,\n        font_px,\n    };\n}\n\nexport function updateFontFace(font_face: string) {\n    return {\n        type: Kind.UpdateFontFace,\n        font_face,\n    };\n}\n\nexport function updateScreenSize(width: number, height: number) {\n    return {\n        type: Kind.UpdateScreenSize,\n        width,\n        height,\n    };\n}\n\n// Note:\n// This function has the same effect as resize() but resize() is used\n// for neovim's UI event and this function is used to change screen bounds\n// via NeovimScreen's API.\nexport function updateScreenBounds(lines: number, cols: number) {\n    return {\n        type: Kind.UpdateScreenBounds,\n        lines,\n        cols,\n    };\n}\n\nexport function enableMouse() {\n    return {\n        type: Kind.EnableMouse,\n    };\n}\n\nexport function disableMouse() {\n    return {\n        type: Kind.DisableMouse,\n    };\n}\n\nexport function dragStart(event: MouseEvent) {\n    return {\n        type: Kind.DragStart,\n        event,\n    };\n}\n\nexport function dragUpdate(event: MouseEvent) {\n    return {\n        type: Kind.DragUpdate,\n        event,\n    };\n}\n\nexport function dragEnd(event: MouseEvent) {\n    return {\n        type: Kind.DragEnd,\n        event,\n    };\n}\n\nexport function bell(visual: boolean) {\n    return {\n        type: Kind.Bell,\n        visual,\n    };\n}\n\nexport function setTitle(title: string) {\n    return {\n        type: Kind.SetTitle,\n        title,\n    };\n}\n\nexport function setIcon(icon_path: string) {\n    return {\n        type: Kind.SetIcon,\n        icon_path,\n    };\n}\n\nexport function wheelScroll(event: WheelEvent) {\n    return {\n        type: Kind.WheelScroll,\n        event,\n    };\n}\n\nexport function scrollScreen(cols: number) {\n    return {\n        type: Kind.ScrollScreen,\n        cols,\n    };\n}\n\nexport function setScrollRegion(region: Region) {\n    return {\n        type: Kind.SetScrollRegion,\n        region,\n    };\n}\n\nexport function notifyFocusChanged(focused: boolean) {\n    return {\n        type: Kind.FocusChanged,\n        focused,\n    };\n}\n\nexport function updateLineHeight(line_height: number) {\n    return {\n        type: Kind.UpdateLineHeight,\n        line_height,\n    };\n}\n\nexport function disableAltKey(disabled: boolean) {\n    return {\n        type: Kind.DisableAltKey,\n        disabled,\n    };\n}\n\nexport function disableMetaKey(disabled: boolean) {\n    return {\n        type: Kind.DisableMetaKey,\n        disabled,\n    };\n}\n\nexport function changeCursorDrawDelay(delay: number) {\n    return {\n        type: Kind.ChangeCursorDrawDelay,\n        delay,\n    };\n}\n\nexport function startBlinkCursor() {\n    return {\n        type: Kind.StartBlinkCursor,\n    };\n}\nexport function stopBlinkCursor() {\n    return {\n        type: Kind.StopBlinkCursor,\n    };\n}\n"
  },
  {
    "path": "src/neovim/cursor.ts",
    "content": "import { EventEmitter } from 'events';\nimport NeovimStore from './store';\nimport log from '../log';\nimport { dragEnd } from './actions';\n\nfunction invertColor(image: ImageData) {\n    const d = image.data;\n    for (let i = 0; i < d.length; i += 4) {\n        d[i] = 255 - d[i]; // Red\n        d[i + 1] = 255 - d[i + 1]; // Green\n        d[i + 2] = 255 - d[i + 2]; // Blue\n    }\n    return image;\n}\n\nclass CursorBlinkTimer extends EventEmitter {\n    enabled: boolean;\n    shown: boolean;\n    private token: number;\n    private readonly callback: () => void;\n\n    constructor(public interval: number) {\n        super();\n        this.token = null;\n        this.enabled = false;\n        this.shown = true;\n        this.callback = this._callback.bind(this);\n    }\n\n    start() {\n        if (this.enabled) {\n            return;\n        }\n        this.shown = true;\n        this.token = window.setTimeout(this.callback, this.interval);\n        this.enabled = true;\n    }\n\n    stop() {\n        if (!this.enabled) {\n            return;\n        }\n        if (this.token !== null) {\n            window.clearTimeout(this.token);\n            this.token = null;\n        }\n        this.enabled = false;\n    }\n\n    reset() {\n        if (this.enabled) {\n            this.stop();\n            this.start();\n        }\n    }\n\n    private _callback() {\n        this.shown = !this.shown;\n        this.emit('tick', this.shown);\n        this.token = window.setTimeout(this.callback, this.interval);\n    }\n}\n\nexport default class NeovimCursor {\n    private readonly element: HTMLCanvasElement;\n    private readonly ctx: CanvasRenderingContext2D;\n    private delay_timer: number;\n    private readonly blink_timer: CursorBlinkTimer;\n    private preedit_is_shown: boolean; // flag to hide cursor when preedit is shown\n\n    constructor(private readonly store: NeovimStore, private readonly screen_ctx: CanvasRenderingContext2D) {\n        this.delay_timer = null;\n        this.blink_timer = new CursorBlinkTimer(this.store.cursor_blink_interval);\n        this.element = this.store.dom.cursor as HTMLCanvasElement;\n        this.element.style.top = '0px';\n        this.element.style.left = '0px';\n        this.ctx = this.element.getContext('2d', { alpha: false });\n        this.onFontSizeUpdated();\n        this.blink_timer.on('tick', (shown: boolean) => {\n            if (shown) {\n                this.redraw();\n            } else {\n                this.dismiss();\n            }\n        });\n        if (this.store.blink_cursor) {\n            this.blink_timer.start();\n        }\n\n        this.element.addEventListener('mouseup', (e: MouseEvent) => {\n            this.store.dispatcher.dispatch(dragEnd(e));\n        });\n        this.element.addEventListener('click', (e: MouseEvent) => {\n            e.preventDefault();\n            const i = this.store.dom.input as HTMLInputElement;\n            if (i) {\n                i.focus();\n            }\n        });\n\n        this.store.on('cursor', this.updateCursorPos.bind(this));\n        this.store.on('update-fg', () => this.redraw());\n        this.store.on('font-size-changed', this.onFontSizeUpdated.bind(this));\n        this.store.on('blink-cursor-started', () => this.blink_timer.start());\n        this.store.on('blink-cursor-stopped', () => this.blink_timer.stop());\n        this.store.on('busy', () => {\n            if (this.store.busy) {\n                this.dismiss();\n                this.blink_timer.stop();\n            } else {\n                this.redraw();\n                if (this.store.blink_cursor) {\n                    this.blink_timer.start();\n                }\n            }\n        });\n        this.store.on('focus-changed', () => this.updateCursorBlinking(this.store.focused));\n        this.store.on('mode', () => this.updateCursorBlinking(this.store.mode !== 'insert'));\n        this.store.on('composition-started', () => this.compositionChanged(true));\n        this.store.on('composition-ended', () => this.compositionChanged(false));\n    }\n\n    onFontSizeUpdated() {\n        const f = this.store.font_attr;\n        this.element.style.width = f.width + 'px';\n        this.element.style.height = f.height + 'px';\n        this.element.width = f.draw_width;\n        this.element.height = f.draw_height;\n        this.redraw();\n    }\n\n    dismiss() {\n        this.ctx.clearRect(0, 0, this.element.width, this.element.height);\n    }\n\n    redraw() {\n        if (this.preedit_is_shown) {\n            if (this.delay_timer !== null) {\n                clearTimeout(this.delay_timer);\n            }\n            this.ctx.clearRect(0, 0, this.element.width, this.element.height);\n            return;\n        }\n\n        if (this.store.cursor_draw_delay <= 0) {\n            this.redrawImpl();\n            return;\n        }\n        if (this.delay_timer !== null) {\n            clearTimeout(this.delay_timer);\n        } else {\n            this.ctx.clearRect(0, 0, this.element.width, this.element.height);\n        }\n        this.delay_timer = window.setTimeout(this.redrawImpl.bind(this), this.store.cursor_draw_delay);\n    }\n\n    updateCursorPos() {\n        const { line, col } = this.store.cursor;\n        const { width, height } = this.store.font_attr;\n\n        const x = col * width;\n        const y = line * height;\n\n        this.element.style.left = x + 'px';\n        this.element.style.top = y + 'px';\n        log.debug(`Cursor is moved to (${x}, ${y})`);\n        this.redraw();\n        this.blink_timer.reset();\n    }\n\n    updateCursorSize() {\n        const info = this.store.modeInfo[this.store.mode];\n        if (!info) {\n            return;\n        }\n        const { cursor_shape, cell_percentage } = info;\n        if (cursor_shape === undefined || cell_percentage === undefined) {\n            return;\n        }\n        const { width, height } = this.store.font_attr;\n        switch (cursor_shape) {\n            case 'horizontal': {\n                const top = height * (1 - cell_percentage / 100);\n                const right = width;\n                const bottom = height;\n                const left = 0;\n                this.element.style.clip = `rect(${top}px ${right}px ${bottom}px ${left}px)`;\n                break;\n            }\n            case 'vertical': {\n                const top = 0;\n                const right = width * (cell_percentage / 100);\n                const bottom = height;\n                const left = 0;\n                this.element.style.clip = `rect(${top}px ${right}px ${bottom}px ${left}px)`;\n                break;\n            }\n            default: {\n                this.element.style.clip = 'auto';\n                break;\n            }\n        }\n    }\n\n    private redrawImpl() {\n        this.delay_timer = null;\n        const { draw_width, draw_height } = this.store.font_attr;\n        const x = this.store.cursor.col * draw_width;\n        const y = this.store.cursor.line * draw_height;\n        const captured = this.screen_ctx.getImageData(x, y, draw_width, draw_height);\n        this.ctx.putImageData(invertColor(captured), 0, 0);\n    }\n\n    private updateCursorBlinking(should_blink: boolean) {\n        this.updateCursorSize();\n        if (should_blink) {\n            if (this.store.blink_cursor) {\n                this.blink_timer.start();\n            }\n        } else {\n            this.blink_timer.stop();\n            if (!this.blink_timer.shown) {\n                this.redraw();\n            }\n        }\n    }\n\n    private compositionChanged(preedit_is_shown: boolean) {\n        this.preedit_is_shown = preedit_is_shown;\n        this.redraw();\n    }\n}\n"
  },
  {
    "path": "src/neovim/input.ts",
    "content": "// keyCode is deprecated but necessary for fallback\n/* tslint:disable:deprecation */\n\nimport NeovimStore from './store';\nimport { compositionStart, compositionEnd, inputToNeovim, notifyFocusChanged } from './actions';\nimport log from '../log';\n\nconst OnDarwin = global.process.platform === 'darwin';\nconst IsAlpha = /^[a-zA-Z]$/;\n\nexport default class NeovimInput {\n    element: HTMLInputElement;\n    // fake span element to measure text width of preedit input\n    fake_element: HTMLSpanElement;\n    ime_running: boolean; // XXX: Local state!\n\n    static shouldIgnoreOnKeydown(event: KeyboardEvent) {\n        const { ctrlKey, shiftKey, altKey, keyCode, metaKey } = event;\n        return (\n            (!ctrlKey && !altKey && !metaKey) ||\n            (shiftKey && keyCode === 16) ||\n            (ctrlKey && keyCode === 17) ||\n            (altKey && keyCode === 18) ||\n            (metaKey && keyCode === 91)\n        );\n    }\n\n    // Note:\n    // Workaround when KeyboardEvent.key is not available.\n    static getVimSpecialCharFromKeyCode(key_code: number, shift: boolean) {\n        switch (key_code) {\n            case 0:\n                return 'Nul';\n            case 8:\n                return 'BS';\n            case 9:\n                return 'Tab';\n            case 10:\n                return 'NL';\n            case 13:\n                return 'CR';\n            case 33:\n                return 'PageUp';\n            case 34:\n                return 'PageDown';\n            case 27:\n                return 'Esc';\n            case 32:\n                return 'Space';\n            case 35:\n                return 'End';\n            case 36:\n                return 'Home';\n            case 37:\n                return 'Left';\n            case 38:\n                return 'Up';\n            case 39:\n                return 'Right';\n            case 40:\n                return 'Down';\n            case 45:\n                return 'Insert';\n            case 46:\n                return 'Del';\n            case 47:\n                return 'Help';\n            case 92:\n                return 'Bslash';\n            case 112:\n                return 'F1';\n            case 113:\n                return 'F2';\n            case 114:\n                return 'F3';\n            case 115:\n                return 'F4';\n            case 116:\n                return 'F5';\n            case 117:\n                return 'F6';\n            case 118:\n                return 'F7';\n            case 119:\n                return 'F8';\n            case 120:\n                return 'F9';\n            case 121:\n                return 'F10';\n            case 122:\n                return 'F11';\n            case 123:\n                return 'F12';\n            case 124:\n                return 'Bar'; // XXX\n            case 127:\n                return 'Del'; // XXX\n            case 188:\n                return shift ? 'LT' : null;\n            default:\n                return null;\n        }\n    }\n\n    // Note:\n    // Special key handling using KeyboardEvent.key.  Thank you @romgrk for the idea.\n    // https://www.w3.org/TR/DOM-Level-3-Events-key/\n    static getVimSpecialCharFromKey(event: KeyboardEvent) {\n        const key = event.key;\n\n        if (key.length === 1) {\n            switch (key) {\n                case '<':\n                    return event.ctrlKey || event.altKey ? 'LT' : null;\n                case ' ':\n                    return 'Space';\n                case '\\0':\n                    return 'Nul';\n                default:\n                    return null;\n            }\n        }\n\n        if (key[0] === 'F') {\n            // F1, F2, F3, ...\n            return /^F\\d+/.test(key) ? key : null;\n        }\n\n        const ctrl = event.ctrlKey;\n        const key_code = event.keyCode;\n\n        switch (key) {\n            case 'Escape': {\n                if (ctrl && key_code !== 27) {\n                    // Note:\n                    // When <C-[> is input\n                    // XXX:\n                    // Keycode of '[' is not available because it is 219 in OS X\n                    // and it is not for '['.\n                    return '[';\n                } else {\n                    return 'Esc';\n                }\n            }\n            case 'Backspace': {\n                if (ctrl && key_code === 72) {\n                    // Note:\n                    // When <C-h> is input (72 is key code of 'h')\n                    return 'h';\n                } else {\n                    return 'BS';\n                }\n            }\n            case 'Tab': {\n                if (ctrl && key_code === 73) {\n                    // Note:\n                    // When <C-i> is input (73 is key code of 'i')\n                    return 'i';\n                } else {\n                    return 'Tab';\n                }\n            }\n            case 'Enter': {\n                // Note: Should consider <NL>?\n                if (ctrl && key_code === 77) {\n                    // Note:\n                    // When <C-m> is input (77 is key code of 'm')\n                    return 'm';\n                } else if (ctrl && key_code === 67) {\n                    // XXX:\n                    // This is workaround for a bug of Chromium.  Ctrl+c emits wrong KeyboardEvent.key.\n                    // (It should be \"\\uxxxx\" but actually \"Enter\")\n                    // https://github.com/rhysd/NyaoVim/issues/37\n                    return 'c';\n                } else {\n                    return 'CR';\n                }\n            }\n            case 'PageUp':\n                return 'PageUp';\n            case 'PageDown':\n                return 'PageDown';\n            case 'End':\n                return 'End';\n            case 'Home':\n                return 'Home';\n            case 'ArrowLeft':\n                return 'Left';\n            case 'ArrowUp':\n                return 'Up';\n            case 'ArrowRight':\n                return 'Right';\n            case 'ArrowDown':\n                return 'Down';\n            case 'Insert':\n                return 'Insert';\n            case 'Delete':\n                return 'Del';\n            case 'Help':\n                return 'Help';\n            case 'Unidentified':\n                return null;\n            default:\n                return null;\n        }\n    }\n\n    static getVimSpecialCharInput(event: KeyboardEvent) {\n        const should_fallback = event.key === undefined || (event.key === '\\0' && event.keyCode !== 0);\n        const special_char = should_fallback\n            ? NeovimInput.getVimSpecialCharFromKeyCode(event.keyCode, event.shiftKey)\n            : NeovimInput.getVimSpecialCharFromKey(event);\n        if (!special_char) {\n            return null;\n        }\n\n        let vim_input = '<';\n        if (event.ctrlKey) {\n            vim_input += 'C-';\n        }\n        if (event.metaKey) {\n            vim_input += 'D-';\n        }\n        if (event.altKey) {\n            vim_input += 'A-';\n        }\n        // Note: <LT> is a special case where shift should not be handled.\n        if (event.shiftKey && special_char !== 'LT') {\n            vim_input += 'S-';\n        }\n        vim_input += special_char + '>';\n        return vim_input;\n    }\n\n    static replaceKeyToAvoidCtrlShiftSpecial(ctrl: boolean, shift: boolean, key: string) {\n        if (!ctrl || shift) {\n            return key;\n        }\n\n        // Note:\n        // These characters are especially replaced in Vim frontend.\n        //     https://github.com/vim/vim/blob/d58b0f982ad758c59abe47627216a15497e9c3c1/src/gui_w32.c#L1956-L1989\n        // Issue:\n        //     https://github.com/rhysd/NyaoVim/issues/87\n        switch (key) {\n            case '6':\n                return '^';\n            case '-':\n                return '_';\n            case '2':\n                return '@';\n            default:\n                return key;\n        }\n    }\n\n    static getVimInputFromKeyCode(event: KeyboardEvent) {\n        let modifiers = '';\n        if (event.ctrlKey) {\n            modifiers += 'C-';\n        }\n        if (event.metaKey) {\n            modifiers += 'D-';\n        }\n        if (event.altKey) {\n            modifiers += 'A-';\n        }\n        // Note: <LT> is a special case where shift should not be handled.\n        if (event.shiftKey) {\n            modifiers += 'S-';\n        }\n        let vim_input = NeovimInput.replaceKeyToAvoidCtrlShiftSpecial(\n            event.ctrlKey,\n            event.shiftKey,\n            String.fromCharCode(event.keyCode).toLowerCase(),\n        );\n        if (modifiers !== '') {\n            vim_input = `<${modifiers}${vim_input}>`;\n        }\n        return vim_input;\n    }\n\n    constructor(private readonly store: NeovimStore) {\n        this.ime_running = false;\n\n        this.element = this.store.dom.input as HTMLInputElement;\n        this.element.addEventListener('compositionstart', this.startComposition.bind(this));\n        this.element.addEventListener('compositionend', this.endComposition.bind(this));\n        this.element.addEventListener('keydown', this.onInputNonText.bind(this));\n        this.element.addEventListener('input', this.onInputText.bind(this));\n        this.element.addEventListener('blur', this.onBlur.bind(this));\n        this.element.addEventListener('focus', this.onFocus.bind(this));\n        this.store.on('cursor', this.updateElementPos.bind(this));\n        this.store.on('font-size-changed', this.updateFontSize.bind(this));\n\n        this.fake_element = this.store.dom.preedit as HTMLSpanElement;\n\n        const { face } = this.store.font_attr;\n        this.element.style.fontFamily = face;\n        this.fake_element.style.fontFamily = face;\n\n        this.updateFontSize();\n\n        this.focus();\n    }\n\n    startComposition(_: Event) {\n        log.debug('start composition');\n\n        // make <input> visible to show preedit\n        this.element.style.color = this.store.fg_color;\n        this.element.style.backgroundColor = this.store.bg_color;\n        this.element.style.width = 'auto';\n\n        // hide cursor\n        this.store.dispatcher.dispatch(compositionStart());\n\n        this.ime_running = true;\n    }\n\n    endComposition(event: CompositionEvent) {\n        log.debug('end composition');\n        this.inputToNeovim(event.data, event);\n\n        // hide HTMLInputElement\n        this.element.style.color = 'transparent';\n        this.element.style.backgroundColor = 'transparent';\n        this.element.style.width = '1px';\n\n        // show cursor\n        this.store.dispatcher.dispatch(compositionEnd());\n\n        this.ime_running = false;\n    }\n\n    focus() {\n        this.element.focus();\n    }\n\n    onFocus() {\n        this.store.dispatcher.dispatch(notifyFocusChanged(true));\n\n        // Note:\n        // Neovim frontend has responsibility to emit 'FocusGained'.\n        // :execute 'normal!' \"\\<FocusGained>\" is available.\n        // (it seems undocumented.)\n        this.store.dispatcher.dispatch(inputToNeovim('<FocusGained>'));\n    }\n\n    onBlur(e: Event) {\n        e.preventDefault();\n        this.store.dispatcher.dispatch(notifyFocusChanged(false));\n\n        // Note:\n        // Neovim frontend has responsibility to emit 'FocusLost'.\n        // :execute 'normal!' \"\\<FocusLost>\" is available.\n        // (it seems undocumented.)\n        this.store.dispatcher.dispatch(inputToNeovim('<FocusLost>'));\n    }\n\n    // Note:\n    // Assumes keydown event is always fired before input event\n    onInputNonText(event: KeyboardEvent) {\n        log.debug('Keydown event:', event);\n        if (this.ime_running) {\n            log.debug('IME is running.  Input canceled.');\n            return;\n        }\n\n        const special_sequence = NeovimInput.getVimSpecialCharInput(event);\n        if (special_sequence) {\n            this.inputToNeovim(special_sequence, event);\n            return;\n        }\n\n        if (NeovimInput.shouldIgnoreOnKeydown(event)) {\n            return;\n        }\n\n        const should_osx_workaround = OnDarwin && event.altKey && !event.ctrlKey && this.store.mode === 'normal';\n        if (this.store.alt_key_disabled && event.altKey) {\n            // Note: Overwrite 'altKey' to false to disable alt key input.\n            Object.defineProperty(event, 'altKey', { value: false });\n        }\n        if (this.store.meta_key_disabled && event.metaKey) {\n            // Note:\n            // Simply ignore input with metakey if metakey is disabled.  This aims to delegate the key input\n            // to menu items on OS X.\n            return;\n        }\n\n        if (should_osx_workaround) {\n            // Note:\n            //\n            // In OS X, option + {key} sequences input special characters which can't be\n            // input with keyboard normally.  (e.g. option+a -> å)\n            // MacVim accepts the special characters only in insert mode, otherwise <A-{char}>\n            // is emitted.\n            //\n            this.inputToNeovim(NeovimInput.getVimInputFromKeyCode(event), event);\n            return;\n        }\n\n        if (event.key) {\n            if (event.key.length === 1) {\n                let input = '<';\n                if (event.ctrlKey) {\n                    input += 'C-';\n                }\n                if (event.metaKey) {\n                    input += 'D-';\n                }\n                if (event.altKey) {\n                    input += 'A-';\n                }\n                if (event.shiftKey && IsAlpha.test(event.key)) {\n                    // Note:\n                    // If input is not an alphabetical character,  it already considers\n                    // Shift modifier.\n                    //  e.g. Ctrl+Shift+2 -> event.key == '@'\n                    // But for alphabets, Vim ignores the case.  For example <C-s> is\n                    // equivalent to <C-S>.  So we need to specify <C-S-s> or <C-S-S>.\n                    input += 'S-';\n                }\n                if (input === '<') {\n                    // Note: No modifier was pressed\n                    this.inputToNeovim(event.key, event);\n                } else {\n                    const key = NeovimInput.replaceKeyToAvoidCtrlShiftSpecial(event.ctrlKey, event.shiftKey, event.key);\n                    this.inputToNeovim(input + key + '>', event);\n                }\n            } else {\n                log.warn(\"Invalid key input on 'keydown': \", event.key);\n            }\n        } else {\n            this.inputToNeovim(NeovimInput.getVimInputFromKeyCode(event), event);\n        }\n    }\n\n    inputToNeovim(input: string, event: Event) {\n        this.store.dispatcher.dispatch(inputToNeovim(input));\n\n        log.debug('Input to neovim:', JSON.stringify(input));\n\n        event.preventDefault();\n        event.stopPropagation();\n        const t = event.target as HTMLInputElement;\n        if (t.value) {\n            t.value = '';\n        }\n    }\n\n    onInputText(event: KeyboardEvent) {\n        log.debug('Input event:', event);\n\n        if (this.ime_running) {\n            log.debug('IME is running.  Input canceled.');\n\n            // update width of the input element\n            this.fake_element.innerText = this.element.value;\n            // to get the width of peedit area, show it just a moment\n            this.fake_element.style.display = 'inline';\n            const width = this.fake_element.getBoundingClientRect().width;\n            this.fake_element.style.display = 'none';\n            this.element.style.width = width + 'px';\n            return;\n        }\n\n        const t = event.target as HTMLInputElement;\n        if (t.value === '') {\n            log.warn('onInputText: Empty');\n            return;\n        }\n\n        const input = t.value !== '<' ? t.value : '<LT>';\n        this.inputToNeovim(input, event);\n    }\n\n    updateElementPos() {\n        const { line, col } = this.store.cursor;\n        const { width, height } = this.store.font_attr;\n\n        const x = col * width;\n        const y = line * height;\n\n        this.element.style.left = x + 'px';\n        this.element.style.top = y + 'px';\n    }\n\n    updateFontSize() {\n        // don't need to consider device pixel ratio for DOM elements\n        const { specified_px: font_size } = this.store.font_attr;\n\n        this.element.style.fontSize = font_size + 'px';\n        this.fake_element.style.fontSize = font_size + 'px';\n    }\n}\n"
  },
  {
    "path": "src/neovim/process.ts",
    "content": "// Note:\n// Use renderer's node.js integration to avoid using ipc for large data transfer\nimport cp = require('child_process');\nconst child_process: typeof cp = global.require('child_process');\nimport NvimClient = require('promised-neovim-client');\nconst attach = (global.require('promised-neovim-client') as typeof NvimClient).attach;\nimport Action = require('./actions');\nimport NeovimStore from './store';\nimport log from '../log';\n\n// Note:\n// TypeScript doesn't allow recursive definition\nexport type RPCValue =\n    | NvimClient.Buffer\n    | NvimClient.Window\n    | NvimClient.Tabpage\n    | number\n    | boolean\n    | string\n    | any[]\n    | { [key: string]: any };\n\nexport default class NeovimProcess {\n    neovim_process: cp.ChildProcess;\n    client: NvimClient.Nvim;\n    started: boolean;\n\n    constructor(private readonly store: NeovimStore, public command: string, public argv: string[]) {\n        this.started = false;\n        this.argv.unshift('--embed');\n    }\n\n    attach(lines: number, columns: number) {\n        let err: Error = null;\n        this.client = null;\n\n        this.neovim_process = child_process.spawn(this.command, this.argv, { stdio: ['pipe', 'pipe', process.stderr] });\n        this.neovim_process.on('error', (e: Error) => {\n            err = e;\n        });\n\n        if (err || this.neovim_process.pid === undefined) {\n            return Promise.reject(err || new Error('Failed to spawn process: ' + this.command));\n        }\n\n        return attach(this.neovim_process.stdin, this.neovim_process.stdout).then(nvim => {\n            this.client = nvim;\n            nvim.on('request', this.onRequested.bind(this));\n            nvim.on('notification', this.onNotified.bind(this));\n            nvim.on('disconnect', this.onDisconnected.bind(this));\n            /* tslint:disable:no-floating-promises */\n            nvim.uiAttach(columns, lines, true, true /*notify*/);\n            /* tslint:enable:no-floating-promises */\n            this.started = true;\n            log.info(`nvim attached: ${this.neovim_process.pid} ${lines}x${columns} ${JSON.stringify(this.argv)}`);\n            this.store.on('input', (i: string) => nvim.input(i));\n            this.store.on('update-screen-bounds', () => nvim.uiTryResize(this.store.size.cols, this.store.size.lines));\n\n            // Note:\n            // Neovim frontend has responsibility to emit 'GUIEnter' on initialization.\n            /* tslint:disable:no-floating-promises */\n            this.client.command('doautocmd <nomodeline> GUIEnter', true);\n            /* tslint:enable:no-floating-promises */\n        });\n    }\n\n    onRequested(method: string, args: RPCValue[], response: RPCValue) {\n        log.info('requested: ', method, args, response);\n    }\n\n    onNotified(method: string, args: RPCValue[]) {\n        if (method === 'redraw') {\n            this.redraw(args as RPCValue[][]);\n        } else {\n            // User defined notifications are passed here.\n            log.debug('Unknown method', method, args);\n        }\n    }\n\n    onDisconnected() {\n        log.info('disconnected: ' + this.neovim_process.pid);\n        // TODO:\n        // Uncomment below line to close window on quit.\n        // I don't do yet for debug.\n        // global.require('electron').remote.getCurrentWindow().close();\n        this.started = false;\n    }\n\n    finalize() {\n        return this.client.uiDetach().then(() => {\n            this.client.quit();\n            this.started = false;\n        });\n    }\n\n    private redraw(events: RPCValue[][]) {\n        const d = this.store.dispatcher;\n        for (const e of events) {\n            const name = e[0] as string;\n            const args = e[1] as RPCValue[];\n            switch (name) {\n                case 'put':\n                    e.shift();\n                    if (e.length !== 0) {\n                        d.dispatch(Action.putText(e as string[][]));\n                    }\n                    break;\n                case 'cursor_goto':\n                    d.dispatch(Action.cursor(args[0] as number, args[1] as number));\n                    break;\n                case 'highlight_set':\n                    e.shift();\n\n                    // Note:\n                    // [[{highlight_set}], [], [{highlight_set}], ...]\n                    //   -> [{highlight_set}, {highlight_set}, ...]\n                    const highlights = [].concat.apply([], e) as Action.HighlightSet[];\n\n                    // Note:\n                    // [{highlight_set}, {highlight_set}, ...]\n                    //   -> {merged highlight_set}\n                    highlights.unshift({});\n                    const merged_highlight = Object.assign.apply(Object, highlights) as Action.HighlightSet;\n\n                    d.dispatch(Action.highlight(merged_highlight));\n                    break;\n                case 'clear':\n                    d.dispatch(Action.clearAll());\n                    break;\n                case 'eol_clear':\n                    d.dispatch(Action.clearEndOfLine());\n                    break;\n                case 'scroll':\n                    d.dispatch(Action.scrollScreen(args[0] as number));\n                    break;\n                case 'set_scroll_region':\n                    d.dispatch(\n                        Action.setScrollRegion({\n                            top: args[0] as number,\n                            bottom: args[1] as number,\n                            left: args[2] as number,\n                            right: args[3] as number,\n                        }),\n                    );\n                    break;\n                case 'resize':\n                    d.dispatch(Action.resize(args[1] as number, args[0] as number));\n                    break;\n                case 'update_fg':\n                    d.dispatch(Action.updateForeground(args[0] as number));\n                    break;\n                case 'update_bg':\n                    d.dispatch(Action.updateBackground(args[0] as number));\n                    break;\n                case 'update_sp':\n                    d.dispatch(Action.updateSpecialColor(args[0] as number));\n                    break;\n                case 'mode_info_set':\n                    // Note:\n                    // [{mode_info_set}, {mode_info_set}]\n                    //   -> { [mode_name]: {mode_info_set} }\n                    const modeInfo = args[1] as Action.ModeInfo[];\n\n                    d.dispatch(\n                        Action.modeInfo(\n                            modeInfo.reduce((set, info) => {\n                                set[info.name] = info;\n                                return set;\n                            }, Object.create(null)),\n                        ),\n                    );\n                    break;\n                case 'mode_change':\n                    d.dispatch(Action.changeMode(args[0] as string));\n                    break;\n                case 'busy_start':\n                    d.dispatch(Action.startBusy());\n                    break;\n                case 'busy_stop':\n                    d.dispatch(Action.stopBusy());\n                    break;\n                case 'mouse_on':\n                    d.dispatch(Action.enableMouse());\n                    break;\n                case 'mouse_off':\n                    d.dispatch(Action.disableMouse());\n                    break;\n                case 'bell':\n                    d.dispatch(Action.bell(false));\n                    break;\n                case 'visual_bell':\n                    d.dispatch(Action.bell(true));\n                    break;\n                case 'set_title':\n                    d.dispatch(Action.setTitle(args[0] as string));\n                    break;\n                case 'set_icon':\n                    d.dispatch(Action.setIcon(args[0] as string));\n                    break;\n                default:\n                    log.warn('Unhandled event: ' + name, args);\n                    break;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/neovim/screen-drag.ts",
    "content": "import NeovimStore from './store';\nimport log from '../log';\n\nconst MouseButtonKind = ['Left', 'Middle', 'Right'];\n\nexport default class ScreenDrag {\n    line: number;\n    col: number;\n    parentX: number;\n    parentY: number;\n\n    static buildInputOf(e: MouseEvent, type: string, line: number, col: number) {\n        let seq = '<';\n        if (e.ctrlKey) {\n            seq += 'C-';\n        }\n        if (e.altKey) {\n            seq += 'A-';\n        }\n        if (e.shiftKey) {\n            seq += 'S-';\n        }\n        seq += MouseButtonKind[e.button] + type + '>';\n        seq += `<${col},${line}>`;\n        return seq;\n    }\n\n    constructor(private readonly store: NeovimStore) {\n        this.line = 0;\n        this.col = 0;\n        this.parentX = 0;\n        this.parentY = 0;\n    }\n\n    start(down_event: MouseEvent) {\n        const wrapper: HTMLElement = this.store.dom.container as HTMLDivElement;\n        if (wrapper !== null && down_event.target !== null) {\n            const rect = (down_event.target as HTMLCanvasElement).getBoundingClientRect();\n            this.parentY = rect.top;\n            this.parentX = rect.left;\n        }\n        [this.line, this.col] = this.getPos(down_event);\n        log.info('Drag start', down_event, this.line, this.col);\n        const input = ScreenDrag.buildInputOf(down_event, 'Mouse', this.line, this.col);\n        log.debug('Mouse input: ' + input);\n        return input;\n    }\n\n    drag(move_event: MouseEvent) {\n        const [line, col] = this.getPos(move_event);\n        if (line === this.line && col === this.col) {\n            log.debug('ignored MouseMove event');\n            return null;\n        }\n        move_event.preventDefault();\n        log.debug('Drag continue', move_event, line, col);\n        const input = ScreenDrag.buildInputOf(move_event, 'Drag', line, col);\n        this.line = line;\n        this.col = col;\n        log.debug('Mouse input: ' + input);\n        return input;\n    }\n\n    end(up_event: MouseEvent) {\n        up_event.preventDefault();\n\n        [this.line, this.col] = this.getPos(up_event);\n        log.info('Drag end', up_event, this.line, this.col);\n\n        const input = ScreenDrag.buildInputOf(up_event, 'Release', this.line, this.col);\n        log.info('Mouse input: ' + input);\n        return input;\n    }\n\n    private getPos(e: MouseEvent) {\n        // Note:\n        // e.offsetX and e.offsetY is not available. On mouseup event, the cursor is under the mouse\n        // pointer becase mousedown event moves the cursor under mouse pointer. In the case, e.target\n        // is a cursor <canvas> element. And offset is calculated based on the cursor element.\n        // So we need to have a screen element's position (parentX and parentY) and calculate offsets\n        // based on it.\n        const offsetY = e.clientY - this.parentY;\n        const offsetX = e.clientX - this.parentX;\n        return [Math.floor(offsetY / this.store.font_attr.height), Math.floor(offsetX / this.store.font_attr.width)];\n    }\n}\n"
  },
  {
    "path": "src/neovim/screen-wheel.ts",
    "content": "import NeovimStore from './store';\nimport log from '../log';\n\n// Note: Mouse has its origin at left-bottom\n//\n//   +y\n//    |\n//    |\n//    |\n//    |\n//    |--------- +x\n\n// Note:\n// Vim handles scroll with 3 lines and 6 columns as one scroll\n// :help <ScrollWheelUp>\n\nexport default class ScreenWheel {\n    x: number;\n    y: number;\n    shift: boolean;\n    ctrl: boolean;\n\n    constructor(private readonly store: NeovimStore) {\n        this.reset();\n    }\n\n    handleEvent(e: WheelEvent) {\n        if (\n            (this.shift === undefined && this.ctrl === undefined) ||\n            (this.shift !== e.shiftKey || this.ctrl !== e.ctrlKey)\n        ) {\n            // Note:\n            // Initialize at first or reset on modifier change\n            this.reset(e.shiftKey, e.ctrlKey);\n        }\n\n        this.x += e.deltaX;\n        this.y += e.deltaY;\n\n        const scroll_x = Math.round(this.x / this.store.font_attr.draw_width / 6);\n        const scroll_y = Math.round(this.y / this.store.font_attr.draw_height / 3);\n\n        if (scroll_x === 0 && scroll_y === 0) {\n            // Note: At least 3 lines or 6 columns are needed to scroll screen\n            return '';\n        }\n\n        const col = Math.floor(e.offsetX / this.store.font_attr.width);\n        const line = Math.floor(e.offsetY / this.store.font_attr.height);\n\n        const input = this.getInput(scroll_x, scroll_y, line, col);\n        log.debug(`Scroll (${scroll_x}, ${scroll_y}) at (${line}, ${col}): ${input}`);\n        this.reset();\n        return input;\n    }\n\n    private reset(shift?: boolean, ctrl?: boolean) {\n        this.x = 0;\n        this.y = 0;\n        this.shift = shift;\n        this.ctrl = ctrl;\n    }\n\n    private getInput(scroll_x: number, scroll_y: number, line: number, col: number) {\n        const pos = `<${col},${line}>`;\n        let modifier = '<';\n        if (this.ctrl) {\n            modifier += 'C-';\n        }\n        if (this.shift) {\n            modifier += 'S-';\n        }\n\n        let seq = '';\n\n        const y_dir = scroll_y > 0 ? 'Down' : 'Up';\n        for (let _ = 0; _ < Math.abs(scroll_y); ++_) {\n            seq += `${modifier}ScrollWheel${y_dir}>${pos}`;\n        }\n\n        const x_dir = scroll_x > 0 ? 'Left' : 'Right';\n        for (let _ = 0; _ < Math.abs(scroll_x); ++_) {\n            seq += `${modifier}ScrollWheel${x_dir}>${pos}`;\n        }\n\n        return seq;\n    }\n}\n"
  },
  {
    "path": "src/neovim/screen.ts",
    "content": "import NeovimStore from './store';\nimport * as A from './actions';\nimport Cursor from './cursor';\nimport Input from './input';\nimport log from '../log';\n\nexport default class NeovimScreen {\n    ctx: CanvasRenderingContext2D;\n    cursor: Cursor;\n    input: Input;\n\n    constructor(private readonly store: NeovimStore, public canvas: HTMLCanvasElement) {\n        this.ctx = this.canvas.getContext('2d', { alpha: false });\n\n        this.store.on('put', this.drawText.bind(this));\n        this.store.on('clear-all', this.clearAll.bind(this));\n        this.store.on('clear-eol', this.clearEol.bind(this));\n        // Note: 'update-bg' clears all texts in screen.\n        this.store.on('update-bg', this.clearAll.bind(this));\n        this.store.on('screen-scrolled', this.scroll.bind(this));\n        this.store.on('line-height-changed', () => this.changeFontSize(this.store.font_attr.specified_px));\n\n        this.changeFontSize(this.store.font_attr.specified_px);\n\n        canvas.addEventListener('click', this.focus.bind(this));\n        canvas.addEventListener('mousedown', this.mouseDown.bind(this));\n        canvas.addEventListener('mouseup', this.mouseUp.bind(this));\n        canvas.addEventListener('mousemove', this.mouseMove.bind(this));\n        canvas.addEventListener('wheel', this.wheel.bind(this));\n\n        this.cursor = new Cursor(this.store, this.ctx);\n        this.input = new Input(this.store);\n    }\n\n    wheel(e: WheelEvent) {\n        this.store.dispatcher.dispatch(A.wheelScroll(e));\n    }\n\n    mouseDown(e: MouseEvent) {\n        this.store.dispatcher.dispatch(A.dragStart(e));\n    }\n\n    mouseUp(e: MouseEvent) {\n        this.store.dispatcher.dispatch(A.dragEnd(e));\n    }\n\n    mouseMove(e: MouseEvent) {\n        if (e.buttons !== 0) {\n            this.store.dispatcher.dispatch(A.dragUpdate(e));\n        }\n    }\n\n    resizeWithPixels(width_px: number, height_px: number) {\n        const res = window.devicePixelRatio || 1;\n        const h = height_px * res;\n        const w = width_px * res;\n        this.resizeImpl(\n            Math.floor(h / this.store.font_attr.draw_height),\n            Math.floor(w / this.store.font_attr.draw_width),\n            w,\n            h,\n        );\n    }\n\n    resize(lines: number, cols: number) {\n        this.resizeImpl(lines, cols, this.store.font_attr.draw_width * cols, this.store.font_attr.draw_height * lines);\n    }\n\n    changeFontSize(specified_px: number) {\n        const res = window.devicePixelRatio || 1;\n        const drawn_px = specified_px * res;\n        this.ctx.font = drawn_px + 'px ' + this.store.font_attr.face;\n        const font_width = this.ctx.measureText('m').width;\n        const font_height = Math.ceil(drawn_px * this.store.line_height);\n        this.store.dispatcher.dispatch(A.updateFontPx(specified_px));\n        this.store.dispatcher.dispatch(A.updateFontSize(font_width, font_height, font_width / res, font_height / res));\n        const { width, height } = this.store.size;\n        this.resizeWithPixels(width, height);\n    }\n\n    changeLineHeight(new_value: number) {\n        this.store.dispatcher.dispatch(A.updateLineHeight(new_value));\n    }\n\n    // Note:\n    //  cols_delta > 0 -> screen up\n    //  cols_delta < 0 -> screen down\n    scroll(cols_delta: number) {\n        if (cols_delta > 0) {\n            this.scrollUp(cols_delta);\n        } else if (cols_delta < 0) {\n            this.scrollDown(-cols_delta);\n        }\n    }\n\n    focus() {\n        this.input.focus();\n    }\n\n    clearAll() {\n        this.ctx.fillStyle = this.store.bg_color;\n        this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);\n    }\n\n    clearEol() {\n        const { line, col } = this.store.cursor;\n        const font_width = this.store.font_attr.draw_width;\n        const clear_length = this.store.size.cols * font_width - col * font_width;\n        log.debug(`Clear until EOL: ${line}:${col} length=${clear_length}`);\n        this.drawBlock(line, col, 1, clear_length, this.store.bg_color);\n    }\n\n    // Origin is at left-above.\n    //\n    //      O-------------> x\n    //      |\n    //      |\n    //      |\n    //      |\n    //      V\n    //      y\n    //\n    convertPositionToLocation(line: number, col: number) {\n        const { width, height } = this.store.font_attr;\n        return {\n            x: col * width,\n            y: line * height,\n        };\n    }\n    convertLocationToPosition(x: number, y: number) {\n        const { width, height } = this.store.font_attr;\n        return {\n            line: Math.floor(y * height),\n            col: Math.floor(x * width),\n        };\n    }\n\n    checkShouldResize() {\n        const p = this.canvas.parentElement;\n        const cw = p.clientWidth;\n        const ch = p.clientHeight;\n        const w = this.canvas.width;\n        const h = this.canvas.height;\n        const res = window.devicePixelRatio || 1;\n        if (cw * res !== w || ch * res !== h) {\n            this.resizeWithPixels(cw, ch);\n        }\n    }\n\n    // Note:\n    // About 'chars' parameter includes characters to render as array of strings\n    // which should be rendered at the each cursor position.\n    // So we renders the strings with forwarding the start position incrementally.\n    // When chars[idx][0] is empty string, it means that 'no character to render,\n    // go ahead'.\n    private drawChars(x: number, y: number, chars: string[][], width: number) {\n        let includes_half_only = true;\n        for (const c of chars) {\n            if (!c[0]) {\n                includes_half_only = false;\n                break;\n            }\n        }\n        if (includes_half_only) {\n            // Note:\n            // If the text includes only half characters, we can render it at once.\n            const text = chars.map(c => c[0] || '').join('');\n            this.ctx.fillText(text, x, y);\n            return;\n        }\n\n        for (const char of chars) {\n            if (!char[0] || char[0] === ' ') {\n                x += width;\n                continue;\n            }\n            this.ctx.fillText(char.join(''), x, y);\n            x += width;\n        }\n    }\n\n    private drawText(chars: string[][]) {\n        const { line, col } = this.store.cursor;\n        const {\n            fg,\n            bg,\n            sp,\n            draw_width,\n            draw_height,\n            face,\n            specified_px,\n            bold,\n            italic,\n            underline,\n            undercurl,\n        } = this.store.font_attr;\n\n        // Draw background\n        this.drawBlock(line, col, 1, chars.length, bg);\n        const res = window.devicePixelRatio || 1;\n        const font_size = specified_px * res;\n\n        let attrs = '';\n        if (bold) {\n            attrs += 'bold ';\n        }\n        if (italic) {\n            attrs += 'italic ';\n        }\n        this.ctx.font = attrs + font_size + 'px ' + face;\n        this.ctx.textBaseline = 'top';\n        this.ctx.fillStyle = fg;\n        const margin = Math.ceil((font_size * (this.store.line_height - 1.0)) / 2);\n        const y = Math.floor(line * draw_height);\n        const x = col * draw_width;\n        this.drawChars(x, y + margin, chars, draw_width);\n        if (undercurl) {\n            this.ctx.strokeStyle = sp || this.store.sp_color || fg; // Note: Fallback for Neovim 0.1.4 or earlier.\n            this.ctx.lineWidth = 1 * res;\n            this.ctx.setLineDash([draw_width / 3, draw_width / 3]);\n            this.ctx.beginPath();\n            const curl_y = y + draw_height - margin;\n            this.ctx.moveTo(x, curl_y);\n            this.ctx.lineTo(x + draw_width * chars.length, curl_y);\n            this.ctx.stroke();\n        } else if (underline) {\n            this.ctx.strokeStyle = fg;\n            this.ctx.lineWidth = 1 * res;\n            this.ctx.setLineDash([]);\n            this.ctx.beginPath();\n            const underline_y = y + draw_height - margin;\n            this.ctx.moveTo(x, underline_y);\n            this.ctx.lineTo(x + draw_width * chars.length, underline_y);\n            this.ctx.stroke();\n        }\n        log.debug(`drawText(): (${x}, ${y})`, chars.length, this.store.cursor);\n    }\n\n    private drawBlock(line: number, col: number, height: number, width: number, color: string) {\n        const { draw_width, draw_height } = this.store.font_attr;\n        this.ctx.fillStyle = color;\n        // Note:\n        // Height doesn't need to be truncated (floor, ceil) but width needs.\n        // The reason is desribed in Note2 of changeFontSize().\n        this.ctx.fillRect(\n            Math.floor(col * draw_width),\n            line * draw_height,\n            Math.ceil(width * draw_width),\n            height * draw_height,\n        );\n    }\n\n    private slideVertical(top: number, height: number, dst_top: number) {\n        const { left, right } = this.store.scroll_region;\n        const { draw_width, draw_height } = this.store.font_attr;\n        const x = left * draw_width;\n        const sy = top * draw_height;\n        const w = (right - left + 1) * draw_width;\n        const h = height * draw_height;\n        const dy = dst_top * draw_height;\n        this.ctx.drawImage(this.canvas, x, sy, w, h, x, dy, w, h);\n    }\n\n    private scrollUp(cols_up: number) {\n        const { top, bottom, left, right } = this.store.scroll_region;\n        this.slideVertical(top + cols_up, bottom - (top + cols_up) + 1, top);\n        this.drawBlock(bottom - cols_up + 1, left, cols_up, right - left + 1, this.store.bg_color);\n        log.debug('Scroll up: ' + cols_up, this.store.scroll_region);\n    }\n\n    private scrollDown(cols_down: number) {\n        const { top, bottom, left, right } = this.store.scroll_region;\n        this.slideVertical(top, bottom - (top + cols_down) + 1, top + cols_down);\n        this.drawBlock(top, left, cols_down, right - left + 1, this.store.bg_color);\n        log.debug('Scroll down: ' + cols_down, this.store.scroll_region);\n    }\n\n    private resizeImpl(lines: number, cols: number, width: number, height: number) {\n        const res = window.devicePixelRatio || 1;\n        if (width !== this.canvas.width) {\n            this.canvas.width = width;\n            this.canvas.style.width = width / res + 'px';\n        }\n        if (height !== this.canvas.height) {\n            this.canvas.height = height;\n            this.canvas.style.height = height / res + 'px';\n        }\n        this.store.dispatcher.dispatch(A.updateScreenSize(width, height));\n        this.store.dispatcher.dispatch(A.updateScreenBounds(lines, cols));\n    }\n}\n"
  },
  {
    "path": "src/neovim/store.ts",
    "content": "import { EventEmitter } from 'events';\nimport { Kind, ActionType, Region, ModeInfoSet } from './actions';\nimport log from '../log';\nimport ScreenDrag from './screen-drag';\nimport ScreenWheel from './screen-wheel';\nimport { DOM } from '../neovim';\nimport { Dispatcher } from 'flux';\n\n// TODO:\n// Debug log should be implemented as the subscriber of store\n// and be controlled by registering it or not, watching NODE_ENV variable.\n\nexport interface Size {\n    lines: number;\n    cols: number;\n    width: number;\n    height: number;\n}\n\nexport interface Cursor {\n    line: number;\n    col: number;\n}\n\nexport interface FontAttributes {\n    fg: string;\n    bg: string;\n    sp: string;\n    bold: boolean;\n    italic: boolean;\n    underline: boolean;\n    undercurl: boolean;\n    draw_width: number;\n    draw_height: number;\n    width: number;\n    height: number;\n    face: string;\n    specified_px: number;\n}\n\nexport type DispatcherType = Dispatcher<ActionType>;\n\n// Note: 0x001203 -> '#001203'\nfunction colorString(new_color: number, fallback: string) {\n    if (typeof new_color !== 'number' || new_color < 0) {\n        return fallback;\n    }\n\n    return (\n        '#' +\n        [16, 8, 0]\n            .map(shift => {\n                const mask = 0xff << shift;\n                const hex = ((new_color & mask) >> shift).toString(16);\n                return hex.length < 2 ? '0' + hex : hex;\n            })\n            .join('')\n    );\n}\n\nexport default class NeovimStore extends EventEmitter {\n    dispatch_token: string;\n\n    size: Size;\n    font_attr: FontAttributes;\n    fg_color: string;\n    bg_color: string;\n    sp_color: string;\n    cursor: Cursor;\n    modeInfo: ModeInfoSet;\n    mode: string;\n    busy: boolean;\n    mouse_enabled: boolean;\n    dragging: ScreenDrag;\n    title: string;\n    icon_path: string;\n    wheel_scrolling: ScreenWheel;\n    scroll_region: Region;\n    dispatcher: Dispatcher<ActionType>;\n    focused: boolean;\n    line_height: number;\n    alt_key_disabled: boolean;\n    meta_key_disabled: boolean;\n    cursor_draw_delay: number;\n    blink_cursor: boolean;\n    cursor_blink_interval: number;\n\n    constructor(public dom: DOM) {\n        super();\n        this.dispatcher = new Dispatcher<ActionType>();\n        this.size = {\n            lines: 0,\n            cols: 0,\n            width: 0,\n            height: 0,\n        };\n        this.font_attr = {\n            fg: 'white',\n            bg: 'black',\n            sp: null,\n            bold: false,\n            italic: false,\n            underline: false,\n            undercurl: false,\n            draw_width: 1,\n            draw_height: 1,\n            width: 1,\n            height: 1,\n            specified_px: 1,\n            face: 'monospace',\n        };\n        this.cursor = {\n            line: 0,\n            col: 0,\n        };\n        this.modeInfo = {};\n        this.mode = 'normal';\n        this.busy = false;\n        this.mouse_enabled = true;\n        this.dragging = null;\n        this.title = '';\n        this.icon_path = '';\n        this.wheel_scrolling = new ScreenWheel(this);\n        this.scroll_region = {\n            left: 0,\n            right: 0,\n            top: 0,\n            bottom: 0,\n        };\n        this.focused = true;\n        this.line_height = 1.2;\n        this.alt_key_disabled = false;\n        this.meta_key_disabled = false;\n        this.cursor_draw_delay = 10;\n        this.blink_cursor = false;\n        this.cursor_blink_interval = 1000;\n        this.dispatch_token = this.dispatcher.register(this.receiveAction.bind(this));\n    }\n\n    private receiveAction(action: ActionType) {\n        switch (action.type) {\n            case Kind.Input: {\n                this.emit('input', action.input);\n                break;\n            }\n            case Kind.PutText: {\n                this.emit('put', action.text);\n                this.cursor.col = this.cursor.col + action.text.length;\n                this.emit('cursor');\n                break;\n            }\n            case Kind.Cursor: {\n                this.cursor = {\n                    line: action.line,\n                    col: action.col,\n                };\n                this.emit('cursor');\n                break;\n            }\n            case Kind.Highlight: {\n                const hl = action.highlight;\n                this.font_attr.bold = hl.bold;\n                this.font_attr.italic = hl.italic;\n                this.font_attr.underline = hl.underline;\n                this.font_attr.undercurl = hl.undercurl;\n                if (hl.reverse === true) {\n                    this.font_attr.fg = colorString(hl.background, this.bg_color);\n                    this.font_attr.bg = colorString(hl.foreground, this.fg_color);\n                } else {\n                    this.font_attr.fg = colorString(hl.foreground, this.fg_color);\n                    this.font_attr.bg = colorString(hl.background, this.bg_color);\n                }\n                this.font_attr.sp = colorString(hl.special, this.sp_color || this.fg_color);\n                log.debug('Highlight is updated: ', this.font_attr);\n                break;\n            }\n            case Kind.FocusChanged: {\n                this.focused = action.focused;\n                this.emit('focus-changed');\n                log.debug('Focus changed: ', this.focused);\n                break;\n            }\n            case Kind.ClearEOL: {\n                this.emit('clear-eol');\n                break;\n            }\n            case Kind.ClearAll: {\n                this.emit('clear-all');\n                this.cursor = {\n                    line: 0,\n                    col: 0,\n                };\n                this.emit('cursor');\n                break;\n            }\n            case Kind.ScrollScreen: {\n                this.emit('screen-scrolled', action.cols);\n                break;\n            }\n            case Kind.SetScrollRegion: {\n                this.scroll_region = action.region;\n                log.debug('Region is set: ', this.scroll_region);\n                this.emit('scroll-region-updated');\n                break;\n            }\n            case Kind.Resize: {\n                if (this.resize(action.lines, action.cols)) {\n                    this.emit('resize');\n                }\n                break;\n            }\n            case Kind.UpdateFG: {\n                this.fg_color = colorString(action.color, this.font_attr.fg);\n                this.emit('update-fg');\n                log.debug('Foreground color is updated: ', this.fg_color);\n                break;\n            }\n            case Kind.UpdateBG: {\n                this.bg_color = colorString(action.color, this.font_attr.bg);\n                this.emit('update-bg');\n                log.debug('Background color is updated: ', this.bg_color);\n                break;\n            }\n            case Kind.UpdateSP: {\n                this.sp_color = colorString(action.color, this.fg_color);\n                this.emit('update-sp-color');\n                log.debug('Special color is updated: ', this.sp_color);\n                break;\n            }\n            case Kind.ModeInfo: {\n                this.modeInfo = action.modeInfo;\n                this.emit('mode-info', this.modeInfo);\n                break;\n            }\n            case Kind.Mode: {\n                this.mode = action.mode;\n                this.emit('mode', this.mode);\n                break;\n            }\n            case Kind.BusyStart: {\n                this.busy = true;\n                this.emit('busy');\n                break;\n            }\n            case Kind.BusyStop: {\n                this.busy = false;\n                this.emit('busy');\n                break;\n            }\n            case Kind.UpdateFontSize: {\n                this.font_attr.draw_width = action.draw_width;\n                this.font_attr.draw_height = action.draw_height;\n                this.font_attr.width = action.width;\n                this.font_attr.height = action.height;\n                log.debug('Actual font size is updated: ', action.width, action.height);\n                this.emit('font-size-changed');\n                break;\n            }\n            case Kind.UpdateFontPx: {\n                this.font_attr.specified_px = action.font_px;\n                this.emit('font-px-specified');\n                break;\n            }\n            case Kind.UpdateFontFace: {\n                this.font_attr.face = action.font_face;\n                this.emit('font-face-specified');\n                break;\n            }\n            case Kind.UpdateScreenSize: {\n                if (this.size.width === action.width && this.size.height === action.height) {\n                    break;\n                }\n                this.size.width = action.width;\n                this.size.height = action.height;\n                this.emit('update-screen-size');\n                log.debug('Screen size is updated: ', action.width, action.height);\n                break;\n            }\n            case Kind.UpdateScreenBounds: {\n                if (this.resize(action.lines, action.cols)) {\n                    this.emit('update-screen-bounds');\n                }\n                break;\n            }\n            case Kind.EnableMouse: {\n                if (!this.mouse_enabled) {\n                    this.mouse_enabled = true;\n                    this.emit('mouse-enabled');\n                    log.info('Mouse enabled.');\n                }\n                break;\n            }\n            case Kind.DisableMouse: {\n                if (this.mouse_enabled) {\n                    this.mouse_enabled = false;\n                    this.emit('mouse-disabled');\n                    log.info('Mouse disabled.');\n                }\n                break;\n            }\n            case Kind.DragStart: {\n                if (this.mouse_enabled) {\n                    this.dragging = new ScreenDrag(this);\n                    this.emit('input', this.dragging.start(action.event));\n                    this.emit('drag-started');\n                } else {\n                    log.debug('Click ignored because mouse is disabled.');\n                }\n                break;\n            }\n            case Kind.DragUpdate: {\n                if (this.mouse_enabled && this.dragging !== null) {\n                    const input = this.dragging.drag(action.event);\n                    if (input) {\n                        this.emit('input', input);\n                        this.emit('drag-updated');\n                    }\n                }\n                break;\n            }\n            case Kind.DragEnd: {\n                if (this.mouse_enabled && this.dragging !== null) {\n                    this.emit('input', this.dragging.end(action.event));\n                    this.emit('drag-ended');\n                    this.dragging = null;\n                }\n                break;\n            }\n            case Kind.WheelScroll: {\n                if (this.mouse_enabled) {\n                    const input = this.wheel_scrolling.handleEvent(action.event as WheelEvent);\n                    if (input) {\n                        this.emit('input', input);\n                        this.emit('wheel-scrolled');\n                    }\n                }\n                break;\n            }\n            case Kind.Bell: {\n                this.emit(action.visual ? 'visual-bell' : 'beep');\n                break;\n            }\n            case Kind.SetTitle: {\n                this.title = action.title;\n                this.emit('title-changed');\n                log.info('Title is set to ', this.title);\n                break;\n            }\n            case Kind.SetIcon: {\n                this.icon_path = action.icon_path;\n                this.emit('icon-changed');\n                log.info('Icon is set to ', this.icon_path);\n                break;\n            }\n            case Kind.UpdateLineHeight: {\n                if (this.line_height !== action.line_height) {\n                    this.line_height = action.line_height;\n                    this.emit('line-height-changed');\n                    log.info('Line height is changed to ', this.line_height);\n                }\n                break;\n            }\n            case Kind.DisableAltKey: {\n                this.alt_key_disabled = action.disabled;\n                this.emit('alt-key-disabled');\n                log.info('Alt key disabled: ', action.disabled);\n                break;\n            }\n            case Kind.DisableMetaKey: {\n                this.meta_key_disabled = action.disabled;\n                this.emit('meta-key-disabled');\n                log.info('Meta key disabled: ', action.disabled);\n                break;\n            }\n            case Kind.ChangeCursorDrawDelay: {\n                this.cursor_draw_delay = action.delay;\n                this.emit('cursor-draw-delay-changed');\n                log.info(`Drawing cursor is delayed by ${action.delay}ms`);\n                break;\n            }\n            case Kind.StartBlinkCursor: {\n                const changed = this.blink_cursor === false;\n                this.blink_cursor = true;\n                if (changed) {\n                    this.emit('blink-cursor-started');\n                }\n                break;\n            }\n            case Kind.StopBlinkCursor: {\n                const changed = this.blink_cursor === true;\n                this.blink_cursor = false;\n                if (changed) {\n                    this.emit('blink-cursor-stopped');\n                }\n                break;\n            }\n            case Kind.CompositionStart: {\n                this.emit('composition-started');\n                break;\n            }\n            case Kind.CompositionEnd: {\n                this.emit('composition-ended');\n                break;\n            }\n            default: {\n                log.warn('Unhandled action: ', action);\n                break;\n            }\n        }\n    }\n\n    private resize(lines: number, cols: number) {\n        if (this.size.lines === lines && this.size.cols === cols) {\n            return false;\n        }\n        this.size.lines = lines;\n        this.size.cols = cols;\n        this.scroll_region = {\n            top: 0,\n            left: 0,\n            right: cols - 1,\n            bottom: lines - 1,\n        };\n        log.debug(`Screen is resized: (${lines} lines, ${cols} cols)`);\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/neovim.ts",
    "content": "import { EventEmitter } from 'events';\nimport Process from './neovim/process';\nimport Screen from './neovim/screen';\nimport NeovimStore from './neovim/store';\nimport {\n    updateFontPx,\n    updateFontFace,\n    updateScreenSize,\n    updateLineHeight,\n    disableAltKey,\n    disableMetaKey,\n    changeCursorDrawDelay,\n    startBlinkCursor,\n    setTitle,\n} from './neovim/actions';\nimport log from './log';\nimport { Nvim } from 'promised-neovim-client';\n\nexport interface DOM {\n    [key: string]: Element;\n}\n\nexport default class Neovim extends EventEmitter {\n    process: Process;\n    screen: Screen;\n    store: NeovimStore;\n\n    constructor(\n        dom: DOM,\n        command: string,\n        argv: string[],\n        font: string,\n        font_size: number,\n        line_height: number,\n        disable_alt_key: boolean,\n        disable_meta_key: boolean,\n        draw_delay: number,\n        blink_cursor: boolean,\n        window_title: string,\n    ) {\n        super();\n\n        this.store = new NeovimStore(dom);\n        this.store.dispatcher.dispatch(updateLineHeight(line_height));\n        this.store.dispatcher.dispatch(updateFontFace(font));\n        this.store.dispatcher.dispatch(updateFontPx(font_size));\n        if (disable_alt_key) {\n            this.store.dispatcher.dispatch(disableAltKey(true));\n        }\n        if (disable_meta_key) {\n            this.store.dispatcher.dispatch(disableMetaKey(true));\n        }\n        this.store.dispatcher.dispatch(changeCursorDrawDelay(draw_delay));\n        if (blink_cursor) {\n            this.store.dispatcher.dispatch(startBlinkCursor());\n        }\n        this.store.dispatcher.dispatch(setTitle(window_title));\n        this.process = new Process(this.store, command, argv);\n    }\n\n    attachCanvas(width: number, height: number, canvas: HTMLCanvasElement) {\n        this.store.dispatcher.dispatch(updateScreenSize(width, height));\n        this.screen = new Screen(this.store, canvas);\n        const { lines, cols } = this.store.size;\n        this.process\n            .attach(lines, cols)\n            .then(() => {\n                this.process.client.on('disconnect', () => this.emit('quit'));\n                this.emit('process-attached');\n            })\n            .catch(err => this.emit('error', err));\n    }\n\n    quit() {\n        this.process.finalize().catch(err => log.error(err));\n    }\n\n    getClient(): Nvim {\n        return this.process.client;\n    }\n\n    focus() {\n        this.screen.focus();\n    }\n\n    // Note:\n    // It is better to use 'argv' property of <neovim-client> for apps using Polymer.\n    setArgv(argv: string[]) {\n        if (!this.process.started) {\n            throw new Error(\n                \"Process is not attached yet.  Use 'process-attached' event to ensure to specify arguments.\",\n            );\n        }\n        return this.process.client.command('args ' + argv.join(' '));\n    }\n}\n"
  },
  {
    "path": "test/.eslintrc",
    "content": "{\n    \"rules\": {\n        \"indent\": [\n            2,\n            4\n        ],\n        \"quotes\": 0,\n        \"linebreak-style\": [\n            2,\n            \"unix\"\n        ],\n        \"semi\": [\n            2,\n            \"always\"\n        ]\n    },\n    \"env\": {\n        \"es6\": true,\n        \"node\": true,\n        \"browser\": true,\n        \"mocha\": true\n    },\n    \"parserOptions\": {\n      \"ecmaVersion\": 6\n    },\n    \"extends\": \"eslint:recommended\",\n    \"ecmaFeatures\": {\n        \"jsx\": true,\n        \"modules\": true,\n        \"experimentalObjectRestSpread\": true\n    }\n}\n"
  },
  {
    "path": "test/e2e/mocha.opts",
    "content": "--ui bdd\n--timeout 300000\n"
  },
  {
    "path": "test/e2e/smoke.ts",
    "content": "import * as path from 'path';\nimport { Application } from 'spectron';\nimport { assert } from 'chai';\n\n// XXX: Get Electron binary path\nconst electron: string = (require as any)('electron');\n\ndescribe('neovim element', function() {\n    this.timeout(10000);\n    before(function() {\n        this.app = new Application({\n            path: electron,\n            args: [path.join(__dirname, '..', '..', '..')],\n            env: {\n                NODE_ENV: 'production',\n            },\n        });\n        return this.app.start().then(() => this.app.client.pause(3000)); // Wait application starting\n    });\n\n    after(function() {\n        if (this.app.isRunning()) {\n            return this.app.stop();\n        }\n    });\n\n    it('can start without an error', function() {\n        return this.app.client\n            .getWindowCount()\n            .then((c: number) => assert.equal(c, 1))\n            .then(() => this.app.browserWindow.isVisible())\n            .then((b: boolean) => assert.isTrue(b))\n            .then(() => this.app.webContents.getURL())\n            .then((u: string) => assert.ok(u))\n            .then(() =>\n                this.app.client.execute(() => {\n                    const e = document.getElementById('neovim') as any;\n                    if (!e) {\n                        return null;\n                    }\n                    return e.editor.screen.ctx.getImageData(10, 10, 1, 1);\n                }),\n            )\n            .then((returned: { value: { data: Uint8ClampedArray } }) => {\n                const rgba = returned.value.data;\n                assert.equal(rgba.length, 4);\n                // White means nothing may not be rendered\n                assert.isFalse(rgba[0] === 255 && rgba[1] === 255 && rgba[2] === 255);\n            })\n            .then(() => this.app.client.getRenderProcessLogs())\n            .then((logs: any[]) => {\n                for (const log of logs) {\n                    assert.notEqual(log.level, 'error', log.message);\n                }\n            })\n            .then(() => this.app.client.getMainProcessLogs())\n            .then((logs: string[]) => {\n                const allowedErrors = [\n                    'net::ERR_FILE_NOT_FOUND',\n                    'Electron Security Warning',\n                    'Unhandled event:',\n                    'DevTools listening on',\n                    'HTML Imports is deprecated and will be removed in M73',\n                    \"'electron.screen' is deprecated\",\n                ];\n                const unexpectedLogs = logs.filter(m => !allowedErrors.some(w => m.includes(w))).filter(m => m !== '');\n                assert.equal(unexpectedLogs.length, 0, `'${unexpectedLogs}'`);\n            });\n    });\n});\n"
  },
  {
    "path": "test/unit/cursor_test.js",
    "content": "global.require = require;\nconst assert = require('chai').assert;\nconst jsdom = require('jsdom');\nconst NeovimStore = require('../../build/src/neovim/store').default;\nconst Cursor = require('../../build/src/neovim/cursor').default;\nconst A = require('../../build/src/neovim/actions');\nconst {dom, document} = require('./dom_faker');\n\ndescribe('Cursor', () => {\n    beforeEach(() => {\n        global.store = new NeovimStore(dom);\n        store.font_attr.width = 7;\n        store.font_attr.height = 14;\n        store.font_attr.draw_width = 7;\n        store.font_attr.draw_height = 14;\n        store.cursor_draw_delay = 0;\n        store.cursor_blink_interval = 100;\n        /* global cursor */\n        global.cursor = new Cursor(store, dom.screen.getContext('2d'));\n    });\n\n    afterEach(() => {\n        delete global.store;\n        delete global.cursor;\n    });\n\n    it('initializes cursor element', () => {\n        const e = cursor.element;\n        assert.equal(e.style.top, '0px');\n        assert.equal(e.style.left, '0px');\n        assert.equal(e.style.width, '7px');\n        assert.equal(e.style.height, '14px');\n        assert.equal(e.width, 7);\n        assert.equal(e.height, 14);\n    });\n\n    it('updates cursor size when font size is changed', () => {\n        store.font_attr.width = 8;\n        store.font_attr.height = 16;\n        store.emit('font-size-changed');\n        assert.equal(cursor.element.style.width, '8px');\n        assert.equal(cursor.element.style.height, '16px');\n    });\n\n    it('moves cursor on cursor moving', () => {\n        store.cursor = {\n            line: 12,\n            col: 24\n        };\n        store.emit('cursor');\n        assert.equal(cursor.element.style.left, store.cursor.col * store.font_attr.width + 'px');\n        assert.equal(cursor.element.style.top, store.cursor.line * store.font_attr.height + 'px');\n    });\n\n    context('on cursor blinking', () => {\n        beforeEach(() => {\n            global.store.dispatcher.dispatch(A.startBlinkCursor());\n        });\n\n        it('starts blink timer at start', () => {\n            assert.isTrue(cursor.blink_timer.enabled, 'Blink timer did not start');\n        });\n\n        it('makes cursor blink actually', done => {\n            var flag = cursor.blink_timer.shown;\n            setTimeout(() => {\n                assert.notEqual(flag, cursor.blink_timer.shown);\n                flag = cursor.blink_timer.shown;\n                setTimeout(() => {\n                    assert.notEqual(flag, cursor.blink_timer.shown);\n                    done();\n                }, 110);\n            }, 110);\n        });\n\n        it('stops blinking on cursor busy', done => {\n            store.busy = true;\n            store.emit('busy');\n            var flag = cursor.blink_timer.shown;\n            setTimeout(() => {\n                assert.equal(flag, cursor.blink_timer.shown);\n                done();\n            }, 110);\n        });\n\n        it('restore cursor after editor backs from busy state', done => {\n            store.busy = true;\n            store.emit('busy');\n            store.busy = false;\n            store.emit('busy');\n            var flag = cursor.blink_timer.shown;\n            setTimeout(() => {\n                assert.notEqual(flag, cursor.blink_timer.shown);\n                done();\n            }, 110);\n        });\n\n        it('stops blinking on insert mode', done => {\n            store.mode = 'insert';\n            store.emit('mode');\n            var flag = cursor.blink_timer.shown;\n            setTimeout(() => {\n                assert.equal(flag, cursor.blink_timer.shown);\n                done();\n            }, 110);\n        });\n\n        it('starts cursor blinking on normal mode again', done => {\n            store.mode = 'insert';\n            store.emit('mode');\n            store.mode = 'normal';\n            store.emit('mode');\n            var flag = cursor.blink_timer.shown;\n            setTimeout(() => {\n                assert.notEqual(flag, cursor.blink_timer.shown);\n                done();\n            }, 110);\n        });\n\n        it('stop cursor blinking when focus lost', done => {\n            store.focused = false;\n            store.emit('focus-changed');\n            var flag = cursor.blink_timer.shown;\n            setTimeout(() => {\n                assert.equal(flag, cursor.blink_timer.shown);\n                done();\n            }, 110);\n        });\n\n        it('starts cursor blinking again when focus gained', done => {\n            store.focused = false;\n            store.emit('focus-changed');\n            store.focused = true;\n            store.emit('focus-changed');\n            var flag = cursor.blink_timer.shown;\n            setTimeout(() => {\n                assert.notEqual(flag, cursor.blink_timer.shown);\n                done();\n            }, 110);\n        });\n\n        it(\"stops cursor blinking after 'stopBlinkCursor' action\", done => {\n            store.blink_cursor = false;\n            store.emit('blink-cursor-stopped');\n            var flag = cursor.blink_timer.shown;\n            setTimeout(() => {\n                assert.equal(flag, cursor.blink_timer.shown);\n                done();\n            }, 110);\n        });\n    });\n});\n\n"
  },
  {
    "path": "test/unit/dom_faker.js",
    "content": "const {JSDOM} = require('jsdom');\nconst jsdom = new JSDOM(`\n<body>\n  <div id=\"container\">\n    <canvas id=\"screen\" width$=\"[[width]]\" height$=\"[[height]]\"></canvas>\n    <canvas id=\"cursor\"></canvas>\n    <input id=\"input\" autocomplete=\"off\" autofocus />\n    <span id=\"preedit\"></span>\n  </div>\n</body>\n`);\nconst document = jsdom.window.document;\n\nconst dom = {\n    cursor: document.getElementById('cursor'),\n    container: document.getElementById('container'),\n    input: document.getElementById('input'),\n    preedit: document.getElementById('preedit'),\n    screen: document.getElementById('screen'),\n};\n\nmodule.exports = {document, dom};\n"
  },
  {
    "path": "test/unit/input_test.js",
    "content": "global.require = require;\nconst assert = require('chai').assert;\nconst {JSDOM} = require('jsdom');\nconst NeovimStore = require('../../build/src/neovim/store').default;\nconst NeovimInput = require('../../build/src/neovim/input').default;\nconst {dom, document} = require('./dom_faker');\n\n(function(){\nlet window;\n\nfunction keydownEvent(opts) {\n    const o = opts || {};\n    if (o.keyCode) {\n        o.charCode = o.which = o.keyCode;\n    } else if (o.key !== undefined) {\n        o.keyCode = o.charCode = o.which = o.key.charCodeAt(0);\n    } else {\n        throw 'Invalid options for KeyboardEvent';\n    }\n    return new window.KeyboardEvent('keydown', o);\n}\n\nfunction dispatchKeydown(opts) {\n    return global.input_element.dispatchEvent(keydownEvent(opts));\n}\n\nfunction inputByKeydown(opts) {\n    global.last_input = '';\n    dispatchKeydown(opts);\n    const i = global.last_input;\n    global.last_input = '';\n    return i;\n}\n\nfunction dispatchInputEvent() {\n    global.input_element.dispatchEvent(\n        new window.Event('input', {\n            bubbles: true,\n            cancelable: false\n        })\n    );\n}\n\nfunction catchInputOnInputEvent(i) {\n    global.last_input = '';\n    global.input_element.value = i;\n    dispatchInputEvent();\n    const tmp = global.last_input;\n    global.last_input = '';\n    return tmp;\n}\n\ndescribe('NeovimInput', () => {\n    before(() => {\n        /* global document input_element window input last_input */\n        global.input_element = dom.input;\n        input_element.value = '';\n        window = document.defaultView;\n        const s = new NeovimStore(dom);\n        /* eslint no-unused-vars:0 */\n        global.input = new NeovimInput(s);\n        global.last_input = '';\n        s.on('input', i => {\n            global.last_input = i;\n        });\n    });\n\n    beforeEach(() => {\n        global.input.store.alt_key_disabled = false;\n        global.input.store.meta_key_disabled = false;\n    });\n\n    after(() => {\n        delete global.input;\n        delete global.input_element;\n    });\n\n    it('focuses on an input element on initialization', () => {\n        assert.equal(document.activeElement.id, 'input');\n    });\n\n    context(\"on 'keydown' event\", () => {\n        it('ignores normal char input without modifiers', () => {\n            global.last_input = '';\n\n            dispatchKeydown({key: 'a'});\n            assert.equal(last_input, '');\n\n            dispatchKeydown({key: '['});\n            assert.equal(last_input, '');\n\n            dispatchKeydown({key: '3', shiftKey: true});\n            assert.equal(last_input, '');\n\n            dispatchKeydown({key: 'あ'});\n            assert.equal(last_input, '');\n        });\n\n        it('accepts input with ctrl key', () => {\n            assert.equal(inputByKeydown({key: 'a', ctrlKey: true}), '<C-a>');\n            assert.equal(inputByKeydown({key: 'o', ctrlKey: true}), '<C-o>');\n            assert.equal(inputByKeydown({key: '3', ctrlKey: true}), '<C-3>');\n        });\n\n        it('accepts input with alt key', () => {\n            assert.equal(inputByKeydown({key: ',', altKey: true}), '<A-,>');\n            assert.equal(inputByKeydown({key: 'r', altKey: true}), '<A-r>');\n            assert.equal(inputByKeydown({key: '4', altKey: true}), '<A-4>');\n        });\n\n        it('accepts input with alt+ctrl keys', () => {\n            assert.equal(inputByKeydown({key: 'a', ctrlKey: true, altKey: true}), '<C-A-a>');  // Ctrl is included in \\u0001\n            assert.equal(inputByKeydown({key: 'o', ctrlKey: true, altKey: true}), '<C-A-o>');  // Ctrl is included in \\u000f\n        });\n\n        it('accepts input with command keys', () => {\n            assert.equal(inputByKeydown({key: 'a', metaKey: true}), '<D-a>');\n            assert.equal(inputByKeydown({key: 'a', metaKey: true, ctrlKey: true}), '<C-D-a>');\n            assert.equal(inputByKeydown({key: 'a', metaKey: true, shiftKey: true}), '<D-S-a>');\n        });\n\n        it('accepts special keys', () => {\n            assert.equal(inputByKeydown({key: 'Tab'}), '<Tab>');\n            assert.equal(inputByKeydown({key: 'Tab', altKey: true}), '<A-Tab>');\n            assert.equal(inputByKeydown({key: 'Tab', ctrlKey: true, shiftKey: true}), '<C-S-Tab>');\n            assert.equal(inputByKeydown({key: 'Enter'}), '<CR>');\n            assert.equal(inputByKeydown({key: 'Enter', altKey: true}), '<A-CR>');\n            assert.equal(inputByKeydown({key: 'Enter', ctrlKey: true, shiftKey: true}), '<C-S-CR>');\n            assert.equal(inputByKeydown({key: 'ArrowLeft'}), '<Left>');\n            assert.equal(inputByKeydown({key: 'ArrowLeft', altKey: true}), '<A-Left>');\n            assert.equal(inputByKeydown({key: 'ArrowLeft', ctrlKey: true, shiftKey: true}), '<C-S-Left>');\n            assert.equal(inputByKeydown({key: '<', ctrlKey: true, shiftKey: true}), '<C-LT>');\n            assert.equal(inputByKeydown({key: '<', altKey: true, shiftKey: true}), '<A-LT>');\n            assert.equal(inputByKeydown({key: '\\0'}), '<Nul>');\n        });\n\n        it('handles <CR>, <Esc>, <BS> and <C-m>, <C-[>, <C-h> edge cases', () => {\n            assert.equal(inputByKeydown({key: 'Enter', keyCode: 13}), '<CR>');\n            assert.equal(inputByKeydown({key: 'Enter', keyCode: 77, ctrlKey: true}), '<C-m>');\n            assert.equal(inputByKeydown({key: 'Enter', keyCode: 13, ctrlKey: true}), '<C-CR>');\n\n            assert.equal(inputByKeydown({key: 'Escape', keyCode: 27}), '<Esc>');\n            assert.equal(inputByKeydown({key: 'Escape', keyCode: 219, ctrlKey: true}), '<C-[>');\n            assert.equal(inputByKeydown({key: 'Escape', keyCode: 27, ctrlKey: true}), '<C-Esc>');\n\n            assert.equal(inputByKeydown({key: 'Backspace', keyCode: 8}), '<BS>');\n            assert.equal(inputByKeydown({key: 'Backspace', keyCode: 72, ctrlKey: true}), '<C-h>');\n            assert.equal(inputByKeydown({key: 'Backspace', keyCode: 8, ctrlKey: true}), '<C-BS>');\n\n            assert.equal(inputByKeydown({key: 'Tab', keyCode: 9}), '<Tab>');\n            assert.equal(inputByKeydown({key: 'Tab', keyCode: 73, ctrlKey: true}), '<C-i>');\n            assert.equal(inputByKeydown({key: 'Tab', keyCode: 9, ctrlKey: true}), '<C-Tab>');\n        });\n\n        it('replaces some special Ctrl+Shift characters following gVim behavior (issue #87)', () => {\n            assert.equal(inputByKeydown({key: '2', keyCode: 50, ctrlKey: true}), '<C-@>');\n            assert.equal(inputByKeydown({key: '6', keyCode: 54, ctrlKey: true}), '<C-^>');\n            assert.equal(inputByKeydown({key: '-', keyCode: 189, ctrlKey: true}), '<C-_>');\n        });\n\n        it(\"handles ' ' edge case\", () => {\n            assert.equal(inputByKeydown({key: ' ', keyCode: 32}), '<Space>');\n            assert.equal(inputByKeydown({key: ' ', keyCode: 32, ctrlKey: true}), '<C-Space>');\n            assert.equal(inputByKeydown({key: ' ', keyCode: 32, shiftKey: true}), '<S-Space>');\n        });\n\n        context('when alt key is disabled', () => {\n            it('ignores event.altKey', () => {\n                global.input.store.alt_key_disabled = true;\n                global.last_input = '';\n\n                dispatchKeydown({key: 'a', altKey: true});\n                assert.equal(last_input, 'a');\n\n                assert.equal(inputByKeydown({key: 'a', altKey: true, ctrlKey: true}), '<C-a>');\n                assert.equal(inputByKeydown({key: 'o', altKey: true, shiftKey: true, ctrlKey: true}), '<C-S-o>');\n            });\n\n            it('does not ignore any other modifiers', () => {\n                global.input.store.alt_key_disabled = true;\n                global.last_input = '';\n\n                dispatchKeydown({key: 'a'});\n                assert.equal(last_input, '');\n\n                assert.equal(inputByKeydown({key: 'a', ctrlKey: true}), '<C-a>');\n                assert.equal(inputByKeydown({key: 'o', ctrlKey: true, shiftKey: true}), '<C-S-o>');\n            });\n        });\n\n        context('when meta key is disabled', () => {\n            it('ignores event.metaKey', () => {\n                global.input.store.meta_key_disabled = true;\n                global.last_input = '';\n\n                dispatchKeydown({key: 'a', metaKey: true});\n                assert.equal(last_input, '');\n                dispatchKeydown({key: 'a', altKey: true, shiftKey: true, ctrlKey: true, metaKey: true});\n                assert.equal(last_input, '');\n            });\n\n            it('does not ignore any other modifiers', () => {\n                global.input.store.meta_key_disabled = true;\n                global.last_input = '';\n\n                dispatchKeydown({key: 'a'});\n                assert.equal(last_input, '');\n\n                assert.equal(inputByKeydown({key: 'Enter', ctrlKey: true}), '<C-CR>');\n                assert.equal(inputByKeydown({key: 'o', altKey: true, ctrlKey: true, shiftKey: true}), '<C-A-S-o>');\n            });\n        });\n\n        context('when shift key is pressed', () => {\n            it('considers shift key on alphabetical key input', () => {\n                assert.equal(inputByKeydown({key: 'a', shiftKey: true, ctrlKey: true}), '<C-S-a>');\n                assert.equal(inputByKeydown({key: 'P', shiftKey: true, ctrlKey: true}), '<C-S-P>');\n                assert.equal(inputByKeydown({key: 'q', shiftKey: true, altKey: true}), '<A-S-q>');\n            });\n\n            it('does not consider shift key on non-slphabetical key input', () => {\n                assert.equal(inputByKeydown({key: '@', shiftKey: true, ctrlKey: true}), '<C-@>');\n                assert.equal(inputByKeydown({key: '{', shiftKey: true, ctrlKey: true}), '<C-{>');\n                assert.equal(inputByKeydown({key: ']', shiftKey: true, ctrlKey: true}), '<C-]>');\n            });\n        });\n    });\n\n    context(\"on 'input' event\", () => {\n        it('sends input character to Neovim', () => {\n            assert.equal(catchInputOnInputEvent('a'), 'a');\n            assert.equal(catchInputOnInputEvent('3'), '3');\n            assert.equal(catchInputOnInputEvent(';'), ';');\n            assert.equal(catchInputOnInputEvent('^'), '^');\n        });\n\n        it('clears value in <input>', () => {\n            global.input_element.value = 'a';\n            dispatchInputEvent();\n            assert.equal(global.input_element.value, '');\n        });\n\n        it(\"handles '<' edge case\", () => {\n            assert.equal(catchInputOnInputEvent('<'), '<LT>');\n        });\n    });\n\n    context(\"on focus events\", () => {\n        it('emits <FocusGained> on focused', () => {\n            const e = new window.Event('focus', {\n                bubbles: true,\n                cancelable: false\n            });\n            global.input_element.dispatchEvent(e);\n            assert.equal(global.last_input, '<FocusGained>');\n        });\n\n        it('emits <FocusLost> on focused', () => {\n            const e = new window.Event('blur', {\n                bubbles: true,\n                cancelable: false\n            });\n            global.input_element.dispatchEvent(e);\n            assert.equal(global.last_input, '<FocusLost>');\n        });\n    });\n\n    it('moves <input> element following cursor', () => {\n        const store = input.store;\n        store.cursor = {\n            line: 12,\n            col: 24\n        };\n        store.font_attr.width = 4;\n        store.font_attr.height = 8;\n\n        store.emit('cursor');\n        assert.equal(input_element.style.left, store.cursor.col * store.font_attr.width + 'px');\n        assert.equal(input_element.style.top, store.cursor.line * store.font_attr.height + 'px');\n    });\n});\n})();\n"
  },
  {
    "path": "test/unit/screen-drag_test.js",
    "content": "global.require = require;\nconst assert = require('chai').assert;\nconst ScreenDrag = require('../../build/src/neovim/screen-drag').default;\nconst NeovimStore = require('../../build/src/neovim/store').default;\nconst {dom, document} = require('./dom_faker');\n\nfunction eventFactory(kind) {\n    return function (opts) {\n        const e = document.createEvent('UIEvents');\n        e.initEvent('mouse' + kind, true, false);\n        if (opts) {\n            for (let k in opts) {\n                e[k] = opts[k];\n            }\n        }\n        return e;\n    };\n}\n\ndescribe('ScreenDrag', () => {\n    const mousedown = eventFactory('down');\n    const mousemove = eventFactory('move');\n    const mouseup = eventFactory('up');\n\n    describe('#start()', () => {\n        const store = new NeovimStore(dom);\n        store.font_attr.height = 14;\n        store.font_attr.width = 7;\n\n        it('generates input to Neovim from left button mousedown event', () => {\n            const d = new ScreenDrag(store);\n            const e = mousedown({\n                ctrlKey: false,\n                altKey: false,\n                shiftKey: false,\n                clientX: 70,\n                clientY: 140,\n                button: 0\n            });\n            assert.equal(d.start(e), '<LeftMouse><10,10>');\n        });\n\n        it('generates input to Neovim from middle button mousedown event', () => {\n            const d = new ScreenDrag(store);\n            const e = mousedown({\n                ctrlKey: true,\n                altKey: false,\n                shiftKey: true,\n                clientX: 70,\n                clientY: 140,\n                button: 1\n            });\n            assert.equal(d.start(e), '<C-S-MiddleMouse><10,10>');\n        });\n\n        it('generates input to Neovim from right button mousedown event', () => {\n            const d = new ScreenDrag(store);\n            const e = mousedown({\n                ctrlKey: true,\n                altKey: true,\n                shiftKey: false,\n                clientX: 0,\n                clientY: 0,\n                button: 2\n            });\n            assert.equal(d.start(e), '<C-A-RightMouse><0,0>');\n        });\n    });\n\n    describe('#drag()', () => {\n        const store = new NeovimStore(dom);\n        store.font_attr.height = 14;\n        store.font_attr.width = 7;\n        const down = mousedown();\n\n        it('generates input to Neovim from left button mousemove event', () => {\n            const d = new ScreenDrag(store);\n            d.start(down);\n            const e = mousemove({\n                ctrlKey: false,\n                altKey: false,\n                shiftKey: false,\n                clientX: 70,\n                clientY: 140,\n                button: 0\n            });\n            assert.equal(d.drag(e), '<LeftDrag><10,10>');\n        });\n\n        it('generates input to Neovim from middle button mousemove event', () => {\n            const d = new ScreenDrag(store);\n            d.start(down);\n            const e = mousemove({\n                ctrlKey: true,\n                altKey: false,\n                shiftKey: true,\n                clientX: 140,\n                clientY: 280,\n                button: 1\n            });\n            assert.equal(d.drag(e), '<C-S-MiddleDrag><20,20>');\n        });\n\n        it('generates input to Neovim from right button mousemove event', () => {\n            const d = new ScreenDrag(store);\n            d.start(down);\n            const e = mousemove({\n                ctrlKey: true,\n                altKey: true,\n                shiftKey: false,\n                clientX: 0,\n                clientY: 0,\n                button: 2\n            });\n            assert.equal(d.drag(e), '<C-A-RightDrag><0,0>');\n        });\n\n        it('returns empty input when coordinate is not changed', () => {\n            const d = new ScreenDrag(store);\n            d.start(down);\n            const e = mousemove({\n                ctrlKey: false,\n                altKey: false,\n                shiftKey: false,\n                clientX: 70,\n                clientY: 140,\n                button: 0\n            });\n            assert.isNotNull(d.drag(e), 'input is null even if event location is changed');\n            assert.isNull(d.drag(e), 'input is NOT null even if event location is not changed from previous drag method call');\n        });\n    });\n\n    describe('#end()', () => {\n        const store = new NeovimStore(dom);\n        store.font_attr.height = 14;\n        store.font_attr.width = 7;\n        const down = mousedown();\n\n        it('generates input to Neovim from left button mouseup event', () => {\n            const d = new ScreenDrag(store);\n            d.start(down);\n            const e = mouseup({\n                ctrlKey: false,\n                altKey: false,\n                shiftKey: false,\n                clientX: 70,\n                clientY: 140,\n                button: 0\n            });\n            assert.equal(d.end(e), '<LeftRelease><10,10>');\n        });\n\n        it('generates input to Neovim from middle button mouseup event', () => {\n            const d = new ScreenDrag(store);\n            d.start(down);\n            const e = mouseup({\n                ctrlKey: true,\n                altKey: false,\n                shiftKey: true,\n                clientX: 140,\n                clientY: 280,\n                button: 1\n            });\n            assert.equal(d.end(e), '<C-S-MiddleRelease><20,20>');\n        });\n\n        it('generates input to Neovim from right button mouseup event', () => {\n            const d = new ScreenDrag(store);\n            d.start(down);\n            const e = mouseup({\n                ctrlKey: true,\n                altKey: true,\n                shiftKey: false,\n                clientX: 0,\n                clientY: 0,\n                button: 2\n            });\n            assert.equal(d.end(e), '<C-A-RightRelease><0,0>');\n        });\n    });\n});\n"
  },
  {
    "path": "test/unit/screen-wheel_test.js",
    "content": "global.require = require;\nconst assert = require('chai').assert;\nconst jsdom = require('jsdom');\nconst document = new jsdom.JSDOM().window.document;\nconst ScreenWheel = require('../../build/src/neovim/screen-wheel').default;\nconst NeovimStore = require('../../build/src/neovim/store').default;\n\n\nfunction wheelEvent(x, y, opts) {\n    var e = document.createEvent('UIEvents');\n    e.initEvent('wheel', true, false);\n    if (opts) {\n        for (var k in opts) {\n            e[k] = opts[k];\n        }\n    }\n    e.deltaX = x;\n    e.deltaY = y;\n    return e;\n}\n\ndescribe('ScreenWheel', () => {\n    var store = null;\n\n    beforeEach(() => {\n        store = new NeovimStore();\n    });\n\n    afterEach(() => {\n        store = null;\n    });\n\n    describe('#handleEvent()', () => {\n        it('outputs empty input when no wheel event', () => {\n            const w = new ScreenWheel(store);\n            for (var i = 0; i < 10; ++i) {\n                const input_to_neovim = w.handleEvent(wheelEvent(0, 0));\n                assert.equal(input_to_neovim, '');\n            }\n        });\n\n        it('makes key sequence from scroll down wheel events', () => {\n            const w = new ScreenWheel(store);\n            const input_to_neovim = w.handleEvent(wheelEvent(0, 100));\n            assert.include(input_to_neovim, '<ScrollWheelDown>');\n        });\n\n        it('makes key sequence from scroll up wheel events', () => {\n            const w = new ScreenWheel(store);\n            const input_to_neovim = w.handleEvent(wheelEvent(0, -100));\n            assert.include(input_to_neovim, '<ScrollWheelUp>');\n        });\n\n        it('makes key sequence from scroll left wheel events', () => {\n            const w = new ScreenWheel(store);\n            const input_to_neovim = w.handleEvent(wheelEvent(100, 0));\n            assert.include(input_to_neovim, '<ScrollWheelLeft>');\n        });\n\n        it('makes key sequence from scroll right wheel events', () => {\n            const w = new ScreenWheel(store);\n            const input_to_neovim = w.handleEvent(wheelEvent(-100, 0));\n            assert.include(input_to_neovim, '<ScrollWheelRight>');\n        });\n\n        it('accumulates amounts of scrolling', () => {\n            const w = new ScreenWheel(store);\n            var input_to_neovim = w.handleEvent(wheelEvent(2, 0));\n            assert.equal(input_to_neovim, '');\n            input_to_neovim = w.handleEvent(wheelEvent(4, 0));\n            assert.notEqual(input_to_neovim, '');\n        });\n\n        it('respects Ctrl and Shift modifier key', () => {\n            var w, input_to_neovim;\n\n            w = new ScreenWheel(store);\n            input_to_neovim = w.handleEvent(wheelEvent(0, 100, { ctrlKey: true }));\n            assert.include(input_to_neovim, '<C-ScrollWheelDown>');\n\n            w = new ScreenWheel(store);\n            input_to_neovim = w.handleEvent(wheelEvent(0, 100, { shiftKey: true }));\n            assert.include(input_to_neovim, '<S-ScrollWheelDown>');\n\n            w = new ScreenWheel(store);\n            input_to_neovim = w.handleEvent(wheelEvent(0, 100, { shiftKey: true, ctrlKey: true }));\n            assert.include(input_to_neovim, '<C-S-ScrollWheelDown>');\n        });\n\n        it('resets amounts of scrolling when modifier key is changed', () => {\n            const w = new ScreenWheel(store);\n            var input_to_neovim = w.handleEvent(wheelEvent(0, 1, { ctrlKey: true }));\n            assert.equal(input_to_neovim, '');\n            input_to_neovim = w.handleEvent(wheelEvent(0, 1, { shiftKey: true }));\n            assert.equal(input_to_neovim, '');\n            input_to_neovim = w.handleEvent(wheelEvent(0, 2, { shiftKey: true }));\n            assert.notEqual(input_to_neovim, '');\n        });\n    });\n});\n"
  },
  {
    "path": "test/unit/store_test.js",
    "content": "global.require = require;\nglobal.window = global;\nconst assert = require('chai').assert;\nconst jsdom = require('jsdom');\nconst A = require('../../build/src/neovim/actions');\nconst NeovimStore = require('../../build/src/neovim/store').default;\nconst ScreenWheel = require('../../build/src/neovim/screen-wheel').default;\nconst document = new jsdom.JSDOM().window.document;\nconst {dom} = require('./dom_faker');\n\ndescribe('NeovimStore', () => {\n    context('initialization', () => {\n        it('creates default state', () => {\n            const s = new NeovimStore(dom);\n            assert.deepEqual(s.size, {\n                lines: 0,\n                cols: 0,\n                width: 0,\n                height: 0\n            });\n            assert.deepEqual(s.font_attr, {\n                fg: 'white',\n                bg: 'black',\n                sp: null,\n                bold: false,\n                italic: false,\n                underline: false,\n                undercurl: false,\n                draw_width: 1,\n                draw_height: 1,\n                width: 1,\n                height: 1,\n                specified_px: 1,\n                face: 'monospace'\n            });\n            assert.deepEqual(s.cursor, {\n                line: 0,\n                col: 0\n            });\n            assert.equal(s.mode, 'normal');\n            assert.isFalse(s.busy);\n            assert.isTrue(s.mouse_enabled);\n            assert.isNull(s.dragging);\n            assert.equal(s.title, '');\n            assert.equal(s.icon_path, '');\n            assert.deepEqual(s.wheel_scrolling, new ScreenWheel(s));\n            assert.deepEqual(s.scroll_region, {\n                left: 0,\n                right: 0,\n                top: 0,\n                bottom: 0\n            });\n            assert.isTrue(s.focused);\n            assert.equal(s.line_height, 1.2);\n            assert.equal(s.alt_key_disabled, false);\n            assert.equal(s.meta_key_disabled, false);\n            assert.equal(s.cursor_draw_delay, 10);\n            assert.equal(s.blink_cursor, false);\n            assert.equal(s.cursor_blink_interval, 1000);\n        });\n    });\n\n    context('on action received', () => {\n        it('accepts input to neovim', () => {\n            const s = new NeovimStore(dom);\n            s.on('input', i => {\n                assert.equal(i, 'madaikeru');\n            });\n            s.dispatcher.dispatch(A.inputToNeovim('madaikeru'));\n        });\n\n        it('handles put event', () => {\n            const s = new NeovimStore(dom);\n            s.on('put', text => {\n                assert.equal(text, 'moudame');\n            });\n            var c = false;\n            s.on('cursor', () => {\n                c = true;\n            });\n            s.dispatcher.dispatch(A.putText('moudame'));\n            assert.deepEqual(s.cursor, {\n                col: 7,\n                line: 0\n            }, 'cursor did not move');\n            assert.isTrue(c, 'cursor event was not fired');\n        });\n\n        it('handles cursor event', () => {\n            const s = new NeovimStore(dom);\n            var c = false;\n            s.on('cursor', () => {\n                c = true;\n            });\n            s.dispatcher.dispatch(A.cursor(114, 514));\n            assert.isTrue(c, 'cursor event was not fired');\n            assert.deepEqual(s.cursor, {\n                line: 114,\n                col: 514\n            });\n        });\n\n        it('handles highlight_set event', () => {\n            const s = new NeovimStore(dom);\n            const hl1 = {\n                background: 0xffffff,\n                bg: 'white',\n                bold: false,\n                fg: 'black',\n                special: 0x111111,\n                foreground: 0x333333,\n                italic: true,\n                reverse: false,\n                undercurl: true,\n                underline: false\n            };\n            s.dispatcher.dispatch(A.highlight(hl1));\n            var f = s.font_attr;\n            assert.isFalse(f.bold, 'bold');\n            assert.isTrue(f.italic, 'italic');\n            assert.isTrue(f.undercurl, 'undercurl');\n            assert.isFalse(f.underline, 'underline');\n            assert.equal(f.bg, '#ffffff');\n            assert.equal(f.fg, '#333333');\n            assert.equal(f.sp, '#111111');\n\n            const hl2 = {\n                background: 0xffffff,\n                foreground: 0x333333,\n                reverse: true\n            };\n            s.dispatcher.dispatch(A.highlight(hl2));\n            f = s.font_attr;\n            assert.isUndefined(f.bold, 'bold');\n            assert.isUndefined(f.italic, 'italic');\n            assert.isUndefined(f.undercurl, 'undercurl');\n            assert.isUndefined(f.underline, 'underline');\n            assert.equal(f.bg, '#333333');\n            assert.equal(f.fg, '#ffffff');\n            assert.equal(f.fg, '#ffffff');\n        });\n\n        it('accespts notify-focus-changed action', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('focus-changed', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.notifyFocusChanged(false));\n            assert.isFalse(s.focused, 'focus did not change');\n            assert.isTrue(flag, 'focus-changed event was not changed');\n\n            s.dispatcher.dispatch(A.notifyFocusChanged(true));\n            assert.isTrue(s.focused, 'focus did not change');\n        });\n\n        it('handles clear-eol event', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('clear-eol', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.clearEndOfLine());\n            assert.isTrue(flag, 'clear-eol event was not fired');\n        });\n\n        it('handles clear event', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('clear-all', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.cursor(42, -42));\n            s.dispatcher.dispatch(A.clearAll());\n            assert.isTrue(flag, 'clear-all event was not fired');\n            assert.deepEqual(s.cursor, {\n                line: 0,\n                col: 0\n            });\n\n            var flag2 = false;\n            s.on('cursor', () => {\n                flag2 = true;\n            });\n            s.dispatcher.dispatch(A.clearAll());\n            assert.isTrue(flag2, 'cursor event was not fired');\n        });\n\n        it('accespts scroll screen action', () => {\n            const s = new NeovimStore(dom);\n            var cols = 0;\n            s.on('screen-scrolled', c => {\n                cols = c;\n            });\n            s.dispatcher.dispatch(A.scrollScreen(42));\n            assert.equal(cols, 42);\n        });\n\n        it('handles sroll_region event', () => {\n            const s = new NeovimStore(dom);\n            const r = {\n                top: 1,\n                left: 2,\n                right: 3,\n                bottom: 4\n            };\n            var flag = false;\n            s.on('scroll-region-updated', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.setScrollRegion(r));\n            assert.isTrue(flag, 'scroll-region-updated event was not fired');\n            assert.deepEqual(s.scroll_region, r);\n        });\n\n        it('handles resize event', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('resize', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.resize(42, 84));\n            assert.isTrue(flag, 'resize event was not fired');\n            assert.equal(s.size.lines, 42, 'lines was not set');\n            assert.equal(s.size.cols, 84, 'cols was not set');\n            assert.deepEqual(s.scroll_region, {\n                top: 0,\n                left: 0,\n                right: 83,\n                bottom: 41\n            });\n\n            flag = false;\n            s.dispatcher.dispatch(A.resize(42, 84));\n            assert.isFalse(flag, 'resize event was wrongly fired');\n        });\n\n        it('handles update-fg event', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('update-fg', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.updateForeground(0x123456));\n            assert.isTrue(flag, 'update-fg event was not fired');\n            assert.equal(s.fg_color, '#123456');\n        });\n\n        it('handles update-bg event', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('update-bg', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.updateBackground(0x123456));\n            assert.isTrue(flag, 'update-bg event was not fired');\n            assert.equal(s.bg_color, '#123456');\n        });\n\n        it('handles update-sp event', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('update-sp-color', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.updateSpecialColor(0x123456));\n            assert.isTrue(flag, 'update-sp event was not fired');\n            assert.equal(s.sp_color, '#123456');\n        });\n\n        it('handles mode event', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('mode', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.changeMode('visual'));\n            assert.isTrue(flag, 'mode event was not fired');\n            assert.equal(s.mode, 'visual');\n            s.dispatcher.dispatch(A.changeMode('select'));\n            assert.equal(s.mode, 'select');\n        });\n\n        it('handles busy_start event', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('busy', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.startBusy());\n            assert.isTrue(flag, 'busy event was not fired');\n            assert.isTrue(s.busy, 'unexpected busy state');\n        });\n\n        it('handles busy_end event', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('busy', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.stopBusy());\n            assert.isTrue(flag, 'busy event was not fired');\n            assert.isFalse(s.busy, 'unexpected non-busy state');\n        });\n\n        it('accespts action to update font size', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('font-size-changed', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.updateFontSize(42, 84, 21, 42));\n            assert.isTrue(flag, 'font-size-changed event was not fired');\n            const f = s.font_attr;\n            assert.equal(f.draw_width, 42);\n            assert.equal(f.draw_height, 84);\n            assert.equal(f.width, 21);\n            assert.equal(f.height, 42);\n        });\n\n        it('accepts action to specify font pixel', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('font-px-specified', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.updateFontPx(144));\n            assert.isTrue(flag, 'font-px-specified event was not fired');\n            assert.equal(s.font_attr.specified_px, 144);\n        });\n\n        it('accepts action to font face', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('font-face-specified', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.updateFontFace('Meiryo'));\n            assert.isTrue(flag, 'font-face-specified event was not fired');\n            assert.equal(s.font_attr.face, 'Meiryo');\n        });\n\n        it('accespts action to update screen size', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('update-screen-size', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.updateScreenSize(400, 300));\n            assert.isTrue(flag, 'update-screen-size event was not fired');\n            assert.equal(s.size.width, 400);\n            assert.equal(s.size.height, 300);\n\n            flag = false;\n            s.dispatcher.dispatch(A.updateScreenSize(400, 300));\n            assert.isFalse(flag, 'unexpected update-screen-size event');\n        });\n\n        it('accespts action to update screen bounds', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('update-screen-bounds', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.updateScreenBounds(42, 84));\n            assert.isTrue(flag, 'update-screen-bounds event was not fired');\n            assert.equal(s.size.lines, 42, 'lines was not set');\n            assert.equal(s.size.cols, 84, 'cols was not set');\n            assert.deepEqual(s.scroll_region, {\n                top: 0,\n                left: 0,\n                right: 83,\n                bottom: 41\n            });\n\n            flag = false;\n            s.dispatcher.dispatch(A.resize(42, 84));\n            assert.isFalse(flag, 'update-screen-bounds event was wrongly fired');\n        });\n\n        it('handles mouse_on event', () => {\n            const s = new NeovimStore(dom);\n            s.mouse_enabled = false;\n            var flag = false;\n            s.on('mouse-enabled', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.enableMouse());\n            assert.isTrue(flag, 'mouse-enabled event was not fired');\n            assert.isTrue(s.mouse_enabled, 'mouse did not set enabled');\n            flag = false;\n            s.dispatcher.dispatch(A.enableMouse());\n            assert.isFalse(flag, 'mouse-enabled event was fired even if mouse_enabled state had been not changed');\n        });\n\n        it('handles mouse_off event', () => {\n            const s = new NeovimStore(dom);\n            s.mouse_enabled = true;\n            var flag = false;\n            s.on('mouse-disabled', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.disableMouse());\n            assert.isTrue(flag, 'mouse-disabled event was not fired');\n            assert.isFalse(s.mouse_enabled, 'mouse did not set disabled');\n            flag = false;\n            s.dispatcher.dispatch(A.disableMouse());\n            assert.isFalse(flag, 'mouse-disabled event was fired even if mouse_enabled state had been not changed');\n        });\n\n        it('accepts drag start action', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('drag-started', () => {\n                flag = true;\n            });\n            const e = document.createEvent('MouseEvent');\n            e.initEvent('mousedown', true, false);\n            s.dispatcher.dispatch(A.dragStart(e));\n            assert.isTrue(flag, 'drag-started event was not fired');\n            assert.isNotNull(s.dragging, 'store.dragging must be created');\n\n            s.mouse_enabled = false;\n            flag = false;\n            s.dispatcher.dispatch(A.dragStart(e));\n            assert.isFalse(flag, 'drag-started event should not be fired while mouse is disabled');\n        });\n\n        it('accepts drag update action', () => {\n            const s = new NeovimStore(dom);\n            const down = document.createEvent('MouseEvent');\n            down.initEvent('mousedown', true, false);\n            down.buttons = 1;\n            s.dispatcher.dispatch(A.dragStart(down));\n\n            var updated = false;\n            s.on('drag-updated', () => {\n                updated = true;\n            });\n            var input = false;\n            s.on('input', i => {\n                assert.notEqual(i, '');\n                input = true;\n            });\n\n            var move = document.createEvent('MouseEvent');\n            move.initEvent('mousemove', true, false);\n            Object.defineProperty(move, 'buttons', {value: 1});\n\n            // To simulate dragging mouse, change the position of event from previous drag start event\n            Object.defineProperty(move, 'clientX', {value: 100});\n            Object.defineProperty(move, 'parentX', {value: 50});\n            Object.defineProperty(move, 'clientY', {value: 100});\n            Object.defineProperty(move, 'parentY', {value: 50});\n\n            s.dispatcher.dispatch(A.dragUpdate(move));\n            assert.isTrue(updated, 'drag-updated event was not fired');\n            assert.isTrue(input, 'input event was not fired');\n            assert.isNotNull(s.dragging, 'store.dragging must be maintained');\n\n            s.mouse_enabled = false;\n            updated = false;\n            move = document.createEvent('MouseEvent');\n            move.initEvent('mousemove', true, false);\n            move.buttons = 1;\n            s.dispatcher.dispatch(A.dragUpdate(move));\n            assert.isFalse(updated, 'drag updated event must not be fired while mouse disabled');\n\n            s.mouse_enabled = true;\n            s.dragging = null;\n            updated = false;\n            move = document.createEvent('MouseEvent');\n            move.initEvent('mousemove', true, false);\n            move.buttons = 1;\n            s.dispatcher.dispatch(A.dragUpdate(move));\n            assert.isFalse(updated, 'drag updated event must not be fired without drag start event');\n        });\n\n        it('accepts drag end action', () => {\n            const s = new NeovimStore(dom);\n            const down = document.createEvent('MouseEvent');\n            down.initEvent('mousedown', true, false);\n            down.buttons = 1;\n            s.dispatcher.dispatch(A.dragStart(down));\n\n            var ended = false;\n            s.on('drag-ended', () => {\n                ended = true;\n            });\n            var input = false;\n            s.on('input', i => {\n                assert.notEqual(i, '');\n                input = true;\n            });\n\n            var up = document.createEvent('MouseEvent');\n            up.initEvent('mouseup', true, false);\n            up.buttons = 1;\n            s.dispatcher.dispatch(A.dragEnd(up));\n            assert.isTrue(ended, 'drag-ended event was not fired');\n            assert.isTrue(input, 'input event was not fired');\n            assert.isNull(s.dragging, 'store.dragging must be cleared');\n\n            s.mouse_enabled = false;\n            ended = false;\n            up = document.createEvent('MouseEvent');\n            up.initEvent('mouseup', true, false);\n            up.buttons = 1;\n            s.dispatcher.dispatch(A.dragUpdate(up));\n            assert.isFalse(ended, 'drag end event must not be fired while mouse disabled');\n\n            s.mouse_enabled = true;\n            s.dragging = null;\n            ended = false;\n            up = document.createEvent('MouseEvent');\n            up.initEvent('mousemove', true, false);\n            up.buttons = 1;\n            s.dispatcher.dispatch(A.dragUpdate(up));\n            assert.isFalse(ended, 'drag updated event must not be fired without drag start event');\n        });\n\n        it('handles beep event', () => {\n            const s = new NeovimStore(dom);\n\n            var flag = false;\n            s.on('beep', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.bell(false));\n            assert.isTrue(flag, 'beep event was not fired');\n\n            flag = false;\n            s.on('visual-bell', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.bell(true));\n            assert.isTrue(flag, 'visual-beep event was not fired');\n        });\n\n        it('handles title-changed event', () => {\n            const s = new NeovimStore(dom);\n\n            var flag = false;\n            s.on('title-changed', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.setTitle('This is that'));\n            assert.isTrue(flag, 'title-changed event was not fired');\n            assert.equal(s.title, 'This is that');\n        });\n\n        it('handles set_icon event', () => {\n            const s = new NeovimStore(dom);\n\n            var flag = false;\n            s.on('icon-changed', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.setIcon('This is that'));\n            assert.isTrue(flag, 'icon-changed event was not fired');\n            assert.equal(s.icon_path, 'This is that');\n        });\n\n        // Skip because creating WheelEvent is not supported by jsdom\n        it.skip('accepts wheel scrolling action', () => {\n            const s = new NeovimStore(dom);\n\n            var wheeled = false;\n            s.on('wheel-scrolled', () => {\n                wheeled = true;\n            });\n            var input = false;\n            s.on('input', i => {\n                assert.notEqual(i, '');\n                input = true;\n            });\n\n            var wheel = document.createEvent('WheelEvent');\n            wheel.initEvent('wheel', true, false);\n            wheel.buttons = 1;\n            s.dispatcher.dispatch(A.wheelScroll(wheel));\n            assert.isTrue(wheeled, 'wheel-scrolled event was not fired');\n            assert.isTrue(input, 'input event was not fired');\n\n            wheeled = false;\n            s.mouse_enabled = false;\n            s.dispatcher.dispatch(A.wheelScroll(wheel));\n            assert.isFalse(wheeled, 'wheel-scrolled event must not be fired while mouse disabled');\n        });\n\n        it('accepts line_height changing event', () => {\n            const s = new NeovimStore(dom);\n\n            var flag = false;\n            s.on('line-height-changed', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.updateLineHeight(1.5));\n            assert.isTrue(flag, 'line-height-changed event was not fired');\n            assert.equal(s.line_height, 1.5);\n\n            flag = false;\n            s.dispatcher.dispatch(A.updateLineHeight(1.5));\n            assert.isFalse(flag, 'line-height-changed event was fired although line height value is not changed');\n            assert.equal(s.line_height, 1.5);\n        });\n\n        it('accepts disabling alt key event', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('alt-key-disabled', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.disableAltKey(true));\n            assert.isTrue(flag, 'alt-key-disabled event was not fired');\n            assert.equal(s.alt_key_disabled, true);\n        });\n\n        it('switches to disable/enable meta key', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('meta-key-disabled', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.disableMetaKey(true));\n            assert.isTrue(flag, 'meta-key-disabled event was not fired');\n            assert.equal(s.meta_key_disabled, true);\n            s.dispatcher.dispatch(A.disableMetaKey(false));\n            assert.equal(s.meta_key_disabled, false);\n        });\n\n        it('accepts cursor draw delay change event', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n            s.on('cursor-draw-delay-changed', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.changeCursorDrawDelay(0));\n            assert.isTrue(flag, 'cursor-draw-delay-changed event was not fired');\n            assert.equal(s.cursor_draw_delay, 0);\n        });\n\n        it('accepts cursor blinking event', () => {\n            const s = new NeovimStore(dom);\n            var flag = false;\n\n            s.on('blink-cursor-stopped', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.startBlinkCursor());\n            s.dispatcher.dispatch(A.stopBlinkCursor());\n            assert.isTrue(flag, 'blink-cursor-stopped event was not fired');\n            flag = false;\n            s.dispatcher.dispatch(A.stopBlinkCursor());\n            assert.isFalse(flag, 'blink-cursor-stopped event was incorrectly fired because cursor blinking state did not change');\n\n            flag = false;\n            s.on('blink-cursor-started', () => {\n                flag = true;\n            });\n            s.dispatcher.dispatch(A.startBlinkCursor());\n            assert.isTrue(flag, 'blink-cursor-started event was not fired');\n            flag = false;\n            s.dispatcher.dispatch(A.startBlinkCursor());\n            assert.isFalse(flag, 'blink-cursor-started event was incorrectly fired because cursor blinking state did not change');\n        });\n    });\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"removeComments\": true,\n    \"preserveConstEnums\": true,\n    \"noImplicitAny\": true,\n    \"noImplicitReturns\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noEmitOnError\": true,\n    \"outDir\": \"build\",\n    \"target\": \"es2015\",\n    \"sourceMap\": true\n  },\n  \"includes\": [\n    \"src/**/*.ts\",\n    \"test/**/*.ts\"\n  ]\n}\n"
  },
  {
    "path": "tslint.json",
    "content": "{\n  \"rules\": {\n    \"align\": [\n      true,\n      \"parameters\",\n      \"statements\"\n    ],\n    \"ban\": false,\n    \"class-name\": true,\n    \"comment-format\": [\n      true,\n      \"check-space\"\n    ],\n    \"curly\": true,\n    \"eofline\": true,\n    \"forin\": true,\n    \"indent\": [\n      true,\n      \"spaces\"\n    ],\n    \"interface-name\": false,\n    \"jsdoc-format\": true,\n    \"label-position\": true,\n    \"max-line-length\": [\n      true,\n      140\n    ],\n    \"member-access\": false,\n    \"member-ordering\": [\n      true,\n      \"public-before-private\",\n      \"static-before-instance\",\n      \"variables-before-functions\"\n    ],\n    \"no-any\": false,\n    \"no-arg\": true,\n    \"no-bitwise\": false,\n    \"no-conditional-assignment\": true,\n    \"no-consecutive-blank-lines\": false,\n    \"no-console\": [\n      true,\n      \"debug\",\n      \"info\",\n      \"time\",\n      \"timeEnd\",\n      \"trace\"\n    ],\n    \"no-construct\": true,\n    \"no-constructor-vars\": false,\n    \"no-debugger\": true,\n    \"no-duplicate-variable\": true,\n    \"no-empty\": true,\n    \"no-eval\": true,\n    \"no-inferrable-types\": false,\n    \"no-internal-module\": true,\n    \"no-require-imports\": false,\n    \"no-shadowed-variable\": true,\n    \"no-string-literal\": true,\n    \"no-switch-case-fall-through\": false,\n    \"no-trailing-whitespace\": true,\n    \"no-unused-expression\": true,\n    \"no-use-before-declare\": true,\n    \"no-var-keyword\": true,\n    \"no-var-requires\": false,\n    \"object-literal-sort-keys\": false,\n    \"one-line\": [\n      true,\n      \"check-open-brace\",\n      \"check-catch\",\n      \"check-else\",\n      \"check-whitespace\"\n    ],\n    \"quotemark\": [\n      true,\n      \"single\",\n      \"avoid-escape\"\n    ],\n    \"radix\": true,\n    \"semicolon\": true,\n    \"switch-default\": true,\n    \"trailing-comma\": [\n      true,\n      {\n        \"multiline\": \"always\",\n        \"singleline\": \"never\"\n      }\n    ],\n    \"triple-equals\": [\n      true,\n      \"allow-null-check\"\n    ],\n    \"typedef\": [\n      false\n    ],\n    \"typedef-whitespace\": [\n      true,\n      {\n        \"call-signature\": \"nospace\",\n        \"index-signature\": \"nospace\",\n        \"parameter\": \"nospace\",\n        \"property-declaration\": \"nospace\",\n        \"variable-declaration\": \"nospace\"\n      }\n    ],\n    \"use-strict\": [\n      false\n    ],\n    \"variable-name\": [\n      false,\n      \"check-format\",\n      \"allow-leading-underscore\",\n      \"ban-keywords\"\n    ],\n    \"whitespace\": [\n      true,\n      \"check-branch\",\n      \"check-decl\",\n      \"check-separator\",\n      \"check-type\"\n    ],\n    \"no-unnecessary-type-assertion\": true,\n    \"no-floating-promises\": true,\n    \"no-for-in-array\": true,\n    \"no-void-expression\": [\n      true,\n      \"ignore-arrow-function-shorthand\"\n    ],\n    \"use-default-type-parameter\": true,\n    \"deprecation\": true,\n    \"no-unnecessary-qualifier\": true,\n    \"no-return-await\": true,\n    \"no-duplicate-switch-case\": true,\n    \"no-implicit-dependencies\": [true, \"dev\"],\n    \"ban-comma-operator\": true,\n    \"no-parameter-reassignment\": false,\n    \"no-duplicate-imports\": true,\n    \"no-this-assignment\": true,\n    \"use-default-type-parameter\": true,\n    \"no-unbound-method\": [true, \"ignore-static\"],\n    \"prefer-object-spread\": true,\n    \"encoding\": true,\n    \"prefer-switch\": true,\n    \"number-literal-format\": false,\n    \"interface-over-type-literal\": true,\n    \"callable-types\": true,\n    \"return-undefined\": true,\n    \"prefer-readonly\": true\n  }\n}\n"
  }
]