Full Code of rhysd/neovim-component for AI

master ffecf24c5a15 cached
58 files
185.2 KB
45.3k tokens
176 symbols
1 requests
Download .txt
Repository: rhysd/neovim-component
Branch: master
Commit: ffecf24c5a15
Files: 58
Total size: 185.2 KB

Directory structure:
gitextract_55qap65l/

├── .gitignore
├── .npmignore
├── .prettierrc.json
├── .travis.yml
├── Guardfile
├── LICENSE.txt
├── README.md
├── bower.json
├── example/
│   ├── image-popup/
│   │   ├── README.md
│   │   ├── cli.js
│   │   ├── index.html
│   │   ├── main.js
│   │   ├── package.json
│   │   └── popup-tooltip.html
│   ├── markdown/
│   │   ├── README.md
│   │   ├── cli.js
│   │   ├── index.html
│   │   ├── main.js
│   │   ├── markdown-viewer.html
│   │   └── package.json
│   ├── mini-browser/
│   │   ├── README.md
│   │   ├── cli.js
│   │   ├── index.html
│   │   ├── main.js
│   │   ├── mini-browser.html
│   │   └── package.json
│   └── minimal/
│       ├── README.md
│       ├── cli.js
│       ├── index.html
│       └── main.js
├── index.d.ts
├── neovim-editor.html
├── package.json
├── scripts/
│   ├── travis-ci-install.sh
│   └── travis-ci-run.sh
├── src/
│   ├── index.ts
│   ├── lib.d.ts
│   ├── log.ts
│   ├── neovim/
│   │   ├── actions.ts
│   │   ├── cursor.ts
│   │   ├── input.ts
│   │   ├── process.ts
│   │   ├── screen-drag.ts
│   │   ├── screen-wheel.ts
│   │   ├── screen.ts
│   │   └── store.ts
│   └── neovim.ts
├── test/
│   ├── .eslintrc
│   ├── e2e/
│   │   ├── mocha.opts
│   │   └── smoke.ts
│   └── unit/
│       ├── cursor_test.js
│       ├── dom_faker.js
│       ├── input_test.js
│       ├── screen-drag_test.js
│       ├── screen-wheel_test.js
│       └── store_test.js
├── tsconfig.json
└── tslint.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/bower_components
node_modules
/build
npm-debug.log
/src/**/*.js
/src/**/*.js.map
/test/e2e/**/*.js
/test/e2e/**/*.js.map

# for now
/package-lock.json


================================================
FILE: .npmignore
================================================
/Guardfile
/bower_components
/test
/scripts
node_modules
npm-debug.log
Guardfile
tslint.json
/build/test


================================================
FILE: .prettierrc.json
================================================
{
  "tabWidth": 4,
  "semi": true,
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 120,
  "overrides": [
    {
      "files": "*.css",
      "options": {
        "tabWidth": 2,
        "printWidth": -1
      }
    }
  ]
}


================================================
FILE: .travis.yml
================================================
language: node_js

os:
    - linux
    - osx

osx_image: xcode10.2

dist: xenial

node_js:
    - stable

install:
    - ./scripts/travis-ci-install.sh

script:
    - ./scripts/travis-ci-run.sh

notifications:
    email:
        on_success: never
        on_failure: change

addons:
    apt:
        sources:
            - sourceline: 'ppa:neovim-ppa/unstable'
        packages:
            - neovim
            - g++
            - build-essential
            - libcairo2-dev
            - libpango1.0-dev
            - libjpeg-dev
            - libgif-dev
            - librsvg2-dev
        update: true
    homebrew:
        packages:
            - neovim
            - pkg-config
            - cairo
            - pango
            - libpng
            - giflib
            - jpeg
            - librsvg
        update: true


================================================
FILE: Guardfile
================================================
ignore /^node_modules/, /^build/, /^bower_components/

def npm_run(sub, file)
  puts "\033[93m#{Time.now}: #{File.basename file}\033[0m"
  success = system "npm run #{sub}"
  if success
    puts "\033[92mOK\033[0m\n\n"
  else
    puts "\033[91mFAIL\033[0m\n\n"
  end
end

def exec(cmdline)
  success = system cmdline
  if success
    puts "\033[92mOK\033[0m\n\n"
  else
    puts "\033[91mFAIL\033[0m\n\n"
  end
end

guard :shell do
  watch %r[^(:?src|test)/.+\.ts$] do |m|
    puts "\033[93m#{Time.now}: #{File.basename m[0]}\033[0m"
    exec 'npm run build'
  end

  watch %r[^test/.+\.js$] do |m|
    puts "\033[93m#{Time.now}: Test #{File.basename m[0]}\033[0m"
    exec "./node_modules/.bin/mocha #{m[0]}"
  end
end


================================================
FILE: LICENSE.txt
================================================
Copyright (c) 2015 rhysd

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
THE USE OR OTHER DEALINGS IN THE SOFTWARE.



