├── .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
    50 | 53 |
    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
    {menuHtml}
    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 |
    108 |
    {spansHtml}
    109 |
    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 | --------------------------------------------------------------------------------