context view in react

pull/1856/head
yann300 3 years ago
parent 3b500ceb71
commit bc7e1279d6
  1. 70
      apps/remix-ide-e2e/src/tests/editor.test.ts
  2. 4
      apps/remix-ide-e2e/src/tests/fileExplorer.test.ts
  3. 5
      apps/remix-ide/src/app.js
  4. 194
      apps/remix-ide/src/app/editor/contextView.js
  5. 5
      apps/remix-ide/src/app/editor/editor.js
  6. 50
      apps/remix-ide/src/app/panels/main-view.js
  7. 1
      libs/remix-core-plugin/src/index.ts
  8. 6
      libs/remix-core-plugin/src/lib/compiler-artefacts.ts
  9. 98
      libs/remix-core-plugin/src/lib/editor-context-listener.ts
  10. 4
      libs/remix-ui/editor-context-view/.babelrc
  11. 19
      libs/remix-ui/editor-context-view/.eslintrc
  12. 7
      libs/remix-ui/editor-context-view/README.md
  13. 1
      libs/remix-ui/editor-context-view/src/index.ts
  14. 20
      libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.css
  15. 159
      libs/remix-ui/editor-context-view/src/lib/remix-ui-editor-context-view.tsx
  16. 16
      libs/remix-ui/editor-context-view/tsconfig.json
  17. 13
      libs/remix-ui/editor-context-view/tsconfig.lib.json
  18. 3
      nx.json
  19. 7
      tsconfig.base.json
  20. 96
      workspace.json

@ -6,10 +6,10 @@ import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done)
init(browser, done, 'http://127.0.0.1:8080', true)
},
'Should zoom in editor ': function (browser: NightwatchBrowser) {
'Should zoom in editor #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('div[data-id="mainPanelPluginsContainer"]')
.clickLaunchIcon('filePanel')
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
@ -22,7 +22,7 @@ module.exports = {
.checkElementStyle('.view-lines', 'font-size', '16px')
},
'Should zoom out editor ': function (browser: NightwatchBrowser) {
'Should zoom out editor #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView')
.checkElementStyle('.view-lines', 'font-size', '16px')
.click('*[data-id="tabProxyZoomOut"]')
@ -30,7 +30,7 @@ module.exports = {
.checkElementStyle('.view-lines', 'font-size', '14px')
},
'Should display compile error in editor ': function (browser: NightwatchBrowser) {
'Should display compile error in editor #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView')
.setEditorValue(storageContractWithError + 'error')
.pause(2000)
@ -42,7 +42,7 @@ module.exports = {
.checkAnnotations('fa-exclamation-square', 29) // error
},
'Should minimize and maximize codeblock in editor ': '' + function (browser: NightwatchBrowser) {
'Should minimize and maximize codeblock in editor #group1': '' + function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView')
.waitForElementVisible('.ace_open')
.click('.ace_start:nth-of-type(1)')
@ -51,7 +51,7 @@ module.exports = {
.waitForElementVisible('.ace_open')
},
'Should add breakpoint to editor ': function (browser: NightwatchBrowser) {
'Should add breakpoint to editor #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView')
.waitForElementNotPresent('.margin-view-overlays .fa-circle')
.execute(() => {
@ -60,7 +60,7 @@ module.exports = {
.waitForElementVisible('.margin-view-overlays .fa-circle')
},
'Should load syntax highlighter for ace light theme': '' + function (browser: NightwatchBrowser) {
'Should load syntax highlighter for ace light theme #group1': '' + function (browser: NightwatchBrowser) {
browser.waitForElementVisible('#editorView')
.checkElementStyle('.ace_keyword', 'color', aceThemes.light.keyword)
.checkElementStyle('.ace_comment.ace_doc', 'color', aceThemes.light.comment)
@ -68,7 +68,7 @@ module.exports = {
.checkElementStyle('.ace_variable', 'color', aceThemes.light.variable)
},
'Should load syntax highlighter for ace dark theme': '' + function (browser: NightwatchBrowser) {
'Should load syntax highlighter for ace dark theme #group1': '' + function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="verticalIconsKindsettings"]')
.click('*[data-id="verticalIconsKindsettings"]')
.waitForElementVisible('*[data-id="settingsTabThemeLabelDark"]')
@ -83,7 +83,7 @@ module.exports = {
*/
},
'Should highlight source code ': function (browser: NightwatchBrowser) {
'Should highlight source code #group1': function (browser: NightwatchBrowser) {
// include all files here because switching between plugins in side-panel removes highlight
browser
.addFile('sourcehighlight.js', sourcehighlightScript)
@ -101,7 +101,7 @@ module.exports = {
.checkElementStyle('.highlightLine51', 'background-color', 'rgb(52, 152, 219)')
},
'Should remove 1 highlight from source code': '' + function (browser: NightwatchBrowser) {
'Should remove 1 highlight from source code #group1': '' + function (browser: NightwatchBrowser) {
browser.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]')
.click('li[data-id="treeViewLitreeViewItemremoveSourcehighlightScript.js"]')
.pause(2000)
@ -115,7 +115,7 @@ module.exports = {
.checkElementStyle('.highlightLine51', 'background-color', 'rgb(52, 152, 219)')
},
'Should remove all highlights from source code ': function (browser: NightwatchBrowser) {
'Should remove all highlights from source code #group1': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('li[data-id="treeViewLitreeViewItemremoveAllSourcehighlightScript.js"]')
.click('li[data-id="treeViewLitreeViewItemremoveAllSourcehighlightScript.js"]')
.pause(2000)
@ -126,6 +126,54 @@ module.exports = {
.waitForElementNotPresent('.highlightLine33', 60000)
.waitForElementNotPresent('.highlightLine41', 60000)
.waitForElementNotPresent('.highlightLine51', 60000)
},
'Should display the context view #group2': function (browser: NightwatchBrowser) {
browser
.openFile('contracts')
.openFile('contracts/1_Storage.sol')
.waitForElementVisible('#editorView')
.setEditorValue(storageContractWithError)
.pause(2000)
.execute(() => {
(document.getElementById('editorView') as any).gotoLine(17, 16)
}, [], () => {})
.waitForElementVisible('.contextview')
.waitForElementContainsText('.contextview .type', 'FunctionDefinition')
.waitForElementContainsText('.contextview .name', 'store')
.execute(() => {
(document.getElementById('editorView') as any).gotoLine(18, 12)
}, [], () => {})
.waitForElementContainsText('.contextview .type', 'uint256')
.waitForElementContainsText('.contextview .name', 'number')
.click('.contextview [data-action="previous"]') // declaration
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '180')
})
.click('.contextview [data-action="next"]') // back to the initial state
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '323')
})
.click('.contextview [data-action="next"]') // next reference
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '489')
})
.click('.contextview [data-action="gotoref"]') // back to the declaration
.execute(() => {
return (document.getElementById('editorView') as any).getCursorPosition()
}, [], (result) => {
console.log('result', result)
browser.assert.equal(result.value, '180')
})
.end()
}
}