================================================
FILE: README.md
================================================
`<neovim-editor>` Web Component
===============================
[![Build Status](https://travis-ci.org/rhysd/neovim-component.svg?branch=master)](https://travis-ci.org/rhysd/neovim-component)


This component provides `<neovim-editor>`, an HTML custom element built on [Polymer v2](https://github.com/Polymer/polymer)
and [flux](https://github.com/facebook/flux).  It provides a frontend for the [Neovim editor](https://github.com/neovim/neovim)
using Neovim's MessagePack API. It allows you to easily embed a Neovim-backed editor into your application.

**This component assumes to be used in Node.js environment. (i.e. Electron)**

You can use this component for modern desktop application frameworks such as [Electron](https://github.com/atom/electron)
or [NW.js](https://github.com/nwjs/nw.js).  You can even use it in Electron-based editors such as [Atom](http://atom.io/)
or [VS Code](https://github.com/Microsoft/vscode).

This component is designed around the [Flux architecture](https://facebook.github.io/flux/docs/overview.html).
You can access the UI event notifications and can call Neovim APIs directly via `<neovim-editor>`'s
APIs.

You can install this component as an [npm package](https://www.npmjs.com/package/neovim-component).

```
$ npm install neovim-component
```

Current supported `nvim` version is v0.1.6 or later.


## Examples

Each example only takes 100~300 lines.

### [Minimal Example](/example/minimal)

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <script src="/path/to/webcomponents-lite.js"></script>
    <link rel="import" href="/path/to/polymer.html" />
    <link rel="import" href="/path/to/neovim-editor.html" />
  </head>
  <body>
    <neovim-editor></neovim-editor>
  </body>
</html>
```

Minimal [Electron](https://github.com/atom/electron) app can be found in the [example directory](/example/minimal).
This is a good start point to use this package and it shows how the component works.

![main screenshot](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/main.gif)

How to run minimal example is:

```sh
$ git clone https://github.com/rhysd/neovim-component.git
$ cd neovim-component
$ npm start
```

### [Markdown Editor Example](/example/markdown)

For a more complicated and realistic example, see the [markdown editor example](/example/markdown).
The markdown previewer is integrated with the Neovim GUI using the `<neovim-editor>` component.

![markdown example screenshot](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/markdown-example.gif)

### [Image Popup Example](/example/image-popup)

This is an image popup widget example [here](/example/image-popup).  The `gi` mapping is defined
to show an image under the cursor in a tooltip.

![image popup example screenshot](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/popup-image-example.gif)

### [Mini Browser Example](/example/mini-browser)

This example shows how to include a mini web-browser using the
[`<webview>` tag from Electron](https://github.com/atom/electron/blob/master/docs/api/web-view-tag.md).

![mini browser example screenshot](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/mini-browser.gif)


## Why Did You Create This?

Vim has very powerful editing features, but Vim is an editor (see `:help design-not`) and unfortunately
lacks support for many graphical tools that writers and programmers like.  NyaoVim adds support for
graphical features without losing Vim's powerful text editing abilities.
[Neovim's msgpack APIs](https://neovim.io/doc/user/msgpack_rpc.html) provide a perfect way to add
a GUI layer using HTML and CSS.  [NyaoVim](https://github.com/rhysd/NyaoVim) is a GUI frontend as
a proof of concept.


## Architecture

![data flow](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/flow.png)

`<neovim-editor>` has an `editor` property to access the internal APIs of the component.

- `editor.screen` is a view of the component (using canvas).  It receives user input and dispatches
  input actions to the data store.
- `editor.process` is a process handler to interact with the backing Neovim process via msgpack-rpc
  APIs.  You can call Neovim's APIs via the Neovim client (`editor.getClient()` helper).
- `editor.store` is the state of this component.  You can access the current state of the editor through
  this object.


## `<neovim-editor>` Properties

You can customize `<neovim-editor>` with the following properties:

| Name                | Description                                | Default       |
| ------------------- | -------------------------------------------| ------------- |
| `width`             | Width of the editor in pixels.             | `null`        |
| `height`            | Height of the editor in pixels.            | `null`        |
| `font`              | Name of the editor's monospace font.       | `"monospace"` |
| `font-size`         | Font-size in pixels.                       | `12`          |
| `line-height`       | Line height rate relative to font size.    | `1.3`         |
| `nvim-cmd`          | Command used to start Neovim.              | `"nvim"`      |
| `argv`              | Arguments passed with the Neovim command.  | `[]`          |
| `on-quit`           | Callback function to run when Neovim quits.| `null`        |
| `on-error`          | Callback function for Neovim errors.       | `null`        |
| `disable-alt-key`   | Do not send alt key input to Neovim.       | `false`       |
| `disable-meta-key`  | Do not send meta key input to Neovim.      | `false`       |
| `cursor-draw-delay` | Delay in millisec before drawing cursor.   | `10`          |
| `no-blink-cursor`   | Blink cursor or not.                       | `false`       |
| `window-title`      | Specify first window title.                | `"Neovim"`    |


## `<neovim-editor>` APIs

### Receive internal various events

You can receive various events (including UI redraw notifications) from the **store**.
The `store` is a part of flux architecture. It's a global instance of [EventEmitter](https://nodejs.org/api/events.html).

You can also access the state of editor via the `store`. Note that all values are read only.
Do not change the values of the `store` directly, it will break the internal state of the component.

```javascript
const neovim_element = document.getElementById('neovim');
const Store = neovim_element.editor.store;

// Handle cursor movements
Store.on('cursor', () => console.log('Cursor is moved to ', Store.cursor));

// Handle mode changes
Store.on('mode', () => console.log('Mode is changed to ', Store.mode));

// Handle text redraws
Store.on('put', () => console.log('UI was redrawn'));

// Accessing the state of the editor.
const bounds = [ Store.size.lines, Store.size.cols ];
const cursor_pos = [ Store.cursor.line, Store.cursor.col ];
```


### Call Neovim APIs

You can call [Neovim APIs](https://neovim.io/doc/user/msgpack_rpc.html#msgpack-rpc-api) via the **client**.
When you call APIs via the client, it sends the call to the underlying Neovim process via MessagePack
RPC and will return a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
which resolves to the returned value.


`<neovim-component>` uses [promised-neovim-client](https://github.com/rhysd/promised-neovim-client) package.
You can see the all API definitions [here](https://github.com/rhysd/promised-neovim-client/blob/promisified/index.d.ts).
If you know further about Neovim APIs, [python client implementation](https://github.com/neovim/python-client)
may be helpful.

```javascript
const neovim_element = document.getElementById('neovim');
const client = neovim_element.editor.getClient();

// Send a command
client.command('vsplit');

// Send input
client.input('<C-w><C-l>');

// Evaluate a Vim script expression
client.eval('"aaa" . "bbb"').then(result => console.log(result));

// Get the 'b:foo' variable
client.getCurrentBuffer()
    .then(buf => buf.getVar('foo'))
    .then(v => console.log(v));

// Query something (windows, buffers, etc.)
// Move to the neighbor window and show its information.
client.getWindows()
    .then(windows => client.secCurrentWindow(windows[1]))
    .then(() => client.getCurrentWindow())
    .then(win => console.log(win));

// Receive an RPC request from Neovim
client.on('request', (n, args, res) => console.log(`Name: ${n}, Args: ${JSON.stringify(args)}, Response: ${res}`));
```


### Editor lifecycle

You can receive notifications related to lifecycle of the editor.

```javascript
const neovim_element = document.getElementById('neovim');

// Called when the Neovim background process attaches
neovim_element.editor.on('process-attached', () => console.log('Neovim process is ready'));

// Called when the Neovim process is disconnected (usually by :quit)
neovim_element.editor.on('quit', () => console.log('Neovim process died'));

// Called when the <neovim-component> detaches
neovim_element.editor.on('detach', () => console.log('Element does not exist in DOM.'));

// Called upon experiencing an error in the internal process 
neovim_element.editor.on('error', err => alert(err.message));
```


### View APIs

- Resize screen

```javascript
const editor = document.getElementById('neovim').editor;
editor.screen.resize(80, 100); // Resize screen to 80 lines and 100 columns
editor.screen.resizeWithPixels(1920, 1080); // Resize screen to 1920px x 1080px
```

- Change font size

```javascript
const editor = document.getElementById('neovim').editor;
editor.screen.changeFontSize(18); // Change font size to 18px
```

- Convert pixels to lines/cols.

```javascript
const editor = document.getElementById('neovim').editor;

const loc = editor.screen.convertPositionToLocation(80, 24);
console.log(loc.x, loc.y);  // Coordinates in pixels of (line, col) = (80, 24)

const pos = editor.screen.convertLocationToPosition(400, 300);
const.log(pos.col, pos.line);  // line/col of location (400px, 300px)
```

- Notify of screen-size changes:

When some process has changed the screen-size **you must notify the `screen`**. The internal `<canvas>`
element has a fixed size and must update itself if there are size changes.  Call `screen.checkShouldResize()`
if the screen size may have changed.  Note that you don't need to care about `resize` event of `<body>`
element.  `<neovim-editor>` component automatically detects this particular resize event and updates
automatically. `screen.checkShouldResize()` will simply be ignored if nothing has actually changed.

```javascript
const editor = document.getElementById('neovim').editor;

function showUpSomeElementInNeovim() {
    const e = document.getElementById('some-elem');

    // New element shows up!  The screen may be resized by the change.
    // 'none' -> 'block'
    e.style.display = 'block';

    // This call tells to editor to adjust itself in the case that it has been resized
    editor.screen.checkShouldResize();
}
```

### Other APIs

- Setting arguments afterwards:

If your app doesn't use Polymer you can set arguments afterwards using JavaScript
Note that it is better to use `argv` property of `<neovim-element>` if possible.

```javascript
const editor = document.getElementById('neovim').editor;
editor.setArgv(['README.md']);
```

- Focusing the editor

`<neovim-editor>` is just a web-component, so it can be focused just like other elements.  
If it loses focus the editor won't receive any input events. 
The `editor` instance has a method to re-focus the editor in JavaScript.
The `store` instance contains the current focus state.

```javascript
const editor = document.getElementById('neovim').editor;
console.log(editor.store.focused);
editor.store.on('focus-changed', () => {
    console.log('Focus was changed: ' + editor.store.focused);
});

// Refocus the editor to ensure it receives user input.
editor.focus();
```

### Log Levels

`<neovim-component>` prints logs in the browser console.  The log level is controlled by the `NODE_ENV`
environment variable:

- `NODE_ENV=debug` will log everything.
- `NODE_ENV=production` ignores all logs except for warnings and errors.
- Setting `NODE_ENV` to empty string or some other value enables logging for info, warnings, and errors.


## TODOs

- [ ] WebGL rendering (using [pixi.js](http://www.pixijs.com/) or [CreateJS](http://www.createjs.com/)).
  [#2](https://github.com/rhysd/neovim-component/issues/2)
- [ ] Follow dynamic device pixel ratio change. [#18](https://github.com/rhysd/neovim-component/issues/18)


================================================
FILE: bower.json
================================================
{
  "name": "neovim-component",
  "description": "Polymer component of Neovim frontend",
  "version": "0.0.2",
  "keywords": [
    "neovim",
    "polymer",
    "WebComponent",
    "Electron",
    "NW.js",
    "editor"
  ],
  "homepage": "",
  "authors": [
    "rhysd <lin90162@yahoo.co.jp>"
  ],
  "main": "neovim-component.html",
  "license": "MIT",
  "dependencies": {
    "polymer": "^2.5.0"
  },
  "resolutions": {
    "webcomponentsjs": "^v1.0.19"
  }
}


================================================
FILE: example/image-popup/README.md
================================================
This is image tooltip example for `<neovim-editor>` component.
`:ShowImage` command shows an image under the cursor.  I created `<popup-tooltip>` component and use it and `<neovim-editor>` together.  Please see `index.html`.

![screenshot](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/popup-image-example.gif)

How to run this example:

```sh
$ cd /path/to/neovim-component
$ npm run dep
$ cd ./example/image-tooltip
$ npm run app # No dependency.  App starts.
```


================================================
FILE: example/image-popup/cli.js
================================================
#! /usr/bin/env node
'use strict';

var argv = process.argv.slice(2);
argv.unshift(__dirname);
if (!process.env['NODE_ENV']) {
    process.env['NODE_ENV'] = 'production';
}
require('child_process').spawn(require('electron'), argv, { stdio: 'inherit' });


================================================
FILE: example/image-popup/index.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes" />
    <title>Image Tooltip Example</title>

    <script src="../../bower_components/webcomponentsjs/webcomponents-lite.js"></script>
    <link rel="import" href="../../bower_components/polymer/polymer.html" />
    <link rel="import" href="../../neovim-editor.html" />
    <link rel="import" href="./popup-tooltip.html" />

    <style>
      html,body {
        height: 100%;
        width: 100%;
        margin: 0px;
        padding: 0px;
        overflow: hidden;
      }
      #neovim {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>

  <body>
    <neovim-editor id="neovim" font="Monaco,Consolas,monospace" font-size="14"></neovim-editor>
    <popup-tooltip id="tooltip"></popup-tooltip>
  </body>

  <script>
    const editor = document.getElementById('neovim').editor;
    const remote = require('electron').remote;
    const tooltip = document.getElementById('tooltip');

    editor.on('error', (err) => alert(err.message));

    // Note: Callback on Neovim process attached
    editor.on('process-attached', function() {
      if (remote.process.argv.length > 2) {
        editor.setArgv(remote.process.argv.slice(2)); // It is better to use 'argv' property.
      }
      const c = editor.getClient();

      // Note:
      // Define mapping 'gi' from JavaScript.
      // Of course you can execute these commands in Vim plugin.
      c.command('nnoremap <silent>gi :<C-u>call rpcnotify(0, "image-tooltip:open", \'<C-r><C-p>\', line(".") - line("w0") + 1, virtcol("."))<CR>');

      // Note:
      // Subscribe notification sent by rpcnotify() from Neovim process
      c.subscribe('image-tooltip:open');
      c.on('notification', function(method, args) {
          if (method === 'image-tooltip:open' && args.length === 3 && args[0]) {
            // Toggle tooltip with image
            if (tooltip.shown) {
              tooltip.dismiss();
            } else {
              // Note: col('.') is 1-based
              const loc = editor.screen.convertPositionToLocation(args[1], args[2] - 1);
              const img = document.createElement('img');
              img.src = args[0];
              tooltip.setContent(img);
              tooltip.show(loc.x, loc.y);
            }
          }
      });
    });
    editor.on('quit', function() {
      remote.require('app').quit();
    });
  </script>
</html>


================================================
FILE: example/image-popup/main.js
================================================
var path = require('path');
var electron = require('electron');
var app = electron.app;
var BrowserWindow = electron.BrowserWindow;

var index_html = 'file://' + path.join(__dirname, 'index.html');

app.on('ready', function() {
    var win = new BrowserWindow({
        width: 800,
        height: 600,
        useContentSize: true,
        webPreferences: {
            blinkFeatures: 'KeyboardEventKey,Accelerated2dCanvas,Canvas2dFixedRenderingMode',
            nodeIntegration: true,
        },
    });

    win.on('closed', function() {
        win = null;
        app.quit();
    });

    win.loadURL(index_html);
    if (process.env['NODE_ENV'] !== 'production') {
        win.webContents.openDevTools({ mode: 'detach' });
    }
});


================================================
FILE: example/image-popup/package.json
================================================
{
  "name": "neovim-component-image-tooltip-example",
  "version": "0.0.1",
  "main": "main.js",
  "scripts": {
    "app": "../../node_modules/.bin/electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}


================================================
FILE: example/image-popup/popup-tooltip.html
================================================
<link rel="import" href="../../bower_components/polymer/polymer.html" />

<dom-module id="popup-tooltip">
  <template>
    <style>
      #popup {
        position: absolute;
        padding: 8px;
        border-radius: 8px;
        display: none;
        background-color: #dddddd;
      }
      #proj {
        width: 0px;
        height: 0px;
        position: absolute;
        display: none;
        border: 13px transparent solid;
        border-top-width: 0;
        border-bottom-color: #dddddd;
      }
      #btn {
        display: block;
        margin: auto;
      }
    </style>

    <span id="proj">
    </span>
    <div id="popup">
      <content></content>
    </div>
  </template>
</dom-module>

<script>
  let popup, proj;

  Polymer({
    is: 'popup-tooltip',

    properties: {
      color: {
        type: String,
        value: ''
      },

      shown: {
        type: Boolean,
        value: false
      },

      x: {
        type: Number,
        value: 0
      },

      y: {
        type: Number,
        value: 0
      }
    },

    ready: function() {
      popup = this.$.popup;
      proj = this.$.proj;

      if (this.color) {
        popup.style.backgroundColor = this.color;
        proj.style.backgroundColor = this.color;
      }
      if (this.shown) {
        this.show(this.x, this.y);
      }
    },

    setContent(elem) {
      const len = popup.childNodes.length;
      if (len > 1) {
        popup.replaceChild(elem, popup.lastChild);
      } else {
        popup.appendChild(elem);
      }
    },

    show: function(x, y) {
      popup.style.left = (x - 20 - 13) + 'px';
      popup.style.top = (y + 13) + 'px';
      popup.style.display = 'block';
      proj.style.left = (x - 13) + 'px';
      proj.style.top = y + 'px';
      proj.style.display = 'inline'
      this.shown = true;
    },

    dismiss: function() {
      popup.style.display = 'none';
      proj.style.display = 'none'
      this.shown = false;
    },

    toggle: function(x, y) {
      if (this.shown) {
        this.dismiss();
      } else {
        this.show(x, y);
      }
    }
  });
</script>


================================================
FILE: example/markdown/README.md
================================================
Markdown Editor Example
=======================

This is a markdown editor example of [<neovim-editor> component](https://github.com/rhysd/neovim-component).

![screenshot](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/markdown-example.gif)

Preview 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.

You can execute this by below commands.

```sh
$ cd /path/to/neovim-component
$ npm run dep
$ cd ./example/markdown
$ npm run dep
$ npm run app
```

When 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.

It 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/)).


================================================
FILE: example/markdown/cli.js
================================================
#! /usr/bin/env node
'use strict';

var argv = process.argv.slice(2);
argv.unshift(__dirname);
if (!process.env['NODE_ENV']) {
    process.env['NODE_ENV'] = 'production';
}
require('child_process').spawn(require('electron'), argv, { stdio: 'inherit' });


================================================
FILE: example/markdown/index.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes" />
    <title>Neovim Embedded Markdown Editor</title>

    <script src="../../bower_components/webcomponentsjs/webcomponents-lite.js"></script>
    <link rel="import" href="../../bower_components/polymer/polymer.html" />
    <link rel="import" href="../../neovim-editor.html" />
    <link rel="import" href="./markdown-viewer.html" />

    <style>
      html {
        height: 100%;
        width: 100%;
      }
      body {
        height: 100%;
        width: 100%;
        margin: 0px;
        padding: 0px;
        overflow: hidden;
        display: flex;
      }
      #neovim {
        width: 50%;
        height: 100%;
      }
      #mdviewer {
        width: 50%;
        height: 100%;
        overflow: auto;
      }
    </style>
  </head>

  <body>
    <neovim-editor id="neovim" font="Monaco,Consolas,monospace" width="500" height="600"></neovim-editor>
    <markdown-viewer id="mdviewer"></markdown-viewer>
  </body>

  <script>
    const remote = require('electron').remote;
    const editor = document.getElementById('neovim').editor;
    const mdviewer = document.getElementById('mdviewer');

    editor.on('error', function(err){ alert(err.message); });

    // Note: Callback on Neovim process attached
    editor.on('process-attached', function() {
      if (remote.process.argv.length > 2) {
        editor.setArgv(remote.process.argv.slice(2)); // It is better to use 'argv' property.
      }
      const c = editor.getClient();

      // Note:
      // Send commands from JavaScript.
      // Of course you can execute these commands in Vim plugin.
      c.command('setf markdown');
      c.command('autocmd TextChanged,TextChangedI * if &ft ==# "markdown" | call rpcnotify(0, "markdown-viewer:text-update", join(getline(1, "$"), "\\n")) | endif');
      c.command('autocmd FileType markdown call rpcnotify(0, "markdown-viewer:text-update", join(getline(1, "$"), "\\n"))');

      // Note:
      // Subscribe notification sent by rpcnotify() from Neovim process
      c.subscribe('markdown-viewer:text-update');
      c.on('notification', function(method, args) {
        if (method === 'markdown-viewer:text-update' &&
          args.length > 0 &&
          typeof args[0] === 'string') {
          mdviewer.contents = args[0];
        }
      });
    });
    editor.on('quit', function() {
      remote.app.quit();
    });
  </script>
</html>


================================================
FILE: example/markdown/main.js
================================================
var path = require('path');
var electron = require('electron');
var app = electron.app;
var BrowserWindow = electron.BrowserWindow;

var index_html = 'file://' + path.join(__dirname, 'index.html');

app.on('ready', function() {
    var win = new BrowserWindow({
        width: 1000,
        height: 600,
        useContentSize: true,
        webPreferences: {
            blinkFeatures: 'KeyboardEventKey,Accelerated2dCanvas,Canvas2dFixedRenderingMode',
            nodeIntegration: true,
        },
    });

    win.on('closed', function() {
        win = null;
        app.quit();
    });

    win.loadURL(index_html);
    if (process.env['NODE_ENV'] !== 'production') {
        win.webContents.openDevTools({ mode: 'detach' });
    }
});


================================================
FILE: example/markdown/markdown-viewer.html
================================================
<link rel="import" href="../../bower_components/polymer/polymer.html" />
<script src="./node_modules/marked/lib/marked.js"></script>
<script src="./node_modules/highlightjs/highlight.pack.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.js"></script>

<dom-module id="markdown-viewer">
  <template>
    <link rel="stylesheet" href="./node_modules/github-markdown-css/github-markdown.css" />
    <link rel="stylesheet" href="./node_modules/highlightjs/styles/github.css" />
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.5.1/katex.min.css" />
    <style>
      .markdown-body {
        box-sizing: border-box;
        min-width: 200px;
        max-width: 980px;
        margin: 0 auto;
        padding: 25px;
        overflow: auto;
      }
    </style>

    <div id="markdown-contents" class="markdown-body">
    </div>
  </template>
</dom-module>

<script>
  marked.setOptions({
    sanitize: true,
    highlight: function(code, lang) {
      if (lang === undefined) {
        return code;
      }
      if (lang === 'katex') {
        return katex.renderToString(code);
      }
      try {
        return hljs.highlight(lang, code).value;
      } catch (e) {
        return code;
      }
    }
  });

  Polymer({
    is: 'markdown-viewer',

    properties: {
      contents: {
        type: String,
        value: '',
        observer: '_contentsChanged'
      }
    },

    _contentsChanged: function(markdown_text) {
      const v = this.$['markdown-contents'];
      v.innerHTML = marked(markdown_text);
    }
  });
</script>


================================================
FILE: example/markdown/package.json
================================================
{
  "name": "neovim-component-markdown-example",
  "version": "0.0.1",
  "main": "main.js",
  "scripts": {
    "dep": "npm install",
    "app": "../../node_modules/.bin/electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "github-markdown-css": "^2.1.1",
    "highlightjs": "^8.7.0",
    "katex": "^0.5.1",
    "marked": "^0.3.5"
  }
}


================================================
FILE: example/mini-browser/README.md
================================================
Mini Browser Example
====================

This is an embedded mini browser example of [<neovim-editor> component](https://github.com/rhysd/neovim-component).

![screenshot](https://raw.githubusercontent.com/rhysd/ss/master/neovim-component/mini-browser.gif)

`: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).

You can execute this example by below commands.

```sh
$ cd /path/to/neovim-component
$ npm run dep
$ cd ./example/mini-browser
$ npm run app
```

I 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.


================================================
FILE: example/mini-browser/cli.js
================================================
#! /usr/bin/env node
'use strict';

var argv = process.argv.slice(2);
argv.unshift(__dirname);
if (!process.env['NODE_ENV']) {
    process.env['NODE_ENV'] = 'production';
}
require('child_process').spawn(require('electron'), argv, { stdio: 'inherit' });


================================================
FILE: example/mini-browser/index.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes" />
    <title>Mini Browser Example</title>

    <script src="../../bower_components/webcomponentsjs/webcomponents-lite.js"></script>
    <link rel="import" href="../../bower_components/polymer/polymer.html" />
    <link rel="import" href="../../neovim-editor.html" />
    <link rel="import" href="./mini-browser.html" />

    <style>
      html {
        height: 100%;
        width: 100%;
      }
      body {
        height: 100%;
        width: 100%;
        margin: 0px;
        padding: 0px;
        overflow: hidden;
        display: flex;
      }
      #neovim {
        flex: auto;
        height: 100%;
      }
      #browser {
        flex: none;
        height: 100%;
      }
    </style>
  </head>

  <body>
    <neovim-editor id="neovim" font="Monaco,Consolas,monospace" font-size="14"></neovim-editor>
    <mini-browser id="browser"></mini-browser>
  </body>

  <script>
  (function() {
    var editor = document.getElementById('neovim').editor;
    var remote = require('electron').remote;
    var browser = document.getElementById('browser');

    function resize() {
      editor.screen.checkShouldResize();
    }

    browser.didClose = function() {
      resize();
      editor.focus();
    };

    browser.didShow = resize;

    editor.on('error', function(err){ alert(err.message); });

    // Note: Callback on Neovim process attached
    editor.on('process-attached', function() {
      if (remote.process.argv.length > 2) {
        editor.setArgv(remote.process.argv.slice(2)); // It is better to use 'argv' property.
      }
      var c = editor.getClient();

      // Note:
      // Define mapping 'gi' from JavaScript.
      // Of course you can execute these commands in Vim plugin.
      c.command('command! -nargs=1 Browse call rpcnotify(0, "mini-browser:url", <q-args>)');
      c.command('command! -nargs=0 CloseBrowser call rpcnotify(0, "mini-browser:close")');

      // Note:
      // Subscribe notification sent by rpcnotify() from Neovim process
      c.subscribe('mini-browser:url');
      c.subscribe('mini-browser:close');
      c.on('notification', function(method, args) {
          if (method === 'mini-browser:url' && args[0]) {
            browser.open(args[0]);
          } else if (method === 'mini-browser:close') {
            browser.close();
          }
      });
    });
    editor.on('quit', function() {
      remote.app.quit();
    });
    window.addEventListener('resize', function() { resize(); });
  })();
  </script>
</html>


================================================
FILE: example/mini-browser/main.js
================================================
var path = require('path');
var electron = require('electron');
var app = electron.app;
var BrowserWindow = electron.BrowserWindow;

var index_html = 'file://' + path.join(__dirname, 'index.html');

app.on('ready', function() {
    var win = new BrowserWindow({
        width: 800,
        height: 600,
        useContentSize: true,
        webPreferences: {
            blinkFeatures: 'KeyboardEventKey,Accelerated2dCanvas,Canvas2dFixedRenderingMode',
            nodeIntegration: true,
        },
    });

    win.on('closed', function() {
        win = null;
        app.quit();
    });

    win.loadURL(index_html);
    if (process.env['NODE_ENV'] !== 'production') {
        win.webContents.openDevTools({ mode: 'detach' });
    }
});


================================================
FILE: example/mini-browser/mini-browser.html
================================================
<link rel="import" href="../../bower_components/polymer/polymer.html" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">

<dom-module id="mini-browser">
  <template>
    <style>
      #body {
        display: none;
        height: 100%;
        width: 375px; /* iPhone6 width */
        flex-direction: column;
        font-family: Consolas,"Liberation Mono",Menlo,Courier,monospace;
      }
      #title-bar {
        height: 24px;
        flex: none;
        display: flex;
        flex-direction: row;
        text-align: center;
        border-bottom: solid 1px dimgray;
        color: dimgray;
        background-color: #dddddd;
      }
      #inner-browser {
        flex: auto;
        height: calc(100vh - 24px);
        width: 375px;
      }
      .button {
        border: solid 1px;
        border-radius: 4px;
        margin: 2px;
        padding: 4px;
        flex: none;
        min-width: 20px;
        font-size: 14px;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
      }
      #title {
        flex: auto;
        font-size: 16px;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        margin: 0px 8px;
        line-height: 24px;
      }
    </style>

    <div id="body">
      <div id="title-bar">
        <div class="button" id="back-btn"><i class="fa fa-arrow-left"></i></div>
        <div class="button" id="forward-btn"><i class="fa fa-arrow-right"></i></div>
        <div id="title"></div>
        <div class="button" id="close-btn"><i class="fa fa-times"></i></div>
      </div>
      <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>
    </div>
  </template>
</dom-module>

<script>
(function() {
  let browser, body, title;
  Polymer({
    is: 'mini-browser',

    properties: {
      url: {
        type: String,
        value: ''
      },
      visible: {
        type: Boolean,
        value: false
      },
      didClose: Object,
      didShow: Object
    },

    ready: function() {
      var self = this;
      body = this.$.body;
      browser = this.$['inner-browser'];
      title = this.$.title;
      this.$['back-btn'].onclick = function() {
        browser.goBack();
      };
      this.$['forward-btn'].onclick = function() {
        browser.goForward();
      };
      this.$['close-btn'].onclick = function() {
        browser.src = '';
        self.close();
      };
      browser.addEventListener('dom-ready', function() {
          title.innerText = browser.getTitle();
      });
      if (this.visible) {
        self.show();
      }
    },

    show() {
      body.style.display = 'flex';
      this.visible = true;
      if (this.didShow) {
        this.didShow();
      }
    },

    open(url) {
      browser.src = url;
      if (!this.visible) {
        this.show();
      }
    },

    close() {
      body.style.display = 'none';
      this.visible = false;
      if (this.didClose) {
        this.didClose();
      }
    }
  });
})();
</script>


================================================
FILE: example/mini-browser/package.json
================================================
{
  "name": "neovim-component-mini-browser-example",
  "version": "0.0.1",
  "main": "main.js",
  "scripts": {
    "app": "../../node_modules/.bin/electron .",
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}


================================================
FILE: example/minimal/README.md
================================================
This is minimal usage of `<neovim-editor>`.  You can easily try this using `npm run` command as below.

```sh
$ cd /path/to/neovim-component
$ npm start # Install dependencies, build source and run this example
```


================================================
FILE: example/minimal/cli.js
================================================
#! /usr/bin/env node
'use strict';

var argv = process.argv.slice(2);
argv.unshift(require('path').join(__dirname, '..', '..'));
if (!process.env['NODE_ENV']) {
    process.env['NODE_ENV'] = 'production';
}
require('child_process').spawn(require('electron'), argv, { stdio: 'inherit' });


================================================
FILE: example/minimal/index.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes" />
    <title>Neovim Web Component Example</title>

    <script src="../../bower_components/webcomponentsjs/webcomponents-lite.js"></script>
    <link rel="import" href="../../bower_components/polymer/polymer.html" />
    <link rel="import" href="../../neovim-editor.html" />

    <style>
      html, body {
        height: 100%;
        width: 100%;
        margin: 0px;
        padding: 0px;
        overflow: hidden;
      }
    </style>
  </head>
  <body>
    <neovim-editor id="neovim" font="Monaco,Consolas,monospace" font-size="14"></neovim-editor>
  </body>
  <script>
    'use strict';
    const neovim_element = document.getElementById('neovim');
    const editor = neovim_element.editor;
    const electron = require('electron');
    const remote = electron.remote;
    const shell = electron.shell;
    editor.on('error', function(err){ alert(err.message); });
    editor.on('process-attached', function() {
      if (remote.process.argv.length > 2) {
        // It is better to use 'argv' property of <neovim-editor>.
        editor.setArgv(remote.process.argv.slice(2));
      }
      neovim_element.addEventListener('drop', function(e) {
        e.preventDefault();
        const f = e.dataTransfer.files[0];
        if (f) {
          editor.getClient().command('e! ' + f.path);  // 'path' member is Electron extension
        }
      });
    });
    editor.on('quit', () => remote.app.quit());
    editor.store.on('beep', () => shell.beep());
    editor.store.on('title-changed', () => {
      document.title = editor.store.title;
    });
    editor.store.on('icon-changed', () => {
      var icon = editor.store.icon_path;
      if (icon === '') {
        return;
      }
      if (process.platform === 'darwin') {
        remote.getCurrentWindow().setRepresentedFilename(icon);
      }
    });
    neovim_element.addEventListener('dragover', e => e.preventDefault());
  </script>
</html>


================================================
FILE: example/minimal/main.js
================================================
var path = require('path');
var electron = require('electron');
var app = electron.app;
var BrowserWindow = electron.BrowserWindow;

var index_html = 'file://' + path.join(__dirname, 'index.html');

app.on('ready', function() {
    var win = new BrowserWindow({
        width: 800,
        height: 600,
        useContentSize: true,
        webPreferences: {
            blinkFeatures: 'KeyboardEventKey,Accelerated2dCanvas,Canvas2dFixedRenderingMode',
            nodeIntegration: true,
        },
    });

    win.on('closed', function() {
        win = null;
        app.quit();
    });

    win.loadURL(index_html);
    if (process.env['NODE_ENV'] !== 'production') {
        win.webContents.openDevTools({ mode: 'detach' });
    }
});


================================================
FILE: index.d.ts
================================================
import { EventEmitter } from 'events';
import { Nvim } from 'promised-neovim-client';
import { Dispatcher } from 'flux';
import cp = require('child_process');
import NvimClient = require('promised-neovim-client');
export declare type RPCValue = NvimClient.Buffer | NvimClient.Window | NvimClient.Tabpage | number | boolean | string | any[] | {
    [key: string]: any;
};

export interface HighlightSet {
    background?: number;
    bg?: string;
    bold?: boolean;
    fg?: string;
    special?: string;
    foreground?: number;
    italic?: boolean;
    reverse?: boolean;
    undercurl?: boolean;
    underline?: boolean;
}
export interface Region {
    top: number;
    left: number;
    right: number;
    bottom: number;
}
export declare enum Kind {
    Bell = 0,
    BusyStart = 1,
    BusyStop = 2,
    ChangeCursorDrawDelay = 3,
    ClearAll = 4,
    ClearEOL = 5,
    CompositionStart = 6,
    CompositionEnd = 7,
    Cursor = 8,
    DisableMouse = 9,
    DisableAltKey = 10,
    DisableMetaKey = 11,
    DragEnd = 12,
    DragStart = 13,
    DragUpdate = 14,
    EnableMouse = 15,
    Highlight = 16,
    Input = 17,
    Mode = 18,
    PutText = 19,
    Resize = 20,
    ScrollScreen = 21,
    SetIcon = 22,
    SetScrollRegion = 23,
    SetTitle = 24,
    StartBlinkCursor = 25,
    StopBlinkCursor = 26,
    UpdateBG = 27,
    UpdateFG = 28,
    UpdateSP = 29,
    UpdateFontFace = 30,
    updateLineHeight = 31,
    UpdateFontPx = 32,
    UpdateFontSize = 33,
    UpdateScreenBounds = 34,
    UpdateScreenSize = 35,
    WheelScroll = 36,
    FocusChanged = 37,
}
export interface ActionType {
    type: Kind;
    col?: number;
    color?: number;
    cols?: number;
    delay?: number;
    disabled?: boolean;
    draw_width?: number;
    draw_height?: number;
    event?: MouseEvent | WheelEvent;
    focused?: boolean;
    font_face?: string;
    font_px?: number;
    height?: number;
    highlight?: HighlightSet;
    icon_path?: string;
    input?: string;
    line?: number;
    lines?: number;
    mode?: string;
    region?: Region;
    text?: string[][];
    title?: string;
    visual?: boolean;
    width?: number;
}
export declare function putText(text: string[][]): {
    type: Kind;
    text: string[][];
};
export declare function cursor(line: number, col: number): {
    type: Kind;
    line: number;
    col: number;
};
export declare function highlight(highlight: HighlightSet): {
    type: Kind;
    highlight: HighlightSet;
};
export declare function clearAll(): {
    type: Kind;
};
export declare function clearEndOfLine(): {
    type: Kind;
};
export declare function compositionStart(): {
    type: Kind;
};
export declare function compositionEnd(): {
    type: Kind;
};
export declare function resize(lines: number, cols: number): {
    type: Kind;
    lines: number;
    cols: number;
};
export declare function updateForeground(color: number): {
    type: Kind;
    color: number;
};
export declare function updateBackground(color: number): {
    type: Kind;
    color: number;
};
export declare function updateSpecialColor(color: number): {
    type: Kind;
    color: number;
};
export declare function changeMode(mode: string): {
    type: Kind;
    mode: string;
};
export declare function startBusy(): {
    type: Kind;
};
export declare function stopBusy(): {
    type: Kind;
};
export declare function updateFontSize(draw_width: number, draw_height: number, width: number, height: number): {
    type: Kind;
    draw_width: number;
    draw_height: number;
    width: number;
    height: number;
};
export declare function inputToNeovim(input: string): {
    type: Kind;
    input: string;
};
export declare function updateFontPx(font_px: number): {
    type: Kind;
    font_px: number;
};
export declare function updateFontFace(font_face: string): {
    type: Kind;
    font_face: string;
};
export declare function updateScreenSize(width: number, height: number): {
    type: Kind;
    width: number;
    height: number;
};
export declare function updateScreenBounds(lines: number, cols: number): {
    type: Kind;
    lines: number;
    cols: number;
};
export declare function enableMouse(): {
    type: Kind;
};
export declare function disableMouse(): {
    type: Kind;
};
export declare function dragStart(event: MouseEvent): {
    type: Kind;
    event: MouseEvent;
};
export declare function dragUpdate(event: MouseEvent): {
    type: Kind;
    event: MouseEvent;
};
export declare function dragEnd(event: MouseEvent): {
    type: Kind;
    event: MouseEvent;
};
export declare function bell(visual: boolean): {
    type: Kind;
    visual: boolean;
};
export declare function setTitle(title: string): {
    type: Kind;
    title: string;
};
export declare function setIcon(icon_path: string): {
    type: Kind;
    icon_path: string;
};
export declare function wheelScroll(event: WheelEvent): {
    type: Kind;
    event: WheelEvent;
};
export declare function scrollScreen(cols: number): {
    type: Kind;
    cols: number;
};
export declare function setScrollRegion(region: Region): {
    type: Kind;
    region: Region;
};
export declare function notifyFocusChanged(focused: boolean): {
    type: Kind;
    focused: boolean;
};
export declare function disableAltKey(disabled: boolean): {
    type: Kind;
    disabled: boolean;
};
export declare function disableMetaKey(disabled: boolean): {
    type: Kind;
    disabled: boolean;
};
export declare function changeCursorDrawDelay(delay: number): {
    type: Kind;
    delay: number;
};
export declare function startBlinkCursor(): {
    type: Kind;
};
export declare function stopBlinkCursor(): {
    type: Kind;
};

export class NeovimCursor {
    constructor(store: NeovimStore, screen_ctx: CanvasRenderingContext2D);
    updateSize(): void;
    redraw(): void;
    onModeChanged(): void;
    updateCursorPos(): void;
}

export class NeovimInput {
    element: HTMLInputElement;
    ime_running: boolean;
    static shouldHandleModifier(event: KeyboardEvent): boolean;
    static getVimSpecialChar(code: number, shift: boolean): string;
    constructor(store: NeovimStore);
    startComposition(event: Event): void;
    endComposition(event: Event): void;
    focus(): void;
    onInsertControlChar(event: KeyboardEvent): void;
    inputToNeovim(input: string, event: Event): void;
    onInsertNormalChar(event: KeyboardEvent): void;
}

export class NeovimProcess {
    command: string;
    argv: string[];
    neovim_process: cp.ChildProcess;
    client: NvimClient.Nvim;
    started: boolean;
    constructor(store: NeovimStore, command: string, argv: string[]);
    attach(lines: number, columns: number): Promise<void>;
    onRequested(method: string, args: RPCValue[], response: RPCValue): void;
    onNotified(method: string, args: RPCValue[]): void;
    onDisconnected(): void;
    finalize(): Promise<void>;
}

export class ScreenDrag {
    line: number;
    col: number;
    static buildInputOf(e: MouseEvent, type: string, line: number, col: number): string;
    constructor(store: NeovimStore);
    start(down_event: MouseEvent): string;
    drag(move_event: MouseEvent): string;
    end(up_event: MouseEvent): string;
}

export class ScreenWheel {
    x: number;
    y: number;
    shift: boolean;
    ctrl: boolean;
    constructor(store: NeovimStore);
    handleEvent(e: WheelEvent): string;
}

export class NeovimScreen {
    canvas: HTMLCanvasElement;
    ctx: CanvasRenderingContext2D;
    cursor: NeovimCursor;
    input: NeovimInput;
    constructor(store: NeovimStore, canvas: HTMLCanvasElement);
    wheel(e: WheelEvent): void;
    mouseDown(e: MouseEvent): void;
    mouseUp(e: MouseEvent): void;
    mouseMove(e: MouseEvent): void;
    resizeWithPixels(width_px: number, height_px: number): void;
    resize(lines: number, cols: number): void;
    changeFontSize(specified_px: number): void;
    changeLineHeight(new_value: number): void;
    scroll(cols_delta: number): void;
    focus(): void;
    clearAll(): void;
    clearEol(): void;
    convertPositionToLocation(line: number, col: number): {
        x: number;
        y: number;
    };
    convertLocationToPosition(x: number, y: number): {
        line: number;
        col: number;
    };
    checkShouldResize(): void;
}

export interface Size {
    lines: number;
    cols: number;
    width: number;
    height: number;
}
export interface Cursor {
    line: number;
    col: number;
}
export interface FontAttributes {
    fg: string;
    bg: string;
    sp: string;
    bold: boolean;
    italic: boolean;
    underline: boolean;
    undercurl: boolean;
    draw_width: number;
    draw_height: number;
    width: number;
    height: number;
    face: string;
    specified_px: number;
}
export declare type DispatcherType = Dispatcher<ActionType>;
export class NeovimStore extends EventEmitter {
    blink_cursor: boolean;
    cursor_blink_interval: number;
    cursor_draw_delay: number;
    dispatch_token: string;
    size: Size;
    focused: boolean;
    font_attr: FontAttributes;
    line_height: number;
    fg_color: string;
    bg_color: string;
    cursor: Cursor;
    mode: string;
    busy: boolean;
    mouse_enabled: boolean;
    dragging: ScreenDrag;
    title: string;
    icon_path: string;
    wheel_scrolling: ScreenWheel;
    scroll_region: Region;
    dispatcher: Dispatcher<ActionType>;
}

export class Neovim extends EventEmitter {
    process: NeovimProcess;
    screen: NeovimScreen;
    store: NeovimStore;
    constructor(
        command: string,
        argv: string[],
        font: string,
        font_size: number,
        line_height: number,
        blink_cursor: boolean,
        window_title: string,
    );
    attachCanvas(width: number, height: number, canvas: HTMLCanvasElement): void;
    quit(): void;
    getClient(): Nvim;
    focus(): void;
    setArgv(argv: string[]): Promise<void>;
}

export class NeovimElement extends HTMLElement {
    disableAltKey: boolean;
    drawDelay: number;
    editor: Neovim;
    width: number;
    height: number;
    fontSize: number;
    font: string;
    lineHeight: number;
    noBlinkCursor: boolean;
    windowTitle: string;
    nvimCmd: string;
    argv: string[];
    onProcessAttached: () => void;
    onQuit: () => void;
    onError: (err: Error) => void;
}


================================================
FILE: neovim-editor.html
================================================
<dom-module id="neovim-editor">
  <template>
    <style>
      :host {
        position: relative;
        min-width: 0px;
        min-height: 0px;
      }
      #container {
        padding: 0px;
        margin: 0px;
        width: 100%;
        height: 100%;
      }
      #cursor {
        padding: 0px;
        margin: 0px;
        position: absolute;
      }
      #input {
        width: 1px;
        color: transparent;
        background-color: transparent;
        padding: 0px;
        border: 0px;
        outline: none;
        vertical-align: middle;
        position: absolute;
        top: 0px;
        left: 0px;
      }
      #preedit {
        display: none;
        visibility: hidden;
        position: fixed;
      }
    </style>
    <div id="container">
      <canvas id="screen" width$="[[width]]" height$="[[height]]"></canvas>
      <canvas id="cursor"></canvas>
      <input id="input" autocomplete="off" autofocus />
      <span id="preedit"></span>
    </div>
  </template>
