Merge branch 'master' into conf

conf
Liana Husikyan 2 years ago committed by GitHub
commit aeaf45db9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .gitignore
  2. 34
      apps/doc-gen/.eslintrc.json
  3. 13
      apps/doc-gen/.prettierrc
  4. 59
      apps/doc-gen/project.json
  5. 3
      apps/doc-gen/src/app/App.css
  6. 42
      apps/doc-gen/src/app/App.tsx
  7. 80
      apps/doc-gen/src/app/docgen-client.ts
  8. 22
      apps/doc-gen/src/app/docgen/common/helpers.ts
  9. 138
      apps/doc-gen/src/app/docgen/common/properties.ts
  10. 84
      apps/doc-gen/src/app/docgen/config.ts
  11. 27
      apps/doc-gen/src/app/docgen/doc-item.ts
  12. 87
      apps/doc-gen/src/app/docgen/render.ts
  13. 138
      apps/doc-gen/src/app/docgen/site.ts
  14. 90
      apps/doc-gen/src/app/docgen/templates.ts
  15. 34
      apps/doc-gen/src/app/docgen/themes/markdown/common.hbs
  16. 8
      apps/doc-gen/src/app/docgen/themes/markdown/contract.hbs
  17. 9
      apps/doc-gen/src/app/docgen/themes/markdown/enum.hbs
  18. 1
      apps/doc-gen/src/app/docgen/themes/markdown/error.hbs
  19. 1
      apps/doc-gen/src/app/docgen/themes/markdown/event.hbs
  20. 1
      apps/doc-gen/src/app/docgen/themes/markdown/function.hbs
  21. 49
      apps/doc-gen/src/app/docgen/themes/markdown/helpers.ts
  22. 1
      apps/doc-gen/src/app/docgen/themes/markdown/modifier.hbs
  23. 8
      apps/doc-gen/src/app/docgen/themes/markdown/page.hbs
  24. 9
      apps/doc-gen/src/app/docgen/themes/markdown/struct.hbs
  25. 1
      apps/doc-gen/src/app/docgen/themes/markdown/user-defined-value-type.hbs
  26. 1
      apps/doc-gen/src/app/docgen/themes/markdown/variable.hbs
  27. 13
      apps/doc-gen/src/app/docgen/utils/ItemError.ts
  28. 5
      apps/doc-gen/src/app/docgen/utils/arrays-equal.ts
  29. 1
      apps/doc-gen/src/app/docgen/utils/assert-equal-types.ts
  30. 6
      apps/doc-gen/src/app/docgen/utils/clone.ts
  31. 12
      apps/doc-gen/src/app/docgen/utils/ensure-array.ts
  32. 18
      apps/doc-gen/src/app/docgen/utils/execall.ts
  33. 5
      apps/doc-gen/src/app/docgen/utils/is-child.ts
  34. 7
      apps/doc-gen/src/app/docgen/utils/item-type.ts
  35. 4
      apps/doc-gen/src/app/docgen/utils/map-keys.ts
  36. 19
      apps/doc-gen/src/app/docgen/utils/map-values.ts
  37. 23
      apps/doc-gen/src/app/docgen/utils/memoized-getter.ts
  38. 145
      apps/doc-gen/src/app/docgen/utils/natspec.ts
  39. 26
      apps/doc-gen/src/app/docgen/utils/read-item-docs.ts
  40. 63
      apps/doc-gen/src/app/docgen/utils/scope.ts
  41. 37
      apps/doc-gen/src/app/hooks/useLocalStorage.tsx
  42. 31
      apps/doc-gen/src/app/views/ErrorView.tsx
  43. 1
      apps/doc-gen/src/app/views/index.ts
  44. BIN
      apps/doc-gen/src/favicon.ico
  45. 13
      apps/doc-gen/src/index.html
  46. 11
      apps/doc-gen/src/main.tsx
  47. 11
      apps/doc-gen/src/types.ts
  48. 23
      apps/doc-gen/tsconfig.app.json
  49. 16
      apps/doc-gen/tsconfig.json
  50. 82
      apps/doc-gen/webpack.config.js
  51. 59
      apps/doc-viewer/project.json
  52. 23
      apps/doc-viewer/src/app/App.tsx
  53. 22
      apps/doc-viewer/src/app/docviewer.ts
  54. BIN
      apps/doc-viewer/src/favicon.ico
  55. 13
      apps/doc-viewer/src/index.html
  56. 10
      apps/doc-viewer/src/main.tsx
  57. 23
      apps/doc-viewer/tsconfig.app.json
  58. 16
      apps/doc-viewer/tsconfig.json
  59. 82
      apps/doc-viewer/webpack.config.js
  60. 2
      apps/etherscan/src/app/components/HeaderWithSettings.tsx
  61. 2
      apps/etherscan/src/app/views/VerifyView.tsx
  62. 6
      apps/remix-ide-e2e/src/commands/addFile.ts
  63. 26
      apps/remix-ide-e2e/src/helpers/init.ts
  64. 5
      apps/remix-ide-e2e/src/tests/ballot_0_4_14.test.ts
  65. 2
      apps/remix-ide-e2e/src/tests/defaultLayout.test.ts
  66. 4
      apps/remix-ide-e2e/src/tests/file_decorator.test.ts
  67. 6
      apps/remix-ide-e2e/src/tests/gist.test.ts
  68. 13
      apps/remix-ide-e2e/src/tests/importFromGithub.test.ts
  69. 4
      apps/remix-ide-e2e/src/tests/recorder.test.ts
  70. 4
      apps/remix-ide-e2e/src/tests/search.test.ts
  71. 4
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  72. 27
      apps/remix-ide-e2e/src/tests/terminal.test.ts
  73. 7
      apps/remix-ide/src/app.js
  74. 12
      apps/remix-ide/src/app/panels/tab-proxy.js
  75. 102
      apps/remix-ide/src/app/plugins/solidity-script.tsx
  76. 2
      apps/remix-ide/src/app/plugins/solidity-umlgen.tsx
  77. 2
      apps/remix-ide/src/app/providers/injected-provider.tsx
  78. 7
      apps/remix-ide/src/app/tabs/compile-tab.js
  79. 18
      apps/remix-ide/src/app/tabs/locales/en/udapp.json
  80. 2
      apps/remix-ide/src/app/ui/landing-page/landing-page.js
  81. 8
      apps/remix-ide/src/assets/css/themes/bootstrap-cerulean.min.css
  82. 8
      apps/remix-ide/src/assets/css/themes/bootstrap-cyborg.min.css
  83. 8
      apps/remix-ide/src/assets/css/themes/bootstrap-flatly.min.css
  84. 9
      apps/remix-ide/src/assets/css/themes/bootstrap-spacelab.min.css
  85. 8
      apps/remix-ide/src/assets/css/themes/remix-black_undtds.css
  86. 8
      apps/remix-ide/src/assets/css/themes/remix-candy_ikhg4m.css
  87. 8
      apps/remix-ide/src/assets/css/themes/remix-dark_tvx1s2.css
  88. 8
      apps/remix-ide/src/assets/css/themes/remix-hacker_owl.css
  89. 8
      apps/remix-ide/src/assets/css/themes/remix-light_powaqg.css
  90. 10
      apps/remix-ide/src/assets/css/themes/remix-midcentury_hrzph3.css
  91. 2
      apps/remix-ide/src/blockchain/blockchain.js
  92. 2
      apps/remix-ide/src/index.html
  93. 2
      apps/remix-ide/src/remixAppManager.js
  94. 6
      libs/remix-solidity/src/compiler/compiler-helpers.ts
  95. 10
      libs/remix-ui/app/src/lib/remix-app/remix-app.tsx
  96. 12
      libs/remix-ui/app/src/lib/remix-app/style/remix-app.css
  97. 15
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  98. 15
      libs/remix-ui/editor/src/lib/helpers/retrieveNodesAtPosition.ts
  99. 12
      libs/remix-ui/editor/src/lib/providers/completionProvider.ts
  100. 99
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  101. Some files were not shown because too many files have changed in this diff Show More

4
.gitignore vendored

@ -57,7 +57,3 @@ testem.log
.DS_Store
.vscode/settings.json
.vscode/launch.json
libs/remix-node/
libs/remix-niks/
apps/remix-react

