pull/1857/head
bunsenstraat 3 years ago
commit db728677f0
  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. 18
      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. 100
      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. 191
      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. 5
      libs/remix-ui/editor/src/lib/remix-ui-editor.css
  19. 46
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  20. 3
      nx.json
  21. 3
      tsconfig.base.json
  22. 100
      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
}

@ -4,8 +4,6 @@ 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`
@ -14,7 +12,7 @@ var css = csjs`
flex-direction : column;
height : 100%;
width : 100%;
}
}
`
// @todo(#650) Extract this into two classes: MainPanel (TabsProxy + Iframe/Editor) & BottomPanel (Terminal)
@ -25,12 +23,12 @@ export class MainView {
self._view = {}
self._components = {}
self._components.registry = globalRegistry
self.contextualListener = contextualListener
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 +37,6 @@ export class MainView {
this.fileManager.unselectCurrentFile()
this.mainPanel.showContent(name)
this._view.editor.style.display = 'none'
this._components.contextView.hide()
this._view.mainPanel.style.display = 'block'
}
@ -63,19 +60,16 @@ export class MainView {
// we check upstream for "fileChanged"
self._view.editor.style.display = 'block'
self._view.mainPanel.style.display = 'none'
self._components.contextView.show()
})
self.tabProxy.event.on('openFile', (file) => {
self._view.editor.style.display = 'block'
self._view.mainPanel.style.display = 'none'
self._components.contextView.show()
})
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()
self._view.mainPanel.style.display = 'none'
})
self.tabProxy.event.on('tabCountChanged', (count) => {
@ -90,10 +84,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 +171,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)

@ -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: [],
methods: ['referencesOf', 'getActiveHighlights', 'gasEstimation', 'declarationOf'],
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,191 @@
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 type astNode = {
name: string,
id: number,
children: Array<any>,
typeDescriptions: any,
nodeType: String,
src: any,
nodeId: any,
position: any
}
export type onContextListenerChangedListener = (nodes: Array<astNode>) => void
export type gasEstimationType = {
executionCost: string,
codeDepositCost: string
}
export interface RemixUiEditorContextViewProps {
hide: boolean,
gotoLine: (line: number, column: number) => void,
openFile: (fileName: string) => void,
getLastCompilationResult: () => any,
offsetToLineColumn: (position: any, file: any, sources: any, asts: any) => any,
getCurrentFileName: () => String
onContextListenerChanged: (listener: onContextListenerChangedListener) => void
referencesOf: (nodes: astNode) => Array<astNode>
getActiveHighlights: () => Array<astNode>
gasEstimation: (node: astNode) => gasEstimationType
declarationOf: (node: astNode) => astNode
}
function isDefinition (node: any) {
return node.nodeType === 'ContractDefinition' ||
node.nodeType === 'FunctionDefinition' ||
node.nodeType === 'ModifierDefinition' ||
node.nodeType === 'VariableDeclaration' ||
node.nodeType === 'StructDefinition' ||
node.nodeType === 'EventDefinition'
}
type nullableAstNode = astNode | null
export function RemixUiEditorContextView (props: RemixUiEditorContextViewProps) {
/*
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 [state, setState] = useState<{
nodes: Array<astNode>,
references: Array<astNode>,
activeHighlights: Array<any>
currentNode: nullableAstNode,
gasEstimation: gasEstimationType
}>({
nodes: [],
references: [],
activeHighlights: [],
currentNode: null,
gasEstimation: { executionCost: '', codeDepositCost: '' }
})
useEffect(() => {
props.onContextListenerChanged(async (nodes: Array<astNode>) => {
if (gotoLineDisableRef.current) {
gotoLineDisableRef.current = false
return
}
let currentNode
if (!props.hide && nodes && nodes.length) {
currentNode = nodes[nodes.length - 1]
if (!isDefinition(currentNode)) {
currentNode = await props.declarationOf(currentNode)
}
}
let references
let gasEstimation
if (currentNode) {
references = await props.referencesOf(currentNode)
if (currentNode.nodeType === 'FunctionDefinition') {
gasEstimation = await props.gasEstimation(currentNode)
}
}
let activeHighlights = await props.getActiveHighlights()
setState(prevState => {
return { ...prevState, nodes, references, activeHighlights, currentNode, gasEstimation }
})
})
}, [])
/*
* show gas estimation
*/
const gasEstimation = (node) => {
if (node.nodeType === 'FunctionDefinition') {
const result: gasEstimationType = state.gasEstimation
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 _render = (node: nullableAstNode) => {
if (!node) return (<div></div>)
const references = state.references
const type = node.typeDescriptions && node.typeDescriptions.typeString ? node.typeDescriptions.typeString : node.nodeType
const referencesCount = `${references ? references.length : '0'} reference(s)`
let ref = 0
const nodes: Array<astNode> = state.activeHighlights
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(node)}
<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">{referencesCount}</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>
)
}
return (
!props.hide && <div className="container-context-view contextviewcontainer bg-light text-dark border-0 py-1">
{_render(state.currentNode)}
</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"]
}

@ -8,4 +8,9 @@
border-radius : 10px;
height: auto;
width: auto;
}
.contextview {
opacity: 1;
position: absolute;
}

@ -1,4 +1,5 @@
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import { RemixUiEditorContextView, astNode } from '@remix-ui/editor-context-view'
import Editor, { loader } from '@monaco-editor/react'
import { reducerActions, reducerListener, initialState } from './actions/editor'
import { language, conf } from './syntax'
@ -49,6 +50,7 @@ loader.config({ paths: { vs: 'assets/js/monaco-editor/dev/vs' } })
/* eslint-disable-next-line */
export interface EditorUIProps {
contextualListener: any
activated: boolean
themeType: string
currentFile: string
@ -62,6 +64,7 @@ export interface EditorUIProps {
}
plugin: {
on: (plugin: string, event: string, listener: any) => void
call: (plugin: string, method: string, arg1?: any, arg2?: any, arg3?: any, arg4?: any) => any
}
editorAPI: {
findMatches: (uri: string, value: string) => any
@ -207,7 +210,12 @@ export const EditorUI = (props: EditorUIProps) => {
'editor.lineHighlightBorder': secondaryColor,
'editor.lineHighlightBackground': textbackground === darkColor ? lightColor : secondaryColor,
'editorGutter.background': lightColor,
'minimap.background': lightColor
'minimap.background': lightColor,
'menu.foreground': textColor,
'menu.background': textbackground,
'menu.selectionBackground': secondaryColor,
'menu.selectionForeground': textColor,
'menu.selectionBorder': secondaryColor
}
})
monacoRef.current.editor.setTheme(themeName)
@ -256,7 +264,7 @@ export const EditorUI = (props: EditorUIProps) => {
range: new monacoRef.current.Range(marker.position.start.line + 1, marker.position.start.column + 1, marker.position.end.line + 1, marker.position.end.column + 1),
options: {
isWholeLine,
inlineClassName: `bg-info highlightLine${marker.position.start.line + 1}`
inlineClassName: `alert-info highlightLine${marker.position.start.line + 1}`
}
})
}
@ -377,15 +385,31 @@ export const EditorUI = (props: EditorUIProps) => {
}
return (
<Editor
width="100%"
height="100%"
path={props.currentFile}
language={editorModelsState[props.currentFile] ? editorModelsState[props.currentFile].language : 'text'}
onMount={handleEditorDidMount}
beforeMount={handleEditorWillMount}
options={{ glyphMargin: true }}
/>
<div className="w-100 h-100 d-flex flex-column-reverse">
<Editor
width="100%"
path={props.currentFile}
language={editorModelsState[props.currentFile] ? editorModelsState[props.currentFile].language : 'text'}
onMount={handleEditorDidMount}
beforeMount={handleEditorWillMount}
options={{ glyphMargin: true }}
/>
<div className="contextview">
<RemixUiEditorContextView
hide={false}
gotoLine={(line, column) => props.plugin.call('editor', 'gotoLine', line, column)}
openFile={(file) => props.plugin.call('editor', 'openFile', file)}
getLastCompilationResult={() => { return props.plugin.call('compilerArtefacts', 'getLastCompilationResult') } }
offsetToLineColumn={(position, file, sources, asts) => { return props.plugin.call('offsetToLineColumnConverter', 'offsetToLineColumn', position, file, sources, asts) } }
getCurrentFileName={() => { return props.plugin.call('fileManager', 'file') } }
onContextListenerChanged={(listener) => { props.plugin.on('contextualListener', 'contextChanged', listener) }}
referencesOf={(node: astNode) => { return props.plugin.call('contextualListener', 'referencesOf', node) }}
getActiveHighlights={() => { return props.plugin.call('contextualListener', 'getActiveHighlights') }}
gasEstimation={(node: astNode) => { return props.plugin.call('contextualListener', 'gasEstimation', node) }}
declarationOf={(node: astNode) => { return props.plugin.call('contextualListener', 'declarationOf', node) }}
/>
</div>
</div>
)
}

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

@ -73,7 +73,8 @@
"libs/remix-ui/vertical-icons-panel/src/index.ts"
],
"@remix-ui/theme-module": ["libs/remix-ui/theme-module/src/index.ts"],
"@remix-ui/panel": ["libs/remix-ui/panel/src/index.ts"]
"@remix-ui/panel": ["libs/remix-ui/panel/src/index.ts"],
"@remix-ui/editor-context-view": ["libs/remix-ui/editor-context-view/src/index.ts"]
}
},
"exclude": ["node_modules", "tmp"]

@ -1131,80 +1131,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/editor-context-view/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "!libs/remix-ui/editor-context-view/**/*"]
}
}
}
}
},
"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/react": {
"application": {
"style": "css",
"linter": "eslint",
"babel": true
},
"@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
},
"component": {
"style": "css"
},
"library": {
"style": "css",
"linter": "eslint"
}
"component": {
"style": "css"
},
"library": {
"style": "css",
"linter": "eslint"
}
},
@ -1212,6 +1180,8 @@
"plugin": {
"linter": "eslint"
}
},
"defaultProject": "remix-ide"
}
}
},
"defaultProject": "remix-ide"
}

Loading…
Cancel
Save