</dom-module>

<script src="./build/index.js"></script>


================================================
FILE: package.json
================================================
{
  "name": "neovim-component",
  "version": "0.10.1",
  "description": "Polymer component for Neovim frontend",
  "main": "example/minimal/main.js",
  "repository": {
    "type": "git",
    "url": "https://github.com/rhysd/neovim-component.git"
  },
  "bugs": {
    "url": "https://github.com/rhysd/neovim-component/issues"
  },
  "scripts": {
    "start": "npm run dep && npm run build && npm run example",
    "browserify": "browserify -o build/index.js build/src/index.js",
    "tsc": "tsc --pretty --project .",
    "build": "npm-run-all tsc browserify",
    "debug": "cross-env ELECTRON_ENABLE_STACK_DUMPING=true NODE_ENV=debug electron .",
    "dep": "npm install && bower install && mkdir build",
    "example": "cross-env NODE_ENV=production electron .",
    "tslint": "tslint --project .",
    "nsp": "nsp check",
    "lint": "npm-run-all -p tslint",
    "format": "prettier --write 'src/**/*.ts' 'test/**/*.ts' 'example/*/*.js'",
    "watch": "guard --watchdir src test",
    "test": "mocha test/unit/ --exit",
    "e2e": "mocha build/test/e2e/ --opts test/e2e/mocha.opts --exit"
  },
  "keywords": [
    "neovim",
    "polymer",
    "WebComponent",
    "Electron",
    "NW.js",
    "editor"
  ],
  "author": "rhysd <lin90162@yahoo.co.jp>",
  "license": "MIT",
  "dependencies": {
    "flux": "^3.1.3",
    "loglevel": "^1.6.3",
    "promised-neovim-client": "^2.0.2"
  },
  "devDependencies": {
    "@types/chai": "^4.1.7",
    "@types/fbemitter": "^2.0.32",
    "@types/flux": "^3.1.9",
    "@types/loglevel": "^1.5.4",
    "@types/mocha": "^5.2.7",
    "@types/node": "^12.0.8",
    "@types/react": "^16.8.19",
    "@types/webdriverio": "^4.10.4",
    "bower": "^1.8.8",
    "browserify": "^16.2.3",
    "canvas": "^2.5.0",
    "chai": "^4.2.0",
    "cross-env": "^5.2.0",
    "electron": "~5.0.3",
    "jsdom": "^15.1.1",
    "mocha": "^6.1.4",
    "npm-run-all": "^4.1.5",
    "nsp": "^3.2.1",
    "prettier": "^1.18.2",
    "spectron": "^5.0.0",
    "tslint": "^5.17.0",
    "typescript": "^3.5.1"
  }
}


================================================
FILE: scripts/travis-ci-install.sh
================================================
#!/bin/bash

set -e

echo Installing dependencies
npm run dep

echo 'uname:'
uname -a

echo 'nvim:'
nvim --version


================================================
FILE: scripts/travis-ci-run.sh
================================================
#!/bin/bash

set -e

echo 'Building package'
npm run build
npm run lint

echo 'Running unit tests'
npm run test

echo 'Running E2E tests'
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
    # E2E tests fail on Linux worker because of Hardware Acceleration.
    # It may work with the environment HA is disabled. But it does not make sense to run tests
    # without HA because most users enable HA usually.
    npm run e2e
fi


================================================
FILE: src/index.ts
================================================
import Neovim, { DOM } from './neovim';

class NeovimEditor extends Polymer.Element {
    static get is() {
        return 'neovim-editor';
    }

    static get properties() {
        return {
            width: Number,
            height: Number,
            fontSize: {
                type: Number,
                value: 12,
            },
            font: {
                type: String,
                value: 'monospace',
            },
            lineHeight: {
                type: Number,
                value: 1.3,
            },
            nvimCmd: {
                type: String,
                value: 'nvim',
            },
            argv: {
                type: Array,
                value: () => [] as string[],
            },
            disableAltKey: {
                type: Boolean,
                value: false,
            },
            disableMetaKey: {
                type: Boolean,
                value: false,
            },
            cursorDrawDelay: {
                type: Number,
                value: 10,
            },
            noBlinkCursor: {
                type: Boolean,
                value: false,
            },
            windowTitle: {
                type: String,
                value: 'Neovim',
            },
            onProcessAttached: Object,
            onQuit: Object,
            onError: Object,
        };
    }

    width: number;
    height: number;
    fontSize: number;
    font: string;
    lineHeight: number;
    nvimCmd: string;
    argv: string[];
    disableAltKey: boolean;
    disableMetaKey: boolean;
    cursorDrawDelay: number;
    noBlinkCursor: boolean;
    windowTitle: string;
    editor: Neovim;
    onProcessAttached: () => void;
    onQuit: () => void;
    onError: (err: Error) => void;
    resizeHandler: NodeJS.Timer;
    resizeListener: () => void;

    ready() {
        super.ready();
        this.editor = new Neovim(
            this.$ as DOM,
            this.nvimCmd,
            this.argv,
            this.font,
            this.fontSize,
            this.lineHeight,
            this.disableAltKey,
            this.disableMetaKey,
            this.cursorDrawDelay,
            !this.noBlinkCursor,
            this.windowTitle,
        );
        this.resizeHandler = null;

        if (this.onError) {
            this.editor.on('error', this.onError);
        }

        if (this.onQuit) {
            this.editor.on('quit', this.onQuit);
        }

        if (this.onProcessAttached) {
            this.editor.on('process-attached', this.onProcessAttached);
        }
    }

    connectedCallback() {
        super.connectedCallback();
        Polymer.RenderStatus.afterNextRender(this, function() {
            // measure size of element
            const parent: HTMLCanvasElement = this.$.container;
            const canvas: HTMLCanvasElement = this.$.screen;
            const width = this.width || parent.offsetWidth;
            const height = this.height || parent.offsetHeight;
            this.editor.attachCanvas(width, height, canvas);
            this.resizeListener = () => {
                if (this.resizeHandler !== null) {
                    clearTimeout(this.resizeHandler);
                }
                this.resizeHandler = setTimeout(() => {
                    this.editor.screen.checkShouldResize();
                    this.resizeHandler = null;
                }, 100);
            };
            window.addEventListener('resize', this.resizeListener);
        });
    }

    disconnectedCallback() {
        super.disconnectedCallback();
        this.editor.emit('detach');
        if (this.resizeListener) {
            window.removeEventListener('resize', this.resizeListener);
        }
    }

    attributeChangedCallback(name: string, oldVal: string | null, newVal: string | null) {
        // @ts-ignore: https://github.com/Polymer/polymer/issues/5087
        super.attributeChangedCallback(name, oldVal, newVal);
        if (this.editor === undefined) {
            return;
        }
        this.editor.emit('change-attribute', name, oldVal, newVal);
    }
}

customElements.define(NeovimEditor.is, NeovimEditor);


================================================
FILE: src/lib.d.ts
================================================
/// <reference path="../bower_components/polymer/types/polymer.d.ts"/>
declare namespace NodeJS {
    interface Global {
        require(mod: string): any;
    }
}


================================================
FILE: src/log.ts
================================================
import log = require('loglevel');

export const NODE_ENV = (() => {
    try {
        return global.require('electron').remote.process.env.NODE_ENV || 'production';
    } catch (e) {
        return 'production';
    }
})();

if (NODE_ENV === 'production') {
    log.setLevel('WARN');
} else if (NODE_ENV === 'debug') {
    log.setLevel('DEBUG');
} else {
    log.setLevel('INFO');
}

export default log;


================================================
FILE: src/neovim/actions.ts
================================================
export interface HighlightSet {
    background?: number;
    bg?: string;
    bold?: boolean;
    fg?: string;
    foreground?: number;
    italic?: boolean;
    reverse?: boolean;
    special?: number;
    undercurl?: boolean;
    underline?: boolean;
}

export interface ModeInfoSet {
    [key: string]: ModeInfo;
}

export interface ModeInfo {
    blinkoff?: number;
    blinkon?: number;
    blinkwait?: number;
    cell_percentage?: number;
    cursor_shape?: string;
    hl_id?: number;
    id_lm?: number;
    mouse_shape: number;
    name: string;
    short_name: string;
}

export interface Region {
    top: number;
    left: number;
    right: number;
    bottom: number;
}

export enum Kind {
    Bell,
    BusyStart,
    BusyStop,
    ChangeCursorDrawDelay,
    ClearAll,
    ClearEOL,
    CompositionStart,
    CompositionEnd,
    Cursor,
    DisableMouse,
    DisableAltKey,
    DisableMetaKey,
    DragEnd,
    DragStart,
    DragUpdate,
    EnableMouse,
    Highlight,
    Input,
    Mode,
    ModeInfo,
    PutText,
    Resize,
    ScrollScreen,
    SetIcon,
    SetScrollRegion,
    SetTitle,
    StartBlinkCursor,
    StopBlinkCursor,
    UpdateBG,
    UpdateFG,
    UpdateSP,
    UpdateFontFace,
    UpdateFontPx,
    UpdateFontSize,
    UpdateLineHeight,
    UpdateScreenBounds,
    UpdateScreenSize,
    WheelScroll,
    FocusChanged,
}

export interface ActionType {
    type: Kind;
    col?: number;
    color?: number;
    cols?: number;
    delay?: number;
    disabled?: boolean;
    draw_width?: number;
    draw_height?: number;
    event?: MouseEvent | WheelEvent;
    focused?: boolean;
    font_face?: string;
    font_px?: number;
    height?: number;
    highlight?: HighlightSet;
    icon_path?: string;
    input?: string;
    line?: number;
    line_height?: number;
    lines?: number;
    modeInfo?: ModeInfoSet;
    mode?: string;
    region?: Region;
    text?: string[][];
    title?: string;
    visual?: boolean;
    width?: number;
}

export function putText(text: string[][]) {
    return {
        type: Kind.PutText,
        text,
    };
}

export function cursor(line: number, col: number) {
    return {
        type: Kind.Cursor,
        line,
        col,
    };
}

export function highlight(hl: HighlightSet) {
    return {
        type: Kind.Highlight,
        highlight: hl,
    };
}

export function clearAll() {
    return {
        type: Kind.ClearAll,
    };
}

export function clearEndOfLine() {
    return {
        type: Kind.ClearEOL,
    };
}

export function compositionStart() {
    return {
        type: Kind.CompositionStart,
    };
}

export function compositionEnd() {
    return {
        type: Kind.CompositionEnd,
    };
}

export function resize(lines: number, cols: number) {
    return {
        type: Kind.Resize,
        lines,
        cols,
    };
}

export function updateForeground(color: number) {
    return {
        type: Kind.UpdateFG,
        color,
    };
}

export function updateBackground(color: number) {
    return {
        type: Kind.UpdateBG,
        color,
    };
}

export function updateSpecialColor(color: number) {
    return {
        type: Kind.UpdateSP,
        color,
    };
}

export function modeInfo(info: ModeInfoSet) {
    return {
        type: Kind.ModeInfo,
        modeInfo: info,
    };
}

export function changeMode(mode: string) {
    return {
        type: Kind.Mode,
        mode,
    };
}

export function startBusy() {
    return {
        type: Kind.BusyStart,
    };
}

export function stopBusy() {
    return {
        type: Kind.BusyStop,
    };
}

export function updateFontSize(draw_width: number, draw_height: number, width: number, height: number) {
    return {
        type: Kind.UpdateFontSize,
        draw_width,
        draw_height,
        width,
        height,
    };
}

export function inputToNeovim(input: string) {
    return {
        type: Kind.Input,
        input,
    };
}

export function updateFontPx(font_px: number) {
    return {
        type: Kind.UpdateFontPx,
        font_px,
    };
}

export function updateFontFace(font_face: string) {
    return {
        type: Kind.UpdateFontFace,
        font_face,
    };
}

export function updateScreenSize(width: number, height: number) {
    return {
        type: Kind.UpdateScreenSize,
        width,
        height,
    };
}

// Note:
// This function has the same effect as resize() but resize() is used
// for neovim's UI event and this function is used to change screen bounds
// via NeovimScreen's API.
export function updateScreenBounds(lines: number, cols: number) {
    return {
        type: Kind.UpdateScreenBounds,
        lines,
        cols,
    };
}

export function enableMouse() {
    return {
        type: Kind.EnableMouse,
    };
}

export function disableMouse() {
    return {
        type: Kind.DisableMouse,
    };
}

export function dragStart(event: MouseEvent) {
    return {
        type: Kind.DragStart,
        event,
    };
}

export function dragUpdate(event: MouseEvent) {
    return {
        type: Kind.DragUpdate,
        event,
    };
}

export function dragEnd(event: MouseEvent) {
    return {
        type: Kind.DragEnd,
        event,
    };
}

export function bell(visual: boolean) {
    return {
        type: Kind.Bell,
        visual,
    };
}

export function setTitle(title: string) {
    return {
        type: Kind.SetTitle,
        title,
    };
}

export function setIcon(icon_path: string) {
    return {
        type: Kind.SetIcon,
        icon_path,
    };
}

export function wheelScroll(event: WheelEvent) {
    return {
        type: Kind.WheelScroll,
        event,
    };
}

export function scrollScreen(cols: number) {
    return {
        type: Kind.ScrollScreen,
        cols,
    };
}

export function setScrollRegion(region: Region) {
    return {
        type: Kind.SetScrollRegion,
        region,
    };
}

export function notifyFocusChanged(focused: boolean) {
    return {
        type: Kind.FocusChanged,
        focused,
    };
}

export function updateLineHeight(line_height: number) {
    return {
        type: Kind.UpdateLineHeight,
        line_height,
    };
}

export function disableAltKey(disabled: boolean) {
    return {
        type: Kind.DisableAltKey,
        disabled,
    };
}

export function disableMetaKey(disabled: boolean) {
    return {
        type: Kind.DisableMetaKey,
        disabled,
    };
}

export function changeCursorDrawDelay(delay: number) {
    return {
        type: Kind.ChangeCursorDrawDelay,
        delay,
    };
}

export function startBlinkCursor() {
    return {
        type: Kind.StartBlinkCursor,
    };
}
export function stopBlinkCursor() {
    return {
        type: Kind.StopBlinkCursor,
    };
}


================================================
FILE: src/neovim/cursor.ts
================================================
import { EventEmitter } from 'events';
import NeovimStore from './store';
import log from '../log';
import { dragEnd } from './actions';

function invertColor(image: ImageData) {
    const d = image.data;
    for (let i = 0; i < d.length; i += 4) {
        d[i] = 255 - d[i]; // Red
        d[i + 1] = 255 - d[i + 1]; // Green
        d[i + 2] = 255 - d[i + 2]; // Blue
    }
    return image;
}

class CursorBlinkTimer extends EventEmitter {
    enabled: boolean;
    shown: boolean;
    private token: number;
    private readonly callback: () => void;

    constructor(public interval: number) {
        super();
        this.token = null;
        this.enabled = false;
        this.shown = true;
        this.callback = this._callback.bind(this);
    }

    start() {
        if (this.enabled) {
            return;
        }
        this.shown = true;
        this.token = window.setTimeout(this.callback, this.interval);
        this.enabled = true;
    }

    stop() {
        if (!this.enabled) {
            return;
        }
        if (this.token !== null) {
            window.clearTimeout(this.token);
            this.token = null;
        }
        this.enabled = false;
    }

    reset() {
        if (this.enabled) {
            this.stop();
            this.start();
        }
    }

    private _callback() {
        this.shown = !this.shown;
        this.emit('tick', this.shown);
        this.token = window.setTimeout(this.callback, this.interval);
    }
}

export default class NeovimCursor {
    private readonly element: HTMLCanvasElement;
    private readonly ctx: CanvasRenderingContext2D;
    private delay_timer: number;
    private readonly blink_timer: CursorBlinkTimer;
    private preedit_is_shown: boolean; // flag to hide cursor when preedit is shown

    constructor(private readonly store: NeovimStore, private readonly screen_ctx: CanvasRenderingContext2D) {
        this.delay_timer = null;
        this.blink_timer = new CursorBlinkTimer(this.store.cursor_blink_interval);
        this.element = this.store.dom.cursor as HTMLCanvasElement;
        this.element.style.top = '0px';
        this.element.style.left = '0px';
        this.ctx = this.element.getContext('2d', { alpha: false });
        this.onFontSizeUpdated();
        this.blink_timer.on('tick', (shown: boolean) => {
            if (shown) {
                this.redraw();
            } else {
                this.dismiss();
            }
        });
        if (this.store.blink_cursor) {
            this.blink_timer.start();
        }

        this.element.addEventListener('mouseup', (e: MouseEvent) => {
            this.store.dispatcher.dispatch(dragEnd(e));
        });
        this.element.addEventListener('click', (e: MouseEvent) => {
            e.preventDefault();
            const i = this.store.dom.input as HTMLInputElement;
            if (i) {
                i.focus();
            }
        });

        this.store.on('cursor', this.updateCursorPos.bind(this));
        this.store.on('update-fg', () => this.redraw());
        this.store.on('font-size-changed', this.onFontSizeUpdated.bind(this));
        this.store.on('blink-cursor-started', () => this.blink_timer.start());
        this.store.on('blink-cursor-stopped', () => this.blink_timer.stop());
        this.store.on('busy', () => {
            if (this.store.busy) {
                this.dismiss();
                this.blink_timer.stop();
            } else {
                this.redraw();
                if (this.store.blink_cursor) {
                    this.blink_timer.start();
                }
            }
        });
        this.store.on('focus-changed', () => this.updateCursorBlinking(this.store.focused));
        this.store.on('mode', () => this.updateCursorBlinking(this.store.mode !== 'insert'));
        this.store.on('composition-started', () => this.compositionChanged(true));
        this.store.on('composition-ended', () => this.compositionChanged(false));
    }

    onFontSizeUpdated() {
        const f = this.store.font_attr;
        this.element.style.width = f.width + 'px';
        this.element.style.height = f.height + 'px';
        this.element.width = f.draw_width;
        this.element.height = f.draw_height;
        this.redraw();
    }

    dismiss() {
        this.ctx.clearRect(0, 0, this.element.width, this.element.height);
    }

    redraw() {
        if (this.preedit_is_shown) {
            if (this.delay_timer !== null) {
                clearTimeout(this.delay_timer);
            }
            this.ctx.clearRect(0, 0, this.element.width, this.element.height);
            return;
        }

        if (this.store.cursor_draw_delay <= 0) {
            this.redrawImpl();
            return;
        }
        if (this.delay_timer !== null) {
            clearTimeout(this.delay_timer);
        } else {
            this.ctx.clearRect(0, 0, this.element.width, this.element.height);
        }
        this.delay_timer = window.setTimeout(this.redrawImpl.bind(this), this.store.cursor_draw_delay);
    }

    updateCursorPos() {
        const { line, col } = this.store.cursor;
        const { width, height } = this.store.font_attr;

        const x = col * width;
        const y = line * height;

        this.element.style.left = x + 'px';
        this.element.style.top = y + 'px';
        log.debug(`Cursor is moved to (${x}, ${y})`);
        this.redraw();
        this.blink_timer.reset();
    }

    updateCursorSize() {
        const info = this.store.modeInfo[this.store.mode];
        if (!info) {
            return;
        }
        const { cursor_shape, cell_percentage } = info;
        if (cursor_shape === undefined || cell_percentage === undefined) {
            return;
        }
        const { width, height } = this.store.font_attr;
        switch (cursor_shape) {
            case 'horizontal': {
                const top = height * (1 - cell_percentage / 100);
                const right = width;
                const bottom = height;
                const left = 0;
                this.element.style.clip = `rect(${top}px ${right}px ${bottom}px ${left}px)`;
                break;
            }
            case 'vertical': {
                const top = 0;
                const right = width * (cell_percentage / 100);
                const bottom = height;
                const left = 0;
                this.element.style.clip = `rect(${top}px ${right}px ${bottom}px ${left}px)`;
                break;
            }
            default: {
                this.element.style.clip = 'auto';
                break;
            }
        }
    }

    private redrawImpl() {
        this.delay_timer = null;
        const { draw_width, draw_height } = this.store.font_attr;
        const x = this.store.cursor.col * draw_width;
        const y = this.store.cursor.line * draw_height;
        const captured = this.screen_ctx.getImageData(x, y, draw_width, draw_height);
        this.ctx.putImageData(invertColor(captured), 0, 0);
    }

    private updateCursorBlinking(should_blink: boolean) {
        this.updateCursorSize();
        if (should_blink) {
            if (this.store.blink_cursor) {
                this.blink_timer.start();
            }
        } else {
            this.blink_timer.stop();
            if (!this.blink_timer.shown) {
                this.redraw();
            }
        }
    }

    private compositionChanged(preedit_is_shown: boolean) {
        this.preedit_is_shown = preedit_is_shown;
        this.redraw();
    }
}


================================================
FILE: src/neovim/input.ts
================================================
// keyCode is deprecated but necessary for fallback
/* tslint:disable:deprecation */

import NeovimStore from './store';
import { compositionStart, compositionEnd, inputToNeovim, notifyFocusChanged } from './actions';
import log from '../log';

const OnDarwin = global.process.platform === 'darwin';
const IsAlpha = /^[a-zA-Z]$/;

export default class NeovimInput {
    element: HTMLInputElement;
    // fake span element to measure text width of preedit input
    fake_element: HTMLSpanElement;
    ime_running: boolean; // XXX: Local state!

    static shouldIgnoreOnKeydown(event: KeyboardEvent) {
        const { ctrlKey, shiftKey, altKey, keyCode, metaKey } = event;
        return (
            (!ctrlKey && !altKey && !metaKey) ||
            (shiftKey && keyCode === 16) ||
            (ctrlKey && keyCode === 17) ||
            (altKey && keyCode === 18) ||
            (metaKey && keyCode === 91)
        );
    }

    // Note:
    // Workaround when KeyboardEvent.key is not available.
    static getVimSpecialCharFromKeyCode(key_code: number, shift: boolean) {
        switch (key_code) {
            case 0:
                return 'Nul';
            case 8:
                return 'BS';
            case 9:
                return 'Tab';
            case 10:
                return 'NL';
            case 13:
                return 'CR';
            case 33:
                return 'PageUp';
            case 34:
                return 'PageDown';
            case 27:
                return 'Esc';
            case 32:
                return 'Space';
            case 35:
                return 'End';
            case 36:
                return 'Home';
            case 37:
                return 'Left';
            case 38:
                return 'Up';
            case 39:
                return 'Right';
            case 40:
                return 'Down';
            case 45:
                return 'Insert';
            case 46:
                return 'Del';
            case 47:
                return 'Help';
            case 92:
                return 'Bslash';
            case 112:
                return 'F1';
            case 113:
                return 'F2';
            case 114:
                return 'F3';
            case 115:
                return 'F4';
            case 116:
                return 'F5';
            case 117:
                return 'F6';
            case 118:
                return 'F7';
            case 119:
                return 'F8';
            case 120:
                return 'F9';
            case 121:
                return 'F10';
            case 122:
                return 'F11';
            case 123:
                return 'F12';
            case 124:
                return 'Bar'; // XXX
            case 127:
                return 'Del'; // XXX
            case 188:
                return shift ? 'LT' : null;
            default:
                return null;
        }
    }

    // Note:
    // Special key handling using KeyboardEvent.key.  Thank you @romgrk for the idea.
    // https://www.w3.org/TR/DOM-Level-3-Events-key/
    static getVimSpecialCharFromKey(event: KeyboardEvent) {
        const key = event.key;

        if (key.length === 1) {
            switch (key) {
                case '<':
                    return event.ctrlKey || event.altKey ? 'LT' : null;
                case ' ':
                    return 'Space';
                case '\0':
                    return 'Nul';
                default:
                    return null;
            }
        }

        if (key[0] === 'F') {
            // F1, F2, F3, ...
            return /^F\d+/.test(key) ? key : null;
        }

        const ctrl = event.ctrlKey;
        const key_code = event.keyCode;

        switch (key) {
            case 'Escape': {
                if (ctrl && key_code !== 27) {
                    // Note:
                    // When <C-[> is input
                    // XXX:
                    // Keycode of '[' is not available because it is 219 in OS X
                    // and it is not for '['.
                    return '[';
                } else {
                    return 'Esc';
                }
            }
            case 'Backspace': {
                if (ctrl && key_code === 72) {
                    // Note:
                    // When <C-h> is input (72 is key code of 'h')
                    return 'h';
                } else {
                    return 'BS';
                }
            }
            case 'Tab': {
                if (ctrl && key_code === 73) {
                    // Note:
                    // When <C-i> is input (73 is key code of 'i')
                    return 'i';
                } else {
                    return 'Tab';
                }
            }
            case 'Enter': {
                // Note: Should consider <NL>?
                if (ctrl && key_code === 77) {
                    // Note:
                    // When <C-m> is input (77 is key code of 'm')
                    return 'm';
                } else if (ctrl && key_code === 67) {
                    // XXX:
                    // This is workaround for a bug of Chromium.  Ctrl+c emits wrong KeyboardEvent.key.
                    // (It should be "\uxxxx" but actually "Enter")
                    // https://github.com/rhysd/NyaoVim/issues/37
                    return 'c';
                } else {
                    return 'CR';
                }
            }
            case 'PageUp':
                return 'PageUp';
            case 'PageDown':
                return 'PageDown';
            case 'End':
                return 'End';
            case 'Home':
                return 'Home';
            case 'ArrowLeft':
                return 'Left';
            case 'ArrowUp':
                return 'Up';
            case 'ArrowRight':
                return 'Right';
            case 'ArrowDown':
                return 'Down';
            case 'Insert':
                return 'Insert';
            case 'Delete':
                return 'Del';
            case 'Help':
                return 'Help';
            case 'Unidentified':
                return null;
            default:
                return null;
        }
    }