@ -0,0 +1,34 @@
{
"extends": [
"plugin:@nrwl/nx/react",
"../../.eslintrc.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}

@ -0,0 +1,13 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"arrowParens": "avoid",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"jsxSingleQuote": false,
"endOfLine": "lf"
}

@ -0,0 +1,59 @@
{
"name": "doc-gen",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/doc-gen/src",
"projectType": "application",
"implicitDependencies": [
],
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "development",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/doc-gen",
"index": "apps/doc-gen/src/index.html",
"baseHref": "/",
"main": "apps/doc-gen/src/main.tsx",
"tsConfig": "apps/doc-gen/tsconfig.app.json",
"assets": [
"apps/doc-gen/src/favicon.ico"
],
"styles": [],
"scripts": [],
"webpackConfig": "apps/doc-gen/webpack.config.js"
},
"configurations": {
"development": {
},
"production": {
"fileReplacements": [
{
"replace": "apps/doc-gen/src/environments/environment.ts",
"with": "apps/doc-gen/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"executor": "@nrwl/webpack:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "doc-gen:build",
"hmr": true
},
"configurations": {
"development": {
"buildTarget": "doc-gen:build:development",
"port": 6003
},
"production": {
"buildTarget": "doc-gen:build:production"
}
}
}
},
"tags": []
}

@ -0,0 +1,3 @@
body {
margin: 0;
}

@ -0,0 +1,42 @@
import React, { useState, useEffect } from 'react'
import {
CompilationResult,
} from '@remixproject/plugin-api/'
import './App.css'
import { DocGenClient } from './docgen-client'
import { Build } from './docgen/site'
export const client = new DocGenClient()
const App = () => {
const [themeType, setThemeType] = useState<string>('dark');
const [hasBuild, setHasBuild] = useState<boolean>(false);
const [fileName, setFileName] = useState<string>('');
useEffect(() => {
const watchThemeSwitch = async () => {
client.eventEmitter.on('themeChanged', (theme: string) => {
setThemeType(theme)
})
client.eventEmitter.on('compilationFinished', (build: Build, fileName: string) => {
setHasBuild(true)
setFileName(fileName)
})
client.eventEmitter.on('docsGenerated', (docs: string[]) => {
console.log('docsGenerated', docs)
})
}
watchThemeSwitch()
}, [])
return (
<div className="p-3">
<h1>Remix Docgen</h1>
{fileName && <h4>File: {fileName.split('/')[1].split('.')[0].concat('.sol')}</h4>}
{hasBuild && <button className="btn btn-primary btn-block mt-4 rounded" onClick={() => client.generateDocs()}>Generate doc</button>}
</div>
)
}
export default App

@ -0,0 +1,80 @@
import { PluginClient } from '@remixproject/plugin'
import { CompilationResult, SourceWithTarget } from '@remixproject/plugin-api'
import { createClient } from '@remixproject/plugin-webview'
import EventEmitter from 'events'
import { Config, defaults } from './docgen/config'
import { Build, buildSite } from './docgen/site'
import { loadTemplates } from './docgen/templates'
import { SolcInput, SolcOutput } from 'solidity-ast/solc'
import { render } from './docgen/render'
export class DocGenClient extends PluginClient {
private currentTheme
public eventEmitter: EventEmitter
private build: Build
public docs: string[] = []
private fileName: string = ''
constructor() {
super()
this.eventEmitter = new EventEmitter()
this.methods = ['generateDocs', 'opendDocs']
createClient(this)
this.onload().then(async () => {
await this.setListeners()
})
}
async setListeners() {
this.currentTheme = await this.call('theme', 'currentTheme')
this.on('theme', 'themeChanged', (theme: any) => {
this.currentTheme = theme
this.eventEmitter.emit('themeChanged', this.currentTheme)
});
this.eventEmitter.emit('themeChanged', this.currentTheme)
this.on('solidity', 'compilationFinished', (fileName: string, source: SourceWithTarget, languageVersion: string, data: CompilationResult) => {
const input: SolcInput = {
sources: source.sources
}
const output: SolcOutput = {
sources: data.sources as any
}
this.build = {
input: input,
output: output
}
this.fileName = fileName
this.eventEmitter.emit('compilationFinished', this.build, fileName)
})
}
async docgen(builds: Build[], userConfig?: Config): Promise<void> {
const config = { ...defaults, ...userConfig }
const templates = await loadTemplates(config.theme, config.root, config.templates)
const site = buildSite(builds, config, templates.properties ?? {})
const renderedSite = render(site, templates, config.collapseNewlines)
const docs: string[] = []
for (const { id, contents } of renderedSite) {
const temp = `${this.fileName.split('/')[1].split('.')[0]}.${id.split('.')[1]}`
const newFileName = `docs/${temp}`
await this.call('fileManager', 'setFile', newFileName , contents)
docs.push(newFileName)
}
this.eventEmitter.emit('docsGenerated', docs)
this.emit('docgen' as any, 'docsGenerated', docs)
this.docs = docs
await this.opendDocs(docs)
}
async opendDocs(docs: string[]) {
await this.call('manager', 'activatePlugin', 'doc-viewer')
await this.call('tabs' as any, 'focus', 'doc-viewer')
await this.call('doc-viewer' as any, 'viewDocs', docs)
}
async generateDocs() {
this.docgen([this.build])
}
}

@ -0,0 +1,22 @@
import { VariableDeclaration } from "solidity-ast";
export function trim(text: string) {
if (typeof text === 'string') {
return text.trim();
}
}
export function joinLines(text?: string) {
if (typeof text === 'string') {
return text.replace(/\n+/g, ' ');
}
}
/**
* Format a variable as its type followed by its name, if available.
*/
export function formatVariable(v: VariableDeclaration): string {
return [v.typeName?.typeDescriptions.typeString].concat(v.name || []).join(' ');
}
export const eq = (a: unknown, b: unknown) => a === b;

@ -0,0 +1,138 @@
import { EnumDefinition, ErrorDefinition, EventDefinition, FunctionDefinition, ModifierDefinition, ParameterList, StructDefinition, UserDefinedValueTypeDefinition, VariableDeclaration } from 'solidity-ast';
import { findAll, isNodeType } from 'solidity-ast/utils';
import { NatSpec, parseNatspec } from '../utils/natspec';
import { DocItemContext, DOC_ITEM_CONTEXT } from '../site';
import { mapValues } from '../utils/map-values';
import { DocItem, docItemTypes } from '../doc-item';
import { formatVariable } from './helpers';
import { PropertyGetter } from '../templates';
import { itemType } from '../utils/item-type';
type TypeDefinition = StructDefinition | EnumDefinition | UserDefinedValueTypeDefinition;
export function type ({ item }: DocItemContext): string {
return itemType(item);
}
export function natspec ({ item }: DocItemContext): NatSpec {
return parseNatspec(item);
}
export function name({ item }: DocItemContext, original?: unknown): string {
if (item.nodeType === 'FunctionDefinition') {
return item.kind === 'function' ? original as string : item.kind;
} else {
return original as string;
}
}
export function fullName ({ item, contract }: DocItemContext): string {
if (contract) {
return `${contract.name}.${item.name}`;
} else {
return `${item.name}`;
}
}
export function signature ({ item }: DocItemContext): string | undefined {
switch (item.nodeType) {
case 'ContractDefinition':
return undefined;
case 'FunctionDefinition': {
const { kind, name } = item;
const params = item.parameters.parameters;
const returns = item.returnParameters.parameters;
const head = (kind === 'function' || kind === 'freeFunction') ? [kind, name].join(' ') : kind;
const res = [
`${head}(${params.map(formatVariable).join(', ')})`,
item.visibility,
];
if (item.stateMutability !== 'nonpayable') {
res.push(item.stateMutability);
}
if (item.virtual) {
res.push('virtual');
}
if (returns.length > 0) {
res.push(`returns (${returns.map(formatVariable).join(', ')})`);
}
return res.join(' ');
}
case 'EventDefinition': {
const params = item.parameters.parameters;
return `event ${item.name}(${params.map(formatVariable).join(', ')})`;
}
case 'ErrorDefinition': {
const params = item.parameters.parameters;
return `error ${item.name}(${params.map(formatVariable).join(', ')})`;
}
case 'ModifierDefinition': {
const params = item.parameters.parameters;
return `modifier ${item.name}(${params.map(formatVariable).join(', ')})`;
}
case 'VariableDeclaration':
return formatVariable(item);
}
}
interface Param extends VariableDeclaration {
type: string;
natspec?: string;
};
function getParams (params: ParameterList, natspec: NatSpec['params'] | NatSpec['returns']): Param[] {
return params.parameters.map((p, i) => ({
...p,
type: p.typeDescriptions.typeString!,
natspec: natspec?.find((q, j) => q.name === undefined ? i === j : p.name === q.name)?.description,
}));
}
export function params ({ item }: DocItemContext): Param[] | undefined {
if ('parameters' in item) {
return getParams(item.parameters, natspec(item[DOC_ITEM_CONTEXT]).params);
}
}
export function returns ({ item }: DocItemContext): Param[] | undefined {
if ('returnParameters' in item) {
return getParams(item.returnParameters, natspec(item[DOC_ITEM_CONTEXT]).returns);
}
}
export function items ({ item }: DocItemContext): DocItem[] | undefined {
return (item.nodeType === 'ContractDefinition')
? item.nodes.filter(isNodeType(docItemTypes)).filter(n => !('visibility' in n) || n.visibility !== 'private')
: undefined;
}
export function functions ({ item }: DocItemContext): FunctionDefinition[] | undefined {
return [...findAll('FunctionDefinition', item)].filter(f => f.visibility !== 'private');
}
export function events ({ item }: DocItemContext): EventDefinition[] | undefined {
return [...findAll('EventDefinition', item)];
}
export function modifiers ({ item }: DocItemContext): ModifierDefinition[] | undefined {
return [...findAll('ModifierDefinition', item)];
}
export function errors ({ item }: DocItemContext): ErrorDefinition[] | undefined {
return [...findAll('ErrorDefinition', item)];
}
export function variables ({ item }: DocItemContext): VariableDeclaration[] | undefined {
return (item.nodeType === 'ContractDefinition')
? item.nodes.filter(isNodeType('VariableDeclaration')).filter(v => v.stateVariable && v.visibility !== 'private')
: undefined;
}
export function types ({ item }: DocItemContext): TypeDefinition[] | undefined {
return [...findAll(['StructDefinition', 'EnumDefinition', 'UserDefinedValueTypeDefinition'], item)];
}

@ -0,0 +1,84 @@
import type { SourceUnit } from 'solidity-ast';
import type { DocItem } from './doc-item';
import type { PageAssigner, PageStructure } from './site';
export interface UserConfig {
/**
* The directory where rendered pages will be written.
* Defaults to 'docs'.
*/
outputDir?: string;
/**
* A directory of custom templates that should take precedence over the
* theme's templates.
*/
templates?: string;
/**
* The name of the built-in templates that will be used by default.
* Defaults to 'markdown'.
*/
theme?: string;
/**
* The way documentable items (contracts, functions, custom errors, etc.)
* will be organized in pages. Built in options are:
* - 'single': all items in one page
* - 'items': one page per item
* - 'files': one page per input Solidity file
* More customization is possible by defining a function that returns a page
* path given the AST node for the item and the source unit where it is
* defined.
* Defaults to 'single'.
*/
pages?: 'single' | 'items' | 'files' | PageAssigner;
/**
* An array of sources subdirectories that should be excluded from
* documentation, relative to the contract sources directory.
*/
exclude?: string[];
/**
* Clean up the output by collapsing 3 or more contiguous newlines into only 2.
* Enabled by default.
*/
collapseNewlines?: boolean;
/**
* The extension for generated pages.
* Defaults to '.md'.
*/
pageExtension?: string;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Other config parameters that will be provided by the environment (e.g. Hardhat)
// rather than by the user manually, unless using the library directly.
export interface Config extends UserConfig {
/**
* The root directory relative to which 'outputDir', 'sourcesDir', and
* 'templates' are specified. Defaults to the working directory.
*/
root?: string;
/**
* The Solidity sources directory.
*/
sourcesDir?: string;
}
export type FullConfig = Required<Config>;
export const defaults: Omit<FullConfig, 'templates'> = {
root: process.cwd(),
sourcesDir: 'contracts',
outputDir: 'docs',
pages: 'single',
exclude: [],
theme: 'markdown',
collapseNewlines: true,
pageExtension: '.md',
};

@ -0,0 +1,27 @@
import { ContractDefinition, ImportDirective, PragmaDirective, SourceUnit, UsingForDirective } from "solidity-ast";
import { Node, NodeType, NodeTypeMap } from "solidity-ast/node";
import { AssertEqual } from "./utils/assert-equal-types";
export type DocItem = Exclude<
SourceUnit['nodes'][number] | ContractDefinition['nodes'][number],
ImportDirective | PragmaDirective | UsingForDirective
>;
export const docItemTypes = [
'ContractDefinition',
'EnumDefinition',
'ErrorDefinition',
'EventDefinition',
'FunctionDefinition',
'ModifierDefinition',
'StructDefinition',
'UserDefinedValueTypeDefinition',
'VariableDeclaration',
] as const;
// Make sure at compile time that docItemTypes contains exactly the node types of DocItem.
const _: AssertEqual<typeof docItemTypes[number], DocItem['nodeType']> = true;
export function isDocItem(node: Node): node is DocItem {
return (docItemTypes as readonly string[]).includes(node.nodeType);
}

@ -0,0 +1,87 @@
import Handlebars, { RuntimeOptions } from 'handlebars';
import { Site, Page, DocItemWithContext, DOC_ITEM_CONTEXT } from './site';
import { Templates } from './templates';
import { itemType } from './utils/item-type';
import fs from 'fs';
export interface RenderedPage {
id: string;
contents: string;
}
interface TemplateOptions {
data: {
site: Site;
};
}
export function render(site: Site, templates: Templates, collapseNewlines?: boolean): RenderedPage[] {
const renderPage = buildRenderer(templates);
const renderedPages: RenderedPage[] = [];
for (const page of site.pages) {
let contents = renderPage(page, { data: { site } });
if (collapseNewlines) {
contents = contents.replace(/\n{3,}/g, '\n\n');
}
renderedPages.push({
id: page.id,
contents,
});
}
return renderedPages;
}
export const itemPartialName = (item: DocItemWithContext) => itemType(item).replace(/ /g, '-').toLowerCase();
function itemPartial(item: DocItemWithContext, options?: RuntimeOptions) {
if (!item.__item_context) {
throw new Error(`Partial 'item' used in unsupported context (not a doc item)`);
}
const partial = options?.partials?.[itemPartialName(item)];
if (!partial) {
throw new Error(`Missing partial '${itemPartialName(item)}'`);
}
return partial(item, options);
}
function readmeHelper(H: typeof Handlebars, path: string, opts: RuntimeOptions) {
const items: DocItemWithContext[] = opts.data.root.items;
const renderedItems = Object.fromEntries(
items.map(item => [
item.name,
new H.SafeString(
H.compile('{{>item}}')(item, opts),
),
]),
);
return new H.SafeString(
H.compile(fs.readFileSync(path, 'utf8'))(renderedItems, opts),
);
}
function buildRenderer(templates: Templates): (page: Page, options: TemplateOptions) => string {
const pageTemplate = templates.partials?.page;
if (pageTemplate === undefined) {
throw new Error(`Missing 'page' template`);
}
const H = Handlebars.create();
for (const [name, getBody] of Object.entries(templates.partials ?? {})) {
let partial: HandlebarsTemplateDelegate | undefined;
H.registerPartial(name, (...args) => {
partial ??= H.compile(getBody());
return partial(...args);
});
}
H.registerHelper('readme', (path: string, opts: RuntimeOptions) => readmeHelper(H, path, opts));
for (const [name, fn] of Object.entries(templates.helpers ?? {})) {
H.registerHelper(name, fn);
}
H.registerPartial('item', itemPartial);
return H.compile('{{>page}}');
}

@ -0,0 +1,138 @@
import path from 'path';
import { ContractDefinition, SourceUnit } from 'solidity-ast';
import { SolcOutput, SolcInput } from 'solidity-ast/solc';
import { astDereferencer, ASTDereferencer, findAll, isNodeType, srcDecoder, SrcDecoder } from 'solidity-ast/utils';
import { FullConfig } from './config';
import { DocItem, docItemTypes, isDocItem } from './doc-item';
import { Properties } from './templates';
import { clone } from './utils/clone';
import { isChild } from './utils/is-child';
import { mapValues } from './utils/map-values';
import { defineGetterMemoized } from './utils/memoized-getter';
export interface Build {
input: SolcInput;
output: SolcOutput;
}
export interface BuildContext extends Build {
deref: ASTDereferencer;
decodeSrc: SrcDecoder;
}
export type SiteConfig = Pick<FullConfig, 'pages' | 'exclude' | 'sourcesDir' | 'pageExtension'>;
export type PageStructure = SiteConfig['pages'];
export type PageAssigner = ((item: DocItem, file: SourceUnit, config: SiteConfig) => string | undefined);
export const pageAssigner: Record<PageStructure & string, PageAssigner> = {
single: (_1, _2, { pageExtension: ext }) => 'index' + ext,
items: (item, _, { pageExtension: ext }) => item.name + ext,
files: (_, file, { pageExtension: ext, sourcesDir }) =>
path.relative(sourcesDir, file.absolutePath).replace('.sol', ext),
};
export interface Site {
items: DocItemWithContext[];
pages: Page[];
}
export interface Page {
id: string;
items: DocItemWithContext[];
}
export const DOC_ITEM_CONTEXT = '__item_context' as const;
export type DocItemWithContext = DocItem & { [DOC_ITEM_CONTEXT]: DocItemContext };
export interface DocItemContext {
page?: string;
item: DocItemWithContext;
contract?: ContractDefinition;
file: SourceUnit;
build: BuildContext;
}
export function buildSite (builds: Build[], siteConfig: SiteConfig, properties: Properties = {}): Site {
const assign = typeof siteConfig.pages === 'string' ? pageAssigner[siteConfig.pages] : siteConfig.pages;
const seen = new Set<string>();
const items: DocItemWithContext[] = [];
const pages: Record<string, DocItemWithContext[]> = {};
// eslint-disable-next-line prefer-const
for (let { input, output } of builds) {
// Clone because we will mutate in order to add item context.
output = { ...output, sources: clone(output.sources) };
const deref = astDereferencer(output);
const decodeSrc = srcDecoder(input, output);
const build = { input, output, deref, decodeSrc };
for (const { ast: file } of Object.values(output.sources)) {
const isNewFile = !seen.has(file.absolutePath);
seen.add(file.absolutePath);
for (const topLevelItem of file.nodes) {
if (!isDocItem(topLevelItem)) continue;
const page = assignIfIncludedSource(assign, topLevelItem, file, siteConfig);
const withContext = defineContext(topLevelItem, build, file, page);
defineProperties(withContext, properties);
if (isNewFile && page !== undefined) {
(pages[page] ??= []).push(withContext);
items.push(withContext);
}
if (!isNodeType('ContractDefinition', topLevelItem)) {
continue;
}
for (const item of topLevelItem.nodes) {
if (!isDocItem(item)) continue;
if (isNewFile && page !== undefined) items.push(item as DocItemWithContext);
const contract = topLevelItem.nodeType === 'ContractDefinition' ? topLevelItem : undefined;
const withContext = defineContext(item, build, file, page, contract);
defineProperties(withContext, properties);
}
}
}
}
return {
items,
pages: Object.entries(pages).map(([id, pageItems]) => ({ id, items: pageItems })),
};
}
function defineContext (item: DocItem, build: BuildContext, file: SourceUnit, page?: string, contract?: ContractDefinition): DocItemWithContext {
return Object.assign(item, {
[DOC_ITEM_CONTEXT]: { build, file, contract, page, item: item as DocItemWithContext },
});
}
function defineProperties (item: DocItemWithContext, properties: Properties) {
for (const [prop, fn] of Object.entries(properties)) {
const original: unknown = (item as any)[prop];
defineGetterMemoized(item as any, prop, () => fn(item.__item_context, original));
}
}
function assignIfIncludedSource (
assign: PageAssigner,
item: DocItem,
file: SourceUnit,
config: SiteConfig,
) {
return isFileIncluded(file.absolutePath, config)
? assign(item, file, config)
: undefined;
}
function isFileIncluded (file: string, config: SiteConfig) {
return (
isChild(file, config.sourcesDir) &&
config.exclude.every(e => !isChild(file, path.join(config.sourcesDir, e)))
);
}

@ -0,0 +1,90 @@
import { mapKeys } from './utils/map-keys';
import { DocItemContext } from './site';
import * as defaultProperties from './common/properties';
export type PropertyGetter = (ctx: DocItemContext, original?: unknown) => unknown;
export type Properties = Record<string, PropertyGetter>;
export interface Templates {
partials?: Record<string, () => string>;
helpers?: Record<string, (...args: unknown[]) => string>;
properties?: Record<string, PropertyGetter>;
}
/**
* Loads the templates that will be used for rendering a site based on a
* default theme and user templates.
*
* The result contains all partials, helpers, and property getters defined in
* the user templates and the default theme, where the user's take precedence
* if there is a clash. Additionally, all theme partials and helpers are
* included with the theme prefix, e.g. `markdown/contract` will be a partial.
*/
export async function loadTemplates(defaultTheme: string, root: string, userTemplatesPath?: string): Promise<Templates> {
const themes = await readThemes();
// Initialize templates with the default theme.
const templates: Required<Templates> = {
partials: { ...themes[defaultTheme]?.partials },
helpers: { ...themes[defaultTheme]?.helpers },
properties: { ...defaultProperties },
};
// Add partials and helpers from all themes, prefixed with the theme name.
for (const [themeName, theme] of Object.entries(themes)) {
const addPrefix = (k: string) => `${themeName}/${k}`;
Object.assign(templates.partials, mapKeys(theme.partials, addPrefix));
Object.assign(templates.helpers, mapKeys(theme.helpers, addPrefix));
}
return templates;
}
/**
* Read templates and helpers from a directory.
*/
export async function readTemplates(): Promise<Required<Templates>> {
return {
partials: await readPartials(),
helpers: await readHelpers('helpers'),
properties: await readHelpers('properties'),
};
}
async function readPartials() {
const partials: NonNullable<Templates['partials']> = {};
const partialNames = ["common", "contract", "enum", "error", "event", "function", "modifier", "page", "struct", "variable", "user-defined-value-type"]
for (const name of partialNames) {
const p = await import('raw-loader!./themes/markdown/' + name + '.hbs')
partials[name] = () => p.default
}
return partials;
}
async function readHelpers(name: string) {
let helpersPath;
const h = await import('./themes/markdown/helpers');
const helpers: Record<string, (...args: any[]) => any> = {};
for (const name in h) {
if (typeof h[name] === 'function') {
helpers[name] = h[name];
}
}
return helpers;
}
/**
* Reads all built-in themes into an object. Partials will always be found in
* src/themes, whereas helpers may instead be found in dist/themes if TypeScript
* can't be imported directly.
*/
async function readThemes(): Promise<Record<string, Required<Templates>>> {
const themes: Record<string, Required<Templates>> = {}
themes['markdown'] = await readTemplates()
return themes
}

@ -0,0 +1,34 @@
{{h}} {{name}}
{{#if signature}}
```solidity
{{{signature}}}
```
{{/if}}
{{{natspec.notice}}}
{{#if natspec.dev}}
_{{{natspec.dev}}}_
{{/if}}
{{#if natspec.params}}
{{h 2}} Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
{{#each params}}
| {{name}} | {{type}} | {{{joinLines natspec}}} |
{{/each}}
{{/if}}
{{#if natspec.returns}}
{{h 2}} Return Values
| Name | Type | Description |
| ---- | ---- | ----------- |
{{#each returns}}
| {{#if name}}{{name}}{{else}}[{{@index}}]{{/if}} | {{type}} | {{{joinLines natspec}}} |
{{/each}}
{{/if}}

@ -0,0 +1,8 @@
{{>common}}
{{#each items}}
{{#hsection}}
{{>item}}
{{/hsection}}
{{/each}}

@ -0,0 +1,9 @@
{{>common}}
```solidity
enum {{name}} {
{{#each members}}
{{name}}{{#unless @last}},{{/unless}}
{{/each}}
}
```

@ -0,0 +1,49 @@
import { HelperOptions, Utils } from 'handlebars';
export * from '../../common/helpers';
/**
* Returns a Markdown heading marker. An optional number increases the heading level.
*
* Input Output
* {{h}} {{name}} # Name
* {{h 2}} {{name}} ## Name
*/
export function h(opts: HelperOptions): string;
export function h(hsublevel: number, opts: HelperOptions): string;
export function h(hsublevel: number | HelperOptions, opts?: HelperOptions) {
const { hlevel } = getHLevel(hsublevel, opts);
return new Array(hlevel).fill('#').join('');
};
/**
* Delineates a section where headings should be increased by 1 or a custom number.
*
* {{#hsection}}
* {{>partial-with-headings}}
* {{/hsection}}
*/
export function hsection(opts: HelperOptions): string;
export function hsection(hsublevel: number, opts: HelperOptions): string;
export function hsection(this: unknown, hsublevel: number | HelperOptions, opts?: HelperOptions) {
let hlevel;
({ hlevel, opts } = getHLevel(hsublevel, opts));
opts.data = Utils.createFrame(opts.data);
opts.data.hlevel = hlevel;
return opts.fn(this as unknown, opts);
}
/**
* Helper for dealing with the optional hsublevel argument.
*/
function getHLevel(hsublevel: number | HelperOptions, opts?: HelperOptions) {
if (typeof hsublevel === 'number') {
opts = opts!;
hsublevel = Math.max(1, hsublevel);
} else {
opts = hsublevel;
hsublevel = 1;
}
const contextHLevel: number = opts.data?.hlevel ?? 0;
return { opts, hlevel: contextHLevel + hsublevel };
}

@ -0,0 +1,8 @@
# Solidity API
{{#each items}}
{{#hsection}}
{{>item}}
{{/hsection}}
{{/each}}

@ -0,0 +1,9 @@
{{>common}}
```solidity
struct {{name}} {
{{#each members}}
{{{typeName.typeDescriptions.typeString}}} {{name}};
{{/each}}
}
```

@ -0,0 +1,13 @@
import { DocItemWithContext, DOC_ITEM_CONTEXT } from '../site';
export class ItemError extends Error {
constructor(msg: string, item: DocItemWithContext) {
const ctx = item[DOC_ITEM_CONTEXT];
const src = ctx && ctx.build.decodeSrc(item);
if (src) {
super(msg + ` (${src})`);
} else {
super(msg);
}
}
}

@ -0,0 +1,5 @@
export function arraysEqual<T>(a: T[], b: T[]): boolean;
export function arraysEqual<T, U>(a: T[], b: T[], mapFn: (x: T) => U): boolean;
export function arraysEqual<T>(a: T[], b: T[], mapFn = (x: T) => x): boolean {
return a.length === b.length && a.every((x, i) => mapFn(x) === mapFn(b[i]!));
}

@ -0,0 +1 @@
export type AssertEqual<T, U> = [T, U] extends [U, T] ? true : never;

@ -0,0 +1,6 @@
/**
* Deep cloning good enough for simple objects like solc output. Types are not
* sound because the function may lose information: non-enumerable properties,
* symbols, undefined values, prototypes, etc.
*/
export const clone = <T>(obj: T): T => JSON.parse(JSON.stringify(obj));

@ -0,0 +1,12 @@
// The function below would not be correctly typed if the return type was T[]
// because T may itself be an array type and Array.isArray would not know the
// difference. Adding IfArray<T> makes sure the return type is always correct.
type IfArray<T> = T extends any[] ? T : never;
export function ensureArray<T>(x: T | T[]): T[] | IfArray<T> {
if (Array.isArray(x)) {
return x;
} else {
return [x];
}
}

@ -0,0 +1,18 @@
/**
* Iterates over all contiguous matches of the regular expression over the
* text. Stops as soon as the regular expression no longer matches at the
* current position.
*/
export function* execAll(re: RegExp, text: string) {
re = new RegExp(re, re.flags + (re.sticky ? '' : 'y'));
while (true) {
const match = re.exec(text);
// We break out of the loop if there is no match or if the empty string is
// matched because no progress will be made and it will loop indefinitely.
if (!match?.[0]) break;
yield match;
}
}

@ -0,0 +1,5 @@
import path from 'path';
export function isChild(file: string, parent: string) {
return path.normalize(file + path.sep).startsWith(path.normalize(parent + path.sep));
}

@ -0,0 +1,7 @@
import { DocItem } from '../doc-item';
export function itemType(item: DocItem): string {
return item.nodeType
.replace(/(Definition|Declaration)$/, '')
.replace(/(\w)([A-Z])/g, '$1 $2');
}

@ -0,0 +1,4 @@
export function mapKeys<T>(obj: Record<string, T>, fn: (key: string) => string): Record<string, T> {
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [fn(k), v]));
}

@ -0,0 +1,19 @@
export function mapValues<T, U>(obj: Record<string, T>, fn: (value: T) => U): Record<string, U> {
const res: Record<string, U> = {};
for (const [k, v] of Object.entries(obj)) {
res[k] = fn(v);
}
return res;
}
export function filterValues<T, U extends T>(obj: Record<string, T>, fn: (value: T) => value is U): Record<string, U>;
export function filterValues<T>(obj: Record<string, T>, fn: (value: T) => boolean): Record<string, T>;
export function filterValues<T>(obj: Record<string, T>, fn: (value: T) => boolean): Record<string, T> {
const res: Record<string, T> = {};
for (const [k, v] of Object.entries(obj)) {
if (fn(v)) {
res[k] = v;
}
}
return res;
}

@ -0,0 +1,23 @@
export function defineGetterMemoized<K extends keyof any, T, O extends { [k in K]?: T }>(obj: O, key: K, getter: () => T) {
let state: 'todo' | 'doing' | 'done' = 'todo';
let value: T;
Object.defineProperty(obj, key, {
enumerable: true,
get() {
switch (state) {
case 'done':
return value;
case 'doing':
throw new Error("Detected recursion");
case 'todo':
state = 'doing';
value = getter();
state = 'done';
return value;
}
}
});
}

@ -0,0 +1,145 @@
import { FunctionDefinition } from 'solidity-ast';
import { findAll } from 'solidity-ast/utils';
import { DocItemWithContext, DOC_ITEM_CONTEXT } from '../site';
import { arraysEqual } from './arrays-equal';
import { execAll } from './execall';
import { itemType } from './item-type';
import { ItemError } from './ItemError';
import { readItemDocs } from './read-item-docs';
import { getContractsInScope } from './scope';
export interface NatSpec {
title?: string;
notice?: string;
dev?: string;
params?: {
name: string;
description: string;
}[];
returns?: {
name?: string;
description: string;
}[];
custom?: {
[tag: string]: string;
};
}
export function parseNatspec(item: DocItemWithContext): NatSpec {
if (!item[DOC_ITEM_CONTEXT]) throw new Error(`Not an item or item is missing context`);
let res: NatSpec = {};
const docSource = readItemDocs(item);
const docString = docSource !== undefined
? cleanUpDocstringFromSource(docSource)
: 'documentation' in item && item.documentation
? typeof item.documentation === 'string'
? item.documentation
: cleanUpDocstringFromSolc(item.documentation.text)
: '';
const tagMatches = execAll(
/^(?:@(\w+|custom:[a-z][a-z-]*) )?((?:(?!^@(?:\w+|custom:[a-z][a-z-]*) )[^])*)/m,
docString,
);
let inheritFrom: FunctionDefinition | undefined;
for (const [, tag = 'notice', content] of tagMatches) {
if (content === undefined) throw new ItemError('Unexpected error', item);
if (tag === 'dev' || tag === 'notice') {
res[tag] ??= '';
res[tag] += content;
}
if (tag === 'title') {
res.title = content.trim();
}
if (tag === 'param') {
const paramMatches = content.match(/(\w+) ([^]*)/);
if (paramMatches) {
const [, name, description] = paramMatches as [string, string, string];
res.params ??= [];
res.params.push({ name, description: description.trim() });
}
}
if (tag === 'return') {
if (!('returnParameters' in item)) {
throw new ItemError(`Item does not contain return parameters`, item);
}
res.returns ??= [];
const i = res.returns.length;
const p = item.returnParameters.parameters[i];
if (p === undefined) {
throw new ItemError('Got more @return tags than expected', item);
}
if (!p.name) {
res.returns.push({ description: content.trim() });
} else {
const paramMatches = content.match(/(\w+)( ([^]*))?/);
if (!paramMatches || paramMatches[1] !== p.name) {
throw new ItemError(`Expected @return tag to start with name '${p.name}'`, item);
}
const [, name, description] = paramMatches as [string, string, string?];
res.returns.push({ name, description: description?.trim() ?? '' });
}
}
if (tag?.startsWith('custom:')) {
const key = tag.replace(/^custom:/, '');
res.custom ??= {};
res.custom[key] ??= '';
res.custom[key] += content;
}
if (tag === 'inheritdoc') {
if (!(item.nodeType === 'FunctionDefinition' || item.nodeType === 'VariableDeclaration')) {
throw new ItemError(`Expected function or variable but saw ${itemType(item)}`, item);
}
const parentContractName = content.trim();
const parentContract = getContractsInScope(item)[parentContractName];
if (!parentContract) {
throw new ItemError(`Parent contract '${parentContractName}' not found`, item);
}
inheritFrom = [...findAll('FunctionDefinition', parentContract)].find(f => item.baseFunctions?.includes(f.id));
}
}
if (docString.length === 0) {
if ('baseFunctions' in item && item.baseFunctions?.length === 1) {
const baseFn = item[DOC_ITEM_CONTEXT].build.deref('FunctionDefinition', item.baseFunctions[0]!);
const shouldInherit = item.nodeType === 'VariableDeclaration' || arraysEqual(item.parameters.parameters, baseFn.parameters.parameters, p => p.name);
if (shouldInherit) {
inheritFrom = baseFn;
}
}
}
if (res.dev) res.dev = res.dev.trim();
if (res.notice) res.notice = res.notice.trim();
if (inheritFrom) {
res = { ...parseNatspec(inheritFrom as DocItemWithContext), ...res };
}
return res;
}
// Fix solc buggy parsing of doc comments.
// Reverse engineered from solc behavior.
function cleanUpDocstringFromSolc(text: string) {
return text
.replace(/\n\n?^[ \t]*(?:\*|\/\/\/)/mg, '\n\n')
.replace(/^[ \t]?/mg, '');
}
function cleanUpDocstringFromSource(text: string) {
return text
.replace(/^\/\*\*(.*)\*\/$/s, '$1')
.trim()
.replace(/^[ \t]*(\*|\/\/\/)[ \t]?/mg, '');
}

@ -0,0 +1,26 @@
import { DocItemWithContext, DOC_ITEM_CONTEXT, Build } from '../site';
export function readItemDocs(item: DocItemWithContext): string | undefined {
const { build } = item[DOC_ITEM_CONTEXT];
// Note that Solidity 0.5 has item.documentation: string even though the
// types do not reflect that. This is why we check typeof === object.
if ('documentation' in item && item.documentation && typeof item.documentation === 'object') {
const { source, start, length } = decodeSrc(item.documentation.src, build);
const content = build.input.sources[source]?.content;
if (content !== undefined) {
return Buffer.from(content, 'utf8').slice(start, start + length).toString('utf8');
}
}
}
function decodeSrc(src: string, build: Build): { source: string; start: number; length: number } {
const [start, length, sourceId] = src.split(':').map(s => parseInt(s));
if (start === undefined || length === undefined || sourceId === undefined) {
throw new Error(`Bad source string ${src}`);
}
const source = Object.keys(build.output.sources).find(s => build.output.sources[s]?.id === sourceId);
if (source === undefined) {
throw new Error(`No source with id ${sourceId}`);
}
return { source, start, length };
}

@ -0,0 +1,63 @@
import { ContractDefinition, SourceUnit } from "solidity-ast";
import { findAll, isNodeType } from "solidity-ast/utils";
import { DocItemWithContext } from "../site";
import { filterValues, mapValues } from './map-values';
import { mapKeys } from './map-keys';
type Definition = SourceUnit['nodes'][number] & { name: string };
type Scope = { [name in string]: () => { namespace: Scope } | { definition: Definition } };
export function getContractsInScope(item: DocItemWithContext) {
const cache = new WeakMap<SourceUnit, Scope>();
return filterValues(
flattenScope(run(item.__item_context.file)),
isNodeType('ContractDefinition'),
);
function run(file: SourceUnit): Scope {
if (cache.has(file)) {
return cache.get(file)!;
}
const scope: Scope = {};
cache.set(file, scope);
for (const c of file.nodes) {
if ('name' in c) {
scope[c.name] = () => ({ definition: c });
}
}
for (const i of findAll('ImportDirective', file)) {
const importedFile = item.__item_context.build.deref('SourceUnit', i.sourceUnit);
const importedScope = run(importedFile);
if (i.unitAlias) {
scope[i.unitAlias] = () => ({ namespace: importedScope });
} else if (i.symbolAliases.length === 0) {
Object.assign(scope, importedScope);
} else {
for (const a of i.symbolAliases) {
// Delayed function call supports circular dependencies
scope[a.local ?? a.foreign.name] = importedScope[a.foreign.name] ?? (() => importedScope[a.foreign.name]!());
}
}
};
return scope;
}
}
function flattenScope(scope: Scope): Record<string, Definition> {
return Object.fromEntries(
Object.entries(scope).flatMap(([k, fn]) => {
const v = fn();
if ('definition' in v) {
return [[k, v.definition] as const];
} else {
return Object.entries(mapKeys(flattenScope(v.namespace), k2 => k + '.' + k2));
}
}),
);
}

@ -0,0 +1,37 @@
import { useState } from "react";
export function useLocalStorage(key: string, initialValue: any) {
// State to store our value
// Pass initial state function to useState so logic is only executed once
const [storedValue, setStoredValue] = useState(() => {
try {
// Get from local storage by key
const item = window.localStorage.getItem(key);
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// If error also return initialValue
console.log(error);
return initialValue;
}
});
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = (value: any) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value;
// Save state
setStoredValue(valueToStore);
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
// A more advanced implementation would handle the error case
console.log(error);
}
};
return [storedValue, setValue];
}

@ -0,0 +1,31 @@
import React from "react";
export const ErrorView: React.FC = () => {
return (
<div
style={{
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<img
style={{ paddingBottom: "2em" }}
width="250"
src="https://res.cloudinary.com/key-solutions/image/upload/v1580400635/solid/error-png.png"
alt="Error page"
/>
<h5>Sorry, something unexpected happened. </h5>
<h5>
Please raise an issue:{" "}
<a
style={{ color: "red" }}
href="https://github.com/Machinalabs/remix-ethdoc-plugin/issues"
>
Here
</a>
</h5>
</div>
);
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Remix Docgen</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<div id="root"></div>
</body>
</html>

@ -0,0 +1,11 @@
import React from "react";
import ReactDOM from "react-dom";
import App from "./app/App";
// import { Routes } from "./routes";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);

@ -0,0 +1,11 @@
export type Documentation = string
export interface EthDocumentation {
[contractName: string]: Documentation
}
export type ContractName = string
export type FileName = string
export type PublishedSite = string

@ -0,0 +1,23 @@
{
"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": [
"jest.config.ts",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

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

@ -0,0 +1,82 @@
const { composePlugins, withNx } = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react')
const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => {
// Update the webpack config as needed here.
// e.g. `config.plugins.push(new MyPlugin())`
// add fallback for node modules
config.resolve.fallback = {
...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"),
"stream": require.resolve("stream-browserify"),
"path": require.resolve("path-browserify"),
"http": require.resolve("stream-http"),
"https": require.resolve("https-browserify"),
"constants": require.resolve("constants-browserify"),
"os": false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"),
"fs": false,
"module": false,
"tls": false,
"net": false,
"readline": false,
"child_process": false,
"buffer": require.resolve("buffer/"),
"vm": require.resolve('vm-browserify'),
}
// add externals
config.externals = {
...config.externals,
solc: 'solc',
}
// add public path
config.output.publicPath = '/'
// add copy & provide plugin
config.plugins.push(
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
}),
new webpack.DefinePlugin({
}),
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
})
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer
config.optimization.minimizer = [
new TerserPlugin({
parallel: true,
terserOptions: {
ecma: 2015,
compress: false,
mangle: false,
format: {
comments: false,
},
},
extractComments: false,
}),
new CssMinimizerPlugin(),
];
return config;
})

@ -0,0 +1,59 @@
{
"name": "doc-viewer",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/doc-viewer/src",
"projectType": "application",
"implicitDependencies": [
],
"targets": {
"build": {
"executor": "@nrwl/webpack:webpack",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "development",
"options": {
"compiler": "babel",
"outputPath": "dist/apps/doc-viewer",
"index": "apps/doc-viewer/src/index.html",
"baseHref": "/",
"main": "apps/doc-viewer/src/main.tsx",
"tsConfig": "apps/doc-viewer/tsconfig.app.json",
"assets": [
"apps/doc-viewer/src/favicon.ico"
],
"styles": [],
"scripts": [],
"webpackConfig": "apps/doc-viewer/webpack.config.js"
},
"configurations": {
"development": {
},
"production": {
"fileReplacements": [
{
"replace": "apps/doc-viewer/src/environments/environment.ts",
"with": "apps/doc-viewer/src/environments/environment.prod.ts"
}
]
}
}
},
"serve": {
"executor": "@nrwl/webpack:dev-server",
"defaultConfiguration": "development",
"options": {
"buildTarget": "doc-viewer:build",
"hmr": true
},
"configurations": {
"development": {
"buildTarget": "doc-viewer:build:development",
"port": 7003
},
"production": {
"buildTarget": "doc-viewer:build:production"
}
}
}
},
"tags": []
}

@ -0,0 +1,23 @@
import React, { useEffect, useState } from "react"
import { DocViewer } from "./docviewer"
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
const client = new DocViewer()
export default function App() {
const [contents, setContents] = useState('')
useEffect(() => {
client.eventEmitter.on('contentsReady', (fileContents: string) => {
setContents(fileContents)
})
}, [])
return (
<>
<div className="m-5 p-2">
<ReactMarkdown children={contents} remarkPlugins={[remarkGfm]}/>
</div>
</>
)
}

@ -0,0 +1,22 @@
import { PluginClient } from '@remixproject/plugin'
import { createClient } from '@remixproject/plugin-webview'
import EventEmitter from 'events'
export class DocViewer extends PluginClient {
mdFile: string
eventEmitter: EventEmitter
constructor() {
super()
this.eventEmitter = new EventEmitter()
this.methods = ['viewDocs']
createClient(this)
this.mdFile = ''
this.onload()
}
async viewDocs(docs: string[]) {
this.mdFile = docs[0]
const contents = await this.call('fileManager', 'readFile', this.mdFile)
this.eventEmitter.emit('contentsReady', contents)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Doc Viewer</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<div id="root"></div>
</body>
</html>

@ -0,0 +1,10 @@
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app/App'
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);

@ -0,0 +1,23 @@
{
"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": [
"jest.config.ts",
"**/*.spec.ts",
"**/*.test.ts",
"**/*.spec.tsx",
"**/*.test.tsx",
"**/*.spec.js",
"**/*.test.js",
"**/*.spec.jsx",
"**/*.test.jsx"
],
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
}

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

@ -0,0 +1,82 @@
const { composePlugins, withNx } = require('@nrwl/webpack')
const { withReact } = require('@nrwl/react')
const webpack = require('webpack')
const TerserPlugin = require("terser-webpack-plugin")
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
// Nx plugins for webpack.
module.exports = composePlugins(withNx(), withReact(), (config) => {
// Update the webpack config as needed here.
// e.g. `config.plugins.push(new MyPlugin())`
// add fallback for node modules
config.resolve.fallback = {
...config.resolve.fallback,
"crypto": require.resolve("crypto-browserify"),
"stream": require.resolve("stream-browserify"),
"path": require.resolve("path-browserify"),
"http": require.resolve("stream-http"),
"https": require.resolve("https-browserify"),
"constants": require.resolve("constants-browserify"),
"os": false, //require.resolve("os-browserify/browser"),
"timers": false, // require.resolve("timers-browserify"),
"zlib": require.resolve("browserify-zlib"),
"fs": false,
"module": false,
"tls": false,
"net": false,
"readline": false,
"child_process": false,
"buffer": require.resolve("buffer/"),
"vm": require.resolve('vm-browserify'),
}
// add externals
config.externals = {
...config.externals,
solc: 'solc',
}
// add public path
config.output.publicPath = '/'
// add copy & provide plugin
config.plugins.push(
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
url: ['url', 'URL'],
process: 'process/browser',
}),
new webpack.DefinePlugin({
}),
)
// souce-map loader
config.module.rules.push({
test: /\.js$/,
use: ["source-map-loader"],
enforce: "pre"
})
config.ignoreWarnings = [/Failed to parse source map/] // ignore source-map-loader warnings
// set minimizer
config.optimization.minimizer = [
new TerserPlugin({
parallel: true,
terserOptions: {
ecma: 2015,
compress: false,
mangle: false,
format: {
comments: false,
},
},
extractComments: false,
}),
new CssMinimizerPlugin(),
];
return config;
})

@ -148,9 +148,7 @@ export const HeaderWithSettings: React.FC<Props> = ({
<h6>{title}</h6>
<div style={{ float: "right" }}>
<HomeIcon from={from} themeType={themeType} />
<ReceiptsIcon from={from} themeType={themeType} />
<SettingsIcon from={from} themeType={themeType} />
</div>
</div>

@ -23,8 +23,6 @@ interface FormValues {
contractAddress: string
}
export const VerifyView: React.FC<Props> = ({
apiKey,
client,

@ -54,9 +54,9 @@ function addFile(browser: NightwatchBrowser, name: string, content: NightwatchCo
suppressNotFoundErrors: true,
timeout: 60000
})
.waitForElementPresent({
selector: `//*[@data-id="tab-active" and contains(@title, "${name}")]`,
locateStrategy: 'xpath',
.waitForElementVisible({
selector: `//*[@data-id='tab-active' and contains(@data-path, "${name}")]`,
locateStrategy: 'xpath'
})
.setEditorValue(content.content)
.getEditorValue((result) => {

@ -7,7 +7,7 @@ type LoadPlugin = {
url: string
}
export default function (browser: NightwatchBrowser, callback: VoidFunction, url?: string, preloadPlugins = true, loadPlugin?: LoadPlugin): void {
export default function (browser: NightwatchBrowser, callback: VoidFunction, url?: string, preloadPlugins = true, loadPlugin?: LoadPlugin, hideToolTips: boolean = true): void {
browser
.url(url || 'http://127.0.0.1:8080')
//.switchBrowserTab(0)
@ -27,6 +27,30 @@ export default function (browser: NightwatchBrowser, callback: VoidFunction, url
})
.verifyLoad()
.perform(() => {
if (hideToolTips) {
browser.execute(function () { // hide tooltips
function addStyle(styleString) {
const style = document.createElement('style');
style.textContent = styleString;
document.head.append(style);
}
addStyle(`
.bs-popover-right {
display:none !important;
}
.bs-popover-top {
display:none !important;
}
.bs-popover-left {
display:none !important;
}
.bs-popover-bottom {
display:none !important;
}
`);
})
}
if (preloadPlugins) {
initModules(browser, () => {
browser

@ -11,7 +11,7 @@ const sources = [
module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, null, false)
init(browser, done)
},
'@sources': function () {
return sources
@ -25,9 +25,6 @@ module.exports = {
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('solidity')
.setSolidityCompilerVersion('soljson-v0.4.14+commit.c2215d46.js')
.waitForElementVisible('[for="autoCompile"]')
.click('[for="autoCompile"]')
.verify.elementPresent('[data-id="compilerContainerAutoCompile"]:checked')
},
'Compile Ballot with compiler version 0.4.14': function (browser: NightwatchBrowser) {

@ -66,7 +66,7 @@ module.exports = {
.waitForElementVisible('div[data-id="filePanelFileExplorerTree"]')
.click('[data-id="treeViewLitreeViewItemcontracts"]')
.openFile('contracts/3_Ballot.sol')
.assert.containsText('div[title="default_workspace/contracts/3_Ballot.sol"]', '3_Ballot.sol')
.assert.containsText('div[data-path="default_workspace/contracts/3_Ballot.sol"]', '3_Ballot.sol')
.end()
}
}

@ -29,8 +29,8 @@ module.exports = {
.waitForElementContainsText('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 'customtext')
.waitForElementContainsText('//*[@class="mainview"]//*[@data-id="file-decoration-custom-contracts/3_Ballot.sol"]', 'customtext')
.moveToElement('//*[@id="fileExplorerView"]//*[@data-id="file-decoration-error-contracts/2_Owner.sol"]', 0, 0)
.waitForElementVisible('//*[@id="error-tooltip-contracts/2_Owner.sol"]')
.waitForElementContainsText('//*[@id="error-tooltip-contracts/2_Owner.sol"]', 'error on owner')
//.waitForElementVisible('//*[@id="error-tooltip-contracts/2_Owner.sol"]')
//.waitForElementContainsText('//*[@id="error-tooltip-contracts/2_Owner.sol"]', 'error on owner')
},
'clear ballot decorator': function (browser: NightwatchBrowser) {

@ -73,7 +73,7 @@ module.exports = {
browser.clickLaunchIcon('home')
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('filePanel')
.click('div[title="home"]')
.click('div[data-id="verticalIconsHomeIcon"]')
.waitForElementVisible('button[data-id="landingPageImportFromGistButton"]')
.pause(1000)
.scrollAndClick('button[data-id="landingPageImportFromGistButton"]')
@ -143,8 +143,8 @@ module.exports = {
.modalFooterOKClick('gisthandler')
.pause(10000)
.openFile(`gist-${testData.validGistId}/README.txt`)
.waitForElementVisible(`div[title='default_workspace/gist-${testData.validGistId}/README.txt']`)
.assert.containsText(`div[title='default_workspace/gist-${testData.validGistId}/README.txt'] > span`, 'README.txt')
.waitForElementVisible(`div[data-path='default_workspace/gist-${testData.validGistId}/README.txt']`)
.assert.containsText(`div[data-path='default_workspace/gist-${testData.validGistId}/README.txt'] > span`, 'README.txt')
.end()
}
}

@ -18,7 +18,7 @@ module.exports = {
browser.clickLaunchIcon('home')
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('filePanel')
.click('div[title="home"]')
.click('div[data-id="verticalIconsHomeIcon"]')
.waitForElementVisible('button[data-id="landingPageImportFromGitHubButton"]')
.pause(1000)
.click('button[data-id="landingPageImportFromGitHubButton"]')
@ -45,7 +45,7 @@ module.exports = {
browser
.waitForElementVisible('*[data-id="remixIdeIconPanel"]', 10000)
.clickLaunchIcon('filePanel')
.click('div[title="home"]')
.click('div[data-id="verticalIconsHomeIcon"]')
.waitForElementVisible('button[data-id="landingPageImportFromGitHubButton"]').pause(1000)
.click('button[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]')
@ -57,14 +57,17 @@ module.exports = {
.waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]')
.click('[data-id="homeTab-modal-footer-ok-react"]')
.openFile('github/OpenZeppelin/openzeppelin-solidity/contracts/access/Roles.sol')
.waitForElementVisible("div[title='default_workspace/github/OpenZeppelin/openzeppelin-solidity/contracts/access/Roles.sol'")
.waitForElementVisible({
selector: `//*[@data-id='tab-active' and @data-path="default_workspace/github/OpenZeppelin/openzeppelin-solidity/contracts/access/Roles.sol"]`,
locateStrategy: 'xpath'
})
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('library Roles {') !== -1, 'content does contain "library Roles {"')
})
},
'Import JSON From GitHub For Valid URL #group2': function (browser: NightwatchBrowser) {
browser
.click('div[title="home"]')
.click('div[data-id="verticalIconsHomeIcon"]')
.click('button[data-id="landingPageImportFromGitHubButton"]')
.waitForElementVisible('input[data-id="homeTabModalDialogCustomPromptText"]').pause(1000)
.execute(() => {
@ -75,7 +78,7 @@ module.exports = {
.waitForElementVisible('*[data-id="homeTab-modal-footer-ok-react"]')
.click('[data-id="homeTab-modal-footer-ok-react"]')
.openFile('github/ethereum/remix-project/package.json')
.waitForElementVisible("div[title='default_workspace/github/ethereum/remix-project/package.json'")
.waitForElementVisible("div[data-path='default_workspace/github/ethereum/remix-project/package.json'")
.getEditorValue((content) => {
browser.assert.ok(content.indexOf('"name": "remix-project",') !== -1, 'content does contain "name": "remix-project"')
})

@ -95,12 +95,10 @@ module.exports = {
.clickInstance(1)
.pause(1000)
.clickFunction('set2 - transact (not payable)', { types: 'uint256 _po', values: '10' })
.testFunction('last',
{
.testFunction('last', {
status: 'true Transaction mined and execution succeed',
'decoded input': { 'uint256 _po': '10' }
})
},
'Run with live "mode" #group2': function (browser: NightwatchBrowser) {

@ -150,14 +150,14 @@ module.exports = {
browser.assert.ok(content.includes("123test' contract"), 'should replace text ok')
})
.waitForElementVisible('*[data-id="undo-replace-README.txt"]')
.click('div[title="default_workspace/contracts/1_Storage.sol"]').pause(2000)
.click('div[data-path="default_workspace/contracts/1_Storage.sol"]').pause(2000)
.waitForElementVisible('*[data-id="undo-replace-contracts/1_Storage.sol"]')
.click('*[data-id="undo-replace-contracts/1_Storage.sol"]').pause(2000)
.getEditorValue((content) => {
browser.assert.ok(content.includes('contract Storage'), 'should undo text ok')
browser.assert.ok(content.includes('title Storage'), 'should undo text ok')
})
.click('div[title="default_workspace/README.txt"]').pause(2000)
.click('div[data-path="default_workspace/README.txt"]').pause(2000)
.waitForElementVisible('*[data-id="undo-replace-README.txt"]')
.click('*[data-id="undo-replace-README.txt"]').pause(2000)
.getEditorValue((content) => {

@ -48,7 +48,7 @@ module.exports = {
.waitForElementPresent('*[data-id="testTabGenerateTestFile"]')
.click('*[data-id="testTabGenerateTestFile"]')
.clickLaunchIcon('filePanel')
.waitForElementPresent('*[title="default_workspace/tests/simple_storage_test.sol"]')
.waitForElementPresent('*[data-path="default_workspace/tests/simple_storage_test.sol"]')
.removeFile('tests/simple_storage_test.sol', 'default_workspace')
},
@ -106,7 +106,7 @@ module.exports = {
'Should fail on compilation, open file on error click, not disappear error #group2': function (browser: NightwatchBrowser) {
browser.waitForElementPresent('*[data-id="verticalIconsKindfilePanel"]')
.addFile('tests/compilationError_test.sol', sources[0]['compilationError_test.sol'])
.click('div[title="default_workspace/tests/compilationError_test.sol"] span[class="close-tabs"]')
.click('div[data-path="default_workspace/tests/compilationError_test.sol"] span[class="close-tabs"]')
.clickLaunchIcon('solidityUnitTesting')
.pause(2000)
.click('*[data-id="testTabCheckAllTests"]')

@ -317,6 +317,33 @@ module.exports = {
.executeScriptInTerminal(`web3.eth.getCode('0x75F509A4eDA030470272DfBAf99A47D587E76709')`) // sepolia contract
.waitForElementContainsText('*[data-id="terminalJournal"]', byteCodeInSepolia, 120000)
},
'Should run free function which logs in the terminal #group10': function (browser: NightwatchBrowser) {
const script = `import "hardhat/console.sol";
function runSomething () view {
console.log("test running free function");
}
`
browser
.addFile('test.sol', { content: script })
.scrollToLine(3)
const path = "//*[@class='view-line' and contains(.,'runSomething') and contains(.,'view')]//span//span[contains(.,'(')]"
const pathRunFunction = `//li//*[@aria-label='Run the free function "runSomething" in the Remix VM']`
browser.waitForElementVisible('#editorView')
.useXpath()
.click(path)
.pause(3000) // the parser need to parse the code
.perform(function () {
const actions = this.actions({ async: true });
return actions
.keyDown(this.Keys.SHIFT)
.keyDown(this.Keys.ALT)
.sendKeys('r')
})
.useCss()
.waitForElementContainsText('*[data-id="terminalJournal"]', 'test running free function', 120000)
}
}

@ -16,6 +16,7 @@ import { PermissionHandlerPlugin } from './app/plugins/permission-handler-plugin
import { AstWalker } from '@remix-project/remix-astwalker'
import { LinkLibraries, DeployLibraries, OpenZeppelinProxy } from '@remix-project/core-plugin'
import { CodeParser } from './app/plugins/parser/code-parser'
import { SolidityScript } from './app/plugins/solidity-script'
import { WalkthroughService } from './walkthroughService'
@ -246,7 +247,7 @@ class AppComponent {
)
const codeParser = new CodeParser(new AstWalker())
const solidityScript = new SolidityScript()
this.notification = new NotificationPlugin()
@ -298,7 +299,8 @@ class AppComponent {
this.walkthroughService,
search,
solidityumlgen,
contractFlattener
contractFlattener,
solidityScript
])
// LAYOUT & SYSTEM VIEWS
@ -414,6 +416,7 @@ class AppComponent {
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'codeParser', 'codeFormatter', 'fileDecorator', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough', 'storage', 'search', 'compileAndRun', 'recorder'])
await this.appManager.activatePlugin(['solidity-script'])
this.appManager.on(
'filePanel',

@ -146,7 +146,7 @@ export class TabProxy extends Plugin {
}
})
this.on('manager', 'pluginActivated', ({ name, location, displayName, icon }) => {
this.on('manager', 'pluginActivated', ({ name, location, displayName, icon, description }) => {
if (location === 'mainPanel') {
this.addTab(
name,
@ -160,7 +160,8 @@ export class TabProxy extends Plugin {
this.emit('closeApp', name)
this.call('manager', 'deactivatePlugin', name)
},
icon
icon,
description
)
this.switchTab(name)
}
@ -223,7 +224,7 @@ export class TabProxy extends Plugin {
this.removeTab(oldName)
}
addTab (name, title, switchTo, close, icon) {
addTab (name, title, switchTo, close, icon, description = '') {
if (this._handlers[name]) return this.renderComponent()
var slash = name.split('/')
@ -252,6 +253,7 @@ export class TabProxy extends Plugin {
const index = this.loadedTabs.findIndex(({ title }) => title === formatPath.join('/'))
if (index > -1) {
const duplicateTabName = this.loadedTabs[index].name
const duplicateTabTooltip = this.loadedTabs[index].description
const duplicateTabPath = duplicateTabName.split('/')
const duplicateTabFormatPath = [...duplicateTabPath].reverse()
const duplicateTabTitle = duplicateTabFormatPath.slice(0, titleLength).reverse().join('/')
@ -260,7 +262,7 @@ export class TabProxy extends Plugin {
name: duplicateTabName,
title: duplicateTabTitle,
icon,
tooltip: duplicateTabName,
tooltip: duplicateTabTooltip || duplicateTabTitle,
iconClass: getPathIcon(duplicateTabName)
}
}
@ -274,7 +276,7 @@ export class TabProxy extends Plugin {
name,
title,
icon,
tooltip: name,
tooltip: description || title,
iconClass: getPathIcon(name)
})
}

@ -0,0 +1,102 @@
import React from 'react' // eslint-disable-line
import { format } from 'util'
import { Plugin } from '@remixproject/engine'
import { compile } from '@remix-project/remix-solidity'
import { TransactionConfig } from 'web3-core'
const _paq = window._paq = window._paq || [] //eslint-disable-line
const profile = {
name: 'solidity-script',
displayName: 'solidity-script',
description: 'solidity-script',
methods: ['execute']
}
export class SolidityScript extends Plugin {
constructor () {
super(profile)
}
async execute (path: string, functionName: string = 'run') {
_paq.push(['trackEvent', 'SolidityScript', 'execute', 'script'])
this.call('terminal', 'log', `running free function ${functionName} from ${path}...`)
let content = await this.call('fileManager', 'readFile', path)
const params = await this.call('solidity', 'getCompilerParameters')
content = `
import "${path}";
contract SolidityScript {
constructor () {}
function remixRun () public {
${functionName}();
}
}`
const targets = { 'script.sol': { content } }
// compile
const compilation = await compile(targets, params, async (url, cb) => {
await this.call('contentImport', 'resolveAndSave', url).then((result) => cb(null, result)).catch((error) => cb(error.message))
})
if (compilation.data.error) {
this.call('terminal', 'log', compilation.data.error.formattedMessage)
}
if (compilation.data.errors && compilation.data.errors.length > 0) {
compilation.data.errors.map((error) => {
this.call('terminal', 'log', error.formattedMessage)
})
}
// get the contract
const contract = compilation.getContract('SolidityScript')
if (!contract) {
console.log('compilation failed')
return
}
const bytecode = '0x' + contract.object.evm.bytecode.object
const web3 = await this.call('blockchain', 'web3VM')
const accounts = await this.call('blockchain', 'getAccounts')
if (!accounts || accounts.length === 0) {
throw new Error('no account available')
}
// deploy the contract
let tx: TransactionConfig = {
from: accounts[0],
data: bytecode
}
const receipt = await web3.eth.sendTransaction(tx)
tx = {
from: accounts[0],
to: receipt.contractAddress,
data: '0x69d4394b' // function remixRun() public
}
const receiptCall = await web3.eth.sendTransaction(tx)
const hhlogs = await web3.eth.getHHLogsForTx(receiptCall.transactionHash)
if (hhlogs && hhlogs.length) {
const finalLogs = <div><div><b>console.log:</b></div>
{
hhlogs.map((log) => {
let formattedLog
// Hardhat implements the same formatting options that can be found in Node.js' console.log,
// which in turn uses util.format: https://nodejs.org/dist/latest-v12.x/docs/api/util.html#util_util_format_format_args
// For example: console.log("Name: %s, Age: %d", remix, 6) will log 'Name: remix, Age: 6'
// We check first arg to determine if 'util.format' is needed
if (typeof log[0] === 'string' && (log[0].includes('%s') || log[0].includes('%d'))) {
formattedLog = format(log[0], ...log.slice(1))
} else {
formattedLog = log.join(' ')
}
return <div>{formattedLog}</div>
})}
</div>
_paq.push(['trackEvent', 'udapp', 'hardhat', 'console.log'])
this.call('terminal', 'logHtml', finalLogs)
}
}
}

@ -18,7 +18,7 @@ const _paq = window._paq = window._paq || []
const profile = {
name: 'solidityumlgen',
displayName: 'Solidity UML Generator',
description: 'Generate UML diagram in svg format from last compiled contract',
description: 'Generates UML diagram in svg format from last compiled contract',
location: 'mainPanel',
methods: ['showUmlDiagram', 'generateUml', 'generateCustomAction'],
events: [],

@ -99,7 +99,7 @@ export abstract class InjectedProvider extends Plugin implements IProvider {
resolve({ jsonrpc: '2.0', error: 'no return data provided', id: data.id })
}
} catch (error) {
resolve({ jsonrpc: '2.0', error: error.message, id: data.id })
resolve({ jsonrpc: '2.0', error: error.data && error.data.message ? error.data.message : error.message, id: data.id })
}
}
}

@ -21,7 +21,7 @@ const profile = {
documentation: 'https://remix-ide.readthedocs.io/en/latest/compile.html',
version: packageJson.version,
maintainedBy: 'Remix',
methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile', 'getCompilerState']
methods: ['getCompilationResult', 'compile', 'compileWithParameters', 'setCompilerConfig', 'compileFile', 'getCompilerState', 'getCompilerParameters', 'getCompiler']
}
// EditorApi:
@ -133,8 +133,13 @@ class CompileTab extends CompilerApiMixin(ViewPlugin) { // implements ICompilerA
}
}
getCompiler () {
return this.compileTabLogic.compiler
}
getCompilerParameters () {
const params = this.queryParams.get()
params.evmVersion = params.evmVersion === 'null' || params.evmVersion === 'undefined' ? null : params.evmVersion
params.optimize = (params.optimize === 'false' || params.optimize === null || params.optimize === undefined) ? false : params.optimize
params.optimize = params.optimize === 'true' ? true : params.optimize
return params

@ -5,8 +5,8 @@
"udapp.value": "Value",
"udapp.contract": "Contract",
"udapp.compiledBy": "Compiled by {compilerName}",
"udapp.infoSyncCompiledContractTooltip": "Click here to import contracts compiled from an external framework.{br}This action is enabled when Remix is connected to an external{br} framework (hardhat, truffle, foundry) through remixd.",
"udapp.remixIpfsUdappTooltip": "Publishing the source code and metadata to IPFS facilitates{br} source code verification using Sourcify and will greatly foster{br} contract adoption (auditing, debugging, calling it, etc...)",
"udapp.infoSyncCompiledContractTooltip": "Click here to import contracts compiled from an external framework.This action is enabled when Remix is connected to an external framework (hardhat, truffle, foundry) through remixd.",
"udapp.remixIpfsUdappTooltip": "Publishing the source code and metadata to IPFS facilitates source code verification using Sourcify and will greatly foster contract adoption (auditing, debugging, calling it, etc...)",
"udapp.signAMessage": "Sign a message",
"udapp.enterAMessageToSign": "Enter a message to sign",
"udapp.hash": "hash",
@ -19,15 +19,15 @@
"udapp.or": "or",
"udapp.atAddress": "At Address",
"udapp.atAddressOptionsTitle1": "address of contract",
"udapp.atAddressOptionsTitle2": "Interact with the deployed contract - requires the .abi file or {br} compiled .sol file to be selected in the editor {br}(with the same compiler configuration)",
"udapp.atAddressOptionsTitle2": "Interact with the deployed contract - requires the .abi file or compiled .sol file to be selected in the editor (with the same compiler configuration)",
"udapp.atAddressOptionsTitle3": "Compile a *.sol file or select a *.abi file.",
"udapp.atAddressOptionsTitle4": "To interact with a deployed contract, either{br} enter its address and compile its source *.sol file {br}(with the same compiler settings) or select its .abi file in the editor. ",
"udapp.atAddressOptionsTitle4": "To interact with a deployed contract, either enter its address and compile its source *.sol file (with the same compiler settings) or select its .abi file in the editor. ",
"udapp.contractOptionsTitle1": "Please compile *.sol file to deploy or access a contract",
"udapp.contractOptionsTitle2": "Select a compiled contract to deploy or to use with At Address.",
"udapp.contractOptionsTitle3": "Select and compile *.sol file to deploy or access a contract.",
"udapp.contractOptionsTitle4": "When there is a compiled .sol file, choose the {br} contract to deploy or to use with AtAddress.'",
"udapp.checkSumWarning": "It seems you are not using a checksumed address.{br}A checksummed address is an address that contains uppercase letters, as specified in {a}.{br}Checksummed addresses are meant to help prevent users from sending transactions to the wrong address.",
"udapp.isOverSizePrompt": "Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails. {br}More info: {a}",
"udapp.contractOptionsTitle4": "When there is a compiled .sol file, choose the contract to deploy or to use with AtAddress.'",
"udapp.checkSumWarning": "It seems you are not using a checksumed address.A checksummed address is an address that contains uppercase letters, as specified in {a}.Checksummed addresses are meant to help prevent users from sending transactions to the wrong address.",
"udapp.isOverSizePrompt": "Contract creation initialization returns data with length of more than 24576 bytes. The deployment will likely fails. More info: {a}",
"udapp.thisContractMayBeAbstract": "This contract may be abstract, it may not implement an abstract parent's methods completely or it may not invoke an inherited contract's constructor correctly.",
"udapp.noCompiledContracts": "No compiled contracts",
"udapp.addressOfContract": "Address of contract",
@ -40,8 +40,8 @@
"udapp.transactionSaveTooltip1": "No transactions to save",
"udapp.transactionSaveTooltip2": "Save {count} transaction as scenario file",
"udapp.transactionSaveTooltip3": "Save {count} transactions as scenario file",
"udapp.infoRecorderTooltip": "Save transactions (deployed contracts and function executions) {br}and replay them in another environment e.g Transactions created {br}in Remix VM can be replayed in the Injected Provider.",
"udapp.livemodeRecorderTooltip": "If contracts are updated after recording transactions,{br} checking this box will run recorded transactions {br}with the latest copy of the compiled contracts",
"udapp.infoRecorderTooltip": "Save transactions (deployed contracts and function executions) and replay them in another environment e.g Transactions created in Remix VM can be replayed in the Injected Provider.",
"udapp.livemodeRecorderTooltip": "If contracts are updated after recording transactions, checking this box will run recorded transactions with the latest copy of the compiled contracts",
"udapp.livemodeRecorderLabel": "Run transactions using the latest compilation result",
"udapp.runRecorderTooltip": "Run transaction(s) from the current scenario file",
"udapp.save": "Save",

@ -9,7 +9,7 @@ const profile = {
displayName: 'Home',
methods: [],
events: [],
description: 'Remix home tab ',
description: 'Remix Home',
icon: 'assets/img/home.webp',
location: 'mainPanel',
version: packageJson.version

@ -5250,7 +5250,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after {
bottom:1px;
border-width:.5rem .5rem 0;
border-top-color:#fff
border-top-color: var(--secondary)
}
.bs-popover-auto[x-placement^=right],.bs-popover-right {
margin-left:.5rem
@ -5269,7 +5269,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after {
left:1px;
border-width:.5rem .5rem .5rem 0;
border-right-color:#fff
border-right-color: var(--secondary)
}
.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom {
margin-top:.5rem
@ -5285,7 +5285,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after {
top:1px;
border-width:0 .5rem .5rem .5rem;
border-bottom-color:#fff
border-bottom-color: var(--secondary)
}
.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before {
position:absolute;
@ -5314,7 +5314,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after {
right:1px;
border-width:.5rem 0 .5rem .5rem;
border-left-color:#fff
border-left-color: var(--secondary)
}
.popover-header {
padding:.5rem .75rem;

@ -5252,7 +5252,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after {
bottom:1px;
border-width:.5rem .5rem 0;
border-top-color:#282828
border-top-color: var(--secondary);
}
.bs-popover-auto[x-placement^=right],.bs-popover-right {
margin-left:.5rem
@ -5271,7 +5271,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after {
left:1px;
border-width:.5rem .5rem .5rem 0;
border-right-color:#282828
border-right-color: var(--secondary);
}
.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom {
margin-top:.5rem
@ -5287,7 +5287,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after {
top:1px;
border-width:0 .5rem .5rem .5rem;
border-bottom-color:#282828
border-bottom-color: var(--secondary);
}
.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before {
position:absolute;
@ -5316,7 +5316,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after {
right:1px;
border-width:.5rem 0 .5rem .5rem;
border-left-color:#282828
border-left-color: var(--secondary);
}
.popover-header {
padding:.5rem .75rem;

@ -4253,7 +4253,7 @@ a.close.disabled {
bottom:0; border-width:.5rem .5rem 0; border-top-color:rgba(0,0,0,.25)
}
.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after {
bottom:1px; border-width:.5rem .5rem 0; border-top-color:#fff
bottom:1px; border-width:.5rem .5rem 0; border-top-color: var(--secondary)
}
.bs-popover-auto[x-placement^=right],.bs-popover-right {
margin-left:.5rem
@ -4265,7 +4265,7 @@ a.close.disabled {
left:0; border-width:.5rem .5rem .5rem 0; border-right-color:rgba(0,0,0,.25)
}
.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after {
left:1px; border-width:.5rem .5rem .5rem 0; border-right-color:#fff
left:1px; border-width:.5rem .5rem .5rem 0; border-right-color: var(--secondary)
}
.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom {
margin-top:.5rem
@ -4277,7 +4277,7 @@ a.close.disabled {
top:0; border-width:0 .5rem .5rem .5rem; border-bottom-color:rgba(0,0,0,.25)
}
.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after {
top:1px; border-width:0 .5rem .5rem .5rem; border-bottom-color:#fff
top:1px; border-width:0 .5rem .5rem .5rem; border-bottom-color: var(--secondary)
}
.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before {
position:absolute; top:0; left:50%; display:block; width:1rem; margin-left:-.5rem; content:""; border-bottom:1px solid #f7f7f7
@ -4292,7 +4292,7 @@ a.close.disabled {
right:0; border-width:.5rem 0 .5rem .5rem; border-left-color:rgba(0,0,0,.25)
}
.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after {
right:1px; border-width:.5rem 0 .5rem .5rem; border-left-color:#fff
right:1px; border-width:.5rem 0 .5rem .5rem; border-left-color: var(--secondary)
}
.popover-header {
padding:.5rem .75rem; margin-bottom:0; font-size:.9375rem; background-color:#f7f7f7; border-bottom:1px solid #ebebeb; border-top-left-radius:calc(.3rem - 1px); border-top-right-radius:calc(.3rem - 1px)

@ -5253,7 +5253,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after {
bottom:1px;
border-width:.5rem .5rem 0;
border-top-color:#fff
border-top-color: var(--secondary)
}
.bs-popover-auto[x-placement^=right],.bs-popover-right {
margin-left:.5rem
@ -5272,7 +5272,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after {
left:1px;
border-width:.5rem .5rem .5rem 0;
border-right-color:#fff
border-right-color: var(--secondary)
}
.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom {
margin-top:.5rem
@ -5288,7 +5288,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after {
top:1px;
border-width:0 .5rem .5rem .5rem;
border-bottom-color:#fff
border-bottom-color: var(--secondary)
}
.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before {
position:absolute;
@ -5317,7 +5317,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after {
right:1px;
border-width:.5rem 0 .5rem .5rem;
border-left-color:#fff
border-left-color: var(--secondary)
}
.popover-header {
padding:.5rem .75rem;
@ -5486,7 +5486,6 @@ a.close.disabled {
.carousel-indicators li {
transition:none
}
}
.carousel-indicators .active {
opacity:1

@ -5310,7 +5310,7 @@ a.close.disabled {
.bs-popover-top > .arrow::after {
bottom: 1px;
border-width: 5px 5px 0;
border-top-color: #000;
border-top-color: var(--secondary);
}
.bs-popover-auto[x-placement^="right"],
.bs-popover-right {
@ -5333,7 +5333,7 @@ a.close.disabled {
.bs-popover-right > .arrow::after {
left: 1px;
border-width: 5px 5px 5px 0;
border-right-color: #000;
border-right-color: var(--secondary);
}
.bs-popover-auto[x-placement^="bottom"],
.bs-popover-bottom {
@ -5353,7 +5353,7 @@ a.close.disabled {
.bs-popover-bottom > .arrow::after {
top: 1px;
border-width: 0 5px 5px 5px;
border-bottom-color: #000;
border-bottom-color: var(--secondary);
}
.bs-popover-auto[x-placement^="bottom"] .popover-header::before,
.bs-popover-bottom .popover-header::before {
@ -5387,7 +5387,7 @@ a.close.disabled {
.bs-popover-left > .arrow::after {
right: 1px;
border-width: 5px 0 5px 5px;
border-left-color: #000;
border-left-color: var(--secondary);
}
.popover-header {
padding: 8px 14px;

@ -5773,7 +5773,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^="top"] > .arrow::after {
bottom: 1px;
border-width: 0.5rem 0.5rem 0;
border-top-color: #fff;
border-top-color: var(--secondary);
}
.bs-popover-right,
@ -5797,7 +5797,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^="right"] > .arrow::after {
left: 1px;
border-width: 0.5rem 0.5rem 0.5rem 0;
border-right-color: #fff;
border-right-color: var(--secondary);
}
.bs-popover-bottom,
@ -5818,7 +5818,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^="bottom"] > .arrow::after {
top: 1px;
border-width: 0 0.5rem 0.5rem 0.5rem;
border-bottom-color: #fff;
border-bottom-color: var(--secondary);
}
.bs-popover-bottom .popover-header::before,
.bs-popover-auto[x-placement^="bottom"] .popover-header::before {
@ -5853,7 +5853,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^="left"] > .arrow::after {
right: 1px;
border-width: 0.5rem 0 0.5rem 0.5rem;
border-left-color: #fff;
border-left-color: var(--secondary);
}
.popover-header {

@ -5312,7 +5312,7 @@ a.close.disabled {
.bs-popover-top > .arrow::after {
bottom: 1px;
border-width: 5px 5px 0;
border-top-color: #000;
border-top-color: var(--secondary);
}
.bs-popover-auto[x-placement^="right"],
.bs-popover-right {
@ -5335,7 +5335,7 @@ a.close.disabled {
.bs-popover-right > .arrow::after {
left: 1px;
border-width: 5px 5px 5px 0;
border-right-color: #000;
border-right-color: var(--secondary);
}
.bs-popover-auto[x-placement^="bottom"],
.bs-popover-bottom {
@ -5355,7 +5355,7 @@ a.close.disabled {
.bs-popover-bottom > .arrow::after {
top: 1px;
border-width: 0 5px 5px 5px;
border-bottom-color: #000;
border-bottom-color: var(--secondary);
}
.bs-popover-auto[x-placement^="bottom"] .popover-header::before,
.bs-popover-bottom .popover-header::before {
@ -5389,7 +5389,7 @@ a.close.disabled {
.bs-popover-left > .arrow::after {
right: 1px;
border-width: 5px 0 5px 5px;
border-left-color: #000;
border-left-color: var(--secondary);
}
.popover-header {
padding: 8px 14px;

@ -5323,7 +5323,7 @@ a.close.disabled {
.bs-popover-top > .arrow::after {
bottom: 1px;
border-width: 5px 5px 0;
border-top-color: #000;
border-top-color: var(--secondary);
}
.bs-popover-auto[x-placement^="right"],
.bs-popover-right {
@ -5346,7 +5346,7 @@ a.close.disabled {
.bs-popover-right > .arrow::after {
left: 1px;
border-width: 5px 5px 5px 0;
border-right-color: #000;
border-right-color: var(--secondary);
}
.bs-popover-auto[x-placement^="bottom"],
.bs-popover-bottom {
@ -5366,7 +5366,7 @@ a.close.disabled {
.bs-popover-bottom > .arrow::after {
top: 1px;
border-width: 0 5px 5px 5px;
border-bottom-color: #000;
border-bottom-color: var(--secondary);
}
.bs-popover-auto[x-placement^="bottom"] .popover-header::before,
.bs-popover-bottom .popover-header::before {
@ -5400,7 +5400,7 @@ a.close.disabled {
.bs-popover-left > .arrow::after {
right: 1px;
border-width: 5px 0 5px 5px;
border-left-color: #000;
border-left-color: var(--secondary);
}
.popover-header {
padding: 8px 14px;

@ -5769,7 +5769,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^="top"] > .arrow::after {
bottom: 1px;
border-width: 0.5rem 0.5rem 0;
border-top-color: #fff;
border-top-color: var(--secondary);
}
.bs-popover-right,
@ -5793,7 +5793,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^="right"] > .arrow::after {
left: 1px;
border-width: 0.5rem 0.5rem 0.5rem 0;
border-right-color: #fff;
border-right-color: var(--secondary);
}
.bs-popover-bottom,
@ -5814,7 +5814,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^="bottom"] > .arrow::after {
top: 1px;
border-width: 0 0.5rem 0.5rem 0.5rem;
border-bottom-color: #fff;
border-bottom-color: var(--secondary);
}
.bs-popover-bottom .popover-header::before,
.bs-popover-auto[x-placement^="bottom"] .popover-header::before {
@ -5849,7 +5849,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^="left"] > .arrow::after {
right: 1px;
border-width: 0.5rem 0 0.5rem 0.5rem;
border-left-color: #fff;
border-left-color: var(--secondary);
}
.popover-header {

@ -5775,7 +5775,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^="top"] > .arrow::after {
bottom: 1px;
border-width: 0.5rem 0.5rem 0;
border-top-color: #eeede9;
border-top-color: var(--secondary);
}
.bs-popover-right,
@ -5799,7 +5799,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^="right"] > .arrow::after {
left: 1px;
border-width: 0.5rem 0.5rem 0.5rem 0;
border-right-color: #eeede9;
border-right-color: var(--secondary);
}
.bs-popover-bottom,
@ -5820,7 +5820,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^="bottom"] > .arrow::after {
top: 1px;
border-width: 0 0.5rem 0.5rem 0.5rem;
border-bottom-color: #eeede9;
border-bottom-color: var(--secondary);
}
.bs-popover-bottom .popover-header::before,
.bs-popover-auto[x-placement^="bottom"] .popover-header::before {
@ -5855,7 +5855,7 @@ a.close.disabled {
.bs-popover-auto[x-placement^="left"] > .arrow::after {
right: 1px;
border-width: 0.5rem 0 0.5rem 0.5rem;
border-left-color: #eeede9;
border-left-color: var(--secondary);
}
.popover-header {
@ -6141,7 +6141,7 @@ button.bg-primary:focus {
}
.bg-secondary {
background-color: #EDC2A1 !important;
background-color: #f1f1f100 !important;
}
a.bg-secondary:hover,

@ -23,7 +23,7 @@ const profile = {
name: 'blockchain',
displayName: 'Blockchain',
description: 'Blockchain - Logic',
methods: ['getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'web3VM', 'getProvider'],
methods: ['getCode', 'getTransactionReceipt', 'addProvider', 'removeProvider', 'getCurrentFork', 'getAccounts', 'web3VM', 'getProvider'],
version: packageJson.version
}

@ -1,6 +1,6 @@
<!DOCTYPE html>
<html>
<html class="overflow-hidden">
<head>
<meta charset="utf-8">

@ -11,7 +11,7 @@ const requiredModules = [ // services + layout views + system views
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout',
'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'openzeppelin-proxy',
'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected', 'injected-trustwallet', 'injected-optimism-provider', 'injected-arbitrum-one-provider', 'vm-custom-fork', 'vm-goerli-fork', 'vm-mainnet-fork', 'vm-sepolia-fork', 'vm-merge', 'vm-london', 'vm-berlin',
'compileAndRun', 'search', 'recorder', 'fileDecorator', 'codeParser', 'codeFormatter', 'solidityumlgen', 'contractflattener']
'compileAndRun', 'search', 'recorder', 'fileDecorator', 'codeParser', 'codeFormatter', 'solidityumlgen', 'contractflattener', 'doc-gen', 'doc-viewer', 'solidity-script']
// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)
const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither']

@ -3,10 +3,8 @@ import { canUseWorker, urlFromVersion } from './compiler-utils'
import { CompilerAbstract } from './compiler-abstract'
import { Compiler } from './compiler'
export const compile = async (compilationTargets, settings, contentResolverCallback) => {
const res = await (() => {
export const compile = (compilationTargets, settings, contentResolverCallback): Promise<CompilerAbstract> => {
return new Promise((resolve, reject) => {
console.log('compilationTargets', compilationTargets)
const compiler = new Compiler(contentResolverCallback)
compiler.set('evmVersion', settings.evmVersion)
compiler.set('optimize', settings.optimize)
@ -18,6 +16,4 @@ export const compile = async (compilationTargets, settings, contentResolverCallb
})
compiler.event.register('compilerLoaded', _ => compiler.compile(compilationTargets, ''))
})
})()
return res
}

@ -9,6 +9,7 @@ import AppDialogs from './components/modals/dialogs'
import DialogViewPlugin from './components/modals/dialogViewPlugin'
import { AppContext } from './context/context'
import { IntlProvider } from 'react-intl'
import { CustomTooltip } from '@remix-ui/helper';
interface IRemixAppUi {
app: any
@ -87,8 +88,15 @@ const RemixApp = (props: IRemixAppUi) => {
<div id="icon-panel" data-id="remixIdeIconPanel" className="iconpanel bg-light">{props.app.menuicons.render()}</div>
<div ref={sidePanelRef} id="side-panel" data-id="remixIdeSidePanel" className={`sidepanel border-right border-left ${hideSidePanel ? 'd-none' : ''}`}>{props.app.sidePanel.render()}</div>
<DragBar resetTrigger={resetTrigger} maximiseTrigger={maximiseTrigger} minWidth={285} refObject={sidePanelRef} hidden={hideSidePanel} setHideStatus={setHideSidePanel}></DragBar>
<div id="main-panel" data-id="remixIdeMainPanel" className='mainpanel'>
<div id="main-panel" data-id="remixIdeMainPanel" className='mainpanel d-flex'>
<RemixUIMainPanel Context={AppContext}></RemixUIMainPanel>
<CustomTooltip
placement="bottom"
tooltipId="overlay-tooltip-all-tabs"
tooltipText="Scroll to see all tabs"
>
<div className='remix-ui-tabs_end remix-bg-opacity position-absolute position-fixed'></div>
</CustomTooltip>
</div>
</div>
<div>{props.app.hiddenPanel.render()}</div>

@ -34,7 +34,6 @@ pre {
width : 320px;
transition : width 0.25s;
}
.highlightcode {
position : absolute;
z-index : 20;
@ -54,7 +53,7 @@ pre {
height : 200px;
}
.centered svg path {
fill: var(--secondary);
fill : var(--secondary);
}
.centered svg polygon {
fill : var(--secondary);
@ -66,7 +65,12 @@ pre {
.matomoBtn {
width : 100px;
}
.splash {
text-align: center;
text-align : center;
}
.remix-ui-tabs_end {
height : 2.15rem;
width : 4rem;
right : -10px;
filter : opacity(0.5);
}

@ -393,11 +393,22 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
</CustomTooltip>
</div>
{ state.isLocalNodeUsed && <div className="mb-2 debuggerConfig custom-control custom-checkbox">
<input className="custom-control-input" id="debugWithLocalNodeInput" onChange={({ target: { checked } }) => {
<CustomTooltip
tooltipId="debuggerGenSourceInput"
tooltipText="Force the debugger to use the current local node"
placement='right'
>
<input
className="custom-control-input"
id="debugWithLocalNodeInput"
onChange={({ target: { checked } }) => {
setState(prevState => {
return { ...prevState, opt: { ...prevState.opt, debugWithLocalNode: checked } }
})
}} type="checkbox" title="Force the debugger to use the current local node" />
}}
type="checkbox"
/>
</CustomTooltip>
<label data-id="debugLocaNodeLabel" className="form-check-label custom-control-label" htmlFor="debugWithLocalNodeInput"><FormattedMessage id='debugger.debugLocaNodeLabel' /></label>
</div>
}

@ -0,0 +1,15 @@
import { EditorAPIType, PluginType } from "../remix-ui-editor"
export const retrieveNodesAtPosition = async (editorAPI: EditorAPIType, plugin: PluginType) => {
const cursorPosition = editorAPI.getCursorPosition()
let nodesAtPosition = await plugin.call('codeParser', 'nodesAtPosition', cursorPosition)
// if no nodes exits at position, try to get the block of which the position is in
const block = await plugin.call('codeParser', 'getANTLRBlockAtPosition', cursorPosition, null)
if (!nodesAtPosition.length) {
if (block) {
nodesAtPosition = await plugin.call('codeParser', 'nodesAtPosition', block.start)
}
}
return { nodesAtPosition, block }
}

@ -3,6 +3,7 @@ import { isArray } from "lodash"
import { EditorUIProps } from "../remix-ui-editor"
import { GeCompletionUnits, GetCompletionKeywords, getCompletionSnippets, GetCompletionTypes, getContextualAutoCompleteBTypeName, getContextualAutoCompleteByGlobalVariable, GetGlobalFunctions, GetGlobalVariable, GetImports } from "./completion/completionGlobals"
import { monacoTypes } from '@remix-ui/editor';
import { retrieveNodesAtPosition } from "../helpers/retrieveNodesAtPosition";
export class RemixCompletionProvider implements monacoTypes.languages.CompletionItemProvider {
props: EditorUIProps
@ -251,17 +252,8 @@ export class RemixCompletionProvider implements monacoTypes.languages.Completion
private getContractCompletions = async () => {
let nodes: any[] = []
const cursorPosition = this.props.editorAPI.getCursorPosition()
let nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', cursorPosition)
// if no nodes exits at position, try to get the block of which the position is in
const block = await this.props.plugin.call('codeParser', 'getANTLRBlockAtPosition', cursorPosition, null)
const { nodesAtPosition, block } = await retrieveNodesAtPosition(this.props.editorAPI, this.props.plugin)
const fileNodes = await this.props.plugin.call('codeParser', 'getCurrentFileNodes')
if (!nodesAtPosition.length) {
if (block) {
nodesAtPosition = await this.props.plugin.call('codeParser', 'nodesAtPosition', block.start)
}
}
// find the contract and get the nodes of the contract and the base contracts and imports
if (isArray(nodesAtPosition) && nodesAtPosition.length) {
let contractNode: any = {}

@ -1,5 +1,5 @@
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'
@ -8,15 +8,14 @@ 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 './remix-ui-editor.css'
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 './remix-ui-editor.css'
enum MarkerSeverity {
@ -93,23 +92,12 @@ export type DecorationsReturn = {
registeredDecorations?: Array<any>
}
/* 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: {
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
}
editorAPI: {
}
export type EditorAPIType = {
findMatches: (uri: string, value: string) => any
getFontSize: () => number,
getValue: (uri: string) => string
@ -120,7 +108,22 @@ export interface EditorUIProps {
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({})
@ -630,9 +633,67 @@ export const EditorUI = (props: EditorUIProps) => {
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)
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 () => {
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 (freeFunctionAction) freeFunctionAction.dispose()
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) => {

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save