├── .babelrc
├── .gitignore
├── bootstrap.js
├── README.md
├── package.json
├── components
├── Emsg.js
├── Msg.js
├── Sign.js
├── Number.js
├── Cursor.js
├── Char.js
├── Tab.js
├── Span.js
├── DocMsg.js
├── Popupmenu.js
├── Cmd.js
├── Statusline.js
├── Line.js
└── Window.js
├── main.js
├── index.html
└── index.js
/.babelrc:
--------------------------------------------------------------------------------
1 | { "presets": ["es2015", "react"] }
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | .tern-project
3 | .tern-port
4 |
--------------------------------------------------------------------------------
/bootstrap.js:
--------------------------------------------------------------------------------
1 | require('babel-register');
2 | require('./main.js');
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # envim
2 |
3 | This project is currectly a proof of concept of PR https://github.com/neovim/neovim/pull/5686 for Neovim, which demonstrates the ability of a remote GUI with this PR.
4 |
5 | It uses Electron and React, and each window is a canvas.
6 |
7 | To run this project, you would need to change the nvim binary path in index.js, and the nvim binary needs to be built with this PR. And run:
8 |
9 | ```
10 | $> npm install
11 | $> electron .
12 | ```
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "envim",
3 | "version": "0.1.0",
4 | "main": "bootstrap.js",
5 | "devDependencies": {
6 | "electron-prebuilt": "^1.2.2"
7 | },
8 | "scripts": {
9 | "start": "electron ."
10 | },
11 | "dependencies": {
12 | "electron": "^1.4.10",
13 | "react": "^15.4.1",
14 | "react-dom": "^15.4.1",
15 | "immutable": "^3.8.1",
16 | "rwlock": "^5.0.0",
17 | "neovim-client": "^2.1.0",
18 | "electron-devtools-installer": "^2.0.1",
19 | "babel-register": "^6.18.0",
20 | "babel-preset-es2015": "^6.18.0",
21 | "babel-preset-react": "^6.16.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/components/Emsg.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import * as _ from 'lodash'
3 |
4 |
5 | class Emsg extends Component {
6 | constructor(props, context) {
7 | super(props, context)
8 | }
9 |
10 | shouldComponentUpdate(nextProps, nextState) {
11 | return (nextProps.emsg != this.props.emsg)
12 | }
13 |
14 | render() {
15 | const { emsg } = this.props
16 | var style = {
17 | width: 300,
18 | // position: "absolute",
19 | // right: 0,
20 | // top: 0,
21 | color: "#a94442",
22 | backgroundColor: "#f2dede",
23 | padding: 21,
24 | boxShadow: "0 6px 12px rgba(0,0,0,.175)",
25 | }
26 | return
{emsg}
27 | }
28 | }
29 |
30 | export default Emsg
31 |
--------------------------------------------------------------------------------
/components/Msg.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import * as _ from 'lodash'
3 |
4 |
5 | class Msg extends Component {
6 | constructor(props, context) {
7 | super(props, context)
8 | }
9 |
10 | shouldComponentUpdate(nextProps, nextState) {
11 | return (nextProps.msg != this.props.msg)
12 | }
13 |
14 | render() {
15 | const { msg } = this.props
16 | var style = {
17 | width: 300,
18 | zIndex: 1000,
19 | // position: "absolute",
20 | // right: 0,
21 | // top: 0,
22 | color: "#fff",
23 | backgroundColor: "#337ab7",
24 | padding: 21,
25 | boxShadow: "0 6px 12px rgba(0,0,0,.175)",
26 | }
27 | return {msg}
28 | }
29 | }
30 |
31 | export default Msg
32 |
--------------------------------------------------------------------------------
/components/Sign.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import * as _ from 'lodash'
3 |
4 |
5 | class Sign extends Component {
6 | constructor(props, context) {
7 | super(props, context)
8 | }
9 |
10 | shouldComponentUpdate(nextProps, nextState) {
11 | return (nextProps.sign != this.props.sign)
12 | }
13 |
14 | render() {
15 | const { height, sign, bg } = this.props
16 | var style = {
17 | position: "absolute",
18 | backgroundColor: bg,
19 | zIndex: 200,
20 | }
21 | var signHtml = []
22 | for (var i = 0; i < height; i++) {
23 | var signText = ' '
24 | var signColumn = sign.get(i)
25 | if (signColumn != undefined) {
26 | signText = _.join(signColumn.sign, '')
27 | }
28 | signHtml.push({signText})
29 | }
30 | return {signHtml}
31 | }
32 | }
33 |
34 | export default Sign
35 |
--------------------------------------------------------------------------------
/components/Number.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import * as _ from 'lodash'
3 |
4 |
5 | class Number extends Component {
6 | constructor(props, context) {
7 | super(props, context)
8 | }
9 |
10 | shouldComponentUpdate(nextProps, nextState) {
11 | return (nextProps.num != this.props.num) || (nextProps.drawSign != this.props.drawSign)
12 | }
13 |
14 | render() {
15 | const { drawSign, height, numWidth, num, bg } = this.props
16 | var left = 0
17 | if (drawSign) {
18 | left = 2 * 7
19 | }
20 | var style = {
21 | width: numWidth * 7,
22 | position: "absolute",
23 | left: left,
24 | paddingLeft: "inherit",
25 | backgroundColor: bg,
26 | zIndex: 100,
27 | }
28 | var spanStyle = {
29 | float: "none",
30 | }
31 | var numHtml = []
32 | for (var i = 0; i < height; i++) {
33 | var numText = ''
34 | var numColumn = num.get(i)
35 | if (numColumn != undefined) {
36 | numText = _.join(numColumn.num, '')
37 | }
38 | numHtml.push({numText})
39 | }
40 | return {numHtml}
41 | }
42 | }
43 |
44 | export default Number
45 |
--------------------------------------------------------------------------------
/components/Cursor.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | class Cursor extends Component {
4 | constructor(props, context) {
5 | super(props, context)
6 | }
7 |
8 | render() {
9 | const { editor, left, top, padding, mode } = this.props
10 | var { drawHeight, paddingTop, lengthShift } = this.props
11 | if (paddingTop == undefined) {
12 | paddingTop = 0
13 | }
14 | if (lengthShift == undefined) {
15 | lengthShift = 0
16 | }
17 |
18 | var fontSize = editor.fontSize
19 | if (drawHeight == undefined) {
20 | drawHeight = editor.drawHeight
21 | }
22 | var fg = editor.fg
23 |
24 | var style = {
25 | width: editor.drawWidth - 0.5,
26 | height: drawHeight - lengthShift,
27 | position: "absolute",
28 | left: left * fontSize / 2 - padding,
29 | zIndex: 1300,
30 | }
31 | if (top != undefined) {
32 | style.top = top * drawHeight + paddingTop + lengthShift / 2
33 | }
34 |
35 |
36 | if (mode == "normal") {
37 | style.backgroundColor = "#ffffff"
38 | style.opacity = 0.5
39 | }
40 |
41 | if (mode == "insert") {
42 | style.borderLeft = "1px solid #fdf6e3"
43 | }
44 |
45 | return ()
46 | }
47 | }
48 |
49 | export default Cursor
50 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | import {app, Menu} from 'electron';
2 | import {BrowserWindow} from 'electron';
3 |
4 | let mainWindow = null;
5 |
6 | import installExtension, { REACT_DEVELOPER_TOOLS } from 'electron-devtools-installer';
7 |
8 | const template = [
9 | {
10 | label: 'Edit',
11 | submenu: [
12 | {
13 | role: 'undo'
14 | },
15 | ]
16 | },
17 | ]
18 |
19 | // app.commandLine.appendSwitch('--force-gpu-rasterization')
20 | app.commandLine.appendSwitch('--disable-accelerated-2d-canvas')
21 | app.on('window-all-closed', () => {
22 | app.quit();
23 | });
24 |
25 | app.on('ready', () => {
26 | const menu = Menu.buildFromTemplate(template)
27 | Menu.setApplicationMenu(menu)
28 | mainWindow = new BrowserWindow({width: 366 * 7, height: 64 * 14 * 1.5 + 35, frame: false});
29 | mainWindow.webContents.openDevTools()
30 | console.log(mainWindow.width, mainWindow.height)
31 |
32 | installExtension(REACT_DEVELOPER_TOOLS)
33 | .then((name) => console.log(`Added Extension: ${name}`))
34 | .catch((err) => console.log('An error occurred: ', err));
35 | // mainWindow.webContents.addDevToolsExtension("~/Library/Application\ Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/0.14.11_0")
36 | mainWindow.on('closed', () => {
37 | mainWindow = null;
38 | });
39 | // mainWindow.loadURL('chrome://gpu');
40 | mainWindow.loadURL('file://' + __dirname + '/index.html');
41 | });
42 |
--------------------------------------------------------------------------------
/components/Char.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import * as _ from 'lodash'
3 |
4 | export default class Char extends Component {
5 | constructor(props, context) {
6 | super(props, context)
7 | // this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this)
8 | }
9 |
10 | shouldComponentUpdate(nextProps, nextState) {
11 | // if (nextProps.char != this.props.char) {
12 | // if (this.props.char != undefined) {
13 | // console.log(this.props.char.toJS())
14 | // } else {
15 | // console.log(this.props.char)
16 | // }
17 |
18 | // if (nextProps.char != undefined) {
19 | // console.log(nextProps.char.toJS())
20 | // } else {
21 | // console.log(nextProps.char)
22 | // }
23 | // }
24 | return nextProps.char != this.props.char
25 | }
26 |
27 | render() {
28 | const { char } = this.props
29 | if (char === undefined) {
30 | return {" "}
31 | }
32 | var style = {}
33 | var highlight = {}
34 | if (char.get("highlight") != undefined) {
35 | highlight = char.get("highlight")
36 | }
37 |
38 | if (highlight.foreground != undefined) {
39 | style.color = highlight.foreground
40 | }
41 | if (highlight.foreground != undefined) {
42 | style.backgroundColor = highlight.background
43 | }
44 | return ({char.get("char")})
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/components/Tab.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import * as _ from 'lodash'
3 | import Line from './Line'
4 |
5 |
6 | class Tab extends Component {
7 | constructor(props, context) {
8 | super(props, context)
9 | }
10 |
11 | shouldComponentUpdate(nextProps, nextState) {
12 | return (nextProps.tab != this.props.tab)
13 | }
14 |
15 | render() {
16 | const { tab, editor } = this.props
17 |
18 | var tabHeight = editor.tabHeight
19 |
20 | var active = tab[0]
21 | var items = tab.splice(1, tab.length)
22 | var spans = []
23 | items.forEach((item, i) => {
24 | var className = "tab"
25 | var paddingTop = (tabHeight - 16 - 2) / 2
26 | var height = tabHeight - 2 - paddingTop
27 | if (active == item[0]) {
28 | className = className + " activetab"
29 | height = height + 2
30 | }
31 | var style = {
32 | paddingTop: paddingTop,
33 | height: height,
34 | }
35 | var txt = item[1]
36 | var tabText
37 | if (txt.startsWith("term://")) {
38 | tabText = txt
39 | } else {
40 | var tabs = item[1].split("/")
41 | tabText = tabs[tabs.length - 1]
42 | }
43 | spans.push({tabText})
44 | })
45 |
46 | var style = {
47 | height: tabHeight - 2,
48 | }
49 | return
54 | }
55 | }
56 |
57 | export default Tab
58 |
--------------------------------------------------------------------------------
/components/Span.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import * as _ from 'lodash'
3 |
4 | export default class Span extends Component {
5 | constructor(props, context) {
6 | super(props, context)
7 | }
8 |
9 | shouldComponentUpdate(nextProps, nextState) {
10 | return (nextProps.span != this.props.span) || (nextProps.last != this.props.last)
11 | }
12 |
13 | render() {
14 | const { span, last, col } = this.props
15 | // console.log("render span", span.toJS())
16 | if (span === undefined) {
17 | return {""}
18 | }
19 | // console.log(span.toJS())
20 | var style = {
21 | position: "absolute",
22 | left: col * 7,
23 | }
24 | var highlight = {}
25 | if (span.get("highlight") != undefined) {
26 | highlight = span.get("highlight")
27 | }
28 |
29 | if (highlight.background != undefined) {
30 | style.backgroundColor = highlight.background
31 | }
32 |
33 | if (highlight.foreground != undefined) {
34 | style.color = highlight.foreground
35 | }
36 | // if (last) {
37 | // style.float = "none"
38 | // }
39 | // if (pos != undefined) {
40 | // style.position = "absolute"
41 | // style.left = pos * 7
42 | // style.float = "none"
43 | // }
44 | var text = span.get("text")
45 | // var charsHtml = []
46 | // text.split('').forEach((char, i) => {
47 | // var charStyle = {
48 | // position: "absolute",
49 | // left: i * 7,
50 | // }
51 | // charsHtml.push({char})
52 | // })
53 | return ({text})
54 | // return ({charsHtml})
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/components/DocMsg.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | class DocMsg extends Component {
4 | constructor(props, context) {
5 | super(props, context)
6 | }
7 |
8 | // shouldComponentUpdate(nextProps, nextState) {
9 | // return (nextProps.editor.docmsg != this.props.editor.docmsg)
10 | // }
11 |
12 | render() {
13 | const { editor } = this.props
14 | const docmsg = editor.docmsg
15 | if (!docmsg) {
16 | return
17 | }
18 | var docpos = editor.docpos
19 | if (!docpos) {
20 | return
21 | }
22 |
23 | const win = editor.wins.get(editor.curWin)
24 | if (win == undefined) {
25 | return
26 | }
27 | const pos = win.get("cursorPos")
28 | if (pos == undefined) {
29 | return
30 | }
31 | const fontSize = editor.fontSize
32 | var lineHeight = editor.lineHeight
33 | var padding = 0
34 | if (win.get("floating")) {
35 | lineHeight = editor.floatingLineHeight
36 | padding = - ((editor.width - 100) * (fontSize / 2) / 2 + 1.5)
37 | }
38 |
39 | var style = {
40 | position: "absolute",
41 | left: (docpos[1] + win.get("col") - (docmsg.indexOf("(") - 5)) * (fontSize / 2) - padding,
42 | bottom: ((win.get("height") - docpos[0]) + win.get("row")) * fontSize * lineHeight,
43 | padding: "4px 6px 4px 6px",
44 | maxWidth: 400,
45 | backgroundColor: "#1f2326",
46 | color: editor.fg,
47 | border: "1px solid #000",
48 | zIndex: 1300,
49 | }
50 |
51 | var doccom = editor.doccom
52 | var funcParams = docmsg.slice(docmsg.indexOf("(") + 1, docmsg.indexOf(")"))
53 | if (funcParams == undefined) {
54 | return
55 | }
56 | var currentParam = ""
57 | var docHtml = ({funcParams})
58 | var paramHmtl = []
59 | if (funcParams.trim()) {
60 | funcParams.split(",").forEach((param, i) => {
61 | var className = ""
62 | if (i == doccom) {
63 | className = "doc-current"
64 | }
65 |
66 | var comma = ""
67 | if (i > 0) {
68 | comma = ", "
69 | }
70 | param = param.trim()
71 | paramHmtl.push({comma}{param})
72 | })
73 | // currentParam = funcParams.split(",")[doccom]
74 | // if (currentParam != undefined) {
75 | // currentParam = currentParam.trim()
76 | // }
77 | }
78 | if (currentParam) {
79 | var parts = funcParams.split(currentParam)
80 | // docHtml = ({parts[0]}{currentParam}{parts[1]})
81 |
82 | }
83 | docHtml = {docmsg.slice(0, docmsg.indexOf("(") - 5)}({paramHmtl}){docmsg.slice(docmsg.indexOf(")") + 1)}
84 | // console.log("current param is", currentParam)
85 | // var msg = JSON.parse(docmsg)
86 | //
87 | // var comma = msg.comma
88 | // var contents = msg.contents
89 | // var funcMsg = contents[0].value
90 | // var funcParams = funcMsg.slice(funcMsg.indexOf("(") + 1, funcMsg.indexOf(")"))
91 | // var currentParam = ""
92 | // if (funcParams.trim()) {
93 | // currentParam = (funcParams.split(",")[comma]).trim()
94 | // }
95 | // console.log("current param is", currentParam)
96 | return {docHtml}
97 | }
98 | }
99 |
100 | export default DocMsg
101 |
--------------------------------------------------------------------------------
/components/Popupmenu.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import * as _ from 'lodash'
3 |
4 |
5 | class Popupmenu extends Component {
6 | constructor(props, context) {
7 | super(props, context)
8 | }
9 |
10 | shouldComponentUpdate(nextProps, nextState) {
11 | return (nextProps.menu != this.props.menu)
12 | }
13 |
14 | render() {
15 | const { menu, editor, floating, drawWidth, drawHeight } = this.props
16 | if (!menu.get("show")) {
17 | return
18 | }
19 | var pos = menu.get("pos")
20 | var style = {
21 | position: "absolute",
22 | zIndex: 100,
23 | top: (pos[0] + 1) * drawHeight,
24 | left: (pos[1] + 1) * drawWidth - 4,
25 | }
26 | if (floating) {
27 | style.left = pos[1] * drawWidth - 2
28 | }
29 | var iconStyle = {
30 | backgroundColor: "#658d30",
31 | paddingLeft: 2,
32 | paddingRight: 2,
33 | }
34 | var innerstyle = {
35 | backgroundColor: "#0e1112",
36 | color: "#cdd3de",
37 | marginLeft: - drawWidth * 4 - 4,
38 | boxShadow: "0px 2px 10px 0px #000",
39 | }
40 | var menuHtml = []
41 | menu.get("items").slice(0,20).forEach((item, i) => {
42 | var iconText = item[1]
43 | var iconClass = "icon-b"
44 | if (!iconText) {
45 | iconText = "b"
46 | }
47 | if (iconText == "function") {
48 | iconClass = "icon-f"
49 | iconText = "f"
50 | } else if (iconText == "func") {
51 | iconClass = "icon-f"
52 | iconText = "f"
53 | } else if (iconText == "var") {
54 | iconClass = "icon-v"
55 | iconText = "v"
56 | } else if (iconText == "statement") {
57 | iconClass = "icon-v"
58 | iconText = "v"
59 | } else if (iconText == "instance") {
60 | iconClass = "icon-v"
61 | iconText = "v"
62 | } else if (iconText == "param") {
63 | iconClass = "icon-v"
64 | iconText = "v"
65 | } else if (iconText == "instance") {
66 | iconClass = "icon-v"
67 | iconText = "v"
68 | } else if (iconText == "import") {
69 | iconClass = "icon-v"
70 | iconText = "v"
71 | } else if (iconText == "const") {
72 | iconClass = "icon-v"
73 | iconText = "c"
74 | } else if (iconText == "type") {
75 | iconClass = "icon-t"
76 | iconText = "t"
77 | } else if (iconText == "class") {
78 | iconClass = "icon-t"
79 | iconText = "c"
80 | } else if (iconText == "module") {
81 | iconClass = "icon-p"
82 | iconText = "m"
83 | } else if (iconText == "keyword") {
84 | iconClass = "icon-p"
85 | iconText = "k"
86 | } else if (iconText == "package") {
87 | iconClass = "icon-p"
88 | iconText = "p"
89 | }
90 | iconClass = "icon " + iconClass
91 | iconText = " " + iconText + " "
92 | var preStyle = {
93 | }
94 | if (i == menu.get("selected")) {
95 | preStyle.backgroundColor = "#519aba"
96 | }
97 | menuHtml.push({iconText} {item[0]} )
98 | })
99 | return
100 | }
101 | }
102 |
103 | export default Popupmenu
104 |
--------------------------------------------------------------------------------
/components/Cmd.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import * as _ from 'lodash'
3 | import Line from './Line'
4 | import Cursor from './Cursor'
5 |
6 | export default class Cmd extends Component {
7 | constructor(props, context) {
8 | super(props, context)
9 | }
10 |
11 | shouldComponentUpdate(nextProps, nextState) {
12 | return true
13 | }
14 |
15 | // componentDidMount(props) {
16 | // }
17 |
18 | // componentDidUpdate() {
19 | // var { pos, text } = this.props
20 | // var cmd = document.getElementById("cmd")
21 | // cmd.focus()
22 | // cmd.value = text
23 | // cmd.setSelectionRange(pos, pos)
24 | // console.log("updated text", text)
25 | // }
26 |
27 | render() {
28 | const { editor, wildmenu, wildmenuMatch } = this.props
29 | var { text, pos } = this.props
30 | if (text == "") {
31 | text = " "
32 | }
33 |
34 | var padding = 16
35 | var cmdmenuHtml
36 | var menuHtml = []
37 | wildmenu.forEach((item, i) => {
38 | var preStyle = {
39 | lineHeight: 2,
40 | }
41 | var innerstyle = {
42 | backgroundColor: "#15191b",
43 | color: "#cdd3de",
44 | paddingLeft: padding,
45 | paddingRight: padding,
46 | }
47 | if (i == wildmenuMatch) {
48 | innerstyle.backgroundColor = "#519aba"
49 | }
50 | menuHtml.push({item})
51 | })
52 |
53 | if (menuHtml.length > 0) {
54 | var cmdmenuStyle = {
55 | paddingBottom: padding / 2,
56 | }
57 | cmdmenuHtml = {menuHtml}
58 | }
59 |
60 | var chars = 70
61 | var width = 7 * chars + padding * 2
62 | var style = {
63 | zIndex: 1000,
64 | position: "absolute",
65 | backgroundColor: "#15191b",
66 | border: "1px solid #000",
67 | borderTop: "none",
68 | // backgroundColor: "#252526",
69 | color: "#cdd3de",
70 | boxShadow: "0px 2px 8px #000",
71 | width: width,
72 | left: (editor.width * 7 - width) / 2,
73 | }
74 | // if (text.length > chars) {
75 | // var offset = 0
76 | // if (pos > chars) {
77 | // offset = pos - chars
78 | // }
79 | // pos = pos - offset
80 | // text = text.slice(offset, text.length)
81 | // if (text.length > chars) {
82 | // text = text.slice(0, chars)
83 | // }
84 | // }
85 | var spanStyle = {
86 | float: "none",
87 | }
88 | var spansHtml = []
89 | var trunks = text.match(new RegExp('.{1,' + chars + '}', 'g'));
90 | trunks.forEach((trunk, i) => {
91 | spansHtml.push({trunk})
92 | })
93 | var top = parseInt(pos / chars)
94 | var left = pos % chars
95 | var cmdlineStyle = {
96 | padding: padding / 2,
97 | }
98 | var cmdlineInnerStyle = {
99 | backgroundColor: "#252526",
100 | // backgroundColor: "#3c3c3c",
101 | paddingLeft: padding / 2,
102 | paddingRight: padding / 2,
103 | lineHeight: 2,
104 | }
105 | return
106 |
107 |
110 | {cmdmenuHtml}
111 |
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/components/Statusline.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import * as _ from 'lodash'
3 |
4 | export default class StatusLine extends Component {
5 | constructor(props, context) {
6 | super(props, context)
7 | }
8 |
9 | shouldComponentUpdate(nextProps, nextState) {
10 | return nextProps.text != this.props.text
11 | }
12 |
13 | render() {
14 | const { editor, text, width } = this.props
15 |
16 | var statusLineStyle = {
17 | height: editor.statusLineHeight - 2,
18 | width: width,
19 | }
20 |
21 | var modes = {
22 | 'n': ['normal', '#6699cc'],
23 | 't': ['terminal', '#6699cc'],
24 | 'i': ['insert', '#99c794'],
25 | 'v': ['visual', '#fac863'],
26 | }
27 |
28 | var parts = text.split(",")
29 | if (parts.length < 11) {
30 | parts = []
31 | }
32 | var spans = []
33 | var ro = parts[8]
34 | var modified = parts[9]
35 | var ale = parts[10]
36 | var tag = parts[11]
37 |
38 | var mode = parts[0]
39 | if (modes[mode]) {
40 | var mode = modes[mode]
41 | var style = {
42 | backgroundColor: mode[1],
43 | padding: "0px 6px 0px 6px",
44 | }
45 | var span = {mode[0]}
46 | spans.push(span)
47 | }
48 |
49 | var branch = parts[1]
50 | if (branch) {
51 | branch = branch.slice(branch.indexOf("(") + 1, branch.indexOf(")"))
52 | var span = {branch}
53 | spans.push(span)
54 | }
55 |
56 | var filename = parts[2]
57 | if (filename) {
58 | if (!filename.startsWith("term://")) {
59 | var items = filename.split("/")
60 | filename = items[items.length - 1]
61 | }
62 | var span = {filename} {modified} {ro}
63 | spans.push(span)
64 | }
65 |
66 | var filetype = parts[3]
67 | if (filetype) {
68 | var span = {filetype.slice(1, filetype.length - 1)}
69 | spans.push(span)
70 | }
71 |
72 | var encode = parts[4]
73 | if (encode) {
74 | var span = {encode}
75 | spans.push(span)
76 | }
77 |
78 | var line = parts[5]
79 | var col = parts[6]
80 | var per = parts[7]
81 | if (per || line || col) {
82 | var pos = ""
83 | if (line) {
84 | pos = "L: " + line
85 | }
86 | if (col) {
87 | pos = pos + " C: " + col
88 | }
89 | if (per) {
90 | pos = pos + " " + per + "%"
91 | }
92 | var span = {pos}
93 | spans.push(span)
94 | }
95 |
96 | if (tag) {
97 | var span = {tag}
98 | spans.push(span)
99 | }
100 |
101 | if (ale) {
102 | // console.log("ale is", ale)
103 | var style = {
104 | }
105 | var error = ""
106 | var warning = ""
107 | var ok = ""
108 | if (ale == "ok") {
109 | style.color = "#99c794"
110 | ok = "✓ ok"
111 | } else if (ale.indexOf(" ") > 0) {
112 | var items = ale.split(" ")
113 | error = items[0].slice(1)
114 | warning = items[1].slice(1)
115 | } else if (ale.startsWith("e")) {
116 | error = ale.slice(1)
117 | } else if (ale.startsWith("w")) {
118 | warning = ale.slice(1)
119 | }
120 | if (error) {
121 | var style = {
122 | color: "#ec5f67",
123 | }
124 | error = {"x " + error}
125 | }
126 | if (warning) {
127 | var style = {
128 | color: "#fac863",
129 | }
130 | warning = {"! " + warning}
131 | }
132 | var span = {error} {warning} {ok}
133 | spans.push(span)
134 | }
135 | return {spans}
136 |
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World!
6 |
84 |
85 |
86 |
87 |
88 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/components/Line.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import * as _ from 'lodash'
3 | import Char from './Char'
4 | import Span from './Span'
5 |
6 | export default class Line extends Component {
7 | constructor(props, context) {
8 | super(props, context)
9 | }
10 |
11 | shouldComponentUpdate(nextProps, nextState) {
12 | // if (nextProps.lineObject != this.props.lineObject) {
13 | // console.log("line update", this.props.i)
14 | // }
15 | return nextProps.lineObject != this.props.lineObject
16 | }
17 |
18 | render() {
19 | const { line, width, row } = this.props
20 | if (line === undefined) {
21 | return
22 | }
23 |
24 | var spans = []
25 | var lastIndex = 0
26 | for (var i = line.size -1; i >= 0; i--) {
27 | var span = line.get(i)
28 | if (span != undefined) {
29 | lastIndex = i
30 | break
31 | }
32 | }
33 |
34 | line.forEach((span, i) => {
35 | if (span != undefined) {
36 | var last = false
37 | if (i == lastIndex) {
38 | last = true
39 | }
40 | spans.push()
41 | }
42 | })
43 |
44 | if (line.size == 1) {
45 | var span = line.get(0)
46 | if (span.get("text") === undefined) {
47 | span = span.set("text", "")
48 | }
49 | if (span.get("text").length < width) {
50 | spans = []
51 | spans.push()
52 | span = span.set("text", " ")
53 | span = span.set("highlight", {})
54 | spans.push()
55 | }
56 | }
57 |
58 | // if (spans.length == 1) {
59 | // if (spans[0].get("text").length < width) {
60 | // spans.push()
61 | // }
62 | // }
63 |
64 | var style = {
65 | position: "relative",
66 | height: 14 * 1.5,
67 | }
68 |
69 | return (
70 | {spans}
71 | )
72 |
73 | var spans = []
74 | line.forEach((char, i) => {
75 | if (spans.length == 0) {
76 | if (char === undefined) {
77 | spans[0] = {highlight: {}, text: " "}
78 | } else {
79 | spans[0] = {highlight: char.get("highlight"), text: char.get("char")}
80 | }
81 | } else {
82 | var span = spans[spans.length - 1]
83 | var spanHighlight = span.highlight
84 | var currentHighlight = {}
85 | var charContent = " "
86 | if (char != undefined) {
87 | currentHighlight = char.get("highlight")
88 | charContent = char.get("char")
89 | }
90 | if (currentHighlight.foreground != spanHighlight.foreground || currentHighlight.background != spanHighlight.background) {
91 | var newSpan = {highlight: currentHighlight, text: charContent}
92 | spans.push(newSpan)
93 | } else {
94 | span.text = span.text + charContent
95 | }
96 | }
97 | })
98 |
99 | return (
100 | {spans.map((span, i) => {
101 | var style = {}
102 | if (i == spans.length - 1) {
103 | style.float = "none";
104 | }
105 | var highlight = span.highlight
106 | if (highlight.foreground != undefined) {
107 | style.color = highlight.foreground
108 | }
109 | if (highlight.background != undefined) {
110 | style.backgroundColor = highlight.background
111 | }
112 | return {span.text}
113 | })}
114 | )
115 |
116 |
117 | return (
118 | {line.map((char, i) => {
119 | return
120 | })}
121 | )
122 | // var a = []
123 | // for (var j = 0; j < width; j++) {
124 | // if (line.get(j) === undefined) {
125 | // a.push({char: " ", highlight: {}})
126 | // // a.push(" ")
127 | // } else {
128 | // a.push({char: line.get(j).get("char"), highlight: line.get(j).get("highlight")})
129 | // }
130 | // }
131 | var lineObject = _.map(a, function(char, i) {
132 | var style = {}
133 | var highlight = {}
134 | if (char.highlight != undefined) {
135 | highlight = char.highlight
136 | }
137 |
138 | if (highlight.foreground != undefined) {
139 | style.color = highlight.foreground
140 | }
141 | if (highlight.foreground != undefined) {
142 | style.backgroundColor = highlight.background
143 | }
144 | return ({char.char})
145 | })
146 | return {lineObject}
147 | // return {_.join(a, '')}
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/components/Window.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import * as _ from 'lodash'
3 | import Line from './Line'
4 | import Cursor from './Cursor'
5 | import Number from './Number'
6 | import Sign from './Sign'
7 | import Popupmenu from './Popupmenu'
8 | import StatusLine from './Statusline'
9 |
10 | // function addElement(parentId, elementTag, elementId, html) {
11 | // var p = document.getElementById(parentId);
12 | // var newElement = document.createElement(elementTag);
13 | // newElement.setAttribute('id', elementId);
14 | // newElement.innerHTML = html;
15 | // p.appendChild(newElement);
16 | // }
17 | function addElement(parentId, html) {
18 | var p = document.getElementById(parentId);
19 | p.innerHTML = html + p.innerHTML;
20 | }
21 |
22 | class Window extends Component {
23 | constructor(props, context) {
24 | super(props, context)
25 | }
26 |
27 | componentDidMount() {
28 | // console.log("componentDidMount")
29 | // const { win, bg, fg, editor, cursor, popupmenuShow, popupmenu } = this.props
30 | // // addElement("windiv" + win.get("id"), "canvas", "wincanvas" + win.get("id"), '')
31 | // addElement("windiv" + win.get("id"), '')
32 | }
33 |
34 | shouldComponentUpdate(nextProps, nextState) {
35 | return (nextProps.win != this.props.win) || (this.props.popupmenu != nextProps.popupmenu) || (this.props.popupmenuShow != nextProps.popupmenuShow) || (nextProps.display != this.props.display) || (this.props.win.get("floating"))
36 | }
37 |
38 | render() {
39 | const { display, win, bg, fg, editor, popupmenuShow, popupmenu } = this.props
40 | var lineHeight = editor.lineHeight
41 | var drawHeight = editor.drawHeight
42 | var drawWidth = editor.drawWidth
43 | if (win.get("floating")) {
44 | lineHeight = editor.floatingLineHeight
45 | drawHeight = editor.floatingDrawHeight
46 | }
47 | var fontSize = editor.fontSize
48 | var pos = [win.get("row"), win.get("col")]
49 | var left = 0
50 | if (pos[1] > 0) {
51 | var left = (pos[1] - 1 ) * (fontSize / 2)
52 | }
53 | var style = {
54 | width: win.get("width") * (fontSize / 2),
55 | height: win.get("height") * fontSize * lineHeight,
56 | position: "absolute",
57 | left: left,
58 | top: pos[0] * fontSize * lineHeight,
59 | backgroundColor: bg,
60 | boxShadow: "inset -3px 0px 10px -3px rgba(0, 0, 0, 0.75)",
61 | color: fg,
62 | zIndex: pos[0] + pos[1],
63 | }
64 | if (!display) {
65 | style.display = "none"
66 | }
67 |
68 | var padding = 0
69 | if (left > 0) {
70 | style.borderLeft = "1px solid #000000"
71 | style.paddingLeft = 6
72 | padding = 6
73 | }
74 |
75 | var paddingTop = 0
76 | if (pos[0] > 0) {
77 | style.borderTop = "1px solid #181d22"
78 | // paddingTop = fontSize * lineHeight
79 | // style.paddingTop = fontSize * lineHeight
80 | style.top = pos[0] * fontSize * lineHeight - 1
81 | }
82 |
83 | if (win.get("floating")){
84 | var editorCursorPos = editor.cursorPos
85 | style.zIndex = 500
86 | style.left = (editor.width - 100) * (fontSize / 2) / 2
87 | style.top = 0
88 | style.border = "1px solid #000000"
89 | style.borderTop = "none"
90 | style.boxShadow = "0px 2px 8px #000"
91 | style.backgroundColor = "#181d22"
92 | if (win.get("preview") && win.get("id") != editor.curWin) {
93 | style.left = editorCursorPos[1] * (fontSize / 2)
94 | style.top = (editorCursorPos[0] + 1) * fontSize * editor.lineHeight
95 | }
96 | }
97 |
98 | if (win.get("buftype") == "nofile") {
99 | style.backgroundColor = "#181d22"
100 | // style.backgroundColor = "#1f2326"
101 | } else if (win.get("buftype") == "quickfix" || win.get("buftype") == "help") {
102 | style.backgroundColor = "#1f2326"
103 | }
104 |
105 | var popupmenuHtml
106 | if (popupmenuShow) {
107 | popupmenuHtml =
108 | }
109 |
110 | var canvasBaseWidth = editor.width
111 | var canvasBaseHeight = editor.height
112 | if (win.get("floating")) {
113 | canvasBaseWidth = win.get("width")
114 | canvasBaseHeight = win.get("height")
115 | }
116 | var canvasStyle = {
117 | width: canvasBaseWidth * (fontSize / 2),
118 | height: (canvasBaseHeight + 1) * fontSize * lineHeight ,
119 | }
120 |
121 | //
122 | return
123 | {popupmenuHtml}
124 |
125 |
126 | }
127 | }
128 |
129 | export default Window
130 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import * as neovimClient from 'neovim-client'
2 | import {spawn } from 'child_process'
3 | import {remote} from 'electron';
4 | import Immutable from 'immutable'
5 | import React from 'react';
6 | import ReactDOM from 'react-dom';
7 | import ReadWriteLock from 'rwlock'
8 | import Window from './components/Window'
9 | import Cursor from './components/Cursor'
10 | import Cmd from './components/Cmd'
11 | import Popupmenu from './components/Popupmenu'
12 | import Emsg from './components/Emsg'
13 | import Msg from './components/Msg'
14 | import DocMsg from './components/DocMsg'
15 | import Tab from './components/Tab'
16 | import StatusLine from './components/Statusline'
17 |
18 | var uniqueId = 0
19 |
20 | var lock = new ReadWriteLock()
21 | var hideEmsg
22 | var hideMsg
23 | var hideCursorMsg
24 |
25 | var editor = {
26 | emsg: "",
27 | msg: "",
28 | cursormsg: "",
29 | docmsg: "",
30 | docpos: [],
31 | doccom: 0,
32 | cmdline: "",
33 | wildmenu: [],
34 | wildmenuMatch: -1,
35 | cmdlineShow: false,
36 | width: 366,
37 | height: 63,
38 | mode: "normal",
39 | lineHeight: 1.5,
40 | floatingLineHeight: 1.8,
41 | fontSize: 14,
42 | statusLine: "",
43 | cmdheight: 1,
44 | cursorPos: [0, 0],
45 | cursorWin: 0,
46 | highlight: {},
47 | scroll: [],
48 | wins: Immutable.Map({}),
49 | activeWins: [],
50 | tab: [],
51 | popupmenu: Immutable.Map({
52 | selected: -1,
53 | show: false,
54 | pos: [],
55 | items: [],
56 | }),
57 | popupmenuWin: 1,
58 | previewWin: 0,
59 | curWin: 0,
60 | }
61 |
62 | resize()
63 |
64 | var cmdPos = [editor.height - editor.cmdheight, 0]
65 | var cmdEnd = [editor.height, editor.width - 1]
66 | editor.cmdPos = cmdPos
67 | editor.cmdEnd = cmdEnd
68 |
69 | // console.log(editor)
70 |
71 | var EnvimState = {editor: editor}
72 |
73 | var EnvimClass
74 |
75 | var EnvimEditor = React.createClass({
76 | getInitialState: function() {
77 | return EnvimState;
78 | },
79 |
80 | componentDidMount: function() {
81 | EnvimClass = this
82 | initEditor()
83 | },
84 |
85 | render: function() {
86 | var winsElement = []
87 | var editor = this.state.editor
88 | var wins = editor.wins
89 | var pos = editor.cursorPos
90 | var tab = editor.tab
91 | var fontSize = this.state.editor.fontSize
92 | var lineHeight = this.state.editor.lineHeight
93 | var drawHeight = editor.drawHeight
94 |
95 | var cmdHtml
96 | if (editor.cmdlineShow) {
97 | cmdHtml =
98 | }
99 | var emsgHtml
100 | if (editor.emsg != "") {
101 | emsgHtml =
102 | }
103 | var msgHtml
104 | if (editor.msg != "") {
105 | msgHtml =
106 | }
107 |
108 | var tabHtml
109 | if (tab.length > 0) {
110 | tabHtml =
111 | }
112 |
113 | if (wins !== undefined) {
114 | wins.map(win => {
115 | var i = win.get('id')
116 | var display = false
117 | if (editor.activeWins.indexOf(i) != -1) {
118 | display = true
119 | }
120 | // var winPos = win.get("pos")
121 | // var winEnd = win.get("end")
122 | var popupmenuShow = false
123 | if (i == editor.curWin) {
124 | var winCursorPos = win.get("cursorPos")
125 | if (winCursorPos != undefined) {
126 | editor.cursorPos = [win.get("row") + winCursorPos[0], win.get("col") + winCursorPos[1]]
127 | }
128 | }
129 | if (i == editor.popupmenuWin) {
130 | popupmenuShow = true
131 | }
132 | winsElement.push()
133 | })
134 | }
135 | var cursorHtml
136 | var cursorMsgHtml
137 | var docMsgHtml
138 | if (wins.get(editor.curWin)) {
139 | var win = wins.get(editor.curWin)
140 | var pos = win.get("cursorPos")
141 | if (pos != undefined) {
142 | var left = pos[1] + win.get("col")
143 | var top = pos[0] + win.get("row")
144 | var padding = 0
145 | if (win.get("col") > 0) {
146 | padding = 0
147 | }
148 | var paddingTop = 0
149 | if (win.get("floating")){
150 | padding = - ((editor.width - 100) * (fontSize / 2) / 2 + 1.5)
151 | lineHeight = editor.floatingLineHeight
152 | drawHeight = editor.floatingDrawHeight
153 | }
154 | cursorHtml =
155 | if (editor.cursormsg) {
156 | var cursorMsgStyle = {
157 | position: "absolute",
158 | left: (pos[1] + win.get("col")) * (fontSize / 2) - padding,
159 | top: ((pos[0] + 1) + win.get("row")) * fontSize * lineHeight + 4,
160 | padding: "4px 6px 4px 6px",
161 | backgroundColor: "#d4d7d6",
162 | color: "#0e1112",
163 | zIndex: 1300,
164 | }
165 | cursorMsgHtml = {editor.cursormsg}
166 | }
167 | if (editor.docmsg) {
168 | var docMsgStyle = {
169 | position: "absolute",
170 | left: (pos[1] + win.get("col")) * (fontSize / 2) - padding,
171 | bottom: ((win.get("height") - pos[0]) + win.get("row")) * fontSize * lineHeight,
172 | padding: "4px 6px 4px 6px",
173 | maxWidth: 400,
174 | backgroundColor: "#1f2326",
175 | color: editor.fg,
176 | border: "1px solid #000",
177 | zIndex: 1300,
178 | }
179 | docMsgHtml = {editor.docmsg}
180 | }
181 | }
182 | }
183 |
184 | var style = {
185 | height: (editor.height - 1) * fontSize * editor.lineHeight,
186 | backgroundColor: editor.bg,
187 | position: "relative",
188 | }
189 |
190 | var msgStyle = {
191 | position: "absolute",
192 | right: 0,
193 | top: 0,
194 | zIndex: 1000,
195 | }
196 | // console.log("statusline is", editor.statusLine)
197 |
198 | return (
199 |
200 | {tabHtml}
201 |
202 | {cmdHtml}
203 | {winsElement}
204 | {cursorHtml}
205 | {cursorMsgHtml}
206 |
207 |
208 | {emsgHtml}
209 | {msgHtml}
210 |
211 |
212 |
213 |
214 | )
215 | }
216 | })
217 |
218 | ReactDOM.render(
219 | ,
220 | document.getElementById('envim-editor')
221 | )
222 |
223 | function initEditor() {
224 | // console.log("now init editor")
225 | var nvim_proc = spawn('/Users/Lulu/neovim/build/bin/nvim', ['/Users/Lulu/go/src/tardis/transport/tcp.go', '--embed'], {})
226 | // console.log(neovimClient);
227 | neovimClient.default(nvim_proc.stdin, nvim_proc.stdout, function (err, nvim) {
228 | // console.log(err, nvim)
229 | EnvimState.nvim = nvim
230 | uiAttach()
231 | nvim.on('disconnect', function() {
232 | remote.getCurrentWindow().close()
233 | })
234 |
235 | var dragging = null
236 | var wheel_scrolling = new ScreenWheel(editor)
237 | document.addEventListener("mousedown", function(e) {
238 | dragging = new ScreenDrag(editor);
239 | nvim.input(dragging.start(e))
240 | })
241 |
242 | document.addEventListener("mouseup", function(e) {
243 | if (dragging != null) {
244 | nvim.input(dragging.end(e))
245 | dragging = null
246 | }
247 | })
248 |
249 | document.addEventListener("mousemove", function(e) {
250 | if (dragging != null) {
251 | nvim.input(dragging.drag(e))
252 | }
253 | })
254 |
255 | document.addEventListener("wheel", function(e) {
256 | nvim.input(wheel_scrolling.handleEvent(e))
257 | })
258 |
259 | document.addEventListener("click", function(e) {
260 | console.log("click", e)
261 | })
262 |
263 | document.addEventListener('keydown', function(e) {
264 | var key = e.key
265 | // console.log("keydown", getVimSpecialCharFromKey(e))
266 | if (key.length > 1) {
267 | key = getVimSpecialCharFromKey(e)
268 | if (key === null) {
269 | return
270 | }
271 | key = '<' + key + '>'
272 | }
273 | if (e.ctrlKey) {
274 | key = ''
275 | } else if (e.altKey) {
276 | let input = event.key;
277 | key = ''
278 | } else if (e.metaKey) {
279 | key = ''
280 | }
281 | if (key == "<") {
282 | key = ''
283 | }
284 | // console.log("keydown",e, key)
285 | nvim.input(key)
286 | })
287 |
288 | window.addEventListener('resize', function() {
289 | checkResize()
290 | })
291 | })
292 | }
293 |
294 | var resizeTimeout
295 |
296 | function checkResize() {
297 | clearTimeout(resizeTimeout)
298 | resizeTimeout = setTimeout(function() {
299 | resize()
300 | var editor = EnvimState.editor
301 | EnvimState.nvim.uiTryResize(editor.width, editor.height);
302 | }, 1000)
303 | }
304 |
305 | function resize() {
306 | var BrowserWindow = remote.getCurrentWindow()
307 | var size = BrowserWindow.getContentSize()
308 | console.log("resize to", size)
309 | editor.size = size
310 | editor.tabHeight = 30
311 | editor.width = Math.round(size[0] / (editor.fontSize / 2), 0)
312 | editor.height = Math.round((size[1] - editor.tabHeight) / (editor.fontSize * editor.lineHeight))
313 | editor.statusLineHeight = size[1] - editor.tabHeight - ((editor.height - 1) * editor.fontSize * editor.lineHeight)
314 | editor.pixel_ratio = window.devicePixelRatio || 1
315 | editor.drawWidth = editor.fontSize / 2
316 | editor.drawHeight = Math.round(editor.fontSize * editor.lineHeight, 0)
317 | editor.floatingDrawHeight = Math.round(editor.fontSize * editor.floatingLineHeight, 0)
318 | editor.cmdPos = [editor.height - editor.cmdheight, 0]
319 | editor.cmdEnd = [editor.height, editor.width - 1]
320 | }
321 |
322 | function uiAttach() {
323 | // console.log("now attach ui")
324 | var nvim = EnvimState.nvim
325 | var editor = EnvimState.editor
326 | nvim._session.request('nvim_ui_attach', [editor.width, editor.height, {'rgb': true, 'window_external': true, 'popupmenu_external': true}], function(err, res) {
327 | // console.log(err)
328 | // console.log(res)
329 | })
330 | onNotify()
331 | }
332 |
333 | function onNotify() {
334 | var nvim = EnvimState.nvim
335 | nvim.on('notification', function(method, args) {
336 | if (args.length > 0) {
337 | lock.writeLock(function (release) {
338 | handleNotification(args, release)
339 | });
340 | }
341 | })
342 | }
343 |
344 | function handleNotification(args, release) {
345 | var editor = new Editor(release)
346 | editor.redraw(args)
347 | }
348 |
349 | class Editor {
350 | constructor(release) {
351 | this.nvim = EnvimState.nvim
352 | this.state = EnvimState
353 | this.release = release
354 | }
355 |
356 | parseArgs(args) {
357 | this.cursormsgSet = false
358 | this.cursorMoved = false
359 | args.map((arg, index) => {
360 | var e = arg[0]
361 | // console.log(e)
362 | // console.log(arg[1])
363 | switch (e) {
364 | case 'cursor_goto':
365 | break
366 | this.cursorGoto(arg.slice(1))
367 | break
368 | case 'put':
369 | // console.log("put")
370 | break
371 | this.put(arg.slice(1))
372 | break
373 | case 'update_fg':
374 | this.state.editor.fg = this.decToHex(arg[1][0])
375 | break
376 | case 'update_bg':
377 | this.state.editor.bg = this.decToHex(arg[1][0])
378 | break
379 | case 'highlight_set':
380 | this.highlightSet(arg.slice(1))
381 | break
382 | case 'eol_clear':
383 | this.eolClear(arg.slice(1))
384 | break
385 | case 'set_scroll_region':
386 | break
387 | this.setScrollRegion(arg.slice(1))
388 | break
389 | case 'win_set_scroll_region':
390 | this.win_set_scroll_region(arg.slice(1))
391 | break
392 | case 'scroll':
393 | console.log('scroll')
394 | console.log(arg.slice(1)[0])
395 | break
396 | this.scroll(arg.slice(1))
397 | break
398 | case 'mode_change':
399 | this.modeChange(arg.slice(1))
400 | break
401 | case 'win_scroll':
402 | this.win_scroll(arg.slice(1))
403 | break
404 | case 'tab':
405 | // console.log("win_update")
406 | this.tab(arg.slice(1))
407 | break
408 | case 'win_clear':
409 | // console.log("win_update")
410 | this.win_clear(arg.slice(1))
411 | break
412 | case 'win_resize':
413 | // console.log("win_update")
414 | this.win_resize(arg.slice(1))
415 | break
416 | case 'win_update':
417 | // console.log("win_update")
418 | this.win_update(arg.slice(1))
419 | break
420 | case 'win_close':
421 | // console.log("win_update")
422 | this.win_close(arg.slice(1))
423 | break
424 | case 'win_draw_sign':
425 | // console.log("win_draw_sign")
426 | this.win_draw_sign(arg.slice(1))
427 | break
428 | case 'win_put':
429 | // console.log("win_put")
430 | this.cursorMoved = true
431 | this.win_put(arg.slice(1))
432 | break
433 | case 'win_status_line':
434 | // console.log("win_put")
435 | this.win_status_line(arg.slice(1))
436 | break
437 | case 'win_cursor_goto':
438 | // console.log("win_cursor_goto")
439 | this.cursorMoved = true
440 | this.win_cursor_goto(arg.slice(1))
441 | break
442 | case 'popupmenu_show':
443 | this.popupmenu_show(arg.slice(1))
444 | break
445 | case 'popupmenu_hide':
446 | this.popupmenu_hide(arg.slice(1))
447 | break
448 | case 'popupmenu_select':
449 | this.popupmenu_select(arg.slice(1))
450 | break
451 | case 'echo':
452 | this.msg(arg.slice(1))
453 | break
454 | case 'echomsg':
455 | this.msg(arg.slice(1))
456 | break
457 | // case 'msg':
458 | // this.msg(arg.slice(1))
459 | // break
460 | case 'emsg':
461 | this.emsg(arg.slice(1))
462 | break
463 | case 'cmdline':
464 | this.cmdline(arg.slice(1))
465 | break
466 | case 'wild_menu':
467 | this.wild_menu(arg.slice(1))
468 | break
469 | case 'wild_menu_clean':
470 | this.wild_menu_clean(arg.slice(1))
471 | break
472 | case 'cmdlinepos':
473 | this.cmdlinepos(arg.slice(1))
474 | break
475 | case 'command_line_enter':
476 | this.command_line_enter(arg.slice(1))
477 | break
478 | case 'command_line_leave':
479 | this.command_line_leave(arg.slice(1))
480 | break
481 | case 'bell':
482 | this.bell(arg.slice(1))
483 | break
484 | case 'busy_start':
485 | this.busy_start(arg.slice(1))
486 | break
487 | case 'busy_stop':
488 | this.busy_stop(arg.slice(1))
489 | break
490 | case 'mouse_on':
491 | this.mouse_on(arg.slice(1))
492 | break
493 | default:
494 | console.log(e)
495 | console.log(arg[1])
496 | break
497 | }
498 | })
499 | if (!this.cursormsgSet && this.cursorMoved && this.state.editor.cursormsg != "") {
500 | var self = this
501 | clearTimeout(hideCursorMsg)
502 | hideCursorMsg = setTimeout(function() {
503 | self.state.editor.cursormsg = ""
504 | self.update()
505 | }, 100)
506 | }
507 | this.update()
508 | this.release()
509 | }
510 |
511 | update() {
512 | EnvimClass.setState(EnvimState)
513 | }
514 |
515 | redraw(args) {
516 | this.parseArgs(args)
517 | }
518 |
519 | bell(args) {
520 | }
521 |
522 | busy_start(args) {
523 | // console.log("busy_start", args)
524 | }
525 |
526 | busy_stop(args) {
527 | // console.log("busy_stop", args)
528 | }
529 |
530 | mouse_on(args) {
531 | // console.log("mouse_on", args)
532 | }
533 |
534 | cursorGoto(args) {
535 | // this.state.editor = this.state.editor.set("cursorPos", args[0])
536 | }
537 |
538 | winCursorPos(pos) {
539 | var currentIndex = 0
540 | this.state.editor.get("windows").forEach((win, index) => {
541 | var winPos = win.get("pos")
542 | var winEnd = win.get("end")
543 | if (pos[0] >= winPos.get(0) && pos[0] <= winEnd.get(0) && pos[1] >= winPos.get(1) && pos[1] <= winEnd.get(1)) {
544 | currentIndex = index
545 | }
546 | })
547 | return currentIndex
548 | }
549 |
550 | spansPutNew(spans, col, text, width) {
551 | var chars = ""
552 | text.forEach((char, i) => {
553 | chars = chars + char
554 | })
555 | spans = spans.set(col, Immutable.Map({highlight: {}, text: chars}))
556 | return spans
557 | }
558 |
559 | spansPut(spans, row, col, text, width, height, numWidth, drawSign, signColumn, numColumn) {
560 | var chars = Immutable.List()
561 | var line = Immutable.List()
562 | var highlight = this.state.editor.highlight
563 | var end = col + text.length
564 | var affectedChars = []
565 | var affectedStart = -1
566 | var affectedEnd = -1
567 |
568 | if (signColumn === undefined) {
569 | signColumn = Immutable.List()
570 | }
571 | if (numColumn === undefined) {
572 | numColumn = Immutable.List()
573 | }
574 |
575 | if (height > row) {
576 | if (drawSign) {
577 | var signOffset = 1;
578 | if (col <= signOffset) {
579 | for (col; col <= signOffset; col++) {
580 | var c = text.shift()
581 | if (c === undefined) {
582 | break
583 | }
584 | var sign = signColumn.get(row)
585 | var signText = []
586 | if (sign != undefined) {
587 | signText = sign.sign
588 | }
589 | signText[col] = c
590 | signColumn = signColumn.set(row, {'sign': signText, 'highlight': highlight})
591 | }
592 | }
593 | }
594 | var numOffset = numWidth + (drawSign ? 2:0) - 1
595 | if (col <= numOffset) {
596 | for (col; col <= numOffset; col++) {
597 | var c = text.shift()
598 | if (c === undefined) {
599 | break
600 | }
601 | var num = numColumn.get(row)
602 | var numText = []
603 | if (num != undefined) {
604 | numText = num.num
605 | }
606 | numText[col - (drawSign ? 2:0)] = c
607 | if (numText.length > numWidth) {
608 | numText = numText.splice(0, numWidth - 1)
609 | }
610 | numColumn = numColumn.set(row, {'num': numText, 'highlight': highlight})
611 | }
612 | }
613 | if (text.length == 0) {
614 | return [signColumn, numColumn, spans]
615 | }
616 | }
617 |
618 | if (spans.size > width) {
619 | var newSpans = Immutable.List()
620 | for (var i = 0; i <= width; i++) {
621 | var span = spans.get(i)
622 | if (span != undefined) {
623 | newSpans = newSpans.set(i, span)
624 | }
625 | }
626 | spans = newSpans
627 | }
628 | // for (var i = spans.size -1; i >= 0; i--) {
629 | // var span = spans.get(i)
630 | // if (span != undefined) {
631 | // var spanText = span.get("text")
632 | // if ((spanText.length + i) > width) {
633 | // console.log(spanText)
634 | // var newSpanText = ""
635 | // for (var j = i; j < width; j ++) {
636 | // newSpanText = newSpanText + spanText[j - i]
637 | // }
638 | // console.log("old text")
639 | // console.log(spanText)
640 | // console.log("new text")
641 | // console.log(newSpanText)
642 | // span = span.set("text", newSpanText)
643 | // spans = spans.set(i, span)
644 | // }
645 | // break
646 | // }
647 | // }
648 | for (var i = 0; i < spans.size; i++) {
649 | var span = spans.get(i)
650 | if (span == undefined && affectedStart == -1 && i == col) {
651 | affectedStart = i
652 | }
653 | if (span != undefined) {
654 | var spanEnd = i + span.get("text").length
655 | if ((i <= col && spanEnd >= col) && affectedStart == -1) {
656 | affectedStart = i
657 | }
658 | if (i <= end && spanEnd >= end) {
659 | affectedEnd = spanEnd
660 | }
661 | if (affectedStart != -1) {
662 | span.get("text").split('').forEach((char, index) => {
663 | chars = chars.set(i + index, {
664 | char: char,
665 | highlight: span.get("highlight"),
666 | })
667 | })
668 | spans = spans.set(i, undefined)
669 | }
670 | if (affectedEnd != -1) {
671 | break
672 | }
673 | }
674 | }
675 | if (affectedStart == -1) {
676 | affectedStart = col
677 | }
678 | if ((col + text.length) > affectedEnd) {
679 | affectedEnd = col + text.length
680 | }
681 | text.forEach((char, i) => {
682 | if ((col + i) < width) {
683 | chars = chars.set(col + i, {
684 | char: char,
685 | highlight: highlight,
686 | })
687 | }
688 | })
689 |
690 | var lastIndex = 0
691 | for (var i = affectedStart; i > 0; i--) {
692 | var span = spans.get(i)
693 | if (span != undefined) {
694 | lastIndex = i
695 | break
696 | }
697 | }
698 |
699 | for (var i = affectedStart; i < affectedEnd; i++){
700 | var char = chars.get(i)
701 | if (spans.get(lastIndex) === undefined) {
702 | if (char === undefined) {
703 | spans = spans.set(i, Immutable.Map({highlight: {}, text: " "}))
704 | } else {
705 | spans = spans.set(i, Immutable.Map({highlight: char.highlight, text: char.char}))
706 | }
707 | if (i > 0) {
708 | var text = ""
709 | for (var j = 0; j < i; j++) {
710 | text = text + " "
711 | }
712 | spans = spans.set(0, Immutable.Map({highlight: {}, text: text}))
713 | }
714 | lastIndex = i
715 | } else {
716 | var span = spans.get(lastIndex)
717 | var spanHighlight = span.get("highlight")
718 | if (span.get("text").length < (i - lastIndex)) {
719 | var newText = ""
720 | for(var j = 0; j < (i - lastIndex - span.get("text").length); j++) {
721 | newText = newText + " "
722 | }
723 |
724 | if (spanHighlight.background != undefined || spanHighlight.foreground != undefined) {
725 | // console.log('"' + newText + '"')
726 | var currentIndex = span.get("text").length + lastIndex
727 | var newSpan = Immutable.Map({highlight: {}, text: newText})
728 | spans = spans.set(currentIndex, newSpan)
729 | lastIndex = currentIndex
730 | } else {
731 | // console.log('"' + span.get("text") + '"')
732 | // console.log('"' + newText + '"')
733 | span = span.set("text", span.get("text") + newText)
734 | spans = spans.set(lastIndex, span)
735 | }
736 | }
737 |
738 | var span = spans.get(lastIndex)
739 | var spanHighlight = span.get("highlight")
740 | var currentHighlight = {}
741 | var charContent = " "
742 | if (char != undefined) {
743 | currentHighlight = char.highlight
744 | charContent = char.char
745 | }
746 |
747 | if (currentHighlight.foreground != spanHighlight.foreground || currentHighlight.background != spanHighlight.background) {
748 | var newSpan = Immutable.Map({highlight: currentHighlight, text: charContent})
749 | spans = spans.set(i, newSpan)
750 | lastIndex = i
751 | } else {
752 | var text = span.get("text") + charContent
753 | span = span.merge({text: text})
754 | spans = spans.set(lastIndex, span)
755 | }
756 | }
757 | }
758 | // console.log(spans.toJS())
759 | // console.log("-------------------")
760 | return [signColumn, numColumn, spans]
761 | }
762 |
763 | cmdlinepos(args) {
764 | var arg = args[0]
765 | this.state.editor.cmdlinePos = arg[0]
766 | }
767 |
768 | msg(args) {
769 | var arg = args[0]
770 | var self = this
771 | var msg = arg[0]
772 | if (msg.startsWith("__cursor__")) {
773 | clearTimeout(hideCursorMsg)
774 | this.state.editor.cursormsg = msg.slice(10)
775 | this.cursormsgSet = true
776 | } else if (msg.startsWith("__doc__")) {
777 | this.state.editor.docmsg = msg.slice(7)
778 | } else if (msg.startsWith("__docpos__")) {
779 | var docpos = msg.slice(10)
780 | docpos = docpos.split(",")
781 | const win = editor.wins.get(editor.curWin)
782 | const pos = win.get("cursorPos")
783 | this.state.editor.docpos = [
784 | parseInt(docpos[0]) + pos[0],
785 | parseInt(docpos[1]) + pos[1],
786 | ]
787 | console.log(this.state.editor.docpos)
788 | } else if (msg.startsWith("__doccom__")) {
789 | this.state.editor.doccom = parseInt(msg.slice(10))
790 | } else {
791 | clearTimeout(hideMsg)
792 | this.state.editor.msg = arg[0]
793 | hideMsg = setTimeout(function() {
794 | self.state.editor.msg = ""
795 | self.update()
796 | }, 5000)
797 | }
798 | }
799 |
800 | emsg(args) {
801 | clearTimeout(hideEmsg)
802 | var arg = args[0]
803 | var self = this
804 | this.state.editor.emsg = arg[0]
805 | hideEmsg = setTimeout(function() {
806 | self.state.editor.emsg = ""
807 | self.update()
808 | }, 5000)
809 | }
810 |
811 | cmdline(args) {
812 | var arg = args[0]
813 | this.state.editor.cmdline = arg[0]
814 | this.state.editor.cmdlinePos = arg[1]
815 | }
816 |
817 | wild_menu(args) {
818 | var arg = args[0]
819 | this.state.editor.wildmenuMatch = arg[0]
820 | this.state.editor.wildmenu = arg.slice(1)
821 | }
822 |
823 | wild_menu_clean(args) {
824 | this.state.editor.wildmenu = []
825 | this.state.editor.wildmenuMatch = -1
826 | }
827 |
828 | command_line_enter(args) {
829 | this.state.editor.cmdlineShow = true
830 | }
831 |
832 | command_line_leave(args) {
833 | this.state.editor.cmdline = ""
834 | this.state.editor.cmdlinePos = 0
835 | this.state.editor.cmdlineShow = false
836 | }
837 |
838 | popupmenu_select(args) {
839 | var arg = args[0]
840 | this.state.editor.popupmenu = this.state.editor.popupmenu.set("selected", arg[0])
841 | }
842 |
843 | popupmenu_hide(args) {
844 | this.state.editor.popupmenu = this.state.editor.popupmenu.set("show", false)
845 | }
846 |
847 | popupmenu_show(args) {
848 | var arg = args[0]
849 | var popupmenu = this.state.editor.popupmenu
850 | popupmenu = popupmenu.set("show", true)
851 | popupmenu = popupmenu.set("items", arg[0])
852 | popupmenu = popupmenu.set("selected", arg[1])
853 | popupmenu = popupmenu.set("pos", [arg[2], arg[3]])
854 | this.state.editor.popupmenuWin = arg[4]
855 | this.state.editor.popupmenu = popupmenu
856 | }
857 |
858 | win_cursor_goto(args) {
859 | var arg = args[0]
860 | // console.log(arg[1], arg[2])
861 | var winId = arg[0]
862 | // console.log("win cursor goto", winId, arg[1], arg[2])
863 | var cursorRow = arg[1]
864 | var cursorCol = arg[2]
865 | var wins = this.state.editor.wins
866 | var win = wins.get(winId)
867 | if (win === undefined) {
868 | win = Immutable.Map({
869 | id: winId,
870 | width: this.state.editor.width,
871 | height: this.state.editor.height,
872 | })
873 | }
874 | if (cursorRow >= win.get('height') || cursorCol >= win.get("width")) {
875 | return
876 | }
877 | win = win.set("cursorPos", [cursorRow, cursorCol])
878 | this.state.editor.cursorWin = winId
879 | this.state.editor.wins = wins.set(winId, win)
880 | }
881 |
882 | win_status_line(args) {
883 | var editor = this.state.editor
884 | var arg = args[0]
885 | var winId = arg[0]
886 | var statusLine = arg[1]
887 |
888 | if (winId == editor.curWin) {
889 | editor.statusLine = statusLine
890 | }
891 | }
892 |
893 | win_put(args) {
894 | if (args.length == 0) {
895 | return
896 | }
897 | var arg = args[0]
898 | var winId = arg[0]
899 | var editor = this.state.editor
900 | var drawWidth = editor.drawWidth * editor.pixel_ratio
901 | var drawHeight = editor.drawHeight * editor.pixel_ratio
902 | // console.log("win_put", winId, args.map(arg => {return arg[1]}))
903 | var char = arg[1]
904 | var wins = this.state.editor.wins
905 | var win = wins.get(winId)
906 | var pixel_ratio = this.state.editor.pixel_ratio
907 | if (win === undefined) {
908 | win = Immutable.Map({
909 | id: winId,
910 | width: this.state.editor.width,
911 | height: this.state.editor.height,
912 | })
913 | }
914 | var fontSize = this.state.editor.fontSize
915 | var lineHeight = this.state.editor.lineHeight
916 | if (win.get("floating")) {
917 | lineHeight = this.state.editor.floatingLineHeight
918 | drawHeight = editor.floatingDrawHeight * editor.pixel_ratio
919 | }
920 | var cursorPos = win.get("cursorPos")
921 | if (cursorPos === undefined) {
922 | cursorPos = [0, 0]
923 | }
924 | var col = cursorPos[1]
925 | var width = win.get("width")
926 | var height = win.get("height")
927 | var numWidth = win.get("numWidth")
928 | var drawSign = win.get("drawSign")
929 | var signColumn = win.get("signColumn")
930 | var numColumn = win.get("numColumn")
931 |
932 | var wincanvasId = "wincanvas" + winId
933 | var c = document.getElementById(wincanvasId)
934 | if (c != undefined && cursorPos[0] < height) {
935 | var ctx = c.getContext("2d")
936 | // var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
937 | // ctx.mozBackingStorePixelRatio ||
938 | // ctx.msBackingStorePixelRatio ||
939 | // ctx.oBackingStorePixelRatio ||
940 | // ctx.backingStorePixelRatio || 1
941 | // console.log("backingStoreRatio is", backingStoreRatio)
942 | var text = (args.map(arg => {return arg[1]})).join("")
943 | var textDrawWidth = ctx.measureText(text).width
944 | ctx.clearRect(
945 | cursorPos[1] * drawWidth,
946 | cursorPos[0] * drawHeight,
947 | text.length * drawWidth,
948 | drawHeight)
949 | if (this.state.editor.highlight.background != undefined) {
950 | ctx.fillStyle = this.state.editor.highlight.background;
951 | ctx.fillRect(cursorPos[1] * drawWidth, cursorPos[0] * drawHeight, args.length * drawWidth, drawHeight)
952 | }
953 |
954 | ctx.font = (fontSize * pixel_ratio) + "px InconsolataforPowerline Nerd Font"
955 | if (this.state.editor.highlight.foreground != undefined) {
956 | ctx.fillStyle = this.state.editor.highlight.foreground;
957 | } else {
958 | ctx.fillStyle = this.state.editor.fg;
959 | }
960 | if (text.trim()) {
961 | if (textDrawWidth != ctx.measureText("a").width * text.length) {
962 | if (text.length == 1) {
963 | // console.log("text is", text, cursorPos[0], cursorPos[1])
964 | ctx.clearRect(
965 | (cursorPos[1] + 1) * drawWidth,
966 | cursorPos[0] * drawHeight,
967 | drawWidth,
968 | drawHeight)
969 | ctx.fillText(
970 | text,
971 | (cursorPos[1]) * drawWidth,
972 | (cursorPos[0] + 1) * drawHeight - ((fontSize * (lineHeight - 1) / 2 + 2.5) * pixel_ratio)
973 | )
974 | if (this.state.editor.highlight.background != undefined) {
975 | ctx.fillStyle = this.state.editor.highlight.background;
976 | ctx.fillRect(
977 | (cursorPos[1] + 1) * drawWidth,
978 | cursorPos[0] * drawHeight,
979 | drawWidth,
980 | drawHeight
981 | )
982 | }
983 | } else {
984 | text.split("").forEach((char, i) => {
985 | ctx.fillText(char, (cursorPos[1] + i) * drawWidth, (cursorPos[0] + 1) * drawHeight - ((fontSize * (lineHeight - 1) / 2 + 2.5) * pixel_ratio))
986 | })
987 | }
988 | } else {
989 | ctx.fillText(text, cursorPos[1] * drawWidth, (cursorPos[0] + 1) * drawHeight - ((fontSize * (lineHeight - 1) / 2 + 2.5) * pixel_ratio))
990 | }
991 | }
992 | // console.log("ctx filltext", wincanvasId, text, cursorPos[1] * 7, (cursorPos[0] + 1) * 14)
993 | // ctx.fillText(text, 0, 14)
994 | }
995 | // if (cursorPos[0] < height) {
996 | // var lines = win.get("lines")
997 | // if (lines === undefined) {
998 | // lines = Immutable.List()
999 | // }
1000 | // var line = lines.get(cursorPos[0])
1001 | // if (line === undefined) {
1002 | // uniqueId = uniqueId + 1
1003 | // line = Immutable.Map({
1004 | // uniqueId: uniqueId,
1005 | // spans: Immutable.List(),
1006 | // })
1007 | // }
1008 | // var spans = line.get("spans")
1009 | // var result = this.spansPut(spans, cursorPos[0], col, args.map(arg => {return arg[1]}), width, height, numWidth, drawSign, signColumn, numColumn)
1010 | // signColumn = result[0]
1011 | // numColumn = result[1]
1012 | // if (spans != result[2]) {
1013 | // line = line.set("spans", result[2])
1014 | // }
1015 | // lines = lines.set(cursorPos[0], line)
1016 | // win = win.set("lines", lines)
1017 | // } else {
1018 | // var statusLine = win.get("statusLine")
1019 | // if (statusLine === undefined) {
1020 | // statusLine = {spans: Immutable.List()}
1021 | // }
1022 | // var spans = statusLine.spans
1023 | // var result = this.spansPut(spans, cursorPos[0], col, args.map(arg => {return arg[1]}), width, height, numWidth, drawSign, signColumn, numColumn)
1024 | // statusLine.spans = result[2]
1025 | // win = win.set("statusLine", statusLine)
1026 | // }
1027 | // win = win.set("signColumn", signColumn)
1028 | // win = win.set("numColumn", numColumn)
1029 | win = win.set("cursorPos", [cursorPos[0], cursorPos[1] + args.length])
1030 | // console.log("win cursor pos after put", cursorPos[0], cursorPos[1] + args.length, (args.map(arg => {return arg[1]})).join(""))
1031 | wins = wins.set(winId, win)
1032 | this.state.editor.wins = wins
1033 | }
1034 |
1035 | win_draw_sign(args) {
1036 | var arg = args[0]
1037 | var winId = arg[0]
1038 | var drawSign = arg[1]
1039 | var wins = this.state.editor.wins
1040 | var win = wins.get(winId)
1041 | var need_update = false;
1042 | if (win == undefined) {
1043 | win = Immutable.Map({
1044 | drawSign: drawSign,
1045 | })
1046 | need_update = true;
1047 | } else {
1048 | if (win.get("drawSign") != drawSign) {
1049 | win = win.set("drawSign", drawSign)
1050 | }
1051 | need_update = true;
1052 | }
1053 | if (need_update) {
1054 | wins = wins.set(winId, win)
1055 | this.state.editor.wins = wins
1056 | }
1057 | }
1058 |
1059 | win_scroll(args) {
1060 | var arg = args[0]
1061 | var winId = arg[0]
1062 | var count = arg[1]
1063 | // console.log("scroll", winId, count)
1064 | var editor = this.state.editor
1065 | var wins = this.state.editor.wins
1066 | var scroll = editor.scroll
1067 | var win = wins.get(winId)
1068 | var fontSize = this.state.editor.fontSize
1069 | var lineHeight = this.state.editor.lineHeight
1070 | var pixel_ratio = this.state.editor.pixel_ratio
1071 | if (win.get("floating")) {
1072 | lineHeight = this.state.editor.floatingLineHeight
1073 | }
1074 | var height = win.get("height")
1075 | if (height == undefined) {
1076 | height = 0
1077 | }
1078 | if (win == undefined) {
1079 | win = Immutable.Map({
1080 | })
1081 | }
1082 |
1083 | // var startRow = 0
1084 | // var destRow = 0
1085 | // if (count > 0) {
1086 | // startRow = count
1087 | // height = height - count
1088 | // } else {
1089 | // destRow = -count
1090 | // height = height + count
1091 | // }
1092 | var startRow = scroll
1093 | var destRow = scroll
1094 | if (count > 0) {
1095 | startRow = scroll + count
1096 | height = height - scroll - count
1097 | } else {
1098 | destRow = scroll - count
1099 | height = height - scroll + count
1100 | }
1101 | // console.log("scroll", startRow, destRow, height)
1102 |
1103 | var wincanvasId = "wincanvas" + winId
1104 | var c = document.getElementById(wincanvasId)
1105 | if (c != undefined) {
1106 | var ctx = c.getContext("2d")
1107 | var width = win.get("width") * (fontSize / 2) * pixel_ratio
1108 | var height = height * fontSize * lineHeight * pixel_ratio
1109 | var startY = startRow * fontSize * lineHeight * pixel_ratio
1110 | var destY = destRow * fontSize * lineHeight * pixel_ratio
1111 |
1112 | var buffer = document.createElement('canvas');
1113 | buffer.width = width
1114 | buffer.height = height
1115 | buffer.getContext('2d').drawImage(c,
1116 | 0,
1117 | startY, width, height,
1118 | 0,
1119 | 0, width, height
1120 | );
1121 |
1122 | ctx.clearRect(
1123 | 0,
1124 | scroll * fontSize * lineHeight * pixel_ratio,
1125 | width,
1126 | (win.get("height") - scroll) * fontSize * lineHeight * pixel_ratio)
1127 |
1128 | ctx.drawImage(buffer,
1129 | 0, destY, width, height)
1130 |
1131 | return
1132 | }
1133 | return
1134 | var lines = Immutable.List()
1135 | var signColumn = Immutable.List()
1136 | var numColumn = Immutable.List()
1137 | var oldLines = win.get("lines")
1138 | var oldSignColumn = win.get("signColumn")
1139 | if (oldSignColumn == undefined) {
1140 | oldSignColumn = Immutable.List()
1141 | }
1142 | var oldNumColumn = win.get("numColumn")
1143 | if (oldNumColumn == undefined) {
1144 | oldNumColumn = Immutable.List()
1145 | }
1146 | if (count > 0) {
1147 | for (var i = count; i < height; i++) {
1148 | lines = lines.push(oldLines.get(i))
1149 | // if (oldSignColumn.get(i) != undefined) {
1150 | // signColumn = signColumn.push(oldSignColumn.get(i))
1151 | // }
1152 | // if (oldNumColumn.get(i) != undefined) {
1153 | // numColumn = numColumn.push(oldNumColumn.get(i))
1154 | // }
1155 | }
1156 | for (var i = 0; i < count; i++) {
1157 | uniqueId = uniqueId + 1
1158 | lines = lines.push(Immutable.Map({
1159 | uniqueId: uniqueId,
1160 | spans: Immutable.List(),
1161 | }))
1162 | // signColumn = signColumn.push({})
1163 | // numColumn = numColumn.push({})
1164 | }
1165 | } else {
1166 | for (var i = 0; i > count; i--) {
1167 | uniqueId = uniqueId + 1
1168 | lines = lines.push(Immutable.Map({
1169 | uniqueId: uniqueId,
1170 | spans: Immutable.List(),
1171 | }))
1172 | // signColumn = signColumn.push({})
1173 | // numColumn = numColumn.push({})
1174 | }
1175 | for (var i = 0; i < height + count; i++) {
1176 | lines = lines.push(oldLines.get(i))
1177 | // if (oldSignColumn.get(i) != undefined) {
1178 | // signColumn = signColumn.push(oldSignColumn.get(i))
1179 | // }
1180 | // if (oldNumColumn.get(i) != undefined) {
1181 | // numColumn = numColumn.push(oldNumColumn.get(i))
1182 | // }
1183 | }
1184 | }
1185 |
1186 | win = win.set("lines", lines)
1187 | // win = win.set("signColumn", signColumn)
1188 | // win = win.set("numColumn", numColumn)
1189 | wins = wins.set(winId, win)
1190 | this.state.editor.wins = wins
1191 | }
1192 |
1193 | win_close(args) {
1194 | // console.log("win_close")
1195 | var arg = args[0]
1196 | var winId = arg[0]
1197 | var wins = this.state.editor.wins
1198 | wins = wins.delete(winId)
1199 | this.state.editor.wins = wins
1200 | }
1201 |
1202 | tab(args) {
1203 | var arg = args[0]
1204 | this.state.editor.tab = arg
1205 | }
1206 |
1207 | win_clear(args) {
1208 | var arg = args[0]
1209 | // console.log("win_clear", arg)
1210 | var wins = this.state.editor.wins
1211 | var fontSize = this.state.editor.fontSize
1212 | var pixel_ratio = this.state.editor.pixel_ratio
1213 | arg.forEach((winId, i) => {
1214 | var wincanvasId = "wincanvas" + winId
1215 | var c = document.getElementById(wincanvasId)
1216 | var lineHeight = this.state.editor.lineHeight
1217 | var win = wins.get(winId)
1218 | if (win.get("floating")) {
1219 | lineHeight = this.state.editor.floatingLineHeight
1220 | }
1221 | if (c != undefined) {
1222 | var ctx = c.getContext("2d")
1223 | ctx.clearRect(0, 0, win.get("width") * (fontSize / 2) * pixel_ratio, (win.get("height") + 1) * fontSize * lineHeight * pixel_ratio)
1224 | }
1225 | })
1226 | // this.state.editor.activeWins = arg
1227 | // wins.map((win, i) => {
1228 | // win = win.delete("lines")
1229 | // wins = wins.set(i, win)
1230 | // })
1231 | // this.state.editor.wins = wins
1232 | }
1233 |
1234 | win_resize(args) {
1235 | var wins = this.state.editor.wins
1236 | var editor = this.state.editor
1237 | var arg = args[0]
1238 | var activeWins = []
1239 | arg.forEach((winArg) => {
1240 | var winId = winArg[0]
1241 | var width = winArg[1]
1242 | var height = winArg[2]
1243 | var oldWidth
1244 | var oldHeight
1245 | var row = winArg[3]
1246 | var col = winArg[4]
1247 | var floating = winArg[5]
1248 | var preview = winArg[6]
1249 | var curWin = winArg[7]
1250 | var buftype = winArg[8]
1251 | if (floating) {
1252 | editor.previewWin = winId
1253 | }
1254 | if (curWin) {
1255 | editor.curWin = winId
1256 | }
1257 | var win = wins.get(winId)
1258 | if (win === undefined) {
1259 | win = Immutable.Map({
1260 | id: winId,
1261 | width: width,
1262 | height: height,
1263 | row: row,
1264 | col: col,
1265 | floating: floating,
1266 | preview: preview,
1267 | buftype: buftype,
1268 | })
1269 | } else {
1270 | oldWidth = win.get("width")
1271 | oldHeight = win.get("height")
1272 | win = win.merge(Immutable.Map({
1273 | id: winId,
1274 | width: width,
1275 | height: height,
1276 | row: row,
1277 | col: col,
1278 | floating: floating,
1279 | preview: preview,
1280 | buftype: buftype,
1281 | }))
1282 | }
1283 | activeWins.push(winId)
1284 | editor.activeWins = activeWins
1285 | wins = wins.set(winId, win)
1286 | this.state.editor.wins = wins
1287 | // this.update()
1288 | // this.canvas_update(winId, oldWidth, oldHeight, width, height)
1289 | })
1290 | this.update()
1291 | // console.log("activeWins in win_resize", this.state.editor.activeWins)
1292 | }
1293 |
1294 | win_update(args) {
1295 | return
1296 | var arg = args[0];
1297 | var need_update = false;
1298 | var wins = this.state.editor.wins
1299 | var winId = arg[0]
1300 | var width = arg[1]
1301 | var height = arg[2]
1302 | var oldWidth
1303 | var oldHeight
1304 | var row = arg[3]
1305 | var col = arg[4]
1306 | // console.log("win_update", winId, row, col)
1307 | var numWidth = arg[5]
1308 | var drawSign = arg[6]
1309 | var win = wins.get(winId)
1310 | if (this.state.editor.activeWins.indexOf(winId) == -1) {
1311 | this.state.editor.activeWins.push(winId)
1312 | }
1313 | if (win == undefined) {
1314 | win = Immutable.Map({
1315 | id: winId,
1316 | width: width,
1317 | height: height,
1318 | row: row,
1319 | col: col,
1320 | numWidth: arg[5],
1321 | drawSign: arg[6],
1322 | })
1323 | need_update = true;
1324 | } else {
1325 | oldWidth = win.get("width")
1326 | oldHeight = win.get("height")
1327 | if (win.get("width") != width || win.get("height") != height || win.get("row") != row || win.get("col") != col || win.get("numWidth") != numWidth || win.get("drawSign") != drawSign) {
1328 | win = win.merge(Immutable.Map({
1329 | id: winId,
1330 | width: width,
1331 | height: height,
1332 | row: row,
1333 | col: col,
1334 | numWidth: arg[5],
1335 | drawSign: arg[6],
1336 | }))
1337 | need_update = true;
1338 | }
1339 | }
1340 | if (need_update) {
1341 | wins = wins.set(winId, win)
1342 | this.state.editor.wins = wins
1343 | }
1344 | }
1345 |
1346 | put(args, move = true) {
1347 | var windows = this.state.editor.get("windows")
1348 | var cursorPos = this.state.editor.get("cursorPos")
1349 | var currentWinIndex = this.winCursorPos(cursorPos)
1350 | var currentWin = windows.get(currentWinIndex)
1351 | var currentWinPos = currentWin.get("pos")
1352 | var currentWinCursorPos = [
1353 | cursorPos[0] - currentWinPos.get(0),
1354 | cursorPos[1] - currentWinPos.get(1),
1355 | ]
1356 |
1357 | if (currentWin.get("lines") === undefined) {
1358 | currentWin = currentWin.set("lines", Immutable.List())
1359 | }
1360 |
1361 | var lines = currentWin.get("lines")
1362 | if (lines.get(currentWinCursorPos[0]) === undefined) {
1363 | uniqueId = uniqueId + 1
1364 | lines = lines.set(currentWinCursorPos[0], Immutable.Map({
1365 | uniqueId: uniqueId,
1366 | spans: Immutable.List(),
1367 | }))
1368 | }
1369 | var line = lines.get(currentWinCursorPos[0])
1370 | var spans = line.get("spans")
1371 | var c = currentWinCursorPos[1]
1372 | var width = currentWin.get("width")
1373 | // spans = this.spanssssPut(spans, c, args.map(arg => {return arg[0]}), width)
1374 | line = line.set("spans", spans)
1375 | lines = lines.set(currentWinCursorPos[0], line)
1376 | currentWin = currentWin.set("lines", lines)
1377 | windows = windows.set(currentWinIndex, currentWin)
1378 | this.state.editor = this.state.editor.set("windows", windows)
1379 | if (move) {
1380 | this.state.editor = this.state.editor.set("cursorPos", [cursorPos[0], cursorPos[1] + args.length])
1381 | }
1382 | }
1383 |
1384 | highlightSet(args) {
1385 | // console.log("hightlight set", args.map(arg => {return arg[0]}))
1386 | var newHighlight = this.state.editor.highlight
1387 | args.forEach((arg) => {
1388 | var highlight = arg[0]
1389 | if (Object.keys(highlight).length == 0) {
1390 | newHighlight = {}
1391 | } else {
1392 | Object.keys(highlight).forEach(key => {
1393 | newHighlight[key] = highlight[key]
1394 | })
1395 | if (newHighlight.reverse) {
1396 | var foreground = newHighlight.foreground
1397 | newHighlight.foreground = newHighlight.background
1398 | newHighlight.background = foreground
1399 | }
1400 | }
1401 | })
1402 | if (newHighlight.foreground != undefined) {
1403 | newHighlight.foreground = this.decToHex(newHighlight.foreground)
1404 | }
1405 | if (newHighlight.background != undefined) {
1406 | newHighlight.background = this.decToHex(newHighlight.background)
1407 | }
1408 |
1409 | this.state.editor.highlight = newHighlight
1410 | }
1411 |
1412 | eolClear(args) {
1413 | var editor = this.state.editor
1414 | var curWin = editor.curWin
1415 | var wins = editor.wins
1416 | var win = wins.get(curWin)
1417 |
1418 | var cursorPos = win.get("cursorPos")
1419 |
1420 | args = []
1421 | var width = win.get("width")
1422 | for (var i = cursorPos[1]; i < width; i++) {
1423 | args.push([curWin, " "])
1424 | }
1425 | // console.log("eolClear", curWin, width, cursorPos[0], cursorPos[1])
1426 | this.win_put(args)
1427 | // this.state.editor = this.state.editor.set("highlight", {})
1428 | }
1429 |
1430 | decToHex(n) {
1431 | var padding = 6;
1432 | var hex = n.toString(16);
1433 | while (hex.length < padding) {
1434 | hex = "0" + hex;
1435 | }
1436 | return "#".concat(hex);
1437 | }
1438 |
1439 | win_set_scroll_region(args) {
1440 | // console.log("set scroll region")
1441 | // console.log(args[0])
1442 | this.state.editor.scroll = args[0][0]
1443 | }
1444 |
1445 | setScrollRegion(args) {
1446 | // console.log("set scroll region")
1447 | // console.log(args[0])
1448 | // this.state.editor.scroll = args[0]
1449 | }
1450 |
1451 | scroll(args) {
1452 | var n = args[0][0]
1453 | var windows = this.state.editor.get("windows")
1454 | var scrollRegion = this.state.editor.get("scroll")
1455 | var pos = [scrollRegion[0], scrollRegion[3]]
1456 | var currentWinIndex = this.winCursorPos(pos)
1457 | var currentWin = windows.get(currentWinIndex)
1458 | var height = currentWin.get("height")
1459 | var lines = Immutable.List()
1460 | var oldLines = currentWin.get("lines")
1461 | if (n > 0) {
1462 | for (var i = n; i < height; i++) {
1463 | lines = lines.push(oldLines.get(i))
1464 | }
1465 | for (var i = 0; i < n; i++) {
1466 | uniqueId = uniqueId + 1
1467 | lines = lines.push(Immutable.Map({
1468 | uniqueId: uniqueId,
1469 | spans: Immutable.List(),
1470 | }))
1471 | }
1472 | } else {
1473 | lines = Immutable.List()
1474 | for (var i = 0; i > n; i--) {
1475 | uniqueId = uniqueId + 1
1476 | lines = lines.push(Immutable.Map({
1477 | uniqueId: uniqueId,
1478 | spans: Immutable.List(),
1479 | }))
1480 | }
1481 | for (var i = 0; i < height + n; i++) {
1482 | lines = lines.push(oldLines.get(i))
1483 | }
1484 | }
1485 | currentWin = currentWin.set("lines", lines)
1486 | windows = windows.set(currentWinIndex, currentWin)
1487 | this.state.editor = this.state.editor.set("windows", windows)
1488 | }
1489 |
1490 | modeChange(args) {
1491 | var mode = args[0][0]
1492 | this.state.editor.mode = mode
1493 | }
1494 | }
1495 |
1496 | function keyFromCharCode (charCode) {
1497 | switch (charCode) {
1498 | case 0:
1499 | return 'Nul';
1500 | case 8:
1501 | return 'BS';
1502 | case 9:
1503 | return 'Tab';
1504 | case 10:
1505 | return 'NL';
1506 | case 12:
1507 | return 'FF';
1508 | case 13:
1509 | return 'Enter';
1510 | case 27:
1511 | return 'Esc';
1512 | case 32:
1513 | return 'Space';
1514 | case 92:
1515 | return 'Bslash';
1516 | case 124:
1517 | return 'Bar';
1518 | case 127:
1519 | return 'Del';
1520 | default:
1521 | return String.fromCharCode(charCode);
1522 | }
1523 | }
1524 |
1525 | function getVimSpecialCharFromKey(event) {
1526 | const key = event.key;
1527 |
1528 | if (key.length === 1) {
1529 | switch (key) {
1530 | case '<': return event.ctrlKey || event.altKey ? 'LT' : null;
1531 | case '\0': return 'Nul';
1532 | default: return null;
1533 | }
1534 | }
1535 |
1536 | if (key[0] === 'F') {
1537 | // F1, F2, F3, ...
1538 | return /^F\d+/.test(key) ? key : null;
1539 | }
1540 |
1541 | const ctrl = event.ctrlKey;
1542 | const key_code = event.keyCode;
1543 |
1544 | switch (key) {
1545 | case 'Escape': {
1546 | if (ctrl && key_code !== 27) {
1547 | // Note:
1548 | // When is input
1549 | // XXX:
1550 | // Keycode of '[' is not available because it is 219 in OS X
1551 | // and it is not for '['.
1552 | return '[';
1553 | } else {
1554 | return 'Esc';
1555 | }
1556 | }
1557 | case 'Backspace': {
1558 | if (ctrl && key_code === 72) {
1559 | // Note:
1560 | // When is input (72 is key code of 'h')
1561 | return 'h';
1562 | } else {
1563 | return 'BS';
1564 | }
1565 | };
1566 | case 'Tab': {
1567 | if (ctrl && key_code === 73) {
1568 | // Note:
1569 | // When is input (73 is key code of 'i')
1570 | return 'i';
1571 | } else {
1572 | return 'Tab';
1573 | }
1574 | };
1575 | case 'Enter': { // Note: Should consider ?
1576 | if (ctrl && key_code === 77) {
1577 | // Note:
1578 | // When is input (77 is key code of 'm')
1579 | return 'm';
1580 | } else if (ctrl && key_code === 67) {
1581 | // XXX:
1582 | // This is workaround for a bug of Chromium. Ctrl+c emits wrong KeyboardEvent.key.
1583 | // (It should be "\uxxxx" but actually "Enter")
1584 | // https://github.com/rhysd/NyaoVim/issues/37
1585 | return 'c';
1586 | } else {
1587 | return 'CR';
1588 | }
1589 | };
1590 | case 'PageUp': return 'PageUp';
1591 | case 'PageDown': return 'PageDown';
1592 | case 'End': return 'End';
1593 | case 'Home': return 'Home';
1594 | case 'ArrowLeft': return 'Left';
1595 | case 'ArrowUp': return 'Up';
1596 | case 'ArrowRight': return 'Right';
1597 | case 'ArrowDown': return 'Down';
1598 | case 'Insert': return 'Insert';
1599 | case 'Delete': return 'Del';
1600 | case 'Help': return 'Help';
1601 | case 'Unidentified': return null;
1602 | default: return null;
1603 | }
1604 | }
1605 |
1606 | const MouseButtonKind = [ 'Left', 'Middle', 'Right' ];
1607 |
1608 | class ScreenDrag {
1609 |
1610 | constructor(editor) {
1611 | this.line = 0;
1612 | this.col = 0;
1613 | this.editor = editor
1614 | }
1615 |
1616 | static buildInputOf(e, type, line, col) {
1617 | let seq = '<';
1618 | if (e.ctrlKey) {
1619 | seq += 'C-';
1620 | }
1621 | if (e.altKey) {
1622 | seq += 'A-';
1623 | }
1624 | if (e.shiftKey) {
1625 | seq += 'S-';
1626 | }
1627 | seq += MouseButtonKind[e.button] + type + '>';
1628 | seq += `<${col},${line}>`;
1629 | return seq;
1630 | }
1631 |
1632 | start(down_event) {
1633 | down_event.preventDefault();
1634 | [this.line, this.col] = this.getPos(down_event);
1635 | console.log('Drag start', down_event, this.line, this.col);
1636 | const input = ScreenDrag.buildInputOf(down_event, 'Mouse', this.line, this.col);
1637 | // log.debug('Mouse input: ' + input);
1638 | return input;
1639 | }
1640 |
1641 | drag(move_event) {
1642 | const [line, col] = this.getPos(move_event);
1643 | if (line === this.line && col === this.col) {
1644 | return null;
1645 | }
1646 | move_event.preventDefault();
1647 | // log.debug('Drag continue', move_event, line, col);
1648 | const input = ScreenDrag.buildInputOf(move_event, 'Drag', line, col);
1649 | this.line = line;
1650 | this.col = col;
1651 | // log.debug('Mouse input: ' + input);
1652 | return input;
1653 | }
1654 |
1655 | end(up_event) {
1656 | up_event.preventDefault();
1657 |
1658 | [this.line, this.col] = this.getPos(up_event);
1659 | console.log('Drag end', up_event, this.line, this.col);
1660 |
1661 | const input = ScreenDrag.buildInputOf(up_event, 'Release', this.line, this.col);
1662 | console.log('Mouse input: ' + input);
1663 | return input;
1664 | }
1665 |
1666 | getPos(e) {
1667 | return [
1668 | Math.floor((e.clientY - this.editor.tabHeight) / (this.editor.fontSize * editor.lineHeight)),
1669 | Math.floor(e.clientX / (this.editor.fontSize / 2)),
1670 | ];
1671 | }
1672 | }
1673 |
1674 | class ScreenWheel {
1675 |
1676 | constructor(editor) {
1677 | this.editor = editor
1678 | this.reset();
1679 | }
1680 |
1681 | handleEvent(e) {
1682 | if ((this.shift === undefined && this.ctrl === undefined) ||
1683 | (this.shift !== e.shiftKey || this.ctrl !== e.ctrlKey)) {
1684 | // Note:
1685 | // Initialize at first or reset on modifier change
1686 | this.reset(e.shiftKey, e.ctrlKey);
1687 | }
1688 |
1689 | this.x += e.deltaX;
1690 | this.y += e.deltaY;
1691 |
1692 | const scroll_x = Math.round(this.x / (editor.fontSize / 2) / 6);
1693 | const scroll_y = Math.round(this.y / (editor.fontSize * editor.lineHeight) / 3);
1694 |
1695 | if (scroll_x === 0 && scroll_y === 0) {
1696 | // Note: At least 3 lines or 6 columns are needed to scroll screen
1697 | return '';
1698 | }
1699 | var line
1700 | var col
1701 | [line, col] = this.getPos(e)
1702 |
1703 | const input = this.getInput(scroll_x, scroll_y, line, col);
1704 | // log.debug(`Scroll (${scroll_x}, ${scroll_y})`);
1705 | this.reset();
1706 | return input;
1707 | }
1708 |
1709 | reset(shift, ctrl) {
1710 | this.x = 0;
1711 | this.y = 0;
1712 | this.shift = shift;
1713 | this.ctrl = ctrl;
1714 | }
1715 |
1716 | getDirection(scroll_x, scroll_y) {
1717 | if (scroll_y !== 0) {
1718 | return scroll_y > 0 ? 'Down' : 'Up';
1719 | } else if (scroll_x !== 0) {
1720 | return scroll_x > 0 ? 'Left' : 'Right';
1721 | } else {
1722 | // Note: Never reach here
1723 | log.error('Null scrolling');
1724 | return '';
1725 | }
1726 | }
1727 |
1728 | getInput(scroll_x, scroll_y, line, col) {
1729 | let seq = '<';
1730 | if (this.ctrl) {
1731 | seq += 'C-';
1732 | }
1733 | if (this.shift) {
1734 | seq += 'S-';
1735 | }
1736 | seq += `ScrollWheel${this.getDirection(scroll_x, scroll_y)}>`;
1737 | seq += `<${col},${line}>`; // This is really needed?
1738 | return seq;
1739 | }
1740 |
1741 | getPos(e) {
1742 | return [
1743 | Math.floor((e.clientY - this.editor.tabHeight) / (this.editor.fontSize * editor.lineHeight)),
1744 | Math.floor(e.clientX / (this.editor.fontSize / 2)),
1745 | ];
1746 | }
1747 | }
1748 |
--------------------------------------------------------------------------------