    static getVimSpecialCharInput(event: KeyboardEvent) {
        const should_fallback = event.key === undefined || (event.key === '\0' && event.keyCode !== 0);
        const special_char = should_fallback
            ? NeovimInput.getVimSpecialCharFromKeyCode(event.keyCode, event.shiftKey)
            : NeovimInput.getVimSpecialCharFromKey(event);
        if (!special_char) {
            return null;
        }

        let vim_input = '<';
        if (event.ctrlKey) {
            vim_input += 'C-';
        }
        if (event.metaKey) {
            vim_input += 'D-';
        }
        if (event.altKey) {
            vim_input += 'A-';
        }
        // Note: <LT> is a special case where shift should not be handled.
        if (event.shiftKey && special_char !== 'LT') {
            vim_input += 'S-';
        }
        vim_input += special_char + '>';
        return vim_input;
    }

    static replaceKeyToAvoidCtrlShiftSpecial(ctrl: boolean, shift: boolean, key: string) {
        if (!ctrl || shift) {
            return key;
        }

        // Note:
        // These characters are especially replaced in Vim frontend.
        //     https://github.com/vim/vim/blob/d58b0f982ad758c59abe47627216a15497e9c3c1/src/gui_w32.c#L1956-L1989
        // Issue:
        //     https://github.com/rhysd/NyaoVim/issues/87
        switch (key) {
            case '6':
                return '^';
            case '-':
                return '_';
            case '2':
                return '@';
            default:
                return key;
        }
    }

    static getVimInputFromKeyCode(event: KeyboardEvent) {
        let modifiers = '';
        if (event.ctrlKey) {
            modifiers += 'C-';
        }
        if (event.metaKey) {
            modifiers += 'D-';
        }
        if (event.altKey) {
            modifiers += 'A-';
        }
        // Note: <LT> is a special case where shift should not be handled.
        if (event.shiftKey) {
            modifiers += 'S-';
        }
        let vim_input = NeovimInput.replaceKeyToAvoidCtrlShiftSpecial(
            event.ctrlKey,
            event.shiftKey,
            String.fromCharCode(event.keyCode).toLowerCase(),
        );
        if (modifiers !== '') {
            vim_input = `<${modifiers}${vim_input}>`;
        }
        return vim_input;
    }

    constructor(private readonly store: NeovimStore) {
        this.ime_running = false;

        this.element = this.store.dom.input as HTMLInputElement;
        this.element.addEventListener('compositionstart', this.startComposition.bind(this));
        this.element.addEventListener('compositionend', this.endComposition.bind(this));
        this.element.addEventListener('keydown', this.onInputNonText.bind(this));
        this.element.addEventListener('input', this.onInputText.bind(this));
        this.element.addEventListener('blur', this.onBlur.bind(this));
        this.element.addEventListener('focus', this.onFocus.bind(this));
        this.store.on('cursor', this.updateElementPos.bind(this));
        this.store.on('font-size-changed', this.updateFontSize.bind(this));

        this.fake_element = this.store.dom.preedit as HTMLSpanElement;

        const { face } = this.store.font_attr;
        this.element.style.fontFamily = face;
        this.fake_element.style.fontFamily = face;

        this.updateFontSize();

        this.focus();
    }

    startComposition(_: Event) {
        log.debug('start composition');

        // make <input> visible to show preedit
        this.element.style.color = this.store.fg_color;
        this.element.style.backgroundColor = this.store.bg_color;
        this.element.style.width = 'auto';

        // hide cursor
        this.store.dispatcher.dispatch(compositionStart());

        this.ime_running = true;
    }

    endComposition(event: CompositionEvent) {
        log.debug('end composition');
        this.inputToNeovim(event.data, event);

        // hide HTMLInputElement
        this.element.style.color = 'transparent';
        this.element.style.backgroundColor = 'transparent';
        this.element.style.width = '1px';

        // show cursor
        this.store.dispatcher.dispatch(compositionEnd());

        this.ime_running = false;
    }

    focus() {
        this.element.focus();
    }

    onFocus() {
        this.store.dispatcher.dispatch(notifyFocusChanged(true));

        // Note:
        // Neovim frontend has responsibility to emit 'FocusGained'.
        // :execute 'normal!' "\<FocusGained>" is available.
        // (it seems undocumented.)
        this.store.dispatcher.dispatch(inputToNeovim('<FocusGained>'));
    }

    onBlur(e: Event) {
        e.preventDefault();
        this.store.dispatcher.dispatch(notifyFocusChanged(false));

        // Note:
        // Neovim frontend has responsibility to emit 'FocusLost'.
        // :execute 'normal!' "\<FocusLost>" is available.
        // (it seems undocumented.)
        this.store.dispatcher.dispatch(inputToNeovim('<FocusLost>'));
    }

    // Note:
    // Assumes keydown event is always fired before input event
    onInputNonText(event: KeyboardEvent) {
        log.debug('Keydown event:', event);
        if (this.ime_running) {
            log.debug('IME is running.  Input canceled.');
            return;
        }

        const special_sequence = NeovimInput.getVimSpecialCharInput(event);
        if (special_sequence) {
            this.inputToNeovim(special_sequence, event);
            return;
        }

        if (NeovimInput.shouldIgnoreOnKeydown(event)) {
            return;
        }

        const should_osx_workaround = OnDarwin && event.altKey && !event.ctrlKey && this.store.mode === 'normal';
        if (this.store.alt_key_disabled && event.altKey) {
            // Note: Overwrite 'altKey' to false to disable alt key input.
            Object.defineProperty(event, 'altKey', { value: false });
        }
        if (this.store.meta_key_disabled && event.metaKey) {
            // Note:
            // Simply ignore input with metakey if metakey is disabled.  This aims to delegate the key input
            // to menu items on OS X.
            return;
        }

        if (should_osx_workaround) {
            // Note:
            //
            // In OS X, option + {key} sequences input special characters which can't be
            // input with keyboard normally.  (e.g. option+a -> å)
            // MacVim accepts the special characters only in insert mode, otherwise <A-{char}>
            // is emitted.
            //
            this.inputToNeovim(NeovimInput.getVimInputFromKeyCode(event), event);
            return;
        }

        if (event.key) {
            if (event.key.length === 1) {
                let input = '<';
                if (event.ctrlKey) {
                    input += 'C-';
                }
                if (event.metaKey) {
                    input += 'D-';
                }
                if (event.altKey) {
                    input += 'A-';
                }
                if (event.shiftKey && IsAlpha.test(event.key)) {
                    // Note:
                    // If input is not an alphabetical character,  it already considers
                    // Shift modifier.
                    //  e.g. Ctrl+Shift+2 -> event.key == '@'
                    // But for alphabets, Vim ignores the case.  For example <C-s> is
                    // equivalent to <C-S>.  So we need to specify <C-S-s> or <C-S-S>.
                    input += 'S-';
                }
                if (input === '<') {
                    // Note: No modifier was pressed
                    this.inputToNeovim(event.key, event);
                } else {
                    const key = NeovimInput.replaceKeyToAvoidCtrlShiftSpecial(event.ctrlKey, event.shiftKey, event.key);
                    this.inputToNeovim(input + key + '>', event);
                }
            } else {
                log.warn("Invalid key input on 'keydown': ", event.key);
            }
        } else {
            this.inputToNeovim(NeovimInput.getVimInputFromKeyCode(event), event);
        }
    }

    inputToNeovim(input: string, event: Event) {
        this.store.dispatcher.dispatch(inputToNeovim(input));

        log.debug('Input to neovim:', JSON.stringify(input));

        event.preventDefault();
        event.stopPropagation();
        const t = event.target as HTMLInputElement;
        if (t.value) {
            t.value = '';
        }
    }

    onInputText(event: KeyboardEvent) {
        log.debug('Input event:', event);

        if (this.ime_running) {
            log.debug('IME is running.  Input canceled.');

            // update width of the input element
            this.fake_element.innerText = this.element.value;
            // to get the width of peedit area, show it just a moment
            this.fake_element.style.display = 'inline';
            const width = this.fake_element.getBoundingClientRect().width;
            this.fake_element.style.display = 'none';
            this.element.style.width = width + 'px';
            return;
        }

        const t = event.target as HTMLInputElement;
        if (t.value === '') {
            log.warn('onInputText: Empty');
            return;
        }

        const input = t.value !== '<' ? t.value : '<LT>';
        this.inputToNeovim(input, event);
    }

    updateElementPos() {
        const { line, col } = this.store.cursor;
        const { width, height } = this.store.font_attr;

        const x = col * width;
        const y = line * height;

        this.element.style.left = x + 'px';
        this.element.style.top = y + 'px';
    }

    updateFontSize() {
        // don't need to consider device pixel ratio for DOM elements
        const { specified_px: font_size } = this.store.font_attr;

        this.element.style.fontSize = font_size + 'px';
        this.fake_element.style.fontSize = font_size + 'px';
    }
}


================================================
FILE: src/neovim/process.ts
================================================
// Note:
// Use renderer's node.js integration to avoid using ipc for large data transfer
import cp = require('child_process');
const child_process: typeof cp = global.require('child_process');
import NvimClient = require('promised-neovim-client');
const attach = (global.require('promised-neovim-client') as typeof NvimClient).attach;
import Action = require('./actions');
import NeovimStore from './store';
import log from '../log';

// Note:
// TypeScript doesn't allow recursive definition
export type RPCValue =
    | NvimClient.Buffer
    | NvimClient.Window
    | NvimClient.Tabpage
    | number
    | boolean
    | string
    | any[]
    | { [key: string]: any };

export default class NeovimProcess {
    neovim_process: cp.ChildProcess;
    client: NvimClient.Nvim;
    started: boolean;

    constructor(private readonly store: NeovimStore, public command: string, public argv: string[]) {
        this.started = false;
        this.argv.unshift('--embed');
    }

    attach(lines: number, columns: number) {
        let err: Error = null;
        this.client = null;

        this.neovim_process = child_process.spawn(this.command, this.argv, { stdio: ['pipe', 'pipe', process.stderr] });
        this.neovim_process.on('error', (e: Error) => {
            err = e;
        });

        if (err || this.neovim_process.pid === undefined) {
            return Promise.reject(err || new Error('Failed to spawn process: ' + this.command));
        }

        return attach(this.neovim_process.stdin, this.neovim_process.stdout).then(nvim => {
            this.client = nvim;
            nvim.on('request', this.onRequested.bind(this));
            nvim.on('notification', this.onNotified.bind(this));
            nvim.on('disconnect', this.onDisconnected.bind(this));
            /* tslint:disable:no-floating-promises */
            nvim.uiAttach(columns, lines, true, true /*notify*/);
            /* tslint:enable:no-floating-promises */
            this.started = true;
            log.info(`nvim attached: ${this.neovim_process.pid} ${lines}x${columns} ${JSON.stringify(this.argv)}`);
            this.store.on('input', (i: string) => nvim.input(i));
            this.store.on('update-screen-bounds', () => nvim.uiTryResize(this.store.size.cols, this.store.size.lines));

            // Note:
            // Neovim frontend has responsibility to emit 'GUIEnter' on initialization.
            /* tslint:disable:no-floating-promises */
            this.client.command('doautocmd <nomodeline> GUIEnter', true);
            /* tslint:enable:no-floating-promises */
        });
    }

    onRequested(method: string, args: RPCValue[], response: RPCValue) {
        log.info('requested: ', method, args, response);
    }

    onNotified(method: string, args: RPCValue[]) {
        if (method === 'redraw') {
            this.redraw(args as RPCValue[][]);
        } else {
            // User defined notifications are passed here.
            log.debug('Unknown method', method, args);
        }
    }

    onDisconnected() {
        log.info('disconnected: ' + this.neovim_process.pid);
        // TODO:
        // Uncomment below line to close window on quit.
        // I don't do yet for debug.
        // global.require('electron').remote.getCurrentWindow().close();
        this.started = false;
    }

    finalize() {
        return this.client.uiDetach().then(() => {
            this.client.quit();
            this.started = false;
        });
    }

    private redraw(events: RPCValue[][]) {
        const d = this.store.dispatcher;
        for (const e of events) {
            const name = e[0] as string;
            const args = e[1] as RPCValue[];
            switch (name) {
                case 'put':
                    e.shift();
                    if (e.length !== 0) {
                        d.dispatch(Action.putText(e as string[][]));
                    }
                    break;
                case 'cursor_goto':
                    d.dispatch(Action.cursor(args[0] as number, args[1] as number));
                    break;
                case 'highlight_set':
                    e.shift();

                    // Note:
                    // [[{highlight_set}], [], [{highlight_set}], ...]
                    //   -> [{highlight_set}, {highlight_set}, ...]
                    const highlights = [].concat.apply([], e) as Action.HighlightSet[];

                    // Note:
                    // [{highlight_set}, {highlight_set}, ...]
                    //   -> {merged highlight_set}
                    highlights.unshift({});
                    const merged_highlight = Object.assign.apply(Object, highlights) as Action.HighlightSet;

                    d.dispatch(Action.highlight(merged_highlight));
                    break;
                case 'clear':
                    d.dispatch(Action.clearAll());
                    break;
                case 'eol_clear':
                    d.dispatch(Action.clearEndOfLine());
                    break;
                case 'scroll':
                    d.dispatch(Action.scrollScreen(args[0] as number));
                    break;
                case 'set_scroll_region':
                    d.dispatch(
                        Action.setScrollRegion({
                            top: args[0] as number,
                            bottom: args[1] as number,
                            left: args[2] as number,
                            right: args[3] as number,
                        }),
                    );
                    break;
                case 'resize':
                    d.dispatch(Action.resize(args[1] as number, args[0] as number));
                    break;
                case 'update_fg':
                    d.dispatch(Action.updateForeground(args[0] as number));
                    break;
                case 'update_bg':
                    d.dispatch(Action.updateBackground(args[0] as number));
                    break;
                case 'update_sp':
                    d.dispatch(Action.updateSpecialColor(args[0] as number));
                    break;
                case 'mode_info_set':
                    // Note:
                    // [{mode_info_set}, {mode_info_set}]
                    //   -> { [mode_name]: {mode_info_set} }
                    const modeInfo = args[1] as Action.ModeInfo[];

                    d.dispatch(
                        Action.modeInfo(
                            modeInfo.reduce((set, info) => {
                                set[info.name] = info;
                                return set;
                            }, Object.create(null)),
                        ),
                    );
                    break;
                case 'mode_change':
                    d.dispatch(Action.changeMode(args[0] as string));
                    break;
                case 'busy_start':
                    d.dispatch(Action.startBusy());
                    break;
                case 'busy_stop':
                    d.dispatch(Action.stopBusy());
                    break;
                case 'mouse_on':
                    d.dispatch(Action.enableMouse());
                    break;
                case 'mouse_off':
                    d.dispatch(Action.disableMouse());
                    break;
                case 'bell':
                    d.dispatch(Action.bell(false));
                    break;
                case 'visual_bell':
                    d.dispatch(Action.bell(true));
                    break;
                case 'set_title':
                    d.dispatch(Action.setTitle(args[0] as string));
                    break;
                case 'set_icon':
                    d.dispatch(Action.setIcon(args[0] as string));
                    break;
                default:
                    log.warn('Unhandled event: ' + name, args);
                    break;
            }
        }
    }
}


================================================
FILE: src/neovim/screen-drag.ts
================================================
import NeovimStore from './store';
import log from '../log';

const MouseButtonKind = ['Left', 'Middle', 'Right'];

export default class ScreenDrag {
    line: number;
    col: number;
    parentX: number;
    parentY: number;

    static buildInputOf(e: MouseEvent, type: string, line: number, col: number) {
        let seq = '<';
        if (e.ctrlKey) {
            seq += 'C-';
        }
        if (e.altKey) {
            seq += 'A-';
        }
        if (e.shiftKey) {
            seq += 'S-';
        }
        seq += MouseButtonKind[e.button] + type + '>';
        seq += `<${col},${line}>`;
        return seq;
    }

    constructor(private readonly store: NeovimStore) {
        this.line = 0;
        this.col = 0;
        this.parentX = 0;
        this.parentY = 0;
    }

    start(down_event: MouseEvent) {
        const wrapper: HTMLElement = this.store.dom.container as HTMLDivElement;
        if (wrapper !== null && down_event.target !== null) {
            const rect = (down_event.target as HTMLCanvasElement).getBoundingClientRect();
            this.parentY = rect.top;
            this.parentX = rect.left;
        }
        [this.line, this.col] = this.getPos(down_event);
        log.info('Drag start', down_event, this.line, this.col);
        const input = ScreenDrag.buildInputOf(down_event, 'Mouse', this.line, this.col);
        log.debug('Mouse input: ' + input);
        return input;
    }

    drag(move_event: MouseEvent) {
        const [line, col] = this.getPos(move_event);
        if (line === this.line && col === this.col) {
            log.debug('ignored MouseMove event');
            return null;
        }
        move_event.preventDefault();
        log.debug('Drag continue', move_event, line, col);
        const input = ScreenDrag.buildInputOf(move_event, 'Drag', line, col);
        this.line = line;
        this.col = col;
        log.debug('Mouse input: ' + input);
        return input;
    }

    end(up_event: MouseEvent) {
        up_event.preventDefault();

        [this.line, this.col] = this.getPos(up_event);
        log.info('Drag end', up_event, this.line, this.col);

        const input = ScreenDrag.buildInputOf(up_event, 'Release', this.line, this.col);
        log.info('Mouse input: ' + input);
        return input;
    }

    private getPos(e: MouseEvent) {
        // Note:
        // e.offsetX and e.offsetY is not available. On mouseup event, the cursor is under the mouse
        // pointer becase mousedown event moves the cursor under mouse pointer. In the case, e.target
        // is a cursor <canvas> element. And offset is calculated based on the cursor element.
        // So we need to have a screen element's position (parentX and parentY) and calculate offsets
        // based on it.
        const offsetY = e.clientY - this.parentY;
        const offsetX = e.clientX - this.parentX;
        return [Math.floor(offsetY / this.store.font_attr.height), Math.floor(offsetX / this.store.font_attr.width)];
    }
}


================================================
FILE: src/neovim/screen-wheel.ts
================================================
import NeovimStore from './store';
import log from '../log';

// Note: Mouse has its origin at left-bottom
//
//   +y
//    |
//    |
//    |
//    |
//    |--------- +x

// Note:
// Vim handles scroll with 3 lines and 6 columns as one scroll
// :help <ScrollWheelUp>

export default class ScreenWheel {
    x: number;
    y: number;
    shift: boolean;
    ctrl: boolean;

    constructor(private readonly store: NeovimStore) {
        this.reset();
    }

    handleEvent(e: WheelEvent) {
        if (
            (this.shift === undefined && this.ctrl === undefined) ||
            (this.shift !== e.shiftKey || this.ctrl !== e.ctrlKey)
        ) {
            // Note:
            // Initialize at first or reset on modifier change
            this.reset(e.shiftKey, e.ctrlKey);
        }

        this.x += e.deltaX;
        this.y += e.deltaY;

        const scroll_x = Math.round(this.x / this.store.font_attr.draw_width / 6);
        const scroll_y = Math.round(this.y / this.store.font_attr.draw_height / 3);

        if (scroll_x === 0 && scroll_y === 0) {
            // Note: At least 3 lines or 6 columns are needed to scroll screen
            return '';
        }

        const col = Math.floor(e.offsetX / this.store.font_attr.width);
        const line = Math.floor(e.offsetY / this.store.font_attr.height);

        const input = this.getInput(scroll_x, scroll_y, line, col);
        log.debug(`Scroll (${scroll_x}, ${scroll_y}) at (${line}, ${col}): ${input}`);
        this.reset();
        return input;
    }

    private reset(shift?: boolean, ctrl?: boolean) {
        this.x = 0;
        this.y = 0;
        this.shift = shift;
        this.ctrl = ctrl;
    }

    private getInput(scroll_x: number, scroll_y: number, line: number, col: number) {
        const pos = `<${col},${line}>`;
        let modifier = '<';
        if (this.ctrl) {
            modifier += 'C-';
        }
        if (this.shift) {
            modifier += 'S-';
        }

        let seq = '';

        const y_dir = scroll_y > 0 ? 'Down' : 'Up';
        for (let _ = 0; _ < Math.abs(scroll_y); ++_) {
            seq += `${modifier}ScrollWheel${y_dir}>${pos}`;
        }

        const x_dir = scroll_x > 0 ? 'Left' : 'Right';
        for (let _ = 0; _ < Math.abs(scroll_x); ++_) {
            seq += `${modifier}ScrollWheel${x_dir}>${pos}`;
        }

        return seq;
    }
}


================================================
FILE: src/neovim/screen.ts
================================================
import NeovimStore from './store';
import * as A from './actions';
import Cursor from './cursor';
import Input from './input';
import log from '../log';

export default class NeovimScreen {
    ctx: CanvasRenderingContext2D;
    cursor: Cursor;
    input: Input;

    constructor(private readonly store: NeovimStore, public canvas: HTMLCanvasElement) {
        this.ctx = this.canvas.getContext('2d', { alpha: false });

        this.store.on('put', this.drawText.bind(this));
        this.store.on('clear-all', this.clearAll.bind(this));
        this.store.on('clear-eol', this.clearEol.bind(this));
        // Note: 'update-bg' clears all texts in screen.
        this.store.on('update-bg', this.clearAll.bind(this));
        this.store.on('screen-scrolled', this.scroll.bind(this));
        this.store.on('line-height-changed', () => this.changeFontSize(this.store.font_attr.specified_px));

        this.changeFontSize(this.store.font_attr.specified_px);

        canvas.addEventListener('click', this.focus.bind(this));
        canvas.addEventListener('mousedown', this.mouseDown.bind(this));
        canvas.addEventListener('mouseup', this.mouseUp.bind(this));
        canvas.addEventListener('mousemove', this.mouseMove.bind(this));
        canvas.addEventListener('wheel', this.wheel.bind(this));

        this.cursor = new Cursor(this.store, this.ctx);
        this.input = new Input(this.store);
    }

    wheel(e: WheelEvent) {
        this.store.dispatcher.dispatch(A.wheelScroll(e));
    }

    mouseDown(e: MouseEvent) {
        this.store.dispatcher.dispatch(A.dragStart(e));
    }

    mouseUp(e: MouseEvent) {
        this.store.dispatcher.dispatch(A.dragEnd(e));
    }

    mouseMove(e: MouseEvent) {
        if (e.buttons !== 0) {
            this.store.dispatcher.dispatch(A.dragUpdate(e));
        }
    }

    resizeWithPixels(width_px: number, height_px: number) {
        const res = window.devicePixelRatio || 1;
        const h = height_px * res;
        const w = width_px * res;
        this.resizeImpl(
            Math.floor(h / this.store.font_attr.draw_height),
            Math.floor(w / this.store.font_attr.draw_width),
            w,
            h,
        );
    }

    resize(lines: number, cols: number) {
        this.resizeImpl(lines, cols, this.store.font_attr.draw_width * cols, this.store.font_attr.draw_height * lines);
    }

    changeFontSize(specified_px: number) {
        const res = window.devicePixelRatio || 1;
        const drawn_px = specified_px * res;
        this.ctx.font = drawn_px + 'px ' + this.store.font_attr.face;
        const font_width = this.ctx.measureText('m').width;
        const font_height = Math.ceil(drawn_px * this.store.line_height);
        this.store.dispatcher.dispatch(A.updateFontPx(specified_px));
        this.store.dispatcher.dispatch(A.updateFontSize(font_width, font_height, font_width / res, font_height / res));
        const { width, height } = this.store.size;
        this.resizeWithPixels(width, height);
    }

