|
|
|
import React, {useState, useRef, useEffect, useReducer} from 'react' // eslint-disable-line
|
|
|
|
import {isArray} from 'lodash'
|
|
|
|
import Editor, {loader, Monaco} from '@monaco-editor/react'
|
|
|
|
import {AlertModal} from '@remix-ui/app'
|
|
|
|
import {reducerActions, reducerListener, initialState} from './actions/editor'
|
|
|
|
import {
|
|
|
|
solidityTokensProvider,
|
|
|
|
solidityLanguageConfig
|
|
|
|
} from './syntaxes/solidity'
|
|
|
|
import {cairoTokensProvider, cairoLanguageConfig} from './syntaxes/cairo'
|
|
|
|
import {
|
|
|
|
zokratesTokensProvider,
|
|
|
|
zokratesLanguageConfig
|
|
|
|
} from './syntaxes/zokrates'
|
|
|
|
import {moveTokenProvider, moveLanguageConfig} from './syntaxes/move'
|
|
|
|
import {monacoTypes} from '@remix-ui/editor'
|
|
|
|
import {loadTypes} from './web-types'
|
|
|
|
import {retrieveNodesAtPosition} from './helpers/retrieveNodesAtPosition'
|
|
|
|
import {RemixHoverProvider} from './providers/hoverProvider'
|
|
|
|
import {RemixReferenceProvider} from './providers/referenceProvider'
|
|
|
|
import {RemixCompletionProvider} from './providers/completionProvider'
|
|
|
|
import {RemixHighLightProvider} from './providers/highlightProvider'
|
|
|
|
import {RemixDefinitionProvider} from './providers/definitionProvider'
|
|
|
|
import {RemixCodeActionProvider} from './providers/codeActionProvider'
|
|
|
|
import './remix-ui-editor.css'
|
|
|
|
import {circomLanguageConfig, circomTokensProvider} from './syntaxes/circom'
|
|
|
|
import {IPosition} from 'monaco-editor'
|
|
|
|
|
|
|
|
enum MarkerSeverity {
|
|
|
|
Hint = 1,
|
|
|
|
Info = 2,
|
|
|
|
Warning = 4,
|
|
|
|
Error = 8
|
|
|
|
}
|
|
|
|
|
|
|
|
type sourceAnnotation = {
|
|
|
|
row: number
|
|
|
|
column: number
|
|
|
|
text: string
|
|
|
|
type: 'error' | 'warning' | 'info'
|
|
|
|
hide: boolean
|
|
|
|
from: string // plugin name
|
|
|
|
}
|
|
|
|
|
|
|
|
type sourceMarker = {
|
|
|
|
position: {
|
|
|
|
start: {
|
|
|
|
line: number
|
|
|
|
column: number
|
|
|
|
}
|
|
|
|
end: {
|
|
|
|
line: number
|
|
|
|
column: number
|
|
|
|
}
|
|
|
|
}
|
|
|
|
from: string // plugin name
|
|
|
|
hide: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
export type lineText = {
|
|
|
|
position: {
|
|
|
|
start: {
|
|
|
|
line: number
|
|
|
|
column: number
|
|
|
|
}
|
|
|
|
end: {
|
|
|
|
line: number
|
|
|
|
column: number
|
|
|
|
}
|
|
|
|
}
|
|
|
|
from?: string // plugin name
|
|
|
|
content: string
|
|
|
|
className: string
|
|
|
|
afterContentClassName: string
|
|
|
|
hide: boolean
|
|
|
|
hoverMessage: monacoTypes.IMarkdownString | monacoTypes.IMarkdownString[]
|
|
|
|
}
|
|
|
|
|
|
|
|
type errorMarker = {
|
|
|
|
message: string
|
|
|
|
severity: monacoTypes.MarkerSeverity | 'warning' | 'info' | 'error' | 'hint'
|
|
|
|
position: {
|
|
|
|
start: {
|
|
|
|
line: number
|
|
|
|
column: number
|
|
|
|
}
|
|
|
|
end: {
|
|
|
|
line: number
|
|
|
|
column: number
|
|
|
|
}
|
|
|
|
}
|
|
|
|
file: string
|
|
|
|
}
|
|
|
|
|
|
|
|
loader.config({paths: {vs: 'assets/js/monaco-editor/min/vs'}})
|
|
|
|
|
|
|
|
export type DecorationsReturn = {
|
|
|
|
currentDecorations: Array<string>
|
|
|
|
registeredDecorations?: Array<any>
|
|
|
|
}
|
|
|
|
|
|
|
|
export type PluginType = {
|
|
|
|
on: (plugin: string, event: string, listener: any) => void
|
|
|
|
call: (
|
|
|
|
plugin: string,
|
|
|
|
method: string,
|
|
|
|
arg1?: any,
|
|
|
|
arg2?: any,
|
|
|
|
arg3?: any,
|
|
|
|
arg4?: any
|
|
|
|
) => any
|
|
|
|
}
|
|
|
|
|
|
|
|
export type EditorAPIType = {
|
|
|
|
findMatches: (uri: string, value: string) => any
|
|
|
|
getFontSize: () => number
|
|
|
|
getValue: (uri: string) => string
|
|
|
|
getCursorPosition: (offset?: boolean) => number | monacoTypes.IPosition
|
|
|
|
getHoverPosition: (position: monacoTypes.IPosition) => number
|
|
|
|
addDecoration: (
|
|
|
|
marker: sourceMarker,
|
|
|
|
filePath: string,
|
|
|
|
typeOfDecoration: string
|
|
|
|
) => DecorationsReturn
|
|
|
|
clearDecorationsByPlugin: (
|
|
|
|
filePath: string,
|
|
|
|
plugin: string,
|
|
|
|
typeOfDecoration: string,
|
|
|
|
registeredDecorations: any,
|
|
|
|
currentDecorations: any
|
|
|
|
) => DecorationsReturn
|
|
|
|
keepDecorationsFor: (
|
|
|
|
filePath: string,
|
|
|
|
plugin: string,
|
|
|
|
typeOfDecoration: string,
|
|
|
|
registeredDecorations: any,
|
|
|
|
currentDecorations: any
|
|
|
|
) => DecorationsReturn
|
|
|
|
addErrorMarker: (errors: errorMarker[], from: string) => void
|
|
|
|
clearErrorMarkers: (
|
|
|
|
sources: string[] | {[fileName: string]: any},
|
|
|
|
from: string
|
|
|
|
) => void
|
|
|
|
}
|
|
|
|
|
|
|
|
/* eslint-disable-next-line */
|
|
|
|
export interface EditorUIProps {
|
|
|
|
contextualListener: any
|
|
|
|
activated: boolean
|
|
|
|
themeType: string
|
|
|
|
currentFile: string
|
|
|
|
events: {
|
|
|
|
onBreakPointAdded: (file: string, line: number) => void
|
|
|
|
onBreakPointCleared: (file: string, line: number) => void
|
|
|
|
onDidChangeContent: (file: string) => void
|
|
|
|
onEditorMounted: () => void
|
|
|
|
}
|
|
|
|
plugin: PluginType
|
|
|
|
editorAPI: EditorAPIType
|
|
|
|
}
|
|
|
|
export const EditorUI = (props: EditorUIProps) => {
|
|
|
|
const [, setCurrentBreakpoints] = useState({})
|
|
|
|
const defaultEditorValue = `
|
|
|
|
\t\t\t\t\t\t\t ____ _____ __ __ ___ __ __ ___ ____ _____
|
|
|
|
\t\t\t\t\t\t\t| _ \\ | ____| | \\/ | |_ _| \\ \\/ / |_ _| | _ \\ | ____|
|
|
|
|
\t\t\t\t\t\t\t| |_) | | _| | |\\/| | | | \\ / | | | | | | | _|
|
|
|
|
\t\t\t\t\t\t\t| _ < | |___ | | | | | | / \\ | | | |_| | | |___
|
|
|
|
\t\t\t\t\t\t\t|_| \\_\\ |_____| |_| |_| |___| /_/\\_\\ |___| |____/ |_____|\n\n
|
|
|
|
\t\t\t\t\t\t\tKeyboard Shortcuts:\n
|
|
|
|
\t\t\t\t\t\t\t\tCTRL + S: Compile the current contract\n
|
|
|
|
\t\t\t\t\t\t\t\tCTRL + Shift + F : Open the File Explorer\n
|
|
|
|
\t\t\t\t\t\t\t\tCTRL + Shift + A : Open the Plugin Manager\n
|
|
|
|
\t\t\t\t\t\t\t\tCTRL + SHIFT + S: Compile the current contract & Run an associated script\n
|
|
|
|
\t\t\t\t\t\t\tEditor Keyboard Shortcuts:\n
|
|
|
|
\t\t\t\t\t\t\t\tCTRL + Alt + F : Format the code in the current file\n
|
|
|
|
\t\t\t\t\t\t\tImportant Links:\n
|
|
|
|
\t\t\t\t\t\t\t\tOfficial website about the Remix Project: https://remix-project.org/\n
|
|
|
|
\t\t\t\t\t\t\t\tOfficial documentation: https://remix-ide.readthedocs.io/en/latest/\n
|
|
|
|
\t\t\t\t\t\t\t\tGithub: https://github.com/ethereum/remix-project\n
|
|
|
|
\t\t\t\t\t\t\t\tGitter: https://gitter.im/ethereum/remix\n
|
|
|
|
\t\t\t\t\t\t\t\tMedium: https://medium.com/remix-ide\n
|
|
|
|
\t\t\t\t\t\t\t\tTwitter: https://twitter.com/ethereumremix\n
|
|
|
|
`
|
|
|
|
const pasteCodeRef = useRef(false)
|
|
|
|
const editorRef = useRef(null)
|
|
|
|
const monacoRef = useRef<Monaco>(null)
|
|
|
|
const currentFileRef = useRef('')
|
|
|
|
const currentUrlRef = useRef('')
|
|
|
|
// const currentDecorations = useRef({ sourceAnnotationsPerFile: {}, markerPerFile: {} }) // decorations that are currently in use by the editor
|
|
|
|
// const registeredDecorations = useRef({}) // registered decorations
|
|
|
|
|
|
|
|
const [editorModelsState, dispatch] = useReducer(reducerActions, initialState)
|
|
|
|
|
|
|
|
const formatColor = (name) => {
|
|
|
|
let color = window
|
|
|
|
.getComputedStyle(document.documentElement)
|
|
|
|
.getPropertyValue(name)
|
|
|
|
.trim()
|
|
|
|
if (color.length === 4) {
|
|
|
|
color = color.concat(color.substr(1))
|
|
|
|
}
|
|
|
|
return color
|
|
|
|
}
|
|
|
|
const defineAndSetTheme = (monaco) => {
|
|
|
|
const themeType = props.themeType === 'dark' ? 'vs-dark' : 'vs'
|
|
|
|
const themeName = props.themeType === 'dark' ? 'remix-dark' : 'remix-light'
|
|
|
|
// see https://microsoft.github.io/monaco-editor/playground.html#customizing-the-appearence-exposed-colors
|
|
|
|
const lightColor = formatColor('--light')
|
|
|
|
const infoColor = formatColor('--info')
|
|
|
|
const darkColor = formatColor('--dark')
|
|
|
|
const secondaryColor = formatColor('--secondary')
|
|
|
|
const primaryColor = formatColor('--primary')
|
|
|
|
const textColor = formatColor('--text') || darkColor
|
|
|
|
const textbackground = formatColor('--text-background') || lightColor
|
|
|
|
|
|
|
|
const blueColor = formatColor('--blue')
|
|
|
|
const successColor = formatColor('--success')
|
|
|
|
const warningColor = formatColor('--warning')
|
|
|
|
const yellowColor = formatColor('--yellow')
|
|
|
|
const pinkColor = formatColor('--pink')
|
|
|
|
const locationColor = '#9e7e08'
|
|
|
|
// const purpleColor = formatColor('--purple')
|
|
|
|
const dangerColor = formatColor('--danger')
|
|
|
|
const greenColor = formatColor('--green')
|
|
|
|
const orangeColor = formatColor('--orange')
|
|
|
|
const grayColor = formatColor('--gray')
|
|
|
|
|
|
|
|
monaco.editor.defineTheme(themeName, {
|
|
|
|
base: themeType,
|
|
|
|
inherit: true, // can also be false to completely replace the builtin rules
|
|
|
|
rules: [
|
|
|
|
{background: darkColor.replace('#', '')},
|
|
|
|
{foreground: textColor.replace('#', '')},
|
|
|
|
|
|
|
|
// global variables
|
|
|
|
{token: 'keyword.abi', foreground: blueColor},
|
|
|
|
{token: 'keyword.block', foreground: blueColor},
|
|
|
|
{token: 'keyword.bytes', foreground: blueColor},
|
|
|
|
{token: 'keyword.msg', foreground: blueColor},
|
|
|
|
{token: 'keyword.tx', foreground: blueColor},
|
|
|
|
|
|
|
|
// global functions
|
|
|
|
{token: 'keyword.assert', foreground: blueColor},
|
|
|
|
{token: 'keyword.require', foreground: blueColor},
|
|
|
|
{token: 'keyword.revert', foreground: blueColor},
|
|
|
|
{token: 'keyword.blockhash', foreground: blueColor},
|
|
|
|
{token: 'keyword.keccak256', foreground: blueColor},
|
|
|
|
{token: 'keyword.sha256', foreground: blueColor},
|
|
|
|
{token: 'keyword.ripemd160', foreground: blueColor},
|
|
|
|
{token: 'keyword.ecrecover', foreground: blueColor},
|
|
|
|
{token: 'keyword.addmod', foreground: blueColor},
|
|
|
|
{token: 'keyword.mulmod', foreground: blueColor},
|
|
|
|
{token: 'keyword.selfdestruct', foreground: blueColor},
|
|
|
|
{token: 'keyword.type ', foreground: blueColor},
|
|
|
|
{token: 'keyword.gasleft', foreground: blueColor},
|
|
|
|
|
|
|
|
// specials
|
|
|
|
{token: 'keyword.super', foreground: infoColor},
|
|
|
|
{token: 'keyword.this', foreground: infoColor},
|
|
|
|
{token: 'keyword.virtual', foreground: infoColor},
|
|
|
|
|
|
|
|
// for state variables
|
|
|
|
{token: 'keyword.constants', foreground: grayColor},
|
|
|
|
{token: 'keyword.override', foreground: grayColor},
|
|
|
|
{token: 'keyword.immutable', foreground: grayColor},
|
|
|
|
|
|
|
|
// data location
|
|
|
|
{token: 'keyword.memory', foreground: locationColor},
|
|
|
|
{token: 'keyword.storage', foreground: locationColor},
|
|
|
|
{token: 'keyword.calldata', foreground: locationColor},
|
|
|
|
|
|
|
|
// for Events
|
|
|
|
{token: 'keyword.indexed', foreground: yellowColor},
|
|
|
|
{token: 'keyword.anonymous', foreground: yellowColor},
|
|
|
|
|
|
|
|
// for functions
|
|
|
|
{token: 'keyword.external', foreground: successColor},
|
|
|
|
{token: 'keyword.internal', foreground: successColor},
|
|
|
|
{token: 'keyword.private', foreground: successColor},
|
|
|
|
{token: 'keyword.public', foreground: successColor},
|
|
|
|
{token: 'keyword.view', foreground: successColor},
|
|
|
|
{token: 'keyword.pure', foreground: successColor},
|
|
|
|
{token: 'keyword.payable', foreground: successColor},
|
|
|
|
{token: 'keyword.nonpayable', foreground: successColor},
|
|
|
|
|
|
|
|
// Errors
|
|
|
|
{token: 'keyword.Error', foreground: dangerColor},
|
|
|
|
{token: 'keyword.Panic', foreground: dangerColor},
|
|
|
|
|
|
|
|
// special functions
|
|
|
|
{token: 'keyword.fallback', foreground: pinkColor},
|
|
|
|
{token: 'keyword.receive', foreground: pinkColor},
|
|
|
|
{token: 'keyword.constructor', foreground: pinkColor},
|
|
|
|
|
|
|
|
// identifiers
|
|
|
|
{token: 'keyword.identifier', foreground: warningColor},
|
|
|
|
{token: 'keyword.for', foreground: warningColor},
|
|
|
|
{token: 'keyword.break', foreground: warningColor},
|
|
|
|
{token: 'keyword.continue', foreground: warningColor},
|
|
|
|
{token: 'keyword.while', foreground: warningColor},
|
|
|
|
{token: 'keyword.do', foreground: warningColor},
|
|
|
|
{token: 'keyword.delete', foreground: warningColor},
|
|
|
|
|
|
|
|
{token: 'keyword.if', foreground: yellowColor},
|
|
|
|
{token: 'keyword.else', foreground: yellowColor},
|
|
|
|
|
|
|
|
{token: 'keyword.throw', foreground: orangeColor},
|
|
|
|
{token: 'keyword.catch', foreground: orangeColor},
|
|
|
|
{token: 'keyword.try', foreground: orangeColor},
|
|
|
|
|
|
|
|
// returns
|
|
|
|
{token: 'keyword.returns', foreground: greenColor},
|
|
|
|
{token: 'keyword.return', foreground: greenColor}
|
|
|
|
],
|
|
|
|
colors: {
|
|
|
|
// see https://code.visualstudio.com/api/references/theme-color for more settings
|
|
|
|
'editor.background': textbackground,
|
|
|
|
'editorSuggestWidget.background': lightColor,
|
|
|
|
'editorSuggestWidget.selectedBackground': secondaryColor,
|
|
|
|
'editorSuggestWidget.selectedForeground': textColor,
|
|
|
|
'editorSuggestWidget.highlightForeground': primaryColor,
|
|
|
|
'editorSuggestWidget.focusHighlightForeground': infoColor,
|
|
|
|
'editor.lineHighlightBorder': secondaryColor,
|
|
|
|
'editor.lineHighlightBackground':
|
|
|
|
textbackground === darkColor ? lightColor : secondaryColor,
|
|
|
|
'editorGutter.background': lightColor,
|
|
|
|
//'editor.selectionHighlightBackground': secondaryColor,
|
|
|
|
'minimap.background': lightColor,
|
|
|
|
'menu.foreground': textColor,
|
|
|
|
'menu.background': textbackground,
|
|
|
|
'menu.selectionBackground': secondaryColor,
|
|
|
|
'menu.selectionForeground': textColor,
|
|
|
|
'menu.selectionBorder': secondaryColor
|
|
|
|
}
|
|
|
|
})
|
|
|
|
monacoRef.current.editor.setTheme(themeName)
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!monacoRef.current) return
|
|
|
|
defineAndSetTheme(monacoRef.current)
|
|
|
|
})
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!editorRef.current || !props.currentFile) return
|
|
|
|
currentFileRef.current = props.currentFile
|
|
|
|
props.plugin
|
|
|
|
.call('fileManager', 'getUrlFromPath', currentFileRef.current)
|
|
|
|
.then((url) => (currentUrlRef.current = url.file))
|
|
|
|
|
|
|
|
const file = editorModelsState[props.currentFile]
|
|
|
|
editorRef.current.setModel(file.model)
|
|
|
|
editorRef.current.updateOptions({
|
|
|
|
readOnly: editorModelsState[props.currentFile].readOnly
|
|
|
|
})
|
|
|
|
if (file.language === 'sol') {
|
|
|
|
monacoRef.current.editor.setModelLanguage(file.model, 'remix-solidity')
|
|
|
|
} else if (file.language === 'cairo') {
|
|
|
|
monacoRef.current.editor.setModelLanguage(file.model, 'remix-cairo')
|
|
|
|
} else if (file.language === 'zokrates') {
|
|
|
|
monacoRef.current.editor.setModelLanguage(file.model, 'remix-zokrates')
|
|
|
|
} else if (file.language === 'move') {
|
|
|
|
monacoRef.current.editor.setModelLanguage(file.model, 'remix-move')
|
|
|
|
} else if (file.language === 'circom') {
|
|
|
|
monacoRef.current.editor.setModelLanguage(file.model, 'remix-circom')
|
|
|
|
}
|
|
|
|
}, [props.currentFile])
|
|
|
|
|
|
|
|
const convertToMonacoDecoration = (
|
|
|
|
decoration: lineText | sourceAnnotation | sourceMarker,
|
|
|
|
typeOfDecoration: string
|
|
|
|
) => {
|
|
|
|
if (typeOfDecoration === 'sourceAnnotationsPerFile') {
|
|
|
|
decoration = decoration as sourceAnnotation
|
|
|
|
return {
|
|
|
|
type: typeOfDecoration,
|
|
|
|
range: new monacoRef.current.Range(
|
|
|
|
decoration.row + 1,
|
|
|
|
1,
|
|
|
|
decoration.row + 1,
|
|
|
|
1
|
|
|
|
),
|
|
|
|
options: {
|
|
|
|
isWholeLine: false,
|
|
|
|
glyphMarginHoverMessage: {
|
|
|
|
value:
|
|
|
|
(decoration.from ? `from ${decoration.from}:\n` : '') +
|
|
|
|
decoration.text
|
|
|
|
},
|
|
|
|
glyphMarginClassName: `fal fa-exclamation-square text-${
|
|
|
|
decoration.type === 'error'
|
|
|
|
? 'danger'
|
|
|
|
: decoration.type === 'warning'
|
|
|
|
? 'warning'
|
|
|
|
: 'info'
|
|
|
|
}`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (typeOfDecoration === 'markerPerFile') {
|
|
|
|
decoration = decoration as sourceMarker
|
|
|
|
let isWholeLine = false
|
|
|
|
if (
|
|
|
|
(decoration.position.start.line === decoration.position.end.line &&
|
|
|
|
decoration.position.end.column - decoration.position.start.column <
|
|
|
|
2) ||
|
|
|
|
decoration.position.start.line !== decoration.position.end.line
|
|
|
|
) {
|
|
|
|
// in this case we force highlighting the whole line (doesn't make sense to highlight 2 chars)
|
|
|
|
isWholeLine = true
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
type: typeOfDecoration,
|
|
|
|
range: new monacoRef.current.Range(
|
|
|
|
decoration.position.start.line + 1,
|
|
|
|
decoration.position.start.column + 1,
|
|
|
|
decoration.position.end.line + 1,
|
|
|
|
decoration.position.end.column + 1
|
|
|
|
),
|
|
|
|
options: {
|
|
|
|
isWholeLine,
|
|
|
|
inlineClassName: `${
|
|
|
|
isWholeLine ? 'alert-info' : 'inline-class'
|
|
|
|
} border-0 highlightLine${decoration.position.start.line + 1}`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (typeOfDecoration === 'lineTextPerFile') {
|
|
|
|
const lineTextDecoration = decoration as lineText
|
|
|
|
return {
|
|
|
|
type: typeOfDecoration,
|
|
|
|
range: new monacoRef.current.Range(
|
|
|
|
lineTextDecoration.position.start.line + 1,
|
|
|
|
lineTextDecoration.position.start.column + 1,
|
|
|
|
lineTextDecoration.position.start.line + 1,
|
|
|
|
1024
|
|
|
|
),
|
|
|
|
options: {
|
|
|
|
after: {
|
|
|
|
content: ` ${lineTextDecoration.content}`,
|
|
|
|
inlineClassName: `${lineTextDecoration.className}`
|
|
|
|
},
|
|
|
|
afterContentClassName: `${lineTextDecoration.afterContentClassName}`,
|
|
|
|
hoverMessage: lineTextDecoration.hoverMessage
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (typeOfDecoration === 'lineTextPerFile') {
|
|
|
|
const lineTextDecoration = decoration as lineText
|
|
|
|
return {
|
|
|
|
type: typeOfDecoration,
|
|
|
|
range: new monacoRef.current.Range(
|
|
|
|
lineTextDecoration.position.start.line + 1,
|
|
|
|
lineTextDecoration.position.start.column + 1,
|
|
|
|
lineTextDecoration.position.start.line + 1,
|
|
|
|
1024
|
|
|
|
),
|
|
|
|
options: {
|
|
|
|
after: {
|
|
|
|
content: ` ${lineTextDecoration.content}`,
|
|
|
|
inlineClassName: `${lineTextDecoration.className}`
|
|
|
|
},
|
|
|
|
afterContentClassName: `${lineTextDecoration.afterContentClassName}`,
|
|
|
|
hoverMessage: lineTextDecoration.hoverMessage
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
props.editorAPI.clearDecorationsByPlugin = (
|
|
|
|
filePath: string,
|
|
|
|
plugin: string,
|
|
|
|
typeOfDecoration: string,
|
|
|
|
registeredDecorations: any,
|
|
|
|
currentDecorations: any
|
|
|
|
) => {
|
|
|
|
const model = editorModelsState[filePath]?.model
|
|
|
|
if (!model)
|
|
|
|
return {
|
|
|
|
currentDecorations: [],
|
|
|
|
registeredDecorations: []
|
|
|
|
}
|
|
|
|
const decorations = []
|
|
|
|
const newRegisteredDecorations = []
|
|
|
|
if (registeredDecorations) {
|
|
|
|
for (const decoration of registeredDecorations) {
|
|
|
|
if (
|
|
|
|
decoration.type === typeOfDecoration &&
|
|
|
|
decoration.value.from !== plugin
|
|
|
|
) {
|
|
|
|
decorations.push(
|
|
|
|
convertToMonacoDecoration(decoration.value, typeOfDecoration)
|
|
|
|
)
|
|
|
|
newRegisteredDecorations.push(decoration)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
currentDecorations: model.deltaDecorations(
|
|
|
|
currentDecorations,
|
|
|
|
decorations
|
|
|
|
),
|
|
|
|
registeredDecorations: newRegisteredDecorations
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
props.editorAPI.keepDecorationsFor = (
|
|
|
|
filePath: string,
|
|
|
|
plugin: string,
|
|
|
|
typeOfDecoration: string,
|
|
|
|
registeredDecorations: any,
|
|
|
|
currentDecorations: any
|
|
|
|
) => {
|
|
|
|
const model = editorModelsState[filePath]?.model
|
|
|
|
if (!model)
|
|
|
|
return {
|
|
|
|
currentDecorations: []
|
|
|
|
}
|
|
|
|
const decorations = []
|
|
|
|
if (registeredDecorations) {
|
|
|
|
for (const decoration of registeredDecorations) {
|
|
|
|
if (decoration.value.from === plugin) {
|
|
|
|
decorations.push(
|
|
|
|
convertToMonacoDecoration(decoration.value, typeOfDecoration)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
currentDecorations: model.deltaDecorations(
|
|
|
|
currentDecorations,
|
|
|
|
decorations
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const addDecoration = (
|
|
|
|
decoration: sourceAnnotation | sourceMarker,
|
|
|
|
filePath: string,
|
|
|
|
typeOfDecoration: string
|
|
|
|
) => {
|
|
|
|
const model = editorModelsState[filePath]?.model
|
|
|
|
if (!model) return {currentDecorations: []}
|
|
|
|
const monacoDecoration = convertToMonacoDecoration(
|
|
|
|
decoration,
|
|
|
|
typeOfDecoration
|
|
|
|
)
|
|
|
|
return {
|
|
|
|
currentDecorations: model.deltaDecorations([], [monacoDecoration]),
|
|
|
|
registeredDecorations: [{value: decoration, type: typeOfDecoration}]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
props.editorAPI.addDecoration = (
|
|
|
|
marker: sourceMarker,
|
|
|
|
filePath: string,
|
|
|
|
typeOfDecoration: string
|
|
|
|
) => {
|
|
|
|
return addDecoration(marker, filePath, typeOfDecoration)
|
|
|
|
}
|
|
|
|
|
|
|
|
props.editorAPI.addErrorMarker = async (
|
|
|
|
errors: errorMarker[],
|
|
|
|
from: string
|
|
|
|
) => {
|
|
|
|
const allMarkersPerfile: Record<
|
|
|
|
string,
|
|
|
|
Array<monacoTypes.editor.IMarkerData>
|
|
|
|
> = {}
|
|
|
|
|
|
|
|
for (const error of errors) {
|
|
|
|
let filePath = error.file
|
|
|
|
|
|
|
|
if (!filePath) return
|
|
|
|
const fileFromUrl = await props.plugin.call(
|
|
|
|
'fileManager',
|
|
|
|
'getPathFromUrl',
|
|
|
|
filePath
|
|
|
|
)
|
|
|
|
filePath = fileFromUrl.file
|
|
|
|
const model = editorModelsState[filePath]?.model
|
|
|
|
const errorServerityMap = {
|
|
|
|
error: MarkerSeverity.Error,
|
|
|
|
warning: MarkerSeverity.Warning,
|
|
|
|
info: MarkerSeverity.Info
|
|
|
|
}
|
|
|
|
if (model) {
|
|
|
|
const markerData: monacoTypes.editor.IMarkerData = {
|
|
|
|
severity:
|
|
|
|
typeof error.severity === 'string'
|
|
|
|
? errorServerityMap[error.severity]
|
|
|
|
: error.severity,
|
|
|
|
startLineNumber:
|
|
|
|
(error.position.start && error.position.start.line) || 0,
|
|
|
|
startColumn:
|
|
|
|
(error.position.start && error.position.start.column) || 0,
|
|
|
|
endLineNumber: (error.position.end && error.position.end.line) || 0,
|
|
|
|
endColumn: (error.position.end && error.position.end.column) || 0,
|
|
|
|
message: error.message
|
|
|
|
}
|
|
|
|
if (!allMarkersPerfile[filePath]) {
|
|
|
|
allMarkersPerfile[filePath] = []
|
|
|
|
}
|
|
|
|
allMarkersPerfile[filePath].push(markerData)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const filePath in allMarkersPerfile) {
|
|
|
|
const model = editorModelsState[filePath]?.model
|
|
|
|
if (model) {
|
|
|
|
monacoRef.current.editor.setModelMarkers(
|
|
|
|
model,
|
|
|
|
from,
|
|
|
|
allMarkersPerfile[filePath]
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
props.editorAPI.clearErrorMarkers = async (
|
|
|
|
sources: string[] | {[fileName: string]: any},
|
|
|
|
from: string
|
|
|
|
) => {
|
|
|
|
if (sources) {
|
|
|
|
for (const source of Array.isArray(sources)
|
|
|
|
? sources
|
|
|
|
: Object.keys(sources)) {
|
|
|
|
const filePath = source
|
|
|
|
const model = editorModelsState[filePath]?.model
|
|
|
|
if (model) {
|
|
|
|
monacoRef.current.editor.setModelMarkers(model, from, [])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
props.editorAPI.findMatches = (uri: string, value: string) => {
|
|
|
|
if (!editorRef.current) return
|
|
|
|
const model = editorModelsState[uri]?.model
|
|
|
|
if (model) return model.findMatches(value)
|
|
|
|
}
|
|
|
|
|
|
|
|
props.editorAPI.getValue = (uri: string) => {
|
|
|
|
if (!editorRef.current) return
|
|
|
|
const model = editorModelsState[uri]?.model
|
|
|
|
if (model) {
|
|
|
|
return model.getValue()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
props.editorAPI.getCursorPosition = (offset: boolean = true) => {
|
|
|
|
if (!monacoRef.current) return
|
|
|
|
const model = editorModelsState[currentFileRef.current]?.model
|
|
|
|
if (model) {
|
|
|
|
return offset
|
|
|
|
? model.getOffsetAt(editorRef.current.getPosition())
|
|
|
|
: editorRef.current.getPosition()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
props.editorAPI.getHoverPosition = (position: monacoTypes.Position) => {
|
|
|
|
if (!monacoRef.current) return
|
|
|
|
const model = editorModelsState[currentFileRef.current]?.model
|
|
|
|
if (model) {
|
|
|
|
return model.getOffsetAt(position)
|
|
|
|
} else {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
props.editorAPI.getFontSize = () => {
|
|
|
|
if (!editorRef.current) return
|
|
|
|
return editorRef.current.getOption(51)
|
|
|
|
}
|
|
|
|
|
|
|
|
;(window as any).addRemixBreakpoint = (position) => {
|
|
|
|
// make it available from e2e testing...
|
|
|
|
const model = editorRef.current.getModel()
|
|
|
|
if (model) {
|
|
|
|
setCurrentBreakpoints((prevState) => {
|
|
|
|
const currentFile = currentUrlRef.current
|
|
|
|
if (!prevState[currentFile]) prevState[currentFile] = {}
|
|
|
|
const decoration = Object.keys(prevState[currentFile]).filter(
|
|
|
|
(line) => parseInt(line) === position.lineNumber
|
|
|
|
)
|
|
|
|
if (decoration.length) {
|
|
|
|
props.events.onBreakPointCleared(currentFile, position.lineNumber)
|
|
|
|
model.deltaDecorations(
|
|
|
|
[prevState[currentFile][position.lineNumber]],
|
|
|
|
[]
|
|
|
|
)
|
|
|
|
delete prevState[currentFile][position.lineNumber]
|
|
|
|
} else {
|
|
|
|
props.events.onBreakPointAdded(currentFile, position.lineNumber)
|
|
|
|
const decorationIds = model.deltaDecorations(
|
|
|
|
[],
|
|
|
|
[
|
|
|
|
{
|
|
|
|
range: new monacoRef.current.Range(
|
|
|
|
position.lineNumber,
|
|
|
|
1,
|
|
|
|
position.lineNumber,
|
|
|
|
1
|
|
|
|
),
|
|
|
|
options: {
|
|
|
|
isWholeLine: false,
|
|
|
|
glyphMarginClassName: 'fas fa-circle text-info'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
)
|
|
|
|
prevState[currentFile][position.lineNumber] = decorationIds[0]
|
|
|
|
}
|
|
|
|
return prevState
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleEditorDidMount(editor) {
|
|
|
|
editorRef.current = editor
|
|
|
|
defineAndSetTheme(monacoRef.current)
|
|
|
|
reducerListener(
|
|
|
|
props.plugin,
|
|
|
|
dispatch,
|
|
|
|
monacoRef.current,
|
|
|
|
editorRef.current,
|
|
|
|
props.events
|
|
|
|
)
|
|
|
|
props.events.onEditorMounted()
|
|
|
|
editor.onMouseUp((e) => {
|
|
|
|
// see https://microsoft.github.io/monaco-editor/typedoc/enums/editor.MouseTargetType.html
|
|
|
|
// 2 is GUTTER_GLYPH_MARGIN
|
|
|
|
// 3 is GUTTER_LINE_NUMBERS
|
|
|
|
if (e && e.target && (e.target.type === 2 || e.target.type === 3)) {
|
|
|
|
;(window as any).addRemixBreakpoint(e.target.position)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
editor.onDidPaste((e) => {
|
|
|
|
if (
|
|
|
|
!pasteCodeRef.current &&
|
|
|
|
e &&
|
|
|
|
e.range &&
|
|
|
|
e.range.startLineNumber >= 0 &&
|
|
|
|
e.range.endLineNumber >= 0 &&
|
|
|
|
e.range.endLineNumber - e.range.startLineNumber > 10
|
|
|
|
) {
|
|
|
|
const modalContent: AlertModal = {
|
|
|
|
id: 'newCodePasted',
|
|
|
|
title: 'Pasted Code Alert',
|
|
|
|
message: (
|
|
|
|
<div>
|
|
|
|
{' '}
|
|
|
|
<i className="fas fa-exclamation-triangle text-danger mr-1"></i>
|
|
|
|
You have just pasted a code snippet or contract in the editor.
|
|
|
|
<div>
|
|
|
|
Make sure you fully understand this code before deploying or
|
|
|
|
interacting with it. Don't get scammed!
|
|
|
|
<div className="mt-2">
|
|
|
|
Running untrusted code can put your wallet{' '}
|
|
|
|
<span className="text-warning"> at risk </span>. In a
|
|
|
|
worst-case scenario, you could{' '}
|
|
|
|
<span className="text-warning">lose all your money</span>.
|
|
|
|
</div>
|
|
|
|
<div className="text-warning mt-2">
|
|
|
|
If you don't fully understand it, please don't run this code.
|
|
|
|
</div>
|
|
|
|
<div className="mt-2">
|
|
|
|
If you are not a smart contract developer, ask someone you
|
|
|
|
trust who has the skills to determine if this code is safe to
|
|
|
|
use.
|
|
|
|
</div>
|
|
|
|
<div className="mt-2">
|
|
|
|
See{' '}
|
|
|
|
<a
|
|
|
|
target="_blank"
|
|
|
|
href="https://remix-ide.readthedocs.io/en/latest/security.html"
|
|
|
|
>
|
|
|
|
{' '}
|
|
|
|
these recommendations{' '}
|
|
|
|
</a>{' '}
|
|
|
|
for more information.
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
props.plugin.call('notification', 'alert', modalContent)
|
|
|
|
pasteCodeRef.current = true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
// zoomin zoomout
|
|
|
|
editor.addCommand(
|
|
|
|
monacoRef.current.KeyMod.CtrlCmd |
|
|
|
|
(monacoRef.current.KeyCode as any).US_EQUAL,
|
|
|
|
() => {
|
|
|
|
editor.updateOptions({fontSize: editor.getOption(51) + 1})
|
|
|
|
}
|
|
|
|
)
|
|
|
|
editor.addCommand(
|
|
|
|
monacoRef.current.KeyMod.CtrlCmd |
|
|
|
|
(monacoRef.current.KeyCode as any).US_MINUS,
|
|
|
|
() => {
|
|
|
|
editor.updateOptions({fontSize: editor.getOption(51) - 1})
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// add context menu items
|
|
|
|
const zoominAction = {
|
|
|
|
id: 'zoomIn',
|
|
|
|
label: 'Zoom In',
|
|
|
|
contextMenuOrder: 0, // choose the order
|
|
|
|
contextMenuGroupId: 'zooming', // create a new grouping
|
|
|
|
keybindings: [
|
|
|
|
// eslint-disable-next-line no-bitwise
|
|
|
|
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.Equal
|
|
|
|
],
|
|
|
|
run: () => {
|
|
|
|
editor.updateOptions({fontSize: editor.getOption(51) + 1})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const zoomOutAction = {
|
|
|
|
id: 'zoomOut',
|
|
|
|
label: 'Zoom Out',
|
|
|
|
contextMenuOrder: 0, // choose the order
|
|
|
|
contextMenuGroupId: 'zooming', // create a new grouping
|
|
|
|
keybindings: [
|
|
|
|
// eslint-disable-next-line no-bitwise
|
|
|
|
monacoRef.current.KeyMod.CtrlCmd | monacoRef.current.KeyCode.Minus
|
|
|
|
],
|
|
|
|
run: () => {
|
|
|
|
editor.updateOptions({fontSize: editor.getOption(51) - 1})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const formatAction = {
|
|
|
|
id: 'autoFormat',
|
|
|
|
label: 'Format Code',
|
|
|
|
contextMenuOrder: 0, // choose the order
|
|
|
|
contextMenuGroupId: 'formatting', // create a new grouping
|
|
|
|
keybindings: [
|
|
|
|
// eslint-disable-next-line no-bitwise
|
|
|
|
monacoRef.current.KeyMod.Shift |
|
|
|
|
monacoRef.current.KeyMod.Alt |
|
|
|
|
monacoRef.current.KeyCode.KeyF
|
|
|
|
],
|
|
|
|
run: async () => {
|
|
|
|
const file = await props.plugin.call('fileManager', 'getCurrentFile')
|
|
|
|
await props.plugin.call('codeFormatter', 'format', file)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const freeFunctionCondition = editor.createContextKey(
|
|
|
|
'freeFunctionCondition',
|
|
|
|
false
|
|
|
|
)
|
|
|
|
let freeFunctionAction
|
|
|
|
const executeFreeFunctionAction = {
|
|
|
|
id: 'executeFreeFunction',
|
|
|
|
label: 'Run a free function in the Remix VM',
|
|
|
|
contextMenuOrder: 0, // choose the order
|
|
|
|
contextMenuGroupId: 'execute', // create a new grouping
|
|
|
|
precondition: 'freeFunctionCondition',
|
|
|
|
keybindings: [
|
|
|
|
// eslint-disable-next-line no-bitwise
|
|
|
|
monacoRef.current.KeyMod.Shift |
|
|
|
|
monacoRef.current.KeyMod.Alt |
|
|
|
|
monacoRef.current.KeyCode.KeyR
|
|
|
|
],
|
|
|
|
run: async () => {
|
|
|
|
const {nodesAtPosition} = await retrieveNodesAtPosition(
|
|
|
|
props.editorAPI,
|
|
|
|
props.plugin
|
|
|
|
)
|
|
|
|
// find the contract and get the nodes of the contract and the base contracts and imports
|
|
|
|
if (
|
|
|
|
nodesAtPosition &&
|
|
|
|
isArray(nodesAtPosition) &&
|
|
|
|
nodesAtPosition.length
|
|
|
|
) {
|
|
|
|
const freeFunctionNode = nodesAtPosition.find(
|
|
|
|
(node) => node.kind === 'freeFunction'
|
|
|
|
)
|
|
|
|
if (freeFunctionNode) {
|
|
|
|
const file = await props.plugin.call(
|
|
|
|
'fileManager',
|
|
|
|
'getCurrentFile'
|
|
|
|
)
|
|
|
|
props.plugin.call(
|
|
|
|
'solidity-script',
|
|
|
|
'execute',
|
|
|
|
file,
|
|
|
|
freeFunctionNode.name
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
props.plugin.call(
|
|
|
|
'notification',
|
|
|
|
'toast',
|
|
|
|
'This can only execute free function'
|
|
|
|
)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
props.plugin.call(
|
|
|
|
'notification',
|
|
|
|
'toast',
|
|
|
|
'Please go to Remix settings and activate the code editor features or wait that the current editor context is loaded.'
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
editor.addAction(formatAction)
|
|
|
|
editor.addAction(zoomOutAction)
|
|
|
|
editor.addAction(zoominAction)
|
|
|
|
freeFunctionAction = editor.addAction(executeFreeFunctionAction)
|
|
|
|
|
|
|
|
// we have to add the command because the menu action isn't always available (see onContextMenuHandlerForFreeFunction)
|
|
|
|
editor.addCommand(
|
|
|
|
monacoRef.current.KeyMod.Shift |
|
|
|
|
monacoRef.current.KeyMod.Alt |
|
|
|
|
monacoRef.current.KeyCode.KeyR,
|
|
|
|
() => executeFreeFunctionAction.run()
|
|
|
|
)
|
|
|
|
|
|
|
|
const contextmenu = editor.getContribution('editor.contrib.contextmenu')
|
|
|
|
const orgContextMenuMethod = contextmenu._onContextMenu
|
|
|
|
const onContextMenuHandlerForFreeFunction = async () => {
|
|
|
|
if (freeFunctionAction) {
|
|
|
|
freeFunctionAction.dispose()
|
|
|
|
freeFunctionAction = null
|
|
|
|
}
|
|
|
|
const file = await props.plugin.call('fileManager', 'getCurrentFile')
|
|
|
|
if (!file.endsWith('.sol')) {
|
|
|
|
freeFunctionCondition.set(false)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const {nodesAtPosition} = await retrieveNodesAtPosition(
|
|
|
|
props.editorAPI,
|
|
|
|
props.plugin
|
|
|
|
)
|
|
|
|
const freeFunctionNode = nodesAtPosition.find(
|
|
|
|
(node) => node.kind === 'freeFunction'
|
|
|
|
)
|
|
|
|
if (freeFunctionNode) {
|
|
|
|
executeFreeFunctionAction.label = `Run the free function "${freeFunctionNode.name}" in the Remix VM`
|
|
|
|
freeFunctionAction = editor.addAction(executeFreeFunctionAction)
|
|
|
|
}
|
|
|
|
freeFunctionCondition.set(!!freeFunctionNode)
|
|
|
|
}
|
|
|
|
contextmenu._onContextMenu = (...args) => {
|
|
|
|
if (args[0]) args[0].event?.preventDefault()
|
|
|
|
onContextMenuHandlerForFreeFunction()
|
|
|
|
.then(() => orgContextMenuMethod.apply(contextmenu, args))
|
|
|
|
.catch(() => orgContextMenuMethod.apply(contextmenu, args))
|
|
|
|
}
|
|
|
|
|
|
|
|
const editorService = editor._codeEditorService
|
|
|
|
const openEditorBase = editorService.openCodeEditor.bind(editorService)
|
|
|
|
editorService.openCodeEditor = async (input, source) => {
|
|
|
|
const result = await openEditorBase(input, source)
|
|
|
|
if (input && input.resource && input.resource.path) {
|
|
|
|
try {
|
|
|
|
await props.plugin.call('fileManager', 'open', input.resource.path)
|
|
|
|
if (input.options && input.options.selection) {
|
|
|
|
editor.revealRange(input.options.selection)
|
|
|
|
editor.setPosition({
|
|
|
|
column: input.options.selection.startColumn,
|
|
|
|
lineNumber: input.options.selection.startLineNumber
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.log(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
// just for e2e testing
|
|
|
|
const loadedElement = document.createElement('span')
|
|
|
|
loadedElement.setAttribute('data-id', 'editorloaded')
|
|
|
|
document.body.appendChild(loadedElement)
|
|
|
|
}
|
|
|
|
|
|
|
|
function handleEditorWillMount(monaco) {
|
|
|
|
monacoRef.current = monaco
|
|
|
|
// Register a new language
|
|
|
|
monacoRef.current.languages.register({id: 'remix-solidity'})
|
|
|
|
monacoRef.current.languages.register({id: 'remix-cairo'})
|
|
|
|
monacoRef.current.languages.register({id: 'remix-zokrates'})
|
|
|
|
monacoRef.current.languages.register({id: 'remix-move'})
|
|
|
|
monacoRef.current.languages.register({id: 'remix-circom'})
|
|
|
|
|
|
|
|
// Register a tokens provider for the language
|
|
|
|
monacoRef.current.languages.setMonarchTokensProvider(
|
|
|
|
'remix-solidity',
|
|
|
|
solidityTokensProvider as any
|
|
|
|
)
|
|
|
|
monacoRef.current.languages.setLanguageConfiguration(
|
|
|
|
'remix-solidity',
|
|
|
|
solidityLanguageConfig as any
|
|
|
|
)
|
|
|
|
|
|
|
|
monacoRef.current.languages.setMonarchTokensProvider(
|
|
|
|
'remix-cairo',
|
|
|
|
cairoTokensProvider as any
|
|
|
|
)
|
|
|
|
monacoRef.current.languages.setLanguageConfiguration(
|
|
|
|
'remix-cairo',
|
|
|
|
cairoLanguageConfig as any
|
|
|
|
)
|
|
|
|
|
|
|
|
monacoRef.current.languages.setMonarchTokensProvider(
|
|
|
|
'remix-zokrates',
|
|
|
|
zokratesTokensProvider as any
|
|
|
|
)
|
|
|
|
monacoRef.current.languages.setLanguageConfiguration(
|
|
|
|
'remix-zokrates',
|
|
|
|
zokratesLanguageConfig as any
|
|
|
|
)
|
|
|
|
|
|
|
|
monacoRef.current.languages.setMonarchTokensProvider(
|
|
|
|
'remix-move',
|
|
|
|
moveTokenProvider as any
|
|
|
|
)
|
|
|
|
monacoRef.current.languages.setLanguageConfiguration(
|
|
|
|
'remix-move',
|
|
|
|
moveLanguageConfig as any
|
|
|
|
)
|
|
|
|
|
|
|
|
monacoRef.current.languages.setMonarchTokensProvider(
|
|
|
|
'remix-circom',
|
|
|
|
circomTokensProvider as any
|
|
|
|
)
|
|
|
|
monacoRef.current.languages.setLanguageConfiguration(
|
|
|
|
'remix-circom',
|
|
|
|
circomLanguageConfig(monacoRef.current) as any
|
|
|
|
)
|
|
|
|
|
|
|
|
monacoRef.current.languages.registerDefinitionProvider(
|
|
|
|
'remix-solidity',
|
|
|
|
new RemixDefinitionProvider(props, monaco)
|
|
|
|
)
|
|
|
|
monacoRef.current.languages.registerDocumentHighlightProvider(
|
|
|
|
'remix-solidity',
|
|
|
|
new RemixHighLightProvider(props, monaco)
|
|
|
|
)
|
|
|
|
monacoRef.current.languages.registerReferenceProvider(
|
|
|
|
'remix-solidity',
|
|
|
|
new RemixReferenceProvider(props, monaco)
|
|
|
|
)
|
|
|
|
monacoRef.current.languages.registerHoverProvider(
|
|
|
|
'remix-solidity',
|
|
|
|
new RemixHoverProvider(props, monaco)
|
|
|
|
)
|
|
|
|
monacoRef.current.languages.registerCompletionItemProvider(
|
|
|
|
'remix-solidity',
|
|
|
|
new RemixCompletionProvider(props, monaco)
|
|
|
|
)
|
|
|
|
monaco.languages.registerCodeActionProvider(
|
|
|
|
'remix-solidity',
|
|
|
|
new RemixCodeActionProvider(props, monaco)
|
|
|
|
)
|
|
|
|
|
|
|
|
loadTypes(monacoRef.current)
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<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,
|
|
|
|
readOnly:
|
|
|
|
(!editorRef.current || !props.currentFile) &&
|
|
|
|
editorModelsState[props.currentFile]?.readOnly
|
|
|
|
}}
|
|
|
|
defaultValue={defaultEditorValue}
|
|
|
|
/>
|
|
|
|
{editorModelsState[props.currentFile]?.readOnly && (
|
|
|
|
<span className="pl-4 h6 mb-0 w-100 alert-info position-absolute bottom-0 end-0">
|
|
|
|
<i className="fas fa-lock-alt p-2"></i>
|
|
|
|
The file is opened in <b>read-only</b> mode.
|
|
|
|
</span>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export default EditorUI
|