diff --git a/package-lock.json b/package-lock.json index 226a9ec0d51..97e2314d622 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5539,6 +5539,18 @@ "servify": "^0.1.12", "ws": "^3.0.0", "xhr-request-promise": "^0.1.2" + }, + "dependencies": { + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } } }, "ethashjs": { @@ -12552,6 +12564,10 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, + "bignumber.js": { + "version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", + "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934" + }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", @@ -12875,7 +12891,6 @@ "integrity": "sha1-PpcwauAk+yThCj11yIQwJWIhUSA=", "dev": true, "requires": { - "bignumber.js": "git+https://github.com/frozeman/bignumber.js-nolookahead.git", "crypto-js": "^3.1.4", "utf8": "^2.1.1", "xhr2": "*", @@ -13176,6 +13191,10 @@ "winston": "^3.0.0" }, "dependencies": { + "bignumber.js": { + "version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", + "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934" + }, "bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", @@ -13188,14 +13207,6 @@ "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", "dev": true }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, "elliptic": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.3.3.tgz", @@ -13332,7 +13343,6 @@ "integrity": "sha1-PpcwauAk+yThCj11yIQwJWIhUSA=", "dev": true, "requires": { - "bignumber.js": "git+https://github.com/frozeman/bignumber.js-nolookahead.git", "crypto-js": "^3.1.4", "utf8": "^2.1.1", "xhr2": "*", @@ -13611,19 +13621,27 @@ "dev": true, "requires": { "underscore": "1.8.3", - "web3-core-helpers": "1.0.0-beta.36", - "websocket": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible" + "web3-core-helpers": "1.0.0-beta.36" }, "dependencies": { "websocket": { "version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2", - "from": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible", - "dev": true, + "from": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2", "requires": { "debug": "^2.2.0", "nan": "^2.3.3", "typedarray-to-buffer": "^3.1.2", "yaeti": "^0.0.6" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } } } } @@ -13659,10 +13677,9 @@ "version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2", "from": "websocket@git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2", "requires": { - "debug": "2.6.9", - "nan": "2.14.0", - "typedarray-to-buffer": "3.1.5", - "yaeti": "0.0.6" + "nan": "^2.3.3", + "typedarray-to-buffer": "^3.1.2", + "yaeti": "^0.0.6" } } } @@ -17097,7 +17114,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -17157,8 +17173,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "scrypt-js": { "version": "2.0.3", @@ -17313,8 +17328,19 @@ "dev": true, "requires": { "underscore": "1.8.3", - "web3-core-helpers": "1.0.0-beta.36", - "websocket": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible" + "web3-core-helpers": "1.0.0-beta.36" + }, + "dependencies": { + "websocket": { + "version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2", + "from": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2", + "requires": { + "debug": "^2.2.0", + "nan": "^2.3.3", + "typedarray-to-buffer": "^3.1.2", + "yaeti": "^0.0.6" + } + } } }, "web3-utils": { @@ -17335,7 +17361,6 @@ "websocket": { "version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2", "from": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible", - "dev": true, "requires": { "debug": "^2.2.0", "nan": "^2.3.3", @@ -17642,13 +17667,11 @@ } }, "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.1.1.tgz", + "integrity": "sha512-o41D/WmDeca0BqYhsr3nJzQyg9NF5X8l/UdnFNux9cS3lwB+swm8qGWX5rn+aD6xfBU3rGmtHij7g7x6LxFU3A==", "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "async-limiter": "^1.0.0" } }, "xhr": { diff --git a/package.json b/package.json index a4543c6dfb5..27c403f0d5d 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,8 @@ "@remixproject/engine": "^0.1.6", "http-server": "^0.11.1", "remixd": "0.1.8-alpha.6", - "standard": "^8.5.0" + "standard": "^8.5.0", + "ws": "^7.1.1" }, "repository": { "type": "git", diff --git a/src/app/editor/editor.js b/src/app/editor/editor.js index 5225bc9dc16..ee66a726d36 100644 --- a/src/app/editor/editor.js +++ b/src/app/editor/editor.js @@ -6,9 +6,11 @@ const ace = require('brace') const globalRegistry = require('../../global/registry') const SourceHighlighters = require('./SourceHighlighters') +const AceLanguageServerService = require('./languageserver') const Range = ace.acequire('ace/range').Range require('brace/ext/language_tools') +require('brace/ext/linking') require('brace/ext/searchbox') const langTools = ace.acequire('ace/ext/language_tools') require('ace-mode-solidity/build/remix-ide/mode-solidity') @@ -38,7 +40,7 @@ document.head.appendChild(yo` class Editor { - constructor (opts = {}, themeModule) { + constructor (opts = {}, themeModule) { // Dependancies this._components = {} this._components.registry = globalRegistry @@ -72,12 +74,24 @@ class Editor { json: 'ace/mode/json', abi: 'ace/mode/json' } + var languageServer = new AceLanguageServerService() // Editor Setup const el = yo`
` this.editor = ace.edit(el) + languageServer.init(this.editor) ace.acequire('ace/ext/language_tools') + ace.acequire('ace/ext/linking') + + //Code Format + this.editor.commands.addCommand({ + name: 'format', + bindKey: {win: "Ctrl-Shift-F", mac: "Command-Shift-F"}, + exec: function(editor) { + languageServer.format(editor) + } + }) // Unmap ctrl-l & cmd-l this.editor.commands.bindKeys({ @@ -118,20 +132,21 @@ class Editor { this.editor.setOptions({ enableBasicAutocompletion: true, - enableLiveAutocompletion: true + enableLiveAutocompletion: true, + enableLinking: true }) - el.className += ' ' + css['ace-editor'] + el.className += ' ' + css['ace-editor'] el.editor = this.editor // required to access the editor during tests this.render = () => el - + // Completer for editor const flowCompleter = { getCompletions: (editor, session, pos, prefix, callback) => { - // @TODO add here other propositions + languageServer.completions(this.editor, callback) } } - langTools.addCompleter(flowCompleter) + langTools.setCompleters([flowCompleter]) // zoom with Ctrl+wheel window.addEventListener('wheel', (e) => { @@ -163,6 +178,10 @@ class Editor { e.stop() }) + this.editor.on("linkClick", e => { + languageServer.gotoDefinition(this.editor, e) + }); + // Do setup on initialisation here this.editor.on('changeSession', () => { this._onChange() diff --git a/src/app/editor/languageserver.js b/src/app/editor/languageserver.js new file mode 100644 index 00000000000..c32c16216f0 --- /dev/null +++ b/src/app/editor/languageserver.js @@ -0,0 +1,296 @@ +'use strict' +const Range = ace.acequire('ace/range').Range + +const SERVER_URL = 'ws://localhost:5007' + +class AceLanguageServerService { + + didOpen(editor) { + this.socket.send(JSON.stringify({ + jsonrpc: "2.0", + method: "textDocument/didOpen", + params: { + textDocument: { + uri: "inmemory://demo/model.sol", + languageId: "solidity-ide", + version: 1, + text: editor.getValue() + } + } + })) + } + + completions(editor, callback) { + this.didOpen(editor) + this.socket.onmessage = (msg) => this.completionsCallback(msg, callback) + + var completionRequest = { + jsonrpc: "2.0", + id: 4, + method: "textDocument/completion", + params: { + textDocument: { + uri: "inmemory://demo/model.sol", + }, + position: { + line: editor.getCursorPosition().row, + character: editor.getCursorPosition().column + }, + context: { + triggerKind: 1 + } + } + } + this.socket.send(JSON.stringify(completionRequest)) + } + + completionsCallback(msg, callback) { + var results = JSON.parse(msg.data).result.items.forEach(item => { + callback(null, [ + { + name: item.label, + value: item.label, + score: 100, + meta: 'yakindu' + }]) + }); + } + + gotoDefinition(editor, data) { + this.didOpen(editor) + this.socket.onmessage = (msg) => this.gotoDefinitionCallback(editor, msg) + + const gotoDefinitionRequest = { + jsonrpc: "2.0", + id: 14, + method: "textDocument/definition", + params: { + textDocument: { + uri: "inmemory://demo/model.sol", + }, + position: { + line: data.position.row, + character: data.position.column + } + } + } + this.socket.send(JSON.stringify(gotoDefinitionRequest)) + } + + gotoDefinitionCallback(editor, msg) { + var textEdit = JSON.parse(msg.data).result.forEach(item => { + var range = new Range( + item.range.start.line, + item.range.start.character, + item.range.end.line, + item.range.end.character + ) + editor.selection.setRange(range) + }) + } + + format(editor) { + this.didOpen(editor) + this.socket.onmessage = (msg) => this.formatCallBack(editor, msg) + + const formatRequest = { + jsonrpc: "2.0", + id: 4, + method: "textDocument/formatting", + params: { + textDocument: { + uri: "inmemory://demo/model.sol" + }, + options: { + tabsize: 4 + } + } + } + this.socket.send(JSON.stringify(formatRequest)) + } + + formatCallBack(editor, msg) { + var originalText = editor.getValue() + var formattedText = "" + var offset = 0 + JSON.parse(msg.data).result.forEach(textEdit => { + + var startPosition = { + row: textEdit.range.start.line, + column: textEdit.range.start.character + } + + var endPosition = { + row: textEdit.range.end.line, + column: textEdit.range.end.character + } + + var startOffset = editor.session.doc.positionToIndex(startPosition) + formattedText += originalText.substr(offset, startOffset - offset) + textEdit.newText + offset = editor.session.doc.positionToIndex(endPosition) + + }) + + editor.setValue(formattedText + originalText.substr(offset)) + } + + init(editor) { + this.socket = new WebSocket(SERVER_URL) + this.socket.onmessage = function (e) { console.log(e.data); }; + this.socket.onopen = () => + this.socket.send(` + { + "jsonrpc":"2.0", + "id":0, + "method":"initialize", + "params":{ + "rootPath":null, + "rootUri":null, + "capabilities":{ + "workspace":{ + "applyEdit":true, + "workspaceEdit":{ + "documentChanges":true + }, + "didChangeConfiguration":{ + "dynamicRegistration":true + }, + "didChangeWatchedFiles":{ + "dynamicRegistration":true + }, + "symbol":{ + "dynamicRegistration":true, + "symbolKind":{ + "valueSet":[ + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26 + ] + } + }, + "executeCommand":{ + "dynamicRegistration":true + }, + "workspaceFolders":true + }, + "textDocument":{ + "publishDiagnostics":{ + "relatedInformation":true + }, + "synchronization":{ + "dynamicRegistration":true, + "willSave":true, + "willSaveWaitUntil":true, + "didSave":true + }, + "completion":{ + "dynamicRegistration":true, + "contextSupport":true, + "completionItem":{ + "snippetSupport":true, + "commitCharactersSupport":true, + "documentationFormat":[ + "markdown", + "plaintext" + ], + "deprecatedSupport":true, + "preselectSupport":true + }, + "completionItemKind":{ + "valueSet":[ + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 + ] + } + }, + "hover":{ + "dynamicRegistration":true, + "contentFormat":[ + "markdown", + "plaintext" + ] + }, + "signatureHelp":{ + "dynamicRegistration":true, + "signatureInformation":{ + "documentationFormat":[ + "markdown", + "plaintext" + ] + } + }, + "definition":{ + "dynamicRegistration":true + }, + "references":{ + "dynamicRegistration":true + }, + "documentHighlight":{ + "dynamicRegistration":true + }, + "documentSymbol":{ + "dynamicRegistration":true, + "symbolKind":{ + "valueSet":[ + 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26 + ] + }, + "hierarchicalDocumentSymbolSupport":true + }, + "codeAction":{ + "dynamicRegistration":true, + "codeActionLiteralSupport":{ + "codeActionKind":{ + "valueSet":[ + "", + "quickfix", + "refactor", + "refactor.extract", + "refactor.inline", + "refactor.rewrite", + "source", + "source.organizeImports" + ] + } + } + }, + "codeLens":{ + "dynamicRegistration":true + }, + "formatting":{ + "dynamicRegistration":true + }, + "rangeFormatting":{ + "dynamicRegistration":true + }, + "onTypeFormatting":{ + "dynamicRegistration":true + }, + "rename":{ + "dynamicRegistration":true + }, + "documentLink":{ + "dynamicRegistration":true + }, + "typeDefinition":{ + "dynamicRegistration":true + }, + "implementation":{ + "dynamicRegistration":true + }, + "colorProvider":{ + "dynamicRegistration":true + }, + "foldingRange":{ + "dynamicRegistration":true, + "rangeLimit":5000, + "lineFoldingOnly":true + } + } + }, + "trace":"off", + "workspaceFolders":null + } + } + `); + } +} + +module.exports = AceLanguageServerService