    changeLineHeight(new_value: number) {
        this.store.dispatcher.dispatch(A.updateLineHeight(new_value));
    }

    // Note:
    //  cols_delta > 0 -> screen up
    //  cols_delta < 0 -> screen down
    scroll(cols_delta: number) {
        if (cols_delta > 0) {
            this.scrollUp(cols_delta);
        } else if (cols_delta < 0) {
            this.scrollDown(-cols_delta);
        }
    }

    focus() {
        this.input.focus();
    }

    clearAll() {
        this.ctx.fillStyle = this.store.bg_color;
        this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
    }

    clearEol() {
        const { line, col } = this.store.cursor;
        const font_width = this.store.font_attr.draw_width;
        const clear_length = this.store.size.cols * font_width - col * font_width;
        log.debug(`Clear until EOL: ${line}:${col} length=${clear_length}`);
        this.drawBlock(line, col, 1, clear_length, this.store.bg_color);
    }

    // Origin is at left-above.
    //
    //      O-------------> x
    //      |
    //      |
    //      |
    //      |
    //      V
    //      y
    //
    convertPositionToLocation(line: number, col: number) {
        const { width, height } = this.store.font_attr;
        return {
            x: col * width,
            y: line * height,
        };
    }
    convertLocationToPosition(x: number, y: number) {
        const { width, height } = this.store.font_attr;
        return {
            line: Math.floor(y * height),
            col: Math.floor(x * width),
        };
    }

    checkShouldResize() {
        const p = this.canvas.parentElement;
        const cw = p.clientWidth;
        const ch = p.clientHeight;
        const w = this.canvas.width;
        const h = this.canvas.height;
        const res = window.devicePixelRatio || 1;
        if (cw * res !== w || ch * res !== h) {
            this.resizeWithPixels(cw, ch);
        }
    }

    // Note:
    // About 'chars' parameter includes characters to render as array of strings
    // which should be rendered at the each cursor position.
    // So we renders the strings with forwarding the start position incrementally.
    // When chars[idx][0] is empty string, it means that 'no character to render,
    // go ahead'.
    private drawChars(x: number, y: number, chars: string[][], width: number) {
        let includes_half_only = true;
        for (const c of chars) {
            if (!c[0]) {
                includes_half_only = false;
                break;
            }
        }
        if (includes_half_only) {
            // Note:
            // If the text includes only half characters, we can render it at once.
            const text = chars.map(c => c[0] || '').join('');
            this.ctx.fillText(text, x, y);
            return;
        }

        for (const char of chars) {
            if (!char[0] || char[0] === ' ') {
                x += width;
                continue;
            }
            this.ctx.fillText(char.join(''), x, y);
            x += width;
        }
    }

    private drawText(chars: string[][]) {
        const { line, col } = this.store.cursor;
        const {
            fg,
            bg,
            sp,
            draw_width,
            draw_height,
            face,
            specified_px,
            bold,
            italic,
            underline,
            undercurl,
        } = this.store.font_attr;

        // Draw background
        this.drawBlock(line, col, 1, chars.length, bg);
        const res = window.devicePixelRatio || 1;
        const font_size = specified_px * res;

        let attrs = '';
        if (bold) {
            attrs += 'bold ';
        }
        if (italic) {
            attrs += 'italic ';
        }
        this.ctx.font = attrs + font_size + 'px ' + face;
        this.ctx.textBaseline = 'top';
        this.ctx.fillStyle = fg;
        const margin = Math.ceil((font_size * (this.store.line_height - 1.0)) / 2);
        const y = Math.floor(line * draw_height);
        const x = col * draw_width;
        this.drawChars(x, y + margin, chars, draw_width);
        if (undercurl) {
            this.ctx.strokeStyle = sp || this.store.sp_color || fg; // Note: Fallback for Neovim 0.1.4 or earlier.
            this.ctx.lineWidth = 1 * res;
            this.ctx.setLineDash([draw_width / 3, draw_width / 3]);
            this.ctx.beginPath();
            const curl_y = y + draw_height - margin;
            this.ctx.moveTo(x, curl_y);
            this.ctx.lineTo(x + draw_width * chars.length, curl_y);
            this.ctx.stroke();
        } else if (underline) {
            this.ctx.strokeStyle = fg;
            this.ctx.lineWidth = 1 * res;
            this.ctx.setLineDash([]);
            this.ctx.beginPath();
            const underline_y = y + draw_height - margin;
            this.ctx.moveTo(x, underline_y);
            this.ctx.lineTo(x + draw_width * chars.length, underline_y);
            this.ctx.stroke();
        }
        log.debug(`drawText(): (${x}, ${y})`, chars.length, this.store.cursor);
    }

    private drawBlock(line: number, col: number, height: number, width: number, color: string) {
        const { draw_width, draw_height } = this.store.font_attr;
        this.ctx.fillStyle = color;
        // Note:
        // Height doesn't need to be truncated (floor, ceil) but width needs.
        // The reason is desribed in Note2 of changeFontSize().
        this.ctx.fillRect(
            Math.floor(col * draw_width),
            line * draw_height,
            Math.ceil(width * draw_width),
            height * draw_height,
        );
    }

    private slideVertical(top: number, height: number, dst_top: number) {
        const { left, right } = this.store.scroll_region;
        const { draw_width, draw_height } = this.store.font_attr;
        const x = left * draw_width;
        const sy = top * draw_height;
        const w = (right - left + 1) * draw_width;
        const h = height * draw_height;
        const dy = dst_top * draw_height;
        this.ctx.drawImage(this.canvas, x, sy, w, h, x, dy, w, h);
    }

    private scrollUp(cols_up: number) {
        const { top, bottom, left, right } = this.store.scroll_region;
        this.slideVertical(top + cols_up, bottom - (top + cols_up) + 1, top);
        this.drawBlock(bottom - cols_up + 1, left, cols_up, right - left + 1, this.store.bg_color);
        log.debug('Scroll up: ' + cols_up, this.store.scroll_region);
    }

    private scrollDown(cols_down: number) {
        const { top, bottom, left, right } = this.store.scroll_region;
        this.slideVertical(top, bottom - (top + cols_down) + 1, top + cols_down);
        this.drawBlock(top, left, cols_down, right - left + 1, this.store.bg_color);
        log.debug('Scroll down: ' + cols_down, this.store.scroll_region);
    }

    private resizeImpl(lines: number, cols: number, width: number, height: number) {
        const res = window.devicePixelRatio || 1;
        if (width !== this.canvas.width) {
            this.canvas.width = width;
            this.canvas.style.width = width / res + 'px';
        }
        if (height !== this.canvas.height) {
            this.canvas.height = height;
            this.canvas.style.height = height / res + 'px';
        }
        this.store.dispatcher.dispatch(A.updateScreenSize(width, height));
        this.store.dispatcher.dispatch(A.updateScreenBounds(lines, cols));
    }
}


================================================
FILE: src/neovim/store.ts
================================================
import { EventEmitter } from 'events';
import { Kind, ActionType, Region, ModeInfoSet } from './actions';
import log from '../log';
import ScreenDrag from './screen-drag';
import ScreenWheel from './screen-wheel';
import { DOM } from '../neovim';
import { Dispatcher } from 'flux';

// TODO:
// Debug log should be implemented as the subscriber of store
// and be controlled by registering it or not, watching NODE_ENV variable.

export interface Size {
    lines: number;
    cols: number;
    width: number;
    height: number;
}

export interface Cursor {
    line: number;
    col: number;
}

export interface FontAttributes {
    fg: string;
    bg: string;
    sp: string;
    bold: boolean;
    italic: boolean;
    underline: boolean;
    undercurl: boolean;
    draw_width: number;
    draw_height: number;
    width: number;
    height: number;
    face: string;
    specified_px: number;
}

export type DispatcherType = Dispatcher<ActionType>;

// Note: 0x001203 -> '#001203'
function colorString(new_color: number, fallback: string) {
    if (typeof new_color !== 'number' || new_color < 0) {
        return fallback;
    }

    return (
        '#' +
        [16, 8, 0]
            .map(shift => {
                const mask = 0xff << shift;
                const hex = ((new_color & mask) >> shift).toString(16);
                return hex.length < 2 ? '0' + hex : hex;
            })
            .join('')
    );
}

export default class NeovimStore extends EventEmitter {
    dispatch_token: string;

    size: Size;
    font_attr: FontAttributes;
    fg_color: string;
    bg_color: string;
    sp_color: string;
    cursor: Cursor;
    modeInfo: ModeInfoSet;
    mode: string;
    busy: boolean;
    mouse_enabled: boolean;
    dragging: ScreenDrag;
    title: string;
    icon_path: string;
    wheel_scrolling: ScreenWheel;
    scroll_region: Region;
    dispatcher: Dispatcher<ActionType>;
    focused: boolean;
    line_height: number;
    alt_key_disabled: boolean;
    meta_key_disabled: boolean;
    cursor_draw_delay: number;
    blink_cursor: boolean;
    cursor_blink_interval: number;

    constructor(public dom: DOM) {
        super();
        this.dispatcher = new Dispatcher<ActionType>();
        this.size = {
            lines: 0,
            cols: 0,
            width: 0,
            height: 0,
        };
        this.font_attr = {
            fg: 'white',
            bg: 'black',
            sp: null,
            bold: false,
            italic: false,
            underline: false,
            undercurl: false,
            draw_width: 1,
            draw_height: 1,
            width: 1,
            height: 1,
            specified_px: 1,
            face: 'monospace',
        };
        this.cursor = {
            line: 0,
            col: 0,
        };
        this.modeInfo = {};
        this.mode = 'normal';
        this.busy = false;
        this.mouse_enabled = true;
        this.dragging = null;
        this.title = '';
        this.icon_path = '';
        this.wheel_scrolling = new ScreenWheel(this);
        this.scroll_region = {
            left: 0,
            right: 0,
            top: 0,
            bottom: 0,
        };
        this.focused = true;
        this.line_height = 1.2;
        this.alt_key_disabled = false;
        this.meta_key_disabled = false;
        this.cursor_draw_delay = 10;
        this.blink_cursor = false;
        this.cursor_blink_interval = 1000;
        this.dispatch_token = this.dispatcher.register(this.receiveAction.bind(this));
    }

    private receiveAction(action: ActionType) {
        switch (action.type) {
            case Kind.Input: {
                this.emit('input', action.input);
                break;
            }
            case Kind.PutText: {
                this.emit('put', action.text);
                this.cursor.col = this.cursor.col + action.text.length;
                this.emit('cursor');
                break;
            }
            case Kind.Cursor: {
                this.cursor = {
                    line: action.line,
                    col: action.col,
                };
                this.emit('cursor');
                break;
            }
            case Kind.Highlight: {
                const hl = action.highlight;
                this.font_attr.bold = hl.bold;
                this.font_attr.italic = hl.italic;
                this.font_attr.underline = hl.underline;
                this.font_attr.undercurl = hl.undercurl;
                if (hl.reverse === true) {
                    this.font_attr.fg = colorString(hl.background, this.bg_color);
                    this.font_attr.bg = colorString(hl.foreground, this.fg_color);
                } else {
                    this.font_attr.fg = colorString(hl.foreground, this.fg_color);
                    this.font_attr.bg = colorString(hl.background, this.bg_color);
                }
                this.font_attr.sp = colorString(hl.special, this.sp_color || this.fg_color);
                log.debug('Highlight is updated: ', this.font_attr);
                break;
            }
            case Kind.FocusChanged: {
                this.focused = action.focused;
                this.emit('focus-changed');
                log.debug('Focus changed: ', this.focused);
                break;
            }
            case Kind.ClearEOL: {
                this.emit('clear-eol');
                break;
            }
            case Kind.ClearAll: {
                this.emit('clear-all');
                this.cursor = {
                    line: 0,
                    col: 0,
                };
                this.emit('cursor');
                break;
            }
            case Kind.ScrollScreen: {
                this.emit('screen-scrolled', action.cols);
                break;
            }
            case Kind.SetScrollRegion: {
                this.scroll_region = action.region;
                log.debug('Region is set: ', this.scroll_region);
                this.emit('scroll-region-updated');
                break;
            }
            case Kind.Resize: {
                if (this.resize(action.lines, action.cols)) {
                    this.emit('resize');
                }
                break;
            }
            case Kind.UpdateFG: {
                this.fg_color = colorString(action.color, this.font_attr.fg);
                this.emit('update-fg');
                log.debug('Foreground color is updated: ', this.fg_color);
                break;
            }
            case Kind.UpdateBG: {
                this.bg_color = colorString(action.color, this.font_attr.bg);
                this.emit('update-bg');
                log.debug('Background color is updated: ', this.bg_color);
                break;
            }
            case Kind.UpdateSP: {
                this.sp_color = colorString(action.color, this.fg_color);
                this.emit('update-sp-color');
                log.debug('Special color is updated: ', this.sp_color);
                break;
            }
            case Kind.ModeInfo: {
                this.modeInfo = action.modeInfo;
                this.emit('mode-info', this.modeInfo);
                break;
            }
            case Kind.Mode: {
                this.mode = action.mode;
                this.emit('mode', this.mode);
                break;
            }
            case Kind.BusyStart: {
                this.busy = true;
                this.emit('busy');
                break;
            }
            case Kind.BusyStop: {
                this.busy = false;
                this.emit('busy');
                break;
            }
            case Kind.UpdateFontSize: {
                this.font_attr.draw_width = action.draw_width;
                this.font_attr.draw_height = action.draw_height;
                this.font_attr.width = action.width;
                this.font_attr.height = action.height;
                log.debug('Actual font size is updated: ', action.width, action.height);
                this.emit('font-size-changed');
                break;
            }
            case Kind.UpdateFontPx: {
                this.font_attr.specified_px = action.font_px;
                this.emit('font-px-specified');
                break;
            }
            case Kind.UpdateFontFace: {
                this.font_attr.face = action.font_face;
                this.emit('font-face-specified');
                break;
            }
            case Kind.UpdateScreenSize: {
                if (this.size.width === action.width && this.size.height === action.height) {
                    break;
                }
                this.size.width = action.width;
                this.size.height = action.height;
                this.emit('update-screen-size');
                log.debug('Screen size is updated: ', action.width, action.height);
                break;
            }
            case Kind.UpdateScreenBounds: {
                if (this.resize(action.lines, action.cols)) {
                    this.emit('update-screen-bounds');
                }
                break;
            }
            case Kind.EnableMouse: {
                if (!this.mouse_enabled) {
                    this.mouse_enabled = true;
                    this.emit('mouse-enabled');
                    log.info('Mouse enabled.');
                }
                break;
            }
            case Kind.DisableMouse: {
                if (this.mouse_enabled) {
                    this.mouse_enabled = false;
                    this.emit('mouse-disabled');
                    log.info('Mouse disabled.');
                }
                break;
            }
            case Kind.DragStart: {
                if (this.mouse_enabled) {
                    this.dragging = new ScreenDrag(this);
                    this.emit('input', this.dragging.start(action.event));
                    this.emit('drag-started');
                } else {
                    log.debug('Click ignored because mouse is disabled.');
                }
                break;
            }
            case Kind.DragUpdate: {
                if (this.mouse_enabled && this.dragging !== null) {
                    const input = this.dragging.drag(action.event);
                    if (input) {
                        this.emit('input', input);
                        this.emit('drag-updated');
                    }
                }
                break;
            }
            case Kind.DragEnd: {
                if (this.mouse_enabled && this.dragging !== null) {
                    this.emit('input', this.dragging.end(action.event));
                    this.emit('drag-ended');
                    this.dragging = null;
                }
                break;
            }
            case Kind.WheelScroll: {
                if (this.mouse_enabled) {
                    const input = this.wheel_scrolling.handleEvent(action.event as WheelEvent);
                    if (input) {
                        this.emit('input', input);
                        this.emit('wheel-scrolled');
                    }
                }
                break;
            }
            case Kind.Bell: {
                this.emit(action.visual ? 'visual-bell' : 'beep');
                break;
            }
            case Kind.SetTitle: {
                this.title = action.title;
                this.emit('title-changed');
                log.info('Title is set to ', this.title);
                break;
            }
            case Kind.SetIcon: {
                this.icon_path = action.icon_path;
                this.emit('icon-changed');
                log.info('Icon is set to ', this.icon_path);
                break;
            }
            case Kind.UpdateLineHeight: {
                if (this.line_height !== action.line_height) {
                    this.line_height = action.line_height;
                    this.emit('line-height-changed');
                    log.info('Line height is changed to ', this.line_height);
                }
                break;
            }
            case Kind.DisableAltKey: {
                this.alt_key_disabled = action.disabled;
                this.emit('alt-key-disabled');
                log.info('Alt key disabled: ', action.disabled);
                break;
            }
            case Kind.DisableMetaKey: {
                this.meta_key_disabled = action.disabled;
                this.emit('meta-key-disabled');
                log.info('Meta key disabled: ', action.disabled);
                break;
            }
            case Kind.ChangeCursorDrawDelay: {
                this.cursor_draw_delay = action.delay;
                this.emit('cursor-draw-delay-changed');
                log.info(`Drawing cursor is delayed by ${action.delay}ms`);
                break;
            }
            case Kind.StartBlinkCursor: {
                const changed = this.blink_cursor === false;
                this.blink_cursor = true;
                if (changed) {
                    this.emit('blink-cursor-started');
                }
                break;
            }
            case Kind.StopBlinkCursor: {
                const changed = this.blink_cursor === true;
                this.blink_cursor = false;
                if (changed) {
                    this.emit('blink-cursor-stopped');
                }
                break;
            }
            case Kind.CompositionStart: {
                this.emit('composition-started');
                break;
            }
            case Kind.CompositionEnd: {
                this.emit('composition-ended');
                break;
            }
            default: {
                log.warn('Unhandled action: ', action);
                break;
            }
        }
    }

    private resize(lines: number, cols: number) {
        if (this.size.lines === lines && this.size.cols === cols) {
            return false;
        }
        this.size.lines = lines;
        this.size.cols = cols;
        this.scroll_region = {
            top: 0,
            left: 0,
            right: cols - 1,
            bottom: lines - 1,
        };
        log.debug(`Screen is resized: (${lines} lines, ${cols} cols)`);
        return true;
    }
}


================================================
FILE: src/neovim.ts
================================================
import { EventEmitter } from 'events';
import Process from './neovim/process';
import Screen from './neovim/screen';
import NeovimStore from './neovim/store';
import {
    updateFontPx,
    updateFontFace,
    updateScreenSize,
    updateLineHeight,
    disableAltKey,
    disableMetaKey,
    changeCursorDrawDelay,
    startBlinkCursor,
    setTitle,
} from './neovim/actions';
import log from './log';
import { Nvim } from 'promised-neovim-client';

export interface DOM {
    [key: string]: Element;
}

export default class Neovim extends EventEmitter {
    process: Process;
    screen: Screen;
    store: NeovimStore;

    constructor(
        dom: DOM,
        command: string,
        argv: string[],
        font: string,
        font_size: number,
        line_height: number,
        disable_alt_key: boolean,
        disable_meta_key: boolean,
        draw_delay: number,
        blink_cursor: boolean,
        window_title: string,
    ) {
        super();

        this.store = new NeovimStore(dom);
        this.store.dispatcher.dispatch(updateLineHeight(line_height));
        this.store.dispatcher.dispatch(updateFontFace(font));
        this.store.dispatcher.dispatch(updateFontPx(font_size));
        if (disable_alt_key) {
            this.store.dispatcher.dispatch(disableAltKey(true));
        }
        if (disable_meta_key) {
            this.store.dispatcher.dispatch(disableMetaKey(true));
        }
        this.store.dispatcher.dispatch(changeCursorDrawDelay(draw_delay));
        if (blink_cursor) {
            this.store.dispatcher.dispatch(startBlinkCursor());
        }
        this.store.dispatcher.dispatch(setTitle(window_title));
        this.process = new Process(this.store, command, argv);
    }

    attachCanvas(width: number, height: number, canvas: HTMLCanvasElement) {
        this.store.dispatcher.dispatch(updateScreenSize(width, height));
        this.screen = new Screen(this.store, canvas);
        const { lines, cols } = this.store.size;
        this.process
            .attach(lines, cols)
            .then(() => {
                this.process.client.on('disconnect', () => this.emit('quit'));
                this.emit('process-attached');
            })
            .catch(err => this.emit('error', err));
    }

    quit() {
        this.process.finalize().catch(err => log.error(err));
    }

    getClient(): Nvim {
        return this.process.client;
    }

    focus() {
        this.screen.focus();
    }

    // Note:
    // It is better to use 'argv' property of <neovim-client> for apps using Polymer.
    setArgv(argv: string[]) {
        if (!this.process.started) {
            throw new Error(
                "Process is not attached yet.  Use 'process-attached' event to ensure to specify arguments.",
            );
        }
        return this.process.client.command('args ' + argv.join(' '));
    }
}


================================================
FILE: test/.eslintrc
================================================
{
    "rules": {
        "indent": [
            2,
            4
        ],
        "quotes": 0,
        "linebreak-style": [
            2,
            "unix"
        ],
        "semi": [
            2,
            "always"
        ]
    },
    "env": {
        "es6": true,
        "node": true,
        "browser": true,
        "mocha": true
    },
    "parserOptions": {
      "ecmaVersion": 6
    },
    "extends": "eslint:recommended",
    "ecmaFeatures": {
        "jsx": true,
        "modules": true,
        "experimentalObjectRestSpread": true
    }
}


================================================
FILE: test/e2e/mocha.opts
================================================
--ui bdd
--timeout 300000


================================================
FILE: test/e2e/smoke.ts
================================================
import * as path from 'path';
import { Application } from 'spectron';
import { assert } from 'chai';

// XXX: Get Electron binary path
const electron: string = (require as any)('electron');

describe('neovim element', function() {
    this.timeout(10000);
    before(function() {
        this.app = new Application({
            path: electron,
            args: [path.join(__dirname, '..', '..', '..')],
            env: {
                NODE_ENV: 'production',
            },
        });
        return this.app.start().then(() => this.app.client.pause(3000)); // Wait application starting
    });

    after(function() {
        if (this.app.isRunning()) {
            return this.app.stop();
        }
    });

    it('can start without an error', function() {
        return this.app.client
            .getWindowCount()
            .then((c: number) => assert.equal(c, 1))
            .then(() => this.app.browserWindow.isVisible())
            .then((b: boolean) => assert.isTrue(b))
            .then(() => this.app.webContents.getURL())
            .then((u: string) => assert.ok(u))
            .then(() =>
                this.app.client.execute(() => {
                    const e = document.getElementById('neovim') as any;
                    if (!e) {
                        return null;
                    }
                    return e.editor.screen.ctx.getImageData(10, 10, 1, 1);
                }),
            )
            .then((returned: { value: { data: Uint8ClampedArray } }) => {
                const rgba = returned.value.data;
                assert.equal(rgba.length, 4);
                // White means nothing may not be rendered
                assert.isFalse(rgba[0] === 255 && rgba[1] === 255 && rgba[2] === 255);
            })
            .then(() => this.app.client.getRenderProcessLogs())
            .then((logs: any[]) => {
                for (const log of logs) {
                    assert.notEqual(log.level, 'error', log.message);
                }
            })
            .then(() => this.app.client.getMainProcessLogs())
            .then((logs: string[]) => {
                const allowedErrors = [
                    'net::ERR_FILE_NOT_FOUND',
                    'Electron Security Warning',
                    'Unhandled event:',
                    'DevTools listening on',
                    'HTML Imports is deprecated and will be removed in M73',
                    "'electron.screen' is deprecated",
                ];
                const unexpectedLogs = logs.filter(m => !allowedErrors.some(w => m.includes(w))).filter(m => m !== '');
                assert.equal(unexpectedLogs.length, 0, `'${unexpectedLogs}'`);
            });
    });
});


================================================
FILE: test/unit/cursor_test.js
================================================
global.require = require;
const assert = require('chai').assert;
const jsdom = require('jsdom');
const NeovimStore = require('../../build/src/neovim/store').default;
const Cursor = require('../../build/src/neovim/cursor').default;
const A = require('../../build/src/neovim/actions');
const {dom, document} = require('./dom_faker');

describe('Cursor', () => {
    beforeEach(() => {
        global.store = new NeovimStore(dom);
        store.font_attr.width = 7;
        store.font_attr.height = 14;
        store.font_attr.draw_width = 7;
        store.font_attr.draw_height = 14;
        store.cursor_draw_delay = 0;
        store.cursor_blink_interval = 100;
        /* global cursor */
        global.cursor = new Cursor(store, dom.screen.getContext('2d'));
    });

    afterEach(() => {
        delete global.store;
        delete global.cursor;
    });

    it('initializes cursor element', () => {
        const e = cursor.element;
        assert.equal(e.style.top, '0px');
        assert.equal(e.style.left, '0px');
        assert.equal(e.style.width, '7px');
        assert.equal(e.style.height, '14px');
        assert.equal(e.width, 7);
        assert.equal(e.height, 14);
    });

    it('updates cursor size when font size is changed', () => {
        store.font_attr.width = 8;
        store.font_attr.height = 16;
        store.emit('font-size-changed');
        assert.equal(cursor.element.style.width, '8px');
        assert.equal(cursor.element.style.height, '16px');
    });

    it('moves cursor on cursor moving', () => {
        store.cursor = {
            line: 12,
            col: 24
        };
        store.emit('cursor');
        assert.equal(cursor.element.style.left, store.cursor.col * store.font_attr.width + 'px');
        assert.equal(cursor.element.style.top, store.cursor.line * store.font_attr.height + 'px');
    });

    context('on cursor blinking', () => {
        beforeEach(() => {
            global.store.dispatcher.dispatch(A.startBlinkCursor());
        });

        it('starts blink timer at start', () => {
            assert.isTrue(cursor.blink_timer.enabled, 'Blink timer did not start');
        });

        it('makes cursor blink actually', done => {
            var flag = cursor.blink_timer.shown;
            setTimeout(() => {
                assert.notEqual(flag, cursor.blink_timer.shown);
                flag = cursor.blink_timer.shown;
                setTimeout(() => {
                    assert.notEqual(flag, cursor.blink_timer.shown);
                    done();
                }, 110);
            }, 110);
        });

        it('stops blinking on cursor busy', done => {
            store.busy = true;
            store.emit('busy');
            var flag = cursor.blink_timer.shown;
            setTimeout(() => {
                assert.equal(flag, cursor.blink_timer.shown);
                done();
            }, 110);
        });

        it('restore cursor after editor backs from busy state', done => {
            store.busy = true;
            store.emit('busy');
            store.busy = false;
            store.emit('busy');
            var flag = cursor.blink_timer.shown;
            setTimeout(() => {
                assert.notEqual(flag, cursor.blink_timer.shown);
                done();
            }, 110);
        });

        it('stops blinking on insert mode', done => {
            store.mode = 'insert';
            store.emit('mode');
            var flag = cursor.blink_timer.shown;
            setTimeout(() => {
                assert.equal(flag, cursor.blink_timer.shown);
                done();
            }, 110);
        });

        it('starts cursor blinking on normal mode again', done => {
            store.mode = 'insert';
            store.emit('mode');
            store.mode = 'normal';
            store.emit('mode');
            var flag = cursor.blink_timer.shown;
            setTimeout(() => {
                assert.notEqual(flag, cursor.blink_timer.shown);
                done();
            }, 110);
        });

        it('stop cursor blinking when focus lost', done => {
            store.focused = false;
            store.emit('focus-changed');
            var flag = cursor.blink_timer.shown;
            setTimeout(() => {
                assert.equal(flag, cursor.blink_timer.shown);
                done();
            }, 110);
        });

        it('starts cursor blinking again when focus gained', done => {
            store.focused = false;
            store.emit('focus-changed');
            store.focused = true;
            store.emit('focus-changed');
            var flag = cursor.blink_timer.shown;
            setTimeout(() => {
                assert.notEqual(flag, cursor.blink_timer.shown);
                done();
            }, 110);
        });

        it("stops cursor blinking after 'stopBlinkCursor' action", done => {
            store.blink_cursor = false;
            store.emit('blink-cursor-stopped');
            var flag = cursor.blink_timer.shown;
            setTimeout(() => {
                assert.equal(flag, cursor.blink_timer.shown);
                done();
            }, 110);
        });
    });
});



================================================
FILE: test/unit/dom_faker.js
================================================
const {JSDOM} = require('jsdom');
const jsdom = new JSDOM(`
<body>
  <div id="container">
    <canvas id="screen" width$="[[width]]" height$="[[height]]"></canvas>
    <canvas id="cursor"></canvas>
    <input id="input" autocomplete="off" autofocus />
    <span id="preedit"></span>
  </div>
</body>
`);
const document = jsdom.window.document;

const dom = {
    cursor: document.getElementById('cursor'),
    container: document.getElementById('container'),
    input: document.getElementById('input'),
    preedit: document.getElementById('preedit'),
    screen: document.getElementById('screen'),
};

module.exports = {document, dom};


================================================
FILE: test/unit/input_test.js
================================================
global.require = require;
const assert = require('chai').assert;
const {JSDOM} = require('jsdom');
const NeovimStore = require('../../build/src/neovim/store').default;
const NeovimInput = require('../../build/src/neovim/input').default;
const {dom, document} = require('./dom_faker');

(function(){
let window;

function keydownEvent(opts) {
    const o = opts || {};
    if (o.keyCode) {
        o.charCode = o.which = o.keyCode;
    } else if (o.key !== undefined) {
        o.keyCode = o.charCode = o.which = o.key.charCodeAt(0);
    } else {
        throw 'Invalid options for KeyboardEvent';
    }
    return new window.KeyboardEvent('keydown', o);
}

function dispatchKeydown(opts) {
    return global.input_element.dispatchEvent(keydownEvent(opts));
}

function inputByKeydown(opts) {
    global.last_input = '';
    dispatchKeydown(opts);
    const i = global.last_input;
    global.last_input = '';
    return i;
}

function dispatchInputEvent() {
    global.input_element.dispatchEvent(
        new window.Event('input', {
            bubbles: true,
            cancelable: false
        })
    );
}

function catchInputOnInputEvent(i) {
    global.last_input = '';
    global.input_element.value = i;
    dispatchInputEvent();
    const tmp = global.last_input;
    global.last_input = '';
    return tmp;
}

describe('NeovimInput', () => {
    before(() => {
        /* global document input_element window input last_input */
        global.input_element = dom.input;
        input_element.value = '';
        window = document.defaultView;
        const s = new NeovimStore(dom);
        /* eslint no-unused-vars:0 */
        global.input = new NeovimInput(s);
        global.last_input = '';
        s.on('input', i => {
            global.last_input = i;
        });
    });

    beforeEach(() => {
        global.input.store.alt_key_disabled = false;
        global.input.store.meta_key_disabled = false;
    });

    after(() => {
        delete global.input;
        delete global.input_element;
    });

    it('focuses on an input element on initialization', () => {
        assert.equal(document.activeElement.id, 'input');
    });

    context("on 'keydown' event", () => {
        it('ignores normal char input without modifiers', () => {
            global.last_input = '';

            dispatchKeydown({key: 'a'});
            assert.equal(last_input, '');

            dispatchKeydown({key: '['});
            assert.equal(last_input, '');

            dispatchKeydown({key: '3', shiftKey: true});
            assert.equal(last_input, '');

            dispatchKeydown({key: 'あ'});
            assert.equal(last_input, '');
        });

        it('accepts input with ctrl key', () => {
            assert.equal(inputByKeydown({key: 'a', ctrlKey: true}), '<C-a>');
            assert.equal(inputByKeydown({key: 'o', ctrlKey: true}), '<C-o>');
            assert.equal(inputByKeydown({key: '3', ctrlKey: true}), '<C-3>');
        });

        it('accepts input with alt key', () => {
            assert.equal(inputByKeydown({key: ',', altKey: true}), '<A-,>');
            assert.equal(inputByKeydown({key: 'r', altKey: true}), '<A-r>');
            assert.equal(inputByKeydown({key: '4', altKey: true}), '<A-4>');
        });

        it('accepts input with alt+ctrl keys', () => {
            assert.equal(inputByKeydown({key: 'a', ctrlKey: true, altKey: true}), '<C-A-a>');  // Ctrl is included in \u0001
            assert.equal(inputByKeydown({key: 'o', ctrlKey: true, altKey: true}), '<C-A-o>');  // Ctrl is included in \u000f
        });

        it('accepts input with command keys', () => {
            assert.equal(inputByKeydown({key: 'a', metaKey: true}), '<D-a>');
            assert.equal(inputByKeydown({key: 'a', metaKey: true, ctrlKey: true}), '<C-D-a>');
            assert.equal(inputByKeydown({key: 'a', metaKey: true, shiftKey: true}), '<D-S-a>');
        });

        it('accepts special keys', () => {
            assert.equal(inputByKeydown({key: 'Tab'}), '<Tab>');
            assert.equal(inputByKeydown({key: 'Tab', altKey: true}), '<A-Tab>');
            assert.equal(inputByKeydown({key: 'Tab', ctrlKey: true, shiftKey: true}), '<C-S-Tab>');
            assert.equal(inputByKeydown({key: 'Enter'}), '<CR>');
            assert.equal(inputByKeydown({key: 'Enter', altKey: true}), '<A-CR>');
            assert.equal(inputByKeydown({key: 'Enter', ctrlKey: true, shiftKey: true}), '<C-S-CR>');
            assert.equal(inputByKeydown({key: 'ArrowLeft'}), '<Left>');
            assert.equal(inputByKeydown({key: 'ArrowLeft', altKey: true}), '<A-Left>');
            assert.equal(inputByKeydown({key: 'ArrowLeft', ctrlKey: true, shiftKey: true}), '<C-S-Left>');
            assert.equal(inputByKeydown({key: '<', ctrlKey: true, shiftKey: true}), '<C-LT>');
            assert.equal(inputByKeydown({key: '<', altKey: true, shiftKey: true}), '<A-LT>');
            assert.equal(inputByKeydown({key: '\0'}), '<Nul>');
        });

        it('handles <CR>, <Esc>, <BS> and <C-m>, <C-[>, <C-h> edge cases', () => {
            assert.equal(inputByKeydown({key: 'Enter', keyCode: 13}), '<CR>');
            assert.equal(inputByKeydown({key: 'Enter', keyCode: 77, ctrlKey: true}), '<C-m>');
            assert.equal(inputByKeydown({key: 'Enter', keyCode: 13, ctrlKey: true}), '<C-CR>');

            assert.equal(inputByKeydown({key: 'Escape', keyCode: 27}), '<Esc>');
            assert.equal(inputByKeydown({key: 'Escape', keyCode: 219, ctrlKey: true}), '<C-[>');
            assert.equal(inputByKeydown({key: 'Escape', keyCode: 27, ctrlKey: true}), '<C-Esc>');

            assert.equal(inputByKeydown({key: 'Backspace', keyCode: 8}), '<BS>');
            assert.equal(inputByKeydown({key: 'Backspace', keyCode: 72, ctrlKey: true}), '<C-h>');
            assert.equal(inputByKeydown({key: 'Backspace', keyCode: 8, ctrlKey: true}), '<C-BS>');

            assert.equal(inputByKeydown({key: 'Tab', keyCode: 9}), '<Tab>');
            assert.equal(inputByKeydown({key: 'Tab', keyCode: 73, ctrlKey: true}), '<C-i>');
            assert.equal(inputByKeydown({key: 'Tab', keyCode: 9, ctrlKey: true}), '<C-Tab>');
        });

        it('replaces some special Ctrl+Shift characters following gVim behavior (issue #87)', () => {
            assert.equal(inputByKeydown({key: '2', keyCode: 50, ctrlKey: true}), '<C-@>');
            assert.equal(inputByKeydown({key: '6', keyCode: 54, ctrlKey: true}), '<C-^>');
            assert.equal(inputByKeydown({key: '-', keyCode: 189, ctrlKey: true}), '<C-_>');
        });

        it("handles ' ' edge case", () => {
            assert.equal(inputByKeydown({key: ' ', keyCode: 32}), '<Space>');
            assert.equal(inputByKeydown({key: ' ', keyCode: 32, ctrlKey: true}), '<C-Space>');
            assert.equal(inputByKeydown({key: ' ', keyCode: 32, shiftKey: true}), '<S-Space>');
        });

        context('when alt key is disabled', () => {
            it('ignores event.altKey', () => {
                global.input.store.alt_key_disabled = true;
                global.last_input = '';

                dispatchKeydown({key: 'a', altKey: true});
                assert.equal(last_input, 'a');

                assert.equal(inputByKeydown({key: 'a', altKey: true, ctrlKey: true}), '<C-a>');
                assert.equal(inputByKeydown({key: 'o', altKey: true, shiftKey: true, ctrlKey: true}), '<C-S-o>');
            });

            it('does not ignore any other modifiers', () => {
                global.input.store.alt_key_disabled = true;
                global.last_input = '';

                dispatchKeydown({key: 'a'});
                assert.equal(last_input, '');

                assert.equal(inputByKeydown({key: 'a', ctrlKey: true}), '<C-a>');
                assert.equal(inputByKeydown({key: 'o', ctrlKey: true, shiftKey: true}), '<C-S-o>');
            });
        });

        context('when meta key is disabled', () => {
            it('ignores event.metaKey', () => {
                global.input.store.meta_key_disabled = true;
                global.last_input = '';

                dispatchKeydown({key: 'a', metaKey: true});
                assert.equal(last_input, '');
                dispatchKeydown({key: 'a', altKey: true, shiftKey: true, ctrlKey: true, metaKey: true});
                assert.equal(last_input, '');
            });

            it('does not ignore any other modifiers', () => {
                global.input.store.meta_key_disabled = true;
                global.last_input = '';

                dispatchKeydown({key: 'a'});
                assert.equal(last_input, '');

                assert.equal(inputByKeydown({key: 'Enter', ctrlKey: true}), '<C-CR>');
                assert.equal(inputByKeydown({key: 'o', altKey: true, ctrlKey: true, shiftKey: true}), '<C-A-S-o>');
            });
        });

        context('when shift key is pressed', () => {
            it('considers shift key on alphabetical key input', () => {
                assert.equal(inputByKeydown({key: 'a', shiftKey: true, ctrlKey: true}), '<C-S-a>');
                assert.equal(inputByKeydown({key: 'P', shiftKey: true, ctrlKey: true}), '<C-S-P>');
                assert.equal(inputByKeydown({key: 'q', shiftKey: true, altKey: true}), '<A-S-q>');
            });

            it('does not consider shift key on non-slphabetical key input', () => {
                assert.equal(inputByKeydown({key: '@', shiftKey: true, ctrlKey: true}), '<C-@>');
                assert.equal(inputByKeydown({key: '{', shiftKey: true, ctrlKey: true}), '<C-{>');
                assert.equal(inputByKeydown({key: ']', shiftKey: true, ctrlKey: true}), '<C-]>');
            });
        });
    });

    context("on 'input' event", () => {
        it('sends input character to Neovim', () => {
            assert.equal(catchInputOnInputEvent('a'), 'a');
            assert.equal(catchInputOnInputEvent('3'), '3');
            assert.equal(catchInputOnInputEvent(';'), ';');
            assert.equal(catchInputOnInputEvent('^'), '^');
        });

        it('clears value in <input>', () => {
            global.input_element.value = 'a';
            dispatchInputEvent();
            assert.equal(global.input_element.value, '');
        });

        it("handles '<' edge case", () => {
            assert.equal(catchInputOnInputEvent('<'), '<LT>');
        });
    });

    context("on focus events", () => {
        it('emits <FocusGained> on focused', () => {
            const e = new window.Event('focus', {
                bubbles: true,
                cancelable: false
            });
            global.input_element.dispatchEvent(e);
            assert.equal(global.last_input, '<FocusGained>');
        });

        it('emits <FocusLost> on focused', () => {
            const e = new window.Event('blur', {
                bubbles: true,
                cancelable: false
            });
            global.input_element.dispatchEvent(e);
            assert.equal(global.last_input, '<FocusLost>');
        });
    });

    it('moves <input> element following cursor', () => {
        const store = input.store;
        store.cursor = {
            line: 12,
            col: 24
        };
        store.font_attr.width = 4;
        store.font_attr.height = 8;

        store.emit('cursor');
        assert.equal(input_element.style.left, store.cursor.col * store.font_attr.width + 'px');
        assert.equal(input_element.style.top, store.cursor.line * store.font_attr.height + 'px');
    });
});
})();


================================================
FILE: test/unit/screen-drag_test.js
================================================
global.require = require;
const assert = require('chai').assert;
const ScreenDrag = require('../../build/src/neovim/screen-drag').default;
const NeovimStore = require('../../build/src/neovim/store').default;
const {dom, document} = require('./dom_faker');

function eventFactory(kind) {
    return function (opts) {
        const e = document.createEvent('UIEvents');
        e.initEvent('mouse' + kind, true, false);
        if (opts) {
            for (let k in opts) {
                e[k] = opts[k];
            }
        }
        return e;
    };
}

describe('ScreenDrag', () => {
    const mousedown = eventFactory('down');
    const mousemove = eventFactory('move');
    const mouseup = eventFactory('up');

    describe('#start()', () => {
        const store = new NeovimStore(dom);
        store.font_attr.height = 14;
        store.font_attr.width = 7;

        it('generates input to Neovim from left button mousedown event', () => {
            const d = new ScreenDrag(store);
            const e = mousedown({
                ctrlKey: false,
                altKey: false,
                shiftKey: false,
                clientX: 70,
                clientY: 140,
                button: 0
            });
            assert.equal(d.start(e), '<LeftMouse><10,10>');
        });

        it('generates input to Neovim from middle button mousedown event', () => {
            const d = new ScreenDrag(store);
            const e = mousedown({
                ctrlKey: true,
                altKey: false,
                shiftKey: true,
                clientX: 70,
                clientY: 140,
                button: 1
            });
            assert.equal(d.start(e), '<C-S-MiddleMouse><10,10>');
        });

        it('generates input to Neovim from right button mousedown event', () => {
            const d = new ScreenDrag(store);
            const e = mousedown({
                ctrlKey: true,
                altKey: true,
                shiftKey: false,
                clientX: 0,
                clientY: 0,
                button: 2
            });
            assert.equal(d.start(e), '<C-A-RightMouse><0,0>');
        });
    });

    describe('#drag()', () => {
        const store = new NeovimStore(dom);
        store.font_attr.height = 14;
        store.font_attr.width = 7;
        const down = mousedown();

        it('generates input to Neovim from left button mousemove event', () => {
            const d = new ScreenDrag(store);
            d.start(down);
            const e = mousemove({
                ctrlKey: false,
                altKey: false,
                shiftKey: false,
                clientX: 70,
                clientY: 140,
                button: 0
            });
            assert.equal(d.drag(e), '<LeftDrag><10,10>');
        });

        it('generates input to Neovim from middle button mousemove event', () => {
            const d = new ScreenDrag(store);
            d.start(down);
            const e = mousemove({
                ctrlKey: true,
                altKey: false,
                shiftKey: true,
                clientX: 140,
                clientY: 280,
                button: 1
            });
            assert.equal(d.drag(e), '<C-S-MiddleDrag><20,20>');
        });

        it('generates input to Neovim from right button mousemove event', () => {
            const d = new ScreenDrag(store);
            d.start(down);
            const e = mousemove({
                ctrlKey: true,
                altKey: true,
                shiftKey: false,
                clientX: 0,
                clientY: 0,
                button: 2
            });
            assert.equal(d.drag(e), '<C-A-RightDrag><0,0>');
        });

        it('returns empty input when coordinate is not changed', () => {
            const d = new ScreenDrag(store);
            d.start(down);
            const e = mousemove({
                ctrlKey: false,
                altKey: false,
                shiftKey: false,
                clientX: 70,
                clientY: 140,
                button: 0
            });
            assert.isNotNull(d.drag(e), 'input is null even if event location is changed');
            assert.isNull(d.drag(e), 'input is NOT null even if event location is not changed from previous drag method call');
        });
    });

    describe('#end()', () => {
        const store = new NeovimStore(dom);
        store.font_attr.height = 14;
        store.font_attr.width = 7;
        const down = mousedown();

        it('generates input to Neovim from left button mouseup event', () => {
            const d = new ScreenDrag(store);
            d.start(down);
            const e = mouseup({
                ctrlKey: false,
                altKey: false,
                shiftKey: false,
                clientX: 70,
                clientY: 140,
                button: 0
            });
            assert.equal(d.end(e), '<LeftRelease><10,10>');
        });

        it('generates input to Neovim from middle button mouseup event', () => {
            const d = new ScreenDrag(store);
            d.start(down);
            const e = mouseup({
                ctrlKey: true,
                altKey: false,
                shiftKey: true,
                clientX: 140,
                clientY: 280,
                button: 1
            });
            assert.equal(d.end(e), '<C-S-MiddleRelease><20,20>');
        });

        it('generates input to Neovim from right button mouseup event', () => {
            const d = new ScreenDrag(store);
            d.start(down);
            const e = mouseup({
                ctrlKey: true,
                altKey: true,
                shiftKey: false,
                clientX: 0,
                clientY: 0,
                button: 2
            });
            assert.equal(d.end(e), '<C-A-RightRelease><0,0>');
        });
    });
});


================================================
FILE: test/unit/screen-wheel_test.js
================================================
global.require = require;
const assert = require('chai').assert;
const jsdom = require('jsdom');
const document = new jsdom.JSDOM().window.document;
const ScreenWheel = require('../../build/src/neovim/screen-wheel').default;
const NeovimStore = require('../../build/src/neovim/store').default;


function wheelEvent(x, y, opts) {
    var e = document.createEvent('UIEvents');
    e.initEvent('wheel', true, false);
    if (opts) {
        for (var k in opts) {
            e[k] = opts[k];
        }
    }
    e.deltaX = x;
    e.deltaY = y;
    return e;
}

describe('ScreenWheel', () => {
    var store = null;

    beforeEach(() => {
        store = new NeovimStore();
    });

    afterEach(() => {
        store = null;
    });

    describe('#handleEvent()', () => {
        it('outputs empty input when no wheel event', () => {
            const w = new ScreenWheel(store);
            for (var i = 0; i < 10; ++i) {
                const input_to_neovim = w.handleEvent(wheelEvent(0, 0));
                assert.equal(input_to_neovim, '');
            }
        });

        it('makes key sequence from scroll down wheel events', () => {
            const w = new ScreenWheel(store);
            const input_to_neovim = w.handleEvent(wheelEvent(0, 100));
            assert.include(input_to_neovim, '<ScrollWheelDown>');
        });

        it('makes key sequence from scroll up wheel events', () => {
            const w = new ScreenWheel(store);
            const input_to_neovim = w.handleEvent(wheelEvent(0, -100));
            assert.include(input_to_neovim, '<ScrollWheelUp>');
        });

        it('makes key sequence from scroll left wheel events', () => {
            const w = new ScreenWheel(store);
            const input_to_neovim = w.handleEvent(wheelEvent(100, 0));
            assert.include(input_to_neovim, '<ScrollWheelLeft>');
        });

        it('makes key sequence from scroll right wheel events', () => {
            const w = new ScreenWheel(store);
            const input_to_neovim = w.handleEvent(wheelEvent(-100, 0));
            assert.include(input_to_neovim, '<ScrollWheelRight>');
        });

        it('accumulates amounts of scrolling', () => {
            const w = new ScreenWheel(store);
            var input_to_neovim = w.handleEvent(wheelEvent(2, 0));
            assert.equal(input_to_neovim, '');
            input_to_neovim = w.handleEvent(wheelEvent(4, 0));
            assert.notEqual(input_to_neovim, '');
        });

        it('respects Ctrl and Shift modifier key', () => {
            var w, input_to_neovim;

            w = new ScreenWheel(store);
            input_to_neovim = w.handleEvent(wheelEvent(0, 100, { ctrlKey: true }));
            assert.include(input_to_neovim, '<C-ScrollWheelDown>');

            w = new ScreenWheel(store);
            input_to_neovim = w.handleEvent(wheelEvent(0, 100, { shiftKey: true }));
            assert.include(input_to_neovim, '<S-ScrollWheelDown>');

            w = new ScreenWheel(store);
            input_to_neovim = w.handleEvent(wheelEvent(0, 100, { shiftKey: true, ctrlKey: true }));
            assert.include(input_to_neovim, '<C-S-ScrollWheelDown>');
        });

        it('resets amounts of scrolling when modifier key is changed', () => {
            const w = new ScreenWheel(store);
            var input_to_neovim = w.handleEvent(wheelEvent(0, 1, { ctrlKey: true }));
            assert.equal(input_to_neovim, '');
            input_to_neovim = w.handleEvent(wheelEvent(0, 1, { shiftKey: true }));
            assert.equal(input_to_neovim, '');
            input_to_neovim = w.handleEvent(wheelEvent(0, 2, { shiftKey: true }));
            assert.notEqual(input_to_neovim, '');
        });
    });
});