@ -4,7 +4,7 @@ import init from '../helpers/init'
import * as path from 'path'
const testData = {
testFile1: path.resolve(__dirname + '/editor.spec.js'), // eslint-disable-line
testFile1: path.resolve(__dirname + '/editor.test.js'), // eslint-disable-line
testFile2: path.resolve(__dirname + '/fileExplorer.test.js'), // eslint-disable-line
testFile3: path.resolve(__dirname + '/generalSettings.test.js') // eslint-disable-line
}
@ -105,7 +105,7 @@ module.exports = {
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile1)
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile2)
.setValue('*[data-id="fileExplorerFileUpload"]', testData.testFile3)
.waitForElementVisible('[data-id="treeViewLitreeViewItemeditor.spec.js"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemeditor.test.js"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemfileExplorer.test.js"]')
.waitForElementVisible('[data-id="treeViewLitreeViewItemgeneralSettings.test.js"]')
.end()

@ -15,7 +15,7 @@ import { FramingService } from './framingService'
import { WalkthroughService } from './walkthroughService'
import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports } from '@remix-project/core-plugin'
import { OffsetToLineColumnConverter, CompilerMetadata, CompilerArtefacts, FetchAndCompile, CompilerImports, EditorContextListener } from '@remix-project/core-plugin'
import migrateFileSystem from './migrateFileSystem'
@ -48,7 +48,6 @@ const TestTab = require('./app/tabs/test-tab')
const FilePanel = require('./app/panels/file-panel')
const Editor = require('./app/editor/editor')
const Terminal = require('./app/panels/terminal')
const ContextualListener = require('./app/editor/contextualListener')
class AppComponent {
constructor (api = {}, events = {}, opts = {}) {
@ -156,7 +155,7 @@ class AppComponent {
}
}
)
const contextualListener = new ContextualListener({ editor })
const contextualListener = new EditorContextListener()
self.engine.register([
blockchain,

@ -1,194 +0,0 @@
'use strict'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
const yo = require('yo-yo')
const globalRegistry = require('../../global/registry')
const css = require('./styles/contextView-styles')
/*
Display information about the current focused code:
- if it's a reference, display information about the declaration
- jump to the declaration
- number of references
- rename declaration/references
*/
class ContextView {
constructor (opts, localRegistry) {
this._components = {}
this._components.registry = localRegistry || globalRegistry
this.contextualListener = opts.contextualListener
this.editor = opts.editor
this._deps = {
compilersArtefacts: this._components.registry.get('compilersartefacts').api,
offsetToLineColumnConverter: this._components.registry.get('offsettolinecolumnconverter').api,
config: this._components.registry.get('config').api,
fileManager: this._components.registry.get('filemanager').api
}
this._view = null
this._nodes = null
this._current = null
this.sourceMappingDecoder = sourceMappingDecoder
this.previousElement = null
this.contextualListener.event.register('contextChanged', nodes => {
this.show()
this._nodes = nodes
this.update()
})
this.contextualListener.event.register('stopHighlighting', () => {
})
}
render () {
const view = yo`
<div class="${css.contextview} ${css.contextviewcontainer} bg-light text-dark border-0">
<div class=${css.container}>
${this._renderTarget()}
</div>
</div>`
if (!this._view) {
this._view = view
}
return view
}
hide () {
if (this._view) {
this._view.style.display = 'none'
}
}
show () {
if (this._view) {
this._view.style.display = 'block'
}
}
update () {
if (this._view) {
yo.update(this._view, this.render())
}
}
_renderTarget () {
let last
const previous = this._current
if (this._nodes && this._nodes.length) {
last = this._nodes[this._nodes.length - 1]
if (isDefinition(last)) {
this._current = last
} else {
const target = this.contextualListener.declarationOf(last)
if (target) {
this._current = target
} else {
this._current = null
}
}
}
if (!this._current || !previous || previous.id !== this._current.id || (this.previousElement && !this.previousElement.children.length)) {
this.previousElement = this._render(this._current, last)
}
return this.previousElement
}
_jumpToInternal (position) {
const jumpToLine = (lineColumn) => {
if (lineColumn.start && lineColumn.start.line && lineColumn.start.column) {
this.editor.gotoLine(lineColumn.start.line, lineColumn.end.column + 1)
}
}
const lastCompilationResult = this._deps.compilersArtefacts.__last
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) {
const lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn(
position,
position.file,
lastCompilationResult.getSourceCode().sources,
lastCompilationResult.getAsts())
const filename = lastCompilationResult.getSourceName(position.file)
// TODO: refactor with rendererAPI.errorClick
if (filename !== this._deps.config.get('currentFile')) {
const provider = this._deps.fileManager.fileProviderOf(filename)
if (provider) {
provider.exists(filename).then(exist => {
this._deps.fileManager.open(filename)
jumpToLine(lineColumn)
}).catch(error => {
if (error) return console.log(error)
})
}
} else {
jumpToLine(lineColumn)
}
}
}
_render (node, nodeAtCursorPosition) {
if (!node) return yo`<div></div>`
let references = this.contextualListener.referencesOf(node)
const type = node.typeDescriptions && node.typeDescriptions.typeString ? node.typeDescriptions.typeString : node.nodeType
references = `${references ? references.length : '0'} reference(s)`
let ref = 0
const nodes = this.contextualListener.getActiveHighlights()
for (const k in nodes) {
if (nodeAtCursorPosition.id === nodes[k].nodeId) {
ref = k
break
}
}
// JUMP BETWEEN REFERENCES
const jump = (e) => {
e.target.dataset.action === 'next' ? ref++ : ref--
if (ref < 0) ref = nodes.length - 1
if (ref >= nodes.length) ref = 0
this._jumpToInternal(nodes[ref].position)
}
const jumpTo = () => {
if (node && node.src) {
const position = this.sourceMappingDecoder.decode(node.src)
if (position) {
this._jumpToInternal(position)
}
}
}
const showGasEstimation = () => {
if (node.nodeType === 'FunctionDefinition') {
const result = this.contextualListener.gasEstimation(node)
const executionCost = ' Execution cost: ' + result.executionCost + ' gas'
const codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas'
const estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}`
return yo`
<div class=${css.gasEstimation}>
<i class="fas fa-gas-pump ${css.gasStationIcon}" title='Gas estimation'></i>
<span>${estimatedGas}</span>
</div>
`
}
}
return yo`
<div class=${css.line}>${showGasEstimation()}
<div title=${type} class=${css.type}>${type}</div>
<div title=${node.name} class=${css.name}>${node.name}</div>
<i class="fas fa-share ${css.jump}" aria-hidden="true" onclick=${jumpTo}></i>
<span class=${css.referencesnb}>${references}</span>
<i data-action='previous' class="fas fa-chevron-up ${css.jump}" aria-hidden="true" onclick=${jump}></i>
<i data-action='next' class="fas fa-chevron-down ${css.jump}" aria-hidden="true" onclick=${jump}></i>
</div>
`
}
}
function isDefinition (node) {
return node.nodeType === 'ContractDefinition' ||
node.nodeType === 'FunctionDefinition' ||
node.nodeType === 'ModifierDefinition' ||
node.nodeType === 'VariableDeclaration' ||
node.nodeType === 'StructDefinition' ||
node.nodeType === 'EventDefinition'
}
module.exports = ContextView

@ -12,7 +12,7 @@ const profile = {
name: 'editor',
description: 'service - editor',
version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addAnnotation', 'gotoLine']
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addAnnotation', 'gotoLine', 'getCursorPosition']
}
class Editor extends Plugin {
@ -75,7 +75,8 @@ class Editor extends Plugin {
this._onChange(this.currentFile)
}
}
this.el.gotoLine = (line) => this.gotoLine(line, 0)
this.el.gotoLine = (line, column) => this.gotoLine(line, column || 0)
this.el.getCursorPosition = () => this.getCursorPosition()
return this.el
}

@ -1,11 +1,14 @@
import React from 'react' // eslint-disable-line
import ReactDOM from 'react-dom'
import { RemixUiEditorContextView } from '@remix-ui/editor-context-view'
var yo = require('yo-yo')
var EventManager = require('../../lib/events')
var globalRegistry = require('../../global/registry')
var { TabProxy } = require('./tab-proxy.js')
var ContextView = require('../editor/contextView')
var csjs = require('csjs-inject')
var css = csjs`
@ -15,6 +18,10 @@ var css = csjs`
height : 100%;
width : 100%;
}
.contextview {
opacity : 1;
position : relative;
}
`
// @todo(#650) Extract this into two classes: MainPanel (TabsProxy + Iframe/Editor) & BottomPanel (Terminal)
@ -25,12 +32,13 @@ export class MainView {
self._view = {}
self._components = {}
self._components.registry = globalRegistry
self.contextualListener = contextualListener
self.hideContextView = false
self.editor = editor
self.fileManager = fileManager
self.mainPanel = mainPanel
self.txListener = globalRegistry.get('txlistener').api
self._components.terminal = terminal
self._components.contextualListener = contextualListener
this.appManager = appManager
this.init()
}
@ -39,7 +47,8 @@ export class MainView {
this.fileManager.unselectCurrentFile()
this.mainPanel.showContent(name)
this._view.editor.style.display = 'none'
this._components.contextView.hide()
this.hideContextView = true
this.renderContextView()
this._view.mainPanel.style.display = 'block'
}
@ -63,19 +72,22 @@ export class MainView {
// we check upstream for "fileChanged"
self._view.editor.style.display = 'block'
self._view.mainPanel.style.display = 'none'
self._components.contextView.show()
this.hideContextView = false
this.renderContextView()
})
self.tabProxy.event.on('openFile', (file) => {
self._view.editor.style.display = 'block'
self._view.mainPanel.style.display = 'none'
self._components.contextView.show()
this.hideContextView = false
this.renderContextView()
})
self.tabProxy.event.on('closeFile', (file) => {
})
self.tabProxy.event.on('switchApp', self.showApp.bind(self))
self.tabProxy.event.on('closeApp', (name) => {
self._view.editor.style.display = 'block'
self._components.contextView.show()
this.hideContextView = false
this.renderContextView()
self._view.mainPanel.style.display = 'none'
})
self.tabProxy.event.on('tabCountChanged', (count) => {
@ -90,10 +102,6 @@ export class MainView {
}
}
const contextView = new ContextView({ contextualListener: self._components.contextualListener, editor: self.editor })
self._components.contextView = contextView
self._components.terminal.event.register('resize', delta => self._adjustLayout('top', delta))
if (self.txListener) {
self._components.terminal.event.register('listenOnNetWork', (listenOnNetWork) => {
@ -181,15 +189,17 @@ export class MainView {
self._view.editor.style.display = 'none'
self._view.mainPanel = self.mainPanel.render()
self._view.terminal = self._components.terminal.render()
self._view.mainview = yo`
<div class=${css.mainview}>
${self.tabProxy.renderTabsbar()}
${self._view.editor}
${self._view.mainPanel}
${self._components.contextView.render()}
<div class="${css.contextview} contextview"></div>
${self._view.terminal}
</div>
`
// INIT
self._adjustLayout('top', self.data._layout.top.offset)
@ -200,6 +210,22 @@ export class MainView {
return self._view.mainview
}
renderContextView () {
if (!this.contextualListener.activated) return
ReactDOM.render(
<RemixUiEditorContextView
hide={this.hideContextView}
contextualListener={this.contextualListener}
gotoLine={(line, column) => this.contextualListener.call('editor', 'gotoLine', line, column)}
openFile={(file) => this.contextualListener.call('editor', 'openFile', file)}
getLastCompilationResult={_ => { return this.contextualListener.call('compilerArtefacts', 'getLastCompilationResult') } }
offsetToLineColumn={(position, file, sources, asts) => { return this.contextualListener.call('offsetToLineColumnConverter', 'offsetToLineColumn', position, file, sources, asts) } }
getCurrentFileName={() => { return this.contextualListener.call('fileManager', 'file') } }
/>
, this._view.mainview.querySelector('.contextview'))
}
registerCommand (name, command, opts) {
var self = this
return self._components.terminal.registerCommand(name, command, opts)

@ -3,3 +3,4 @@ export { CompilerMetadata } from './lib/compiler-metadata'
export { FetchAndCompile } from './lib/compiler-fetch-and-compile'
export { CompilerImports } from './lib/compiler-content-imports'
export { CompilerArtefacts } from './lib/compiler-artefacts'
export { EditorContextListener } from './lib/editor-context-listener'

@ -4,7 +4,7 @@ import { CompilerAbstract } from '@remix-project/remix-solidity'
const profile = {
name: 'compilerArtefacts',
methods: ['get', 'addResolvedContract', 'getCompilerAbstract', 'getAllContractDatas'],
methods: ['get', 'addResolvedContract', 'getCompilerAbstract', 'getAllContractDatas', 'getLastCompilationResult'],
events: [],
version: '0.0.1'
}
@ -59,6 +59,10 @@ export class CompilerArtefacts extends Plugin {
})
}
getLastCompilationResult () {
return this.compilersArtefacts.__last
}
getAllContractDatas () {
const contractsData = {}
Object.keys(this.compilersArtefactsPerFile).map((targetFile) => {

@ -1,47 +1,48 @@
'use strict'
import { Plugin } from '@remixproject/engine'
import * as packageJson from '../../../../../package.json'
import { sourceMappingDecoder } from '@remix-project/remix-debug'
const { AstWalker } = require('@remix-project/remix-astwalker')
const EventManager = require('../../lib/events')
const globalRegistry = require('../../global/registry')
const profile = {
name: 'contextualListener',
methods: [],
events: [],
version: packageJson.version
version: '0.0.1'
}
/*
trigger contextChanged(nodes)
*/
class ContextualListener extends Plugin {
constructor (opts) {
export class EditorContextListener extends Plugin {
_index: any
_activeHighlights: Array<any>
astWalker: any
currentPosition: any
currentFile: String
nodes: Array<any>
results: any
estimationObj: any
creationCost: any
codeDepositCost: any
contract: any
activated: boolean
constructor () {
super(profile)
this.event = new EventManager()
this._components = {}
this._components.registry = globalRegistry
this.editor = opts.editor
this.pluginManager = opts.pluginManager
this._deps = {
compilersArtefacts: this._components.registry.get('compilersartefacts').api,
config: this._components.registry.get('config').api,
offsetToLineColumnConverter: this._components.registry.get('offsettolinecolumnconverter').api
}
this.activated = false
this._index = {
Declarations: {},
FlatReferences: {}
}
this._activeHighlights = []
this.editor.event.register('contentChanged', () => { this._stopHighlighting() })
this.sourceMappingDecoder = sourceMappingDecoder
this.astWalker = new AstWalker()
}
onActivation () {
this.on('editor', 'contentChanged', () => { this._stopHighlighting() })
this.on('solidity', 'compilationFinished', (file, source, languageVersion, data) => {
if (languageVersion.indexOf('soljson') !== 0) return
this._stopHighlighting()
@ -52,11 +53,18 @@ class ContextualListener extends Plugin {
this._buildIndex(data, source)
})
setInterval(() => {
if (this._deps.compilersArtefacts.__last && this._deps.compilersArtefacts.__last.languageversion.indexOf('soljson') === 0) {
this._highlightItems(this.editor.getCursorPosition(), this._deps.compilersArtefacts.__last, this._deps.config.get('currentFile'))
setInterval(async () => {
const compilationResult = await this.call('compilerArtefacts', 'getLastCompilationResult')
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) {
this._highlightItems(
await this.call('editor', 'getCursorPosition'),
compilationResult,
await this.call('fileManager', 'file')
)
}
}, 1000)
this.activated = true
}
getActiveHighlights () {
@ -74,7 +82,7 @@ class ContextualListener extends Plugin {
return this._index.Declarations[node.id]
}
_highlightItems (cursorPosition, compilationResult, file) {
async _highlightItems (cursorPosition, compilationResult, file) {
if (this.currentPosition === cursorPosition) return
if (this.currentFile !== file) {
this.currentFile = file
@ -85,12 +93,12 @@ class ContextualListener extends Plugin {
this.currentPosition = cursorPosition
this.currentFile = file
if (compilationResult && compilationResult.data && compilationResult.data.sources[file]) {
const nodes = this.sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[file])
const nodes = sourceMappingDecoder.nodesAtPosition(null, cursorPosition, compilationResult.data.sources[file])
this.nodes = nodes
if (nodes && nodes.length && nodes[nodes.length - 1]) {
this._highlightExpressions(nodes[nodes.length - 1], compilationResult)
await this._highlightExpressions(nodes[nodes.length - 1], compilationResult)
}
this.event.trigger('contextChanged', [nodes])
this.emit('contextChanged', nodes)
}
}
@ -111,21 +119,19 @@ class ContextualListener extends Plugin {
}
}
_highlight (node, compilationResult) {
async _highlight (node, compilationResult) {
if (!node) return
const position = this.sourceMappingDecoder.decode(node.src)
const eventId = this._highlightInternal(position, node)
const lastCompilationResult = this._deps.compilersArtefacts.__last
if (eventId && lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0) {
this._activeHighlights.push({ eventId, position, fileTarget: lastCompilationResult.getSourceName(position.file), nodeId: node.id })
const position = sourceMappingDecoder.decode(node.src)
await this._highlightInternal(position, node, compilationResult)
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) {
this._activeHighlights.push({ position, fileTarget: compilationResult.getSourceName(position.file), nodeId: node.id })
}
}
_highlightInternal (position, node) {
async _highlightInternal (position, node, compilationResult) {
if (node.nodeType === 'Block') return
const lastCompilationResult = this._deps.compilersArtefacts.__last
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0) {
let lineColumn = this._deps.offsetToLineColumnConverter.offsetToLineColumn(position, position.file, lastCompilationResult.getSourceCode().sources, lastCompilationResult.getAsts())
if (compilationResult && compilationResult.languageversion.indexOf('soljson') === 0) {
let lineColumn = await this.call('offsetToLineColumnConverter', 'offsetToLineColumn', position, position.file, compilationResult.getSourceCode().sources, compilationResult.getAsts())
if (node.nodes && node.nodes.length) {
// If node has children, highlight the entire line. if not, just highlight the current source position of the node.
lineColumn = {
@ -139,38 +145,38 @@ class ContextualListener extends Plugin {
}
}
}
const fileName = lastCompilationResult.getSourceName(position.file)
const fileName = compilationResult.getSourceName(position.file)
if (fileName) {
return this.call('editor', 'highlight', lineColumn, fileName, '', { focus: false })
return await this.call('editor', 'highlight', lineColumn, fileName, '', { focus: false })
}
}
return null
}
_highlightExpressions (node, compilationResult) {
const highlights = (id) => {
async _highlightExpressions (node, compilationResult) {
const highlights = async (id) => {
if (this._index.Declarations && this._index.Declarations[id]) {
const refs = this._index.Declarations[id]
for (const ref in refs) {
const node = refs[ref]
this._highlight(node, compilationResult)
await this._highlight(node, compilationResult)
}
}
}
if (node && node.referencedDeclaration) {
highlights(node.referencedDeclaration)
await highlights(node.referencedDeclaration)
const current = this._index.FlatReferences[node.referencedDeclaration]
this._highlight(current, compilationResult)
await this._highlight(current, compilationResult)
} else {
highlights(node.id)
this._highlight(node, compilationResult)
await highlights(node.id)
await this._highlight(node, compilationResult)
}
this.results = compilationResult
}
_stopHighlighting () {
this.call('editor', 'discardHighlight')
this.event.trigger('stopHighlighting', [])
this.emit('stopHighlighting')
this._activeHighlights = []
}
@ -229,5 +235,3 @@ class ContextualListener extends Plugin {
return '(' + params.toString() + ')'
}
}
module.exports = ContextualListener

@ -0,0 +1,4 @@
{
"presets": ["@nrwl/react/babel"],
"plugins": []
}

@ -0,0 +1,19 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": "../../../.eslintrc",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}

@ -0,0 +1,7 @@
# remix-ui-editor-context-view
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test remix-ui-editor-context-view` to execute the unit tests via [Jest](https://jestjs.io).

@ -0,0 +1 @@
export * from './lib/remix-ui-editor-context-view';

@ -1,17 +1,9 @@
var csjs = require('csjs-inject')
var css = csjs`
.contextview {
opacity : 1;
position : relative;
height : 25px;
}
.container {
.container-context-view {
padding : 1px 15px;
}
.line {
display : flex;
justify-content : flex-end;
align-items : center;
text-overflow : ellipsis;
overflow : hidden;
@ -48,12 +40,4 @@ var css = csjs`
z-index : 50;
border-radius : 1px;
border : 2px solid var(--secondary);
}
.contextviewcontainer{
z-index : 50;
border-radius : 1px;
border : 2px solid var(--secondary);
}
`
module.exports = css
}

@ -0,0 +1,159 @@
import React, { useEffect, useState, useRef } from 'react' // eslint-disable-line
import { sourceMappingDecoder } from '@remix-project/remix-debug'
import './remix-ui-editor-context-view.css';
/* eslint-disable-next-line */
export interface RemixUiEditorContextViewProps {
hide: boolean,
contextualListener: any,
gotoLine: (line: number, column: number) => void,
openFile: (fileName: string) => void,
getLastCompilationResult: () => any,
offsetToLineColumn: (position: any, file: any, sources: any, asts: any) => any,
getCurrentFileName: () => String
}
function isDefinition (node: any) {
return node.nodeType === 'ContractDefinition' ||
node.nodeType === 'FunctionDefinition' ||
node.nodeType === 'ModifierDefinition' ||
node.nodeType === 'VariableDeclaration' ||
node.nodeType === 'StructDefinition' ||
node.nodeType === 'EventDefinition'
}
type astNode = {
name: string,
id: number,
children: Array<any>,
typeDescriptions: any,
nodeType: String,
src: any,
nodeId: any,
position: any
}
type nullableAstNode = astNode | null
export function RemixUiEditorContextView(props: RemixUiEditorContextViewProps) {
const nodesRef = useRef<Array<astNode>>([])
/*
gotoLineDisableRef is used to temporarily disable the update of the view.
e.g when the user ask the component to "gotoLine" we don't want to rerender the component (but just to put the mouse on the desired line)
*/
const gotoLineDisableRef = useRef(false)
const [nodesState, setNode] = useState<Array<astNode>>([])
const contextualListener = props.contextualListener
useEffect(() => {
contextualListener.on('contextualListener', 'contextChanged', (nodes: Array<astNode>) => {
if (gotoLineDisableRef.current) {
gotoLineDisableRef.current = false
return
}
nodesRef.current = nodes
setNode(nodes)
})
}, [])
const _render = (node: nullableAstNode) => {
if (!node) return (<div></div>)
let references = contextualListener.referencesOf(node)
const type = node.typeDescriptions && node.typeDescriptions.typeString ? node.typeDescriptions.typeString : node.nodeType
references = `${references ? references.length : '0'} reference(s)`
let ref = 0
const nodes: Array<astNode> = contextualListener.getActiveHighlights()
/*
* show gas estimation
*/
const gasEstimation = () => {
if (node.nodeType === 'FunctionDefinition') {
const result = contextualListener.gasEstimation(node)
const executionCost = ' Execution cost: ' + result.executionCost + ' gas'
const codeDepositCost = 'Code deposit cost: ' + result.codeDepositCost + ' gas'
const estimatedGas = result.codeDepositCost ? `${codeDepositCost}, ${executionCost}` : `${executionCost}`
return (
<div className="gasEstimation">
<i className="fas fa-gas-pump gasStationIcon" title='Gas estimation'></i>
<span>{estimatedGas}</span>
</div>
)
} else {
return (<div></div>)
}
}
/*
* onClick jump to ast node in the editor
*/
const _jumpToInternal = async (position: any) => {
const jumpToLine = async (fileName: string, lineColumn: any) => {
if (fileName !== await props.getCurrentFileName()) {
await props.openFile(fileName)
}
if (lineColumn.start && lineColumn.start.line && lineColumn.start.column) {
gotoLineDisableRef.current = true
props.gotoLine(lineColumn.start.line, lineColumn.end.column + 1)
}
}
const lastCompilationResult = await props.getLastCompilationResult()
if (lastCompilationResult && lastCompilationResult.languageversion.indexOf('soljson') === 0 && lastCompilationResult.data) {
const lineColumn = await props.offsetToLineColumn(
position,
position.file,
lastCompilationResult.getSourceCode().sources,
lastCompilationResult.getAsts())
const filename = lastCompilationResult.getSourceName(position.file)
// TODO: refactor with rendererAPI.errorClick
jumpToLine(filename, lineColumn)
}
}
const jumpTo = () => {
if (node && node.src) {
const position = sourceMappingDecoder.decode(node.src)
if (position) {
_jumpToInternal(position)
}
}
}
// JUMP BETWEEN REFERENCES
const jump = (e: any) => {
e.target.dataset.action === 'next' ? ref++ : ref--
if (ref < 0) ref = nodes.length - 1
if (ref >= nodes.length) ref = 0
_jumpToInternal(nodes[ref].position)
}
return (
<div className="line">{gasEstimation()}
<div title={type} className="type">{type}</div>
<div title={node.name} className="name mr-2">{node.name}</div>
<i className="fas fa-share jump" data-action='gotoref' aria-hidden="true" onClick={jumpTo}></i>
<span className="referencesnb">{references}</span>
<i data-action='previous' className="fas fa-chevron-up jump" aria-hidden="true" onClick={jump}></i>
<i data-action='next' className="fas fa-chevron-down jump" aria-hidden="true" onClick={jump}></i>
</div>
)
}
let last: nullableAstNode = null
if (!props.hide && nodesRef.current && nodesRef.current.length) {
last = nodesRef.current[nodesRef.current.length - 1]
if (!isDefinition(last)) {
last = contextualListener.declarationOf(last)
}
}
return (
!props.hide && <div className="container-context-view contextviewcontainer bg-light text-dark border-0">
{_render(last)}
</div>
);
}
export default RemixUiEditorContextView

@ -0,0 +1,16 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react",
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

@ -0,0 +1,13 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"types": ["node"]
},
"files": [
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
"../../../node_modules/@nrwl/react/typings/image.d.ts"
],
"exclude": ["**/*.spec.ts", "**/*.spec.tsx"],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

@ -147,6 +147,9 @@
},
"remix-ui-theme-module": {
"tags": []
},
"remix-ui-editor-context-view": {
"tags": []
}
},
"targetDependencies": {

@ -69,10 +69,9 @@
"@remix-ui/tabs": ["libs/remix-ui/tabs/src/index.ts"],
"@remix-ui/helper": ["libs/remix-ui/helper/src/index.ts"],
"@remix-ui/app": ["libs/remix-ui/app/src/index.ts"],
"@remix-ui/vertical-icons-panel": [
"libs/remix-ui/vertical-icons-panel/src/index.ts"
],
"@remix-ui/theme-module": ["libs/remix-ui/theme-module/src/index.ts"]
"@remix-ui/vertical-icons-panel": ["libs/remix-ui/vertical-icons-panel/src/index.ts"],
"@remix-ui/theme-module": ["libs/remix-ui/theme-module/src/index.ts"],
"@remix-ui/editor-context-view": ["libs/remix-ui/editor-context-view/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]

@ -1116,80 +1116,48 @@
}
}
}
},
"remix-ui-editor-context-view": {
"root": "libs/remix-ui/editor-context-view",
"sourceRoot": "libs/remix-ui/editor-context-view/src",
"projectType": "library",
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/vertical-icons-panel/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "!libs/remix-ui/vertical-icons-panel/**/*"]
}
}
}
}
},
"remix-ui-editor": {
"root": "libs/remix-ui/editor",
"sourceRoot": "libs/remix-ui/editor/src",
"projectType": "library",
"schematics": {},
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"babel": true
},
"component": {
"style": "css"
},
"library": {
"style": "css",
"linter": "eslint"
}
},
"cli": {
"defaultCollection": "@nrwl/react"
},
"schematics": {
"@nrwl/workspace": {
"library": {
"linter": "eslint"
}
},
"@nrwl/nx-plugin": {
"plugin": {
"@nrwl/cypress": {
"cypress-project": {
"linter": "eslint"
}
},
"@nrwl/web": {
"application": {
"linter": "eslint"
}
},
"@nrwl/node": {
"application": {
"linter": "eslint"
},
"library": {
"linter": "eslint"
}
}
},
"cli": {
"defaultCollection": "@nrwl/react"
},
"schematics": {
"@nrwl/workspace": {
"library": {
"linter": "eslint"
}
},
"@nrwl/cypress": {
"cypress-project": {
"linter": "eslint"
}
"@nrwl/react": {
"application": {
"style": "css",
"linter": "eslint",
"babel": true
},
"@nrwl/react": {
"application": {
"style": "css",
"linter": "eslint",
"babel": true
},
"component": {
"style": "css"
},
"library": {
"style": "css",
"linter": "eslint"
}
"component": {
"style": "css"
},
"library": {
"style": "css",
"linter": "eslint"
}
},
@ -1199,4 +1167,6 @@
}
},
"defaultProject": "remix-ide"
}
}
}

Loading…
Cancel
Save