================================================
FILE: test/unit/store_test.js
================================================
global.require = require;
global.window = global;
const assert = require('chai').assert;
const jsdom = require('jsdom');
const A = require('../../build/src/neovim/actions');
const NeovimStore = require('../../build/src/neovim/store').default;
const ScreenWheel = require('../../build/src/neovim/screen-wheel').default;
const document = new jsdom.JSDOM().window.document;
const {dom} = require('./dom_faker');

describe('NeovimStore', () => {
    context('initialization', () => {
        it('creates default state', () => {
            const s = new NeovimStore(dom);
            assert.deepEqual(s.size, {
                lines: 0,
                cols: 0,
                width: 0,
                height: 0
            });
            assert.deepEqual(s.font_attr, {
                fg: 'white',
                bg: 'black',
                sp: null,
                bold: false,
                italic: false,
                underline: false,
                undercurl: false,
                draw_width: 1,
                draw_height: 1,
                width: 1,
                height: 1,
                specified_px: 1,
                face: 'monospace'
            });
            assert.deepEqual(s.cursor, {
                line: 0,
                col: 0
            });
            assert.equal(s.mode, 'normal');
            assert.isFalse(s.busy);
            assert.isTrue(s.mouse_enabled);
            assert.isNull(s.dragging);
            assert.equal(s.title, '');
            assert.equal(s.icon_path, '');
            assert.deepEqual(s.wheel_scrolling, new ScreenWheel(s));
            assert.deepEqual(s.scroll_region, {
                left: 0,
                right: 0,
                top: 0,
                bottom: 0
            });
            assert.isTrue(s.focused);
            assert.equal(s.line_height, 1.2);
            assert.equal(s.alt_key_disabled, false);
            assert.equal(s.meta_key_disabled, false);
            assert.equal(s.cursor_draw_delay, 10);
            assert.equal(s.blink_cursor, false);
            assert.equal(s.cursor_blink_interval, 1000);
        });
    });

    context('on action received', () => {
        it('accepts input to neovim', () => {
            const s = new NeovimStore(dom);
            s.on('input', i => {
                assert.equal(i, 'madaikeru');
            });
            s.dispatcher.dispatch(A.inputToNeovim('madaikeru'));
        });

        it('handles put event', () => {
            const s = new NeovimStore(dom);
            s.on('put', text => {
                assert.equal(text, 'moudame');
            });
            var c = false;
            s.on('cursor', () => {
                c = true;
            });
            s.dispatcher.dispatch(A.putText('moudame'));
            assert.deepEqual(s.cursor, {
                col: 7,
                line: 0
            }, 'cursor did not move');
            assert.isTrue(c, 'cursor event was not fired');
        });

        it('handles cursor event', () => {
            const s = new NeovimStore(dom);
            var c = false;
            s.on('cursor', () => {
                c = true;
            });
            s.dispatcher.dispatch(A.cursor(114, 514));
            assert.isTrue(c, 'cursor event was not fired');
            assert.deepEqual(s.cursor, {
                line: 114,
                col: 514
            });
        });

        it('handles highlight_set event', () => {
            const s = new NeovimStore(dom);
            const hl1 = {
                background: 0xffffff,
                bg: 'white',
                bold: false,
                fg: 'black',
                special: 0x111111,
                foreground: 0x333333,
                italic: true,
                reverse: false,
                undercurl: true,
                underline: false
            };
            s.dispatcher.dispatch(A.highlight(hl1));
            var f = s.font_attr;
            assert.isFalse(f.bold, 'bold');
            assert.isTrue(f.italic, 'italic');
            assert.isTrue(f.undercurl, 'undercurl');
            assert.isFalse(f.underline, 'underline');
            assert.equal(f.bg, '#ffffff');
            assert.equal(f.fg, '#333333');
            assert.equal(f.sp, '#111111');

            const hl2 = {
                background: 0xffffff,
                foreground: 0x333333,
                reverse: true
            };
            s.dispatcher.dispatch(A.highlight(hl2));
            f = s.font_attr;
            assert.isUndefined(f.bold, 'bold');
            assert.isUndefined(f.italic, 'italic');
            assert.isUndefined(f.undercurl, 'undercurl');
            assert.isUndefined(f.underline, 'underline');
            assert.equal(f.bg, '#333333');
            assert.equal(f.fg, '#ffffff');
            assert.equal(f.fg, '#ffffff');
        });

        it('accespts notify-focus-changed action', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('focus-changed', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.notifyFocusChanged(false));
            assert.isFalse(s.focused, 'focus did not change');
            assert.isTrue(flag, 'focus-changed event was not changed');

            s.dispatcher.dispatch(A.notifyFocusChanged(true));
            assert.isTrue(s.focused, 'focus did not change');
        });

        it('handles clear-eol event', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('clear-eol', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.clearEndOfLine());
            assert.isTrue(flag, 'clear-eol event was not fired');
        });

        it('handles clear event', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('clear-all', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.cursor(42, -42));
            s.dispatcher.dispatch(A.clearAll());
            assert.isTrue(flag, 'clear-all event was not fired');
            assert.deepEqual(s.cursor, {
                line: 0,
                col: 0
            });

            var flag2 = false;
            s.on('cursor', () => {
                flag2 = true;
            });
            s.dispatcher.dispatch(A.clearAll());
            assert.isTrue(flag2, 'cursor event was not fired');
        });

        it('accespts scroll screen action', () => {
            const s = new NeovimStore(dom);
            var cols = 0;
            s.on('screen-scrolled', c => {
                cols = c;
            });
            s.dispatcher.dispatch(A.scrollScreen(42));
            assert.equal(cols, 42);
        });

        it('handles sroll_region event', () => {
            const s = new NeovimStore(dom);
            const r = {
                top: 1,
                left: 2,
                right: 3,
                bottom: 4
            };
            var flag = false;
            s.on('scroll-region-updated', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.setScrollRegion(r));
            assert.isTrue(flag, 'scroll-region-updated event was not fired');
            assert.deepEqual(s.scroll_region, r);
        });

        it('handles resize event', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('resize', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.resize(42, 84));
            assert.isTrue(flag, 'resize event was not fired');
            assert.equal(s.size.lines, 42, 'lines was not set');
            assert.equal(s.size.cols, 84, 'cols was not set');
            assert.deepEqual(s.scroll_region, {
                top: 0,
                left: 0,
                right: 83,
                bottom: 41
            });

            flag = false;
            s.dispatcher.dispatch(A.resize(42, 84));
            assert.isFalse(flag, 'resize event was wrongly fired');
        });

        it('handles update-fg event', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('update-fg', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.updateForeground(0x123456));
            assert.isTrue(flag, 'update-fg event was not fired');
            assert.equal(s.fg_color, '#123456');
        });

        it('handles update-bg event', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('update-bg', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.updateBackground(0x123456));
            assert.isTrue(flag, 'update-bg event was not fired');
            assert.equal(s.bg_color, '#123456');
        });

        it('handles update-sp event', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('update-sp-color', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.updateSpecialColor(0x123456));
            assert.isTrue(flag, 'update-sp event was not fired');
            assert.equal(s.sp_color, '#123456');
        });

        it('handles mode event', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('mode', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.changeMode('visual'));
            assert.isTrue(flag, 'mode event was not fired');
            assert.equal(s.mode, 'visual');
            s.dispatcher.dispatch(A.changeMode('select'));
            assert.equal(s.mode, 'select');
        });

        it('handles busy_start event', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('busy', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.startBusy());
            assert.isTrue(flag, 'busy event was not fired');
            assert.isTrue(s.busy, 'unexpected busy state');
        });

        it('handles busy_end event', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('busy', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.stopBusy());
            assert.isTrue(flag, 'busy event was not fired');
            assert.isFalse(s.busy, 'unexpected non-busy state');
        });

        it('accespts action to update font size', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('font-size-changed', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.updateFontSize(42, 84, 21, 42));
            assert.isTrue(flag, 'font-size-changed event was not fired');
            const f = s.font_attr;
            assert.equal(f.draw_width, 42);
            assert.equal(f.draw_height, 84);
            assert.equal(f.width, 21);
            assert.equal(f.height, 42);
        });

        it('accepts action to specify font pixel', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('font-px-specified', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.updateFontPx(144));
            assert.isTrue(flag, 'font-px-specified event was not fired');
            assert.equal(s.font_attr.specified_px, 144);
        });

        it('accepts action to font face', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('font-face-specified', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.updateFontFace('Meiryo'));
            assert.isTrue(flag, 'font-face-specified event was not fired');
            assert.equal(s.font_attr.face, 'Meiryo');
        });

        it('accespts action to update screen size', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('update-screen-size', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.updateScreenSize(400, 300));
            assert.isTrue(flag, 'update-screen-size event was not fired');
            assert.equal(s.size.width, 400);
            assert.equal(s.size.height, 300);

            flag = false;
            s.dispatcher.dispatch(A.updateScreenSize(400, 300));
            assert.isFalse(flag, 'unexpected update-screen-size event');
        });

        it('accespts action to update screen bounds', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('update-screen-bounds', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.updateScreenBounds(42, 84));
            assert.isTrue(flag, 'update-screen-bounds event was not fired');
            assert.equal(s.size.lines, 42, 'lines was not set');
            assert.equal(s.size.cols, 84, 'cols was not set');
            assert.deepEqual(s.scroll_region, {
                top: 0,
                left: 0,
                right: 83,
                bottom: 41
            });

            flag = false;
            s.dispatcher.dispatch(A.resize(42, 84));
            assert.isFalse(flag, 'update-screen-bounds event was wrongly fired');
        });

        it('handles mouse_on event', () => {
            const s = new NeovimStore(dom);
            s.mouse_enabled = false;
            var flag = false;
            s.on('mouse-enabled', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.enableMouse());
            assert.isTrue(flag, 'mouse-enabled event was not fired');
            assert.isTrue(s.mouse_enabled, 'mouse did not set enabled');
            flag = false;
            s.dispatcher.dispatch(A.enableMouse());
            assert.isFalse(flag, 'mouse-enabled event was fired even if mouse_enabled state had been not changed');
        });

        it('handles mouse_off event', () => {
            const s = new NeovimStore(dom);
            s.mouse_enabled = true;
            var flag = false;
            s.on('mouse-disabled', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.disableMouse());
            assert.isTrue(flag, 'mouse-disabled event was not fired');
            assert.isFalse(s.mouse_enabled, 'mouse did not set disabled');
            flag = false;
            s.dispatcher.dispatch(A.disableMouse());
            assert.isFalse(flag, 'mouse-disabled event was fired even if mouse_enabled state had been not changed');
        });

        it('accepts drag start action', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('drag-started', () => {
                flag = true;
            });
            const e = document.createEvent('MouseEvent');
            e.initEvent('mousedown', true, false);
            s.dispatcher.dispatch(A.dragStart(e));
            assert.isTrue(flag, 'drag-started event was not fired');
            assert.isNotNull(s.dragging, 'store.dragging must be created');

            s.mouse_enabled = false;
            flag = false;
            s.dispatcher.dispatch(A.dragStart(e));
            assert.isFalse(flag, 'drag-started event should not be fired while mouse is disabled');
        });

        it('accepts drag update action', () => {
            const s = new NeovimStore(dom);
            const down = document.createEvent('MouseEvent');
            down.initEvent('mousedown', true, false);
            down.buttons = 1;
            s.dispatcher.dispatch(A.dragStart(down));

            var updated = false;
            s.on('drag-updated', () => {
                updated = true;
            });
            var input = false;
            s.on('input', i => {
                assert.notEqual(i, '');
                input = true;
            });

            var move = document.createEvent('MouseEvent');
            move.initEvent('mousemove', true, false);
            Object.defineProperty(move, 'buttons', {value: 1});

            // To simulate dragging mouse, change the position of event from previous drag start event
            Object.defineProperty(move, 'clientX', {value: 100});
            Object.defineProperty(move, 'parentX', {value: 50});
            Object.defineProperty(move, 'clientY', {value: 100});
            Object.defineProperty(move, 'parentY', {value: 50});

            s.dispatcher.dispatch(A.dragUpdate(move));
            assert.isTrue(updated, 'drag-updated event was not fired');
            assert.isTrue(input, 'input event was not fired');
            assert.isNotNull(s.dragging, 'store.dragging must be maintained');

            s.mouse_enabled = false;
            updated = false;
            move = document.createEvent('MouseEvent');
            move.initEvent('mousemove', true, false);
            move.buttons = 1;
            s.dispatcher.dispatch(A.dragUpdate(move));
            assert.isFalse(updated, 'drag updated event must not be fired while mouse disabled');

            s.mouse_enabled = true;
            s.dragging = null;
            updated = false;
            move = document.createEvent('MouseEvent');
            move.initEvent('mousemove', true, false);
            move.buttons = 1;
            s.dispatcher.dispatch(A.dragUpdate(move));
            assert.isFalse(updated, 'drag updated event must not be fired without drag start event');
        });

        it('accepts drag end action', () => {
            const s = new NeovimStore(dom);
            const down = document.createEvent('MouseEvent');
            down.initEvent('mousedown', true, false);
            down.buttons = 1;
            s.dispatcher.dispatch(A.dragStart(down));

            var ended = false;
            s.on('drag-ended', () => {
                ended = true;
            });
            var input = false;
            s.on('input', i => {
                assert.notEqual(i, '');
                input = true;
            });

            var up = document.createEvent('MouseEvent');
            up.initEvent('mouseup', true, false);
            up.buttons = 1;
            s.dispatcher.dispatch(A.dragEnd(up));
            assert.isTrue(ended, 'drag-ended event was not fired');
            assert.isTrue(input, 'input event was not fired');
            assert.isNull(s.dragging, 'store.dragging must be cleared');

            s.mouse_enabled = false;
            ended = false;
            up = document.createEvent('MouseEvent');
            up.initEvent('mouseup', true, false);
            up.buttons = 1;
            s.dispatcher.dispatch(A.dragUpdate(up));
            assert.isFalse(ended, 'drag end event must not be fired while mouse disabled');

            s.mouse_enabled = true;
            s.dragging = null;
            ended = false;
            up = document.createEvent('MouseEvent');
            up.initEvent('mousemove', true, false);
            up.buttons = 1;
            s.dispatcher.dispatch(A.dragUpdate(up));
            assert.isFalse(ended, 'drag updated event must not be fired without drag start event');
        });

        it('handles beep event', () => {
            const s = new NeovimStore(dom);

            var flag = false;
            s.on('beep', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.bell(false));
            assert.isTrue(flag, 'beep event was not fired');

            flag = false;
            s.on('visual-bell', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.bell(true));
            assert.isTrue(flag, 'visual-beep event was not fired');
        });

        it('handles title-changed event', () => {
            const s = new NeovimStore(dom);

            var flag = false;
            s.on('title-changed', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.setTitle('This is that'));
            assert.isTrue(flag, 'title-changed event was not fired');
            assert.equal(s.title, 'This is that');
        });

        it('handles set_icon event', () => {
            const s = new NeovimStore(dom);

            var flag = false;
            s.on('icon-changed', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.setIcon('This is that'));
            assert.isTrue(flag, 'icon-changed event was not fired');
            assert.equal(s.icon_path, 'This is that');
        });

        // Skip because creating WheelEvent is not supported by jsdom
        it.skip('accepts wheel scrolling action', () => {
            const s = new NeovimStore(dom);

            var wheeled = false;
            s.on('wheel-scrolled', () => {
                wheeled = true;
            });
            var input = false;
            s.on('input', i => {
                assert.notEqual(i, '');
                input = true;
            });

            var wheel = document.createEvent('WheelEvent');
            wheel.initEvent('wheel', true, false);
            wheel.buttons = 1;
            s.dispatcher.dispatch(A.wheelScroll(wheel));
            assert.isTrue(wheeled, 'wheel-scrolled event was not fired');
            assert.isTrue(input, 'input event was not fired');

            wheeled = false;
            s.mouse_enabled = false;
            s.dispatcher.dispatch(A.wheelScroll(wheel));
            assert.isFalse(wheeled, 'wheel-scrolled event must not be fired while mouse disabled');
        });

        it('accepts line_height changing event', () => {
            const s = new NeovimStore(dom);

            var flag = false;
            s.on('line-height-changed', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.updateLineHeight(1.5));
            assert.isTrue(flag, 'line-height-changed event was not fired');
            assert.equal(s.line_height, 1.5);

            flag = false;
            s.dispatcher.dispatch(A.updateLineHeight(1.5));
            assert.isFalse(flag, 'line-height-changed event was fired although line height value is not changed');
            assert.equal(s.line_height, 1.5);
        });

        it('accepts disabling alt key event', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('alt-key-disabled', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.disableAltKey(true));
            assert.isTrue(flag, 'alt-key-disabled event was not fired');
            assert.equal(s.alt_key_disabled, true);
        });

        it('switches to disable/enable meta key', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('meta-key-disabled', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.disableMetaKey(true));
            assert.isTrue(flag, 'meta-key-disabled event was not fired');
            assert.equal(s.meta_key_disabled, true);
            s.dispatcher.dispatch(A.disableMetaKey(false));
            assert.equal(s.meta_key_disabled, false);
        });

        it('accepts cursor draw delay change event', () => {
            const s = new NeovimStore(dom);
            var flag = false;
            s.on('cursor-draw-delay-changed', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.changeCursorDrawDelay(0));
            assert.isTrue(flag, 'cursor-draw-delay-changed event was not fired');
            assert.equal(s.cursor_draw_delay, 0);
        });

        it('accepts cursor blinking event', () => {
            const s = new NeovimStore(dom);
            var flag = false;

            s.on('blink-cursor-stopped', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.startBlinkCursor());
            s.dispatcher.dispatch(A.stopBlinkCursor());
            assert.isTrue(flag, 'blink-cursor-stopped event was not fired');
            flag = false;
            s.dispatcher.dispatch(A.stopBlinkCursor());
            assert.isFalse(flag, 'blink-cursor-stopped event was incorrectly fired because cursor blinking state did not change');

            flag = false;
            s.on('blink-cursor-started', () => {
                flag = true;
            });
            s.dispatcher.dispatch(A.startBlinkCursor());
            assert.isTrue(flag, 'blink-cursor-started event was not fired');
            flag = false;
            s.dispatcher.dispatch(A.startBlinkCursor());
            assert.isFalse(flag, 'blink-cursor-started event was incorrectly fired because cursor blinking state did not change');
        });
    });
});


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "module": "commonjs",
    "removeComments": true,
    "preserveConstEnums": true,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noEmitOnError": true,
    "outDir": "build",
    "target": "es2015",
    "sourceMap": true
  },
  "includes": [
    "src/**/*.ts",
    "test/**/*.ts"
  ]
}


================================================
FILE: tslint.json
================================================
{
  "rules": {
    "align": [
      true,
      "parameters",
      "statements"
    ],
    "ban": false,
    "class-name": true,
    "comment-format": [
      true,
      "check-space"
    ],
    "curly": true,
    "eofline": true,
    "forin": true,
    "indent": [
      true,
      "spaces"
    ],
    "interface-name": false,
    "jsdoc-format": true,
    "label-position": true,
    "max-line-length": [
      true,
      140
    ],
    "member-access": false,
    "member-ordering": [
      true,
      "public-before-private",
      "static-before-instance",
      "variables-before-functions"
    ],
    "no-any": false,
    "no-arg": true,
    "no-bitwise": false,
    "no-conditional-assignment": true,
    "no-consecutive-blank-lines": false,
    "no-console": [
      true,
      "debug",
      "info",
      "time",
      "timeEnd",
      "trace"
    ],
    "no-construct": true,
    "no-constructor-vars": false,
    "no-debugger": true,
    "no-duplicate-variable": true,
    "no-empty": true,
    "no-eval": true,
    "no-inferrable-types": false,
    "no-internal-module": true,
    "no-require-imports": false,
    "no-shadowed-variable": true,
    "no-string-literal": true,
    "no-switch-case-fall-through": false,
    "no-trailing-whitespace": true,
    "no-unused-expression": true,
    "no-use-before-declare": true,
    "no-var-keyword": true,
    "no-var-requires": false,
    "object-literal-sort-keys": false,
    "one-line": [
      true,
      "check-open-brace",
      "check-catch",
      "check-else",
      "check-whitespace"
    ],
    "quotemark": [
      true,
      "single",
      "avoid-escape"
    ],
    "radix": true,
    "semicolon": true,
    "switch-default": true,
    "trailing-comma": [
      true,
      {
        "multiline": "always",
        "singleline": "never"
      }
    ],
    "triple-equals": [
      true,
      "allow-null-check"
    ],
    "typedef": [
      false
    ],
    "typedef-whitespace": [
      true,
      {
        "call-signature": "nospace",
        "index-signature": "nospace",
        "parameter": "nospace",
        "property-declaration": "nospace",
        "variable-declaration": "nospace"
      }
    ],
    "use-strict": [
      false
    ],
    "variable-name": [
      false,
      "check-format",
      "allow-leading-underscore",
      "ban-keywords"
    ],
    "whitespace": [
      true,
      "check-branch",
      "check-decl",
      "check-separator",
      "check-type"
    ],
    "no-unnecessary-type-assertion": true,
    "no-floating-promises": true,
    "no-for-in-array": true,
    "no-void-expression": [
      true,
      "ignore-arrow-function-shorthand"
    ],
    "use-default-type-parameter": true,
    "deprecation": true,
    "no-unnecessary-qualifier": true,
    "no-return-await": true,
    "no-duplicate-switch-case": true,
    "no-implicit-dependencies": [true, "dev"],
    "ban-comma-operator": true,
    "no-parameter-reassignment": false,
    "no-duplicate-imports": true,
    "no-this-assignment": true,
    "use-default-type-parameter": true,
    "no-unbound-method": [true, "ignore-static"],
    "prefer-object-spread": true,
    "encoding": true,
    "prefer-switch": true,
    "number-literal-format": false,
    "interface-over-type-literal": true,
    "callable-types": true,
    "return-undefined": true,
    "prefer-readonly": true
  }
}
Download .txt
gitextract_55qap65l/

├── .gitignore
├── .npmignore
├── .prettierrc.json
├── .travis.yml
├── Guardfile
├── LICENSE.txt
├── README.md
├── bower.json
├── example/
│   ├── image-popup/
│   │   ├── README.md
│   │   ├── cli.js
│   │   ├── index.html
│   │   ├── main.js
│   │   ├── package.json
│   │   └── popup-tooltip.html
│   ├── markdown/
│   │   ├── README.md
│   │   ├── cli.js
│   │   ├── index.html
│   │   ├── main.js
│   │   ├── markdown-viewer.html
│   │   └── package.json
│   ├── mini-browser/
│   │   ├── README.md
│   │   ├── cli.js
│   │   ├── index.html
│   │   ├── main.js
│   │   ├── mini-browser.html
│   │   └── package.json
│   └── minimal/
│       ├── README.md
│       ├── cli.js
│       ├── index.html
│       └── main.js
├── index.d.ts
├── neovim-editor.html
├── package.json
├── scripts/
│   ├── travis-ci-install.sh
│   └── travis-ci-run.sh
├── src/
│   ├── index.ts
│   ├── lib.d.ts
│   ├── log.ts
│   ├── neovim/
│   │   ├── actions.ts
│   │   ├── cursor.ts
│   │   ├── input.ts
│   │   ├── process.ts
│   │   ├── screen-drag.ts
│   │   ├── screen-wheel.ts
│   │   ├── screen.ts
│   │   └── store.ts
│   └── neovim.ts
├── test/
│   ├── .eslintrc
│   ├── e2e/
│   │   ├── mocha.opts
│   │   └── smoke.ts
│   └── unit/
│       ├── cursor_test.js
│       ├── dom_faker.js
│       ├── input_test.js
│       ├── screen-drag_test.js
│       ├── screen-wheel_test.js
│       └── store_test.js
├── tsconfig.json
└── tslint.json
Download .txt
SYMBOL INDEX (176 symbols across 16 files)

FILE: index.d.ts
  type RPCValue (line 6) | type RPCValue = NvimClient.Buffer | NvimClient.Window | NvimClient.Tabpa...
  type HighlightSet (line 10) | interface HighlightSet {
  type Region (line 22) | interface Region {
  type Kind (line 28) | enum Kind {
  type ActionType (line 68) | interface ActionType {
  class NeovimCursor (line 240) | class NeovimCursor {
  class NeovimInput (line 248) | class NeovimInput {
  class NeovimProcess (line 262) | class NeovimProcess {
  class ScreenDrag (line 276) | class ScreenDrag {
  class ScreenWheel (line 286) | class ScreenWheel {
  class NeovimScreen (line 295) | class NeovimScreen {
  type Size (line 324) | interface Size {
  type Cursor (line 330) | interface Cursor {
  type FontAttributes (line 334) | interface FontAttributes {
  type DispatcherType (line 349) | type DispatcherType = Dispatcher<ActionType>;
  class NeovimStore (line 350) | class NeovimStore extends EventEmitter {
  class Neovim (line 373) | class Neovim extends EventEmitter {
  class NeovimElement (line 393) | class NeovimElement extends HTMLElement {

FILE: src/index.ts
  class NeovimEditor (line 3) | class NeovimEditor extends Polymer.Element {
    method is (line 4) | static get is() {
    method properties (line 8) | static get properties() {
    method ready (line 77) | ready() {
    method connectedCallback (line 107) | connectedCallback() {
    method disconnectedCallback (line 129) | disconnectedCallback() {
    method attributeChangedCallback (line 137) | attributeChangedCallback(name: string, oldVal: string | null, newVal: ...

FILE: src/lib.d.ts
  type Global (line 3) | interface Global {

FILE: src/log.ts
  constant NODE_ENV (line 3) | const NODE_ENV = (() => {

FILE: src/neovim.ts
  type DOM (line 19) | interface DOM {
  class Neovim (line 23) | class Neovim extends EventEmitter {
    method constructor (line 28) | constructor(
    method attachCanvas (line 61) | attachCanvas(width: number, height: number, canvas: HTMLCanvasElement) {
    method quit (line 74) | quit() {
    method getClient (line 78) | getClient(): Nvim {
    method focus (line 82) | focus() {
    method setArgv (line 88) | setArgv(argv: string[]) {

FILE: src/neovim/actions.ts
  type HighlightSet (line 1) | interface HighlightSet {
  type ModeInfoSet (line 14) | interface ModeInfoSet {
  type ModeInfo (line 18) | interface ModeInfo {
  type Region (line 31) | interface Region {
  type Kind (line 38) | enum Kind {
  type ActionType (line 80) | interface ActionType {
  function putText (line 109) | function putText(text: string[][]) {
  function cursor (line 116) | function cursor(line: number, col: number) {
  function highlight (line 124) | function highlight(hl: HighlightSet) {
  function clearAll (line 131) | function clearAll() {
  function clearEndOfLine (line 137) | function clearEndOfLine() {
  function compositionStart (line 143) | function compositionStart() {
  function compositionEnd (line 149) | function compositionEnd() {
  function resize (line 155) | function resize(lines: number, cols: number) {
  function updateForeground (line 163) | function updateForeground(color: number) {
  function updateBackground (line 170) | function updateBackground(color: number) {
  function updateSpecialColor (line 177) | function updateSpecialColor(color: number) {
  function modeInfo (line 184) | function modeInfo(info: ModeInfoSet) {
  function changeMode (line 191) | function changeMode(mode: string) {
  function startBusy (line 198) | function startBusy() {
  function stopBusy (line 204) | function stopBusy() {
  function updateFontSize (line 210) | function updateFontSize(draw_width: number, draw_height: number, width: ...
  function inputToNeovim (line 220) | function inputToNeovim(input: string) {
  function updateFontPx (line 227) | function updateFontPx(font_px: number) {
  function updateFontFace (line 234) | function updateFontFace(font_face: string) {
  function updateScreenSize (line 241) | function updateScreenSize(width: number, height: number) {
  function updateScreenBounds (line 253) | function updateScreenBounds(lines: number, cols: number) {
  function enableMouse (line 261) | function enableMouse() {
  function disableMouse (line 267) | function disableMouse() {
  function dragStart (line 273) | function dragStart(event: MouseEvent) {
  function dragUpdate (line 280) | function dragUpdate(event: MouseEvent) {
  function dragEnd (line 287) | function dragEnd(event: MouseEvent) {
  function bell (line 294) | function bell(visual: boolean) {
  function setTitle (line 301) | function setTitle(title: string) {
  function setIcon (line 308) | function setIcon(icon_path: string) {
  function wheelScroll (line 315) | function wheelScroll(event: WheelEvent) {
  function scrollScreen (line 322) | function scrollScreen(cols: number) {
  function setScrollRegion (line 329) | function setScrollRegion(region: Region) {
  function notifyFocusChanged (line 336) | function notifyFocusChanged(focused: boolean) {
  function updateLineHeight (line 343) | function updateLineHeight(line_height: number) {
  function disableAltKey (line 350) | function disableAltKey(disabled: boolean) {
  function disableMetaKey (line 357) | function disableMetaKey(disabled: boolean) {
  function changeCursorDrawDelay (line 364) | function changeCursorDrawDelay(delay: number) {
  function startBlinkCursor (line 371) | function startBlinkCursor() {
  function stopBlinkCursor (line 376) | function stopBlinkCursor() {

FILE: src/neovim/cursor.ts
  function invertColor (line 6) | function invertColor(image: ImageData) {
  class CursorBlinkTimer (line 16) | class CursorBlinkTimer extends EventEmitter {
    method constructor (line 22) | constructor(public interval: number) {
    method start (line 30) | start() {
    method stop (line 39) | stop() {
    method reset (line 50) | reset() {
    method _callback (line 57) | private _callback() {
  class NeovimCursor (line 64) | class NeovimCursor {
    method constructor (line 71) | constructor(private readonly store: NeovimStore, private readonly scre...
    method onFontSizeUpdated (line 123) | onFontSizeUpdated() {
    method dismiss (line 132) | dismiss() {
    method redraw (line 136) | redraw() {
    method updateCursorPos (line 157) | updateCursorPos() {
    method updateCursorSize (line 171) | updateCursorSize() {
    method redrawImpl (line 205) | private redrawImpl() {
    method updateCursorBlinking (line 214) | private updateCursorBlinking(should_blink: boolean) {
    method compositionChanged (line 228) | private compositionChanged(preedit_is_shown: boolean) {

FILE: src/neovim/input.ts
  class NeovimInput (line 11) | class NeovimInput {
    method shouldIgnoreOnKeydown (line 17) | static shouldIgnoreOnKeydown(event: KeyboardEvent) {
    method getVimSpecialCharFromKeyCode (line 30) | static getVimSpecialCharFromKeyCode(key_code: number, shift: boolean) {
    method getVimSpecialCharFromKey (line 108) | static getVimSpecialCharFromKey(event: KeyboardEvent) {
    method getVimSpecialCharInput (line 208) | static getVimSpecialCharInput(event: KeyboardEvent) {
    method replaceKeyToAvoidCtrlShiftSpecial (line 235) | static replaceKeyToAvoidCtrlShiftSpecial(ctrl: boolean, shift: boolean...
    method getVimInputFromKeyCode (line 257) | static getVimInputFromKeyCode(event: KeyboardEvent) {
    method constructor (line 283) | constructor(private readonly store: NeovimStore) {
    method startComposition (line 307) | startComposition(_: Event) {
    method endComposition (line 321) | endComposition(event: CompositionEvent) {
    method focus (line 336) | focus() {
    method onFocus (line 340) | onFocus() {
    method onBlur (line 350) | onBlur(e: Event) {
    method onInputNonText (line 363) | onInputNonText(event: KeyboardEvent) {
    method inputToNeovim (line 440) | inputToNeovim(input: string, event: Event) {
    method onInputText (line 453) | onInputText(event: KeyboardEvent) {
    method updateElementPos (line 479) | updateElementPos() {
    method updateFontSize (line 490) | updateFontSize() {

FILE: src/neovim/process.ts
  type RPCValue (line 13) | type RPCValue =
  class NeovimProcess (line 23) | class NeovimProcess {
    method constructor (line 28) | constructor(private readonly store: NeovimStore, public command: strin...
    method attach (line 33) | attach(lines: number, columns: number) {
    method onRequested (line 67) | onRequested(method: string, args: RPCValue[], response: RPCValue) {
    method onNotified (line 71) | onNotified(method: string, args: RPCValue[]) {
    method onDisconnected (line 80) | onDisconnected() {
    method finalize (line 89) | finalize() {
    method redraw (line 96) | private redraw(events: RPCValue[][]) {

FILE: src/neovim/screen-drag.ts
  class ScreenDrag (line 6) | class ScreenDrag {
    method buildInputOf (line 12) | static buildInputOf(e: MouseEvent, type: string, line: number, col: nu...
    method constructor (line 28) | constructor(private readonly store: NeovimStore) {
    method start (line 35) | start(down_event: MouseEvent) {
    method drag (line 49) | drag(move_event: MouseEvent) {
    method end (line 64) | end(up_event: MouseEvent) {
    method getPos (line 75) | private getPos(e: MouseEvent) {

FILE: src/neovim/screen-wheel.ts
  class ScreenWheel (line 17) | class ScreenWheel {
    method constructor (line 23) | constructor(private readonly store: NeovimStore) {
    method handleEvent (line 27) | handleEvent(e: WheelEvent) {
    method reset (line 57) | private reset(shift?: boolean, ctrl?: boolean) {
    method getInput (line 64) | private getInput(scroll_x: number, scroll_y: number, line: number, col...

FILE: src/neovim/screen.ts
  class NeovimScreen (line 7) | class NeovimScreen {
    method constructor (line 12) | constructor(private readonly store: NeovimStore, public canvas: HTMLCa...
    method wheel (line 35) | wheel(e: WheelEvent) {
    method mouseDown (line 39) | mouseDown(e: MouseEvent) {
    method mouseUp (line 43) | mouseUp(e: MouseEvent) {
    method mouseMove (line 47) | mouseMove(e: MouseEvent) {
    method resizeWithPixels (line 53) | resizeWithPixels(width_px: number, height_px: number) {
    method resize (line 65) | resize(lines: number, cols: number) {
    method changeFontSize (line 69) | changeFontSize(specified_px: number) {
    method changeLineHeight (line 81) | changeLineHeight(new_value: number) {
    method scroll (line 88) | scroll(cols_delta: number) {
    method focus (line 96) | focus() {
    method clearAll (line 100) | clearAll() {
    method clearEol (line 105) | clearEol() {
    method convertPositionToLocation (line 123) | convertPositionToLocation(line: number, col: number) {
    method convertLocationToPosition (line 130) | convertLocationToPosition(x: number, y: number) {
    method checkShouldResize (line 138) | checkShouldResize() {
    method drawChars (line 156) | private drawChars(x: number, y: number, chars: string[][], width: numb...
    method drawText (line 182) | private drawText(chars: string[][]) {
    method drawBlock (line 239) | private drawBlock(line: number, col: number, height: number, width: nu...
    method slideVertical (line 253) | private slideVertical(top: number, height: number, dst_top: number) {
    method scrollUp (line 264) | private scrollUp(cols_up: number) {
    method scrollDown (line 271) | private scrollDown(cols_down: number) {
    method resizeImpl (line 278) | private resizeImpl(lines: number, cols: number, width: number, height:...

FILE: src/neovim/store.ts
  type Size (line 13) | interface Size {
  type Cursor (line 20) | interface Cursor {
  type FontAttributes (line 25) | interface FontAttributes {
  type DispatcherType (line 41) | type DispatcherType = Dispatcher<ActionType>;
  function colorString (line 44) | function colorString(new_color: number, fallback: string) {
  class NeovimStore (line 61) | class NeovimStore extends EventEmitter {
    method constructor (line 88) | constructor(public dom: DOM) {
    method receiveAction (line 140) | private receiveAction(action: ActionType) {
    method resize (line 412) | private resize(lines: number, cols: number) {

FILE: test/unit/input_test.js
  function keydownEvent (line 11) | function keydownEvent(opts) {
  function dispatchKeydown (line 23) | function dispatchKeydown(opts) {
  function inputByKeydown (line 27) | function inputByKeydown(opts) {
  function dispatchInputEvent (line 35) | function dispatchInputEvent() {
  function catchInputOnInputEvent (line 44) | function catchInputOnInputEvent(i) {

FILE: test/unit/screen-drag_test.js
  function eventFactory (line 7) | function eventFactory(kind) {

FILE: test/unit/screen-wheel_test.js
  function wheelEvent (line 9) | function wheelEvent(x, y, opts) {
Condensed preview — 58 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (200K chars).
[
  {
    "path": ".gitignore",
    "chars": 152,
    "preview": "/bower_components\nnode_modules\n/build\nnpm-debug.log\n/src/**/*.js\n/src/**/*.js.map\n/test/e2e/**/*.js\n/test/e2e/**/*.js.ma"
  },
  {
    "path": ".npmignore",
    "chars": 105,
    "preview": "/Guardfile\n/bower_components\n/test\n/scripts\nnode_modules\nnpm-debug.log\nGuardfile\ntslint.json\n/build/test\n"
  },
  {
    "path": ".prettierrc.json",
    "chars": 239,
    "preview": "{\n  \"tabWidth\": 4,\n  \"semi\": true,\n  \"singleQuote\": true,\n  \"trailingComma\": \"all\",\n  \"printWidth\": 120,\n  \"overrides\": "
  },
  {
    "path": ".travis.yml",
    "chars": 826,
    "preview": "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    - "
  },
  {
    "path": "Guardfile",
    "chars": 720,
    "preview": "ignore /^node_modules/, /^build/, /^bower_components/\n\ndef npm_run(sub, file)\n  puts \"\\033[93m#{Time.now}: #{File.basena"
  },
  {
    "path": "LICENSE.txt",
    "chars": 1050,
    "preview": "Copyright (c) 2015 rhysd\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software "
  },
  {
    "path": "README.md",
    "chars": 12554,
    "preview": "`<neovim-editor>` Web Component\n===============================\n[![Build Status](https://travis-ci.org/rhysd/neovim-comp"
  },
  {
    "path": "bower.json",
    "chars": 459,
    "preview": "{\n  \"name\": \"neovim-component\",\n  \"description\": \"Polymer component of Neovim frontend\",\n  \"version\": \"0.0.2\",\n  \"keywor"
  },
  {
    "path": "example/image-popup/README.md",
    "chars": 487,
    "preview": "This is image tooltip example for `<neovim-editor>` component.\n`:ShowImage` command shows an image under the cursor.  I "
  },
  {
    "path": "example/image-popup/cli.js",
    "chars": 254,
    "preview": "#! /usr/bin/env node\n'use strict';\n\nvar argv = process.argv.slice(2);\nargv.unshift(__dirname);\nif (!process.env['NODE_EN"
  },
  {
    "path": "example/image-popup/index.html",
    "chars": 2520,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, mini"
  },
  {
    "path": "example/image-popup/main.js",
    "chars": 740,
    "preview": "var path = require('path');\nvar electron = require('electron');\nvar app = electron.app;\nvar BrowserWindow = electron.Bro"
  },
  {
    "path": "example/image-popup/package.json",
    "chars": 225,
    "preview": "{\n  \"name\": \"neovim-component-image-tooltip-example\",\n  \"version\": \"0.0.1\",\n  \"main\": \"main.js\",\n  \"scripts\": {\n    \"app"
  },
  {
    "path": "example/image-popup/popup-tooltip.html",
    "chars": 2116,
    "preview": "<link rel=\"import\" href=\"../../bower_components/polymer/polymer.html\" />\n\n<dom-module id=\"popup-tooltip\">\n  <template>\n "
  },
  {
    "path": "example/markdown/README.md",
    "chars": 1304,
    "preview": "Markdown Editor Example\n=======================\n\nThis is a markdown editor example of [<neovim-editor> component](https:"
  },
  {
    "path": "example/markdown/cli.js",
    "chars": 254,
    "preview": "#! /usr/bin/env node\n'use strict';\n\nvar argv = process.argv.slice(2);\nargv.unshift(__dirname);\nif (!process.env['NODE_EN"
  },
  {
    "path": "example/markdown/index.html",
    "chars": 2537,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, mini"
  },
  {
    "path": "example/markdown/main.js",
    "chars": 741,
    "preview": "var path = require('path');\nvar electron = require('electron');\nvar app = electron.app;\nvar BrowserWindow = electron.Bro"
  },
  {
    "path": "example/markdown/markdown-viewer.html",
    "chars": 1607,
    "preview": "<link rel=\"import\" href=\"../../bower_components/polymer/polymer.html\" />\n<script src=\"./node_modules/marked/lib/marked.j"
  },
  {
    "path": "example/markdown/package.json",
    "chars": 383,
    "preview": "{\n  \"name\": \"neovim-component-markdown-example\",\n  \"version\": \"0.0.1\",\n  \"main\": \"main.js\",\n  \"scripts\": {\n    \"dep\": \"n"
  },
  {
    "path": "example/mini-browser/README.md",
    "chars": 1015,
    "preview": "Mini Browser Example\n====================\n\nThis is an embedded mini browser example of [<neovim-editor> component](https"
  },
  {
    "path": "example/mini-browser/cli.js",
    "chars": 254,
    "preview": "#! /usr/bin/env node\n'use strict';\n\nvar argv = process.argv.slice(2);\nargv.unshift(__dirname);\nif (!process.env['NODE_EN"
  },
  {
    "path": "example/mini-browser/index.html",
    "chars": 2655,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, mini"
  },
  {
    "path": "example/mini-browser/main.js",
    "chars": 740,
    "preview": "var path = require('path');\nvar electron = require('electron');\nvar app = electron.app;\nvar BrowserWindow = electron.Bro"
  },
  {
    "path": "example/mini-browser/mini-browser.html",
    "chars": 3232,
    "preview": "<link rel=\"import\" href=\"../../bower_components/polymer/polymer.html\" />\n<link rel=\"stylesheet\" href=\"https://maxcdn.boo"
  },
  {
    "path": "example/mini-browser/package.json",
    "chars": 224,
    "preview": "{\n  \"name\": \"neovim-component-mini-browser-example\",\n  \"version\": \"0.0.1\",\n  \"main\": \"main.js\",\n  \"scripts\": {\n    \"app\""
  },
  {
    "path": "example/minimal/README.md",
    "chars": 215,
    "preview": "This is minimal usage of `<neovim-editor>`.  You can easily try this using `npm run` command as below.\n\n```sh\n$ cd /path"
  },
  {
    "path": "example/minimal/cli.js",
    "chars": 288,
    "preview": "#! /usr/bin/env node\n'use strict';\n\nvar argv = process.argv.slice(2);\nargv.unshift(require('path').join(__dirname, '..',"
  },
  {
    "path": "example/minimal/index.html",
    "chars": 2078,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, mini"
  },
  {
    "path": "example/minimal/main.js",
    "chars": 740,
    "preview": "var path = require('path');\nvar electron = require('electron');\nvar app = electron.app;\nvar BrowserWindow = electron.Bro"
  },
  {
    "path": "index.d.ts",
    "chars": 10244,
    "preview": "import { EventEmitter } from 'events';\nimport { Nvim } from 'promised-neovim-client';\nimport { Dispatcher } from 'flux';"
  },
  {
    "path": "neovim-editor.html",
    "chars": 1058,
    "preview": "<dom-module id=\"neovim-editor\">\n  <template>\n    <style>\n      :host {\n        position: relative;\n        min-width: 0p"
  },
  {
    "path": "package.json",
    "chars": 2021,
    "preview": "{\n  \"name\": \"neovim-component\",\n  \"version\": \"0.10.1\",\n  \"description\": \"Polymer component for Neovim frontend\",\n  \"main"
  },
  {
    "path": "scripts/travis-ci-install.sh",
    "chars": 115,
    "preview": "#!/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",
    "chars": 420,
    "preview": "#!/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 'R"
  },
  {
    "path": "src/index.ts",
    "chars": 4172,
    "preview": "import Neovim, { DOM } from './neovim';\n\nclass NeovimEditor extends Polymer.Element {\n    static get is() {\n        retu"
  },
  {
    "path": "src/lib.d.ts",
    "chars": 164,
    "preview": "/// <reference path=\"../bower_components/polymer/types/polymer.d.ts\"/>\ndeclare namespace NodeJS {\n    interface Global {"
  },
  {
    "path": "src/log.ts",
    "chars": 404,
    "preview": "import log = require('loglevel');\n\nexport const NODE_ENV = (() => {\n    try {\n        return global.require('electron')."
  },
  {
    "path": "src/neovim/actions.ts",
    "chars": 6717,
    "preview": "export interface HighlightSet {\n    background?: number;\n    bg?: string;\n    bold?: boolean;\n    fg?: string;\n    foreg"
  },
  {
    "path": "src/neovim/cursor.ts",
    "chars": 7523,
    "preview": "import { EventEmitter } from 'events';\nimport NeovimStore from './store';\nimport log from '../log';\nimport { dragEnd } f"
  },
  {
    "path": "src/neovim/input.ts",
    "chars": 16230,
    "preview": "// keyCode is deprecated but necessary for fallback\n/* tslint:disable:deprecation */\n\nimport NeovimStore from './store';"
  },
  {
    "path": "src/neovim/process.ts",
    "chars": 7948,
    "preview": "// Note:\n// Use renderer's node.js integration to avoid using ipc for large data transfer\nimport cp = require('child_pro"
  },
  {
    "path": "src/neovim/screen-drag.ts",
    "chars": 3001,
    "preview": "import NeovimStore from './store';\nimport log from '../log';\n\nconst MouseButtonKind = ['Left', 'Middle', 'Right'];\n\nexpo"
  },
  {
    "path": "src/neovim/screen-wheel.ts",
    "chars": 2383,
    "preview": "import NeovimStore from './store';\nimport log from '../log';\n\n// Note: Mouse has its origin at left-bottom\n//\n//   +y\n//"
  },
  {
    "path": "src/neovim/screen.ts",
    "chars": 10447,
    "preview": "import NeovimStore from './store';\nimport * as A from './actions';\nimport Cursor from './cursor';\nimport Input from './i"
  },
  {
    "path": "src/neovim/store.ts",
    "chars": 14322,
    "preview": "import { EventEmitter } from 'events';\nimport { Kind, ActionType, Region, ModeInfoSet } from './actions';\nimport log fro"
  },
  {
    "path": "src/neovim.ts",
    "chars": 2876,
    "preview": "import { EventEmitter } from 'events';\nimport Process from './neovim/process';\nimport Screen from './neovim/screen';\nimp"
  },
  {
    "path": "test/.eslintrc",
    "chars": 564,
    "preview": "{\n    \"rules\": {\n        \"indent\": [\n            2,\n            4\n        ],\n        \"quotes\": 0,\n        \"linebreak-sty"
  },
  {
    "path": "test/e2e/mocha.opts",
    "chars": 26,
    "preview": "--ui bdd\n--timeout 300000\n"
  },
  {
    "path": "test/e2e/smoke.ts",
    "chars": 2714,
    "preview": "import * as path from 'path';\nimport { Application } from 'spectron';\nimport { assert } from 'chai';\n\n// XXX: Get Electr"
  },
  {
    "path": "test/unit/cursor_test.js",
    "chars": 5178,
    "preview": "global.require = require;\nconst assert = require('chai').assert;\nconst jsdom = require('jsdom');\nconst NeovimStore = req"
  },
  {
    "path": "test/unit/dom_faker.js",
    "chars": 638,
    "preview": "const {JSDOM} = require('jsdom');\nconst jsdom = new JSDOM(`\n<body>\n  <div id=\"container\">\n    <canvas id=\"screen\" width$"
  },
  {
    "path": "test/unit/input_test.js",
    "chars": 11523,
    "preview": "global.require = require;\nconst assert = require('chai').assert;\nconst {JSDOM} = require('jsdom');\nconst NeovimStore = r"
  },
  {
    "path": "test/unit/screen-drag_test.js",
    "chars": 5898,
    "preview": "global.require = require;\nconst assert = require('chai').assert;\nconst ScreenDrag = require('../../build/src/neovim/scre"
  },
  {
    "path": "test/unit/screen-wheel_test.js",
    "chars": 3734,
    "preview": "global.require = require;\nconst assert = require('chai').assert;\nconst jsdom = require('jsdom');\nconst document = new js"
  },
  {
    "path": "test/unit/store_test.js",
    "chars": 24880,
    "preview": "global.require = require;\nglobal.window = global;\nconst assert = require('chai').assert;\nconst jsdom = require('jsdom');"
  },
  {
    "path": "tsconfig.json",
    "chars": 390,
    "preview": "{\n  \"compilerOptions\": {\n    \"module\": \"commonjs\",\n    \"removeComments\": true,\n    \"preserveConstEnums\": true,\n    \"noIm"
  },
  {
    "path": "tslint.json",
    "chars": 3365,
    "preview": "{\n  \"rules\": {\n    \"align\": [\n      true,\n      \"parameters\",\n      \"statements\"\n    ],\n    \"ban\": false,\n    \"class-nam"
  }
]

About this extraction

This page contains the full source code of the rhysd/neovim-component GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 58 files (185.2 KB), approximately 45.3k tokens, and a symbol index with 176 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!