Merge pull request #2092 from ethereum/search

Search
pull/2119/head^2
bunsenstraat 3 years ago committed by GitHub
commit 7e0ef62be2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 122
      apps/remix-ide-e2e/src/tests/search.test.ts
  2. 10
      apps/remix-ide/src/app.js
  3. 2
      apps/remix-ide/src/app/components/vertical-icons.tsx
  4. 32
      apps/remix-ide/src/app/tabs/search.tsx
  5. 77
      apps/remix-ide/src/assets/img/Search_Icon.svg
  6. 2
      apps/remix-ide/src/remixAppManager.js
  7. 2
      libs/remix-ui/app/src/index.ts
  8. 4
      libs/remix-ui/app/src/lib/remix-app/components/modals/modal-wrapper.tsx
  9. 10
      libs/remix-ui/app/src/lib/remix-app/context/provider.tsx
  10. 3
      libs/remix-ui/app/src/lib/remix-app/interface/index.ts
  11. 3
      libs/remix-ui/app/src/lib/remix-app/reducer/modals.ts
  12. 3
      libs/remix-ui/modal-dialog/src/lib/types/index.ts
  13. 4
      libs/remix-ui/search/.babelrc
  14. 19
      libs/remix-ui/search/.eslintrc.json
  15. 5
      libs/remix-ui/search/.prettierrc
  16. 1
      libs/remix-ui/search/src/index.ts
  17. 32
      libs/remix-ui/search/src/lib/components/Exclude.tsx
  18. 77
      libs/remix-ui/search/src/lib/components/Find.tsx
  19. 28
      libs/remix-ui/search/src/lib/components/Include.tsx
  20. 32
      libs/remix-ui/search/src/lib/components/OverWriteCheck.tsx
  21. 25
      libs/remix-ui/search/src/lib/components/Replace.tsx
  22. 29
      libs/remix-ui/search/src/lib/components/Search.tsx
  23. 27
      libs/remix-ui/search/src/lib/components/results/ResultFileName.tsx
  24. 104
      libs/remix-ui/search/src/lib/components/results/ResultItem.tsx
  25. 63
      libs/remix-ui/search/src/lib/components/results/ResultSummary.tsx
  26. 17
      libs/remix-ui/search/src/lib/components/results/Results.tsx
  27. 108
      libs/remix-ui/search/src/lib/components/results/SearchHelper.ts
  28. 239
      libs/remix-ui/search/src/lib/context/context.tsx
  29. 101
      libs/remix-ui/search/src/lib/reducers/Reducer.ts
  30. 106
      libs/remix-ui/search/src/lib/search.css
  31. 66
      libs/remix-ui/search/src/lib/types/index.ts
  32. 19
      libs/remix-ui/search/tsconfig.json
  33. 13
      libs/remix-ui/search/tsconfig.lib.json
  34. 3
      nx.json
  35. 4
      package.json
  36. 1
      tsconfig.base.json
  37. 15
      workspace.json

@ -0,0 +1,122 @@
'use strict'
import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', true)
},
'Should find text': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.click('*[plugin="search"]').waitForElementVisible('*[id="search_input"]')
.setValue('*[id="search_input"]', 'read').pause(1000)
.waitForElementContainsText('*[data-id="search_results"]', '3_BALLOT.SOL', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'contracts', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'README.TXT', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'file must')
.waitForElementContainsText('*[data-id="search_results"]', 'be compiled')
.waitForElementContainsText('*[data-id="search_results"]', 'that person al')
.waitForElementContainsText('*[data-id="search_results"]', 'sender.voted')
.waitForElementContainsText('*[data-id="search_results"]', 'read')
.elements('css selector','.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 6)
})
},
'Should find regex': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="search_use_regex"]').click('*[data-id="search_use_regex"]')
.waitForElementVisible('*[id="search_input"]')
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', '^contract').pause(1000)
.waitForElementContainsText('*[data-id="search_results"]', '3_BALLOT.SOL', 60000)
.waitForElementContainsText('*[data-id="search_results"]', '2_OWNER.SOL', 60000)
.waitForElementContainsText('*[data-id="search_results"]', '1_STORAGE.SOL', 60000)
.waitForElementContainsText('*[data-id="search_results"]', '4_BALLOT_TEST.SOL', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'tests', 60000)
.elements('css selector','.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 4)
})
},
'Should find matchcase': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="search_use_regex"]').click('*[data-id="search_use_regex"]')
.waitForElementVisible('*[data-id="search_case_sensitive"]').click('*[data-id="search_case_sensitive"]')
.elements('css selector','.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 0)
})
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'Contract').pause(1000)
.elements('css selector','.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 6)
})
.waitForElementContainsText('*[data-id="search_results"]', 'DEPLOY_ETHERS.JS', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'DEPLOY_WEB3.JS', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'scripts', 60000)
},
'Should find matchword': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="search_case_sensitive"]').click('*[data-id="search_case_sensitive"]')
.waitForElementVisible('*[data-id="search_whole_word"]').click('*[data-id="search_whole_word"]')
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'contract').pause(1000)
.elements('css selector','.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 27)
})
},
'Should replace text': function (browser: NightwatchBrowser) {
browser
.setValue('*[id="search_replace"]', 'replacing').pause(1000)
.waitForElementVisible('*[data-id="contracts/2_Owner.sol-30-71"]')
.moveToElement('*[data-id="contracts/2_Owner.sol-30-71"]', 10, 10)
.waitForElementVisible('*[data-id="replace-contracts/2_Owner.sol-30-71"]')
.click('*[data-id="replace-contracts/2_Owner.sol-30-71"]').pause(2000).
modalFooterOKClick('confirmreplace').pause(2000).
getEditorValue((content) => {
browser.assert.ok(content.includes('replacing deployer for a constructor'), 'should replace text ok')
})
},
'Should replace text without confirmation': function (browser: NightwatchBrowser) {
browser.click('*[data-id="confirm_replace_label"]').pause(500)
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'replacing').pause(1000)
.setValue('*[id="search_replace"]', '2').pause(1000)
.waitForElementVisible('*[data-id="contracts/2_Owner.sol-30-71"]')
.moveToElement('*[data-id="contracts/2_Owner.sol-30-71"]', 10, 10)
.waitForElementVisible('*[data-id="replace-contracts/2_Owner.sol-30-71"]')
.click('*[data-id="replace-contracts/2_Owner.sol-30-71"]').pause(2000).
getEditorValue((content) => {
browser.assert.ok(content.includes('replacing2 deployer for a constructor'), 'should replace text ok')
})
},
'Should find text with include': function (browser: NightwatchBrowser) {
browser
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'contract').pause(1000)
.setValue('*[id="search_include"]', 'contracts/**').pause(2000)
.elements('css selector','.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 4)
})
},
'Should find text with exclude': function (browser: NightwatchBrowser) {
browser
.clearValue('*[id="search_include"]').pause(2000)
.setValue('*[id="search_include"]', '**').pause(2000)
.elements('css selector','.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 26)
})
.setValue('*[id="search_exclude"]', ',contracts/**').pause(2000)
.elements('css selector','.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 22)
})
},
'should clear search': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[id="search_input"]')
.setValue('*[id="search_input"]', 'nodata').pause(1000)
.elements('css selector','.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 0)
})
}
}

@ -30,6 +30,7 @@ const isElectron = require('is-electron')
const remixLib = require('@remix-project/remix-lib')
import { QueryParams } from '@remix-project/remix-lib'
import { SearchPlugin } from './app/tabs/search'
const Storage = remixLib.Storage
const RemixDProvider = require('./app/files/remixDProvider')
const Config = require('./config')
@ -147,6 +148,9 @@ class AppComponent {
// ----------------- Storage plugin ---------------------------------
const storagePlugin = new StoragePlugin()
//----- search
const search = new SearchPlugin()
// ----------------- import content service ------------------------
const contentImport = new CompilerImports()
@ -221,7 +225,8 @@ class AppComponent {
dGitProvider,
storagePlugin,
hardhatProvider,
this.walkthroughService
this.walkthroughService,
search
])
// LAYOUT & SYSTEM VIEWS
@ -332,8 +337,7 @@ class AppComponent {
await this.appManager.activatePlugin(['settings', 'config'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough','storage'])
// await this.appManager.activatePlugin(['scriptRunner'])
await this.appManager.activatePlugin(['walkthrough','storage', 'search'])
this.appManager.on(
'filePanel',

@ -29,7 +29,7 @@ export class VerticalIcons extends Plugin {
}
renderComponent () {
const fixedOrder = ['filePanel', 'solidity','udapp', 'debugger', 'solidityStaticAnalysis', 'solidityUnitTesting', 'pluginManager']
const fixedOrder = ['filePanel', 'search', 'solidity','udapp', 'debugger', 'solidityStaticAnalysis', 'solidityUnitTesting', 'pluginManager']
const divived = Object.values(this.icons).map((value) => { return {
...value,

@ -0,0 +1,32 @@
import { ViewPlugin } from '@remixproject/engine-web'
import * as packageJson from '../../../../../package.json'
import React from 'react' // eslint-disable-line
import { SearchTab } from '@remix-ui/search'
const profile = {
name: 'search',
displayName: 'Search',
methods: [''],
events: [],
icon: 'assets/img/Search_Icon.svg',
description: '',
kind: '',
location: 'sidePanel',
documentation: '',
version: packageJson.version
}
export class SearchPlugin extends ViewPlugin {
constructor () {
super(profile)
}
render() {
return (
<div id='searchTab'>
<SearchTab plugin={this}></SearchTab>
</div>
);
}
}

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="500"
height="500"
viewBox="0 0 500.00001 500.00001"
id="svg4162"
version="1.1"
inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="Search_Icon.svg">
<defs
id="defs4164" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.954"
inkscape:cx="250"
inkscape:cy="250"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="1366"
inkscape:window-height="706"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1" />
<metadata
id="metadata4167">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-552.36216)">
<g
id="g1400"
transform="translate(-4.3609793,-7.6704785)">
<path
inkscape:connector-curvature="0"
id="path4714"
d="M 232.83952,614.96702 A 154.04816,154.04794 0 0 0 78.79153,769.01382 154.04816,154.04794 0 0 0 232.83952,923.06184 154.04816,154.04794 0 0 0 386.88751,769.01382 154.04816,154.04794 0 0 0 232.83952,614.96702 Z m 0,26.77613 A 129.95832,127.2707 0 0 1 362.79832,769.01382 129.95832,127.2707 0 0 1 232.83952,896.28449 129.95832,127.2707 0 0 1 102.88194,769.01382 129.95832,127.2707 0 0 1 232.83952,641.74315 Z"
style="opacity:1;fill:#2b0000;fill-opacity:1;stroke:none;stroke-opacity:1" />
<rect
ry="18.08342"
rx="33.249443"
transform="matrix(0.65316768,0.7572133,-0.60689051,0.79478545,0,0)"
y="319.55432"
x="794.8775"
height="36.16684"
width="173.02675"
id="rect4721"
style="opacity:1;fill:#2b0000;fill-opacity:1;stroke:none;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -7,7 +7,7 @@ const _paq = window._paq = window._paq || []
const requiredModules = [ // services + layout views + system views
'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme',
'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic', 'gistHandler', 'layout', 'notification', 'permissionhandler', 'walkthrough', 'storage']
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic', 'gistHandler', 'layout', 'notification', 'permissionhandler', 'walkthrough', 'storage', 'search']
const dependentModules = ['git', 'hardhat', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd)

@ -1,6 +1,6 @@
export { default as RemixApp } from './lib/remix-app/remix-app'
export { dispatchModalContext, AppContext } from './lib/remix-app/context/context'
export { ModalProvider } from './lib/remix-app/context/provider'
export { ModalProvider, useDialogDispatchers } from './lib/remix-app/context/provider'
export { AppModal } from './lib/remix-app/interface/index'
export { AlertModal } from './lib/remix-app/interface/index'
export { ModalTypes } from './lib/remix-app/types/index'

@ -10,6 +10,7 @@ interface ModalWrapperProps extends ModalDialogProps {
const ModalWrapper = (props: ModalWrapperProps) => {
const [state, setState] = useState<ModalDialogProps>()
const ref = useRef()
const data = useRef()
const onFinishPrompt = async () => {
if (ref.current === undefined) {
@ -21,7 +22,7 @@ const ModalWrapper = (props: ModalWrapperProps) => {
}
const onOkFn = async () => {
(props.okFn) ? props.okFn() : props.resolve(true)
(props.okFn) ? props.okFn(data.current) : props.resolve(data.current || true)
}
const onCancelFn = async () => {
@ -37,6 +38,7 @@ const ModalWrapper = (props: ModalWrapperProps) => {
}
useEffect(() => {
data.current = props.data
if (props.modalType) {
switch (props.modalType) {
case ModalTypes.prompt:

@ -15,18 +15,18 @@ export const ModalProvider = ({ children = [], reducer = modalReducer, initialSt
})
}
const modal = (data: AppModal) => {
const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn } = data
const modal = (modalData: AppModal) => {
const { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType, defaultValue, hideFn, data } = modalData
return new Promise((resolve, reject) => {
dispatch({
type: modalActionTypes.setModal,
payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn, resolve, next: onNextFn }
payload: { id, title, message, okLabel, okFn, cancelLabel, cancelFn, modalType: modalType || ModalTypes.default, defaultValue: defaultValue, hideFn, resolve, next: onNextFn, data }
})
})
}
const alert = (data: AlertModal) => {
return modal({ id: data.id, title: data.title || 'Alert', message: data.message || data.title, okLabel: 'OK', okFn: (value?:any) => {}, cancelLabel: '', cancelFn: () => {} })
const alert = (modalData: AlertModal) => {
return modal({ id: modalData.id, title: modalData.title || 'Alert', message: modalData.message || modalData.title, okLabel: 'OK', okFn: (value?:any) => {}, cancelLabel: '', cancelFn: () => {} })
}
const handleHideModal = () => {

@ -15,7 +15,8 @@ export interface AppModal {
defaultValue?: string
hideFn?: () => void,
resolve?: (value?:any) => void,
next?: () => void
next?: () => void,
data?: any
}
export interface AlertModal {

@ -20,7 +20,8 @@ export const modalReducer = (state: ModalState = ModalInitialState, action: Moda
defaultValue: action.payload.defaultValue,
hideFn: action.payload.hideFn,
resolve: action.payload.resolve,
next: action.payload.next
next: action.payload.next,
data: action.payload.data
}
const modalList: AppModal[] = state.modals.slice()

@ -14,5 +14,6 @@ export interface ModalDialogProps {
handleHide: (hideState?: boolean) => void,
children?: React.ReactNode,
resolve?: (value?:any) => void,
next?: () => void
next?: () => void,
data?: any
}

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

@ -0,0 +1,19 @@
{
"env": {
"browser": true,
"es6": true
},
"ignorePatterns": ["!**/*"],
"extends": "../../../.eslintrc.json",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 11,
"sourceType": "module"
},
"rules": {
"standard/no-callback-literal": "off"
}
}

@ -0,0 +1,5 @@
{
"tabWidth": 2,
"singleQuote": true,
"semi": false
}

@ -0,0 +1 @@
export { SearchTab } from './lib/components/Search';

@ -0,0 +1,32 @@
import React, { useContext, useEffect, useRef, useState } from 'react'
import { SearchContext } from '../context/context'
export const Exclude = props => {
const { setExclude, state } = useContext(SearchContext)
const [excludeInput, setExcludeInput] = useState<string>('.git/**/*,.deps/**/*')
const timeOutId = useRef(null)
const change = e => {
setExcludeInput(e.target.value)
clearTimeout(timeOutId.current)
timeOutId.current = setTimeout(() => setExclude(e.target.value), 500)
}
useEffect(() => {
setExclude(excludeInput)
}, [])
return (
<>
<div className="search_plugin_find-part">
<label>exclude</label>
<input
id='search_exclude'
placeholder="Exclude ie .git/**/*"
className="form-control"
onChange={change}
value={excludeInput}
></input>
</div>
</>
)
}

@ -0,0 +1,77 @@
import React, { useContext, useRef } from 'react'
import { SearchContext } from '../context/context'
export const Find = () => {
const {
setFind,
state,
toggleCaseSensitive,
toggleMatchWholeWord,
toggleUseRegex
} = useContext(SearchContext)
const timeOutId = useRef(null)
const change = e => {
clearTimeout(timeOutId.current)
timeOutId.current = setTimeout(() => setFind(e.target.value), 500)
}
return (
<>
<div className="search_plugin_find-part">
<label>search</label>
<div className="search_plugin_search-input">
<input
id='search_input'
placeholder="Search"
className="form-control"
onChange={change}
></input>
<div className="search_plugin_controls">
<div
data-id='search_case_sensitive'
title="Match Case"
className={`monaco-custom-checkbox codicon codicon-case-sensitive ${
state.casesensitive ? 'checked' : ''
}`}
role="checkbox"
aria-checked="false"
aria-label="Match Case"
aria-disabled="false"
onClick={() => {
toggleCaseSensitive()
}}
></div>
<div
data-id='search_whole_word'
title="Match Whole Word"
className={`monaco-custom-checkbox codicon codicon-whole-word ${
state.matchWord ? 'checked' : ''
}`}
role="checkbox"
aria-checked="false"
aria-label="Match Whole Word"
aria-disabled="false"
onClick={() => {
toggleMatchWholeWord()
}}
></div>
<div
data-id='search_use_regex'
title="Use Regular Expression"
className={`monaco-custom-checkbox codicon codicon-regex ${
state.useRegExp ? 'checked' : ''
}`}
role="checkbox"
aria-checked="false"
aria-label="Use Regular Expression"
aria-disabled="false"
onClick={() => {
toggleUseRegex()
}}
></div>
</div>
</div>
</div>
</>
)
}

@ -0,0 +1,28 @@
import React, { useContext, useRef, useState } from 'react'
import { SearchContext } from '../context/context'
export const Include = props => {
const { setInclude } = useContext(SearchContext)
const [includeInput, setIncludeInput] = useState<string>('')
const timeOutId = useRef(null)
const change = e => {
setIncludeInput(e.target.value)
clearTimeout(timeOutId.current)
timeOutId.current = setTimeout(() => setInclude(e.target.value), 500)
}
return (
<>
<div className="search_plugin_find-part">
<label>include</label>
<input
id='search_include'
placeholder="Include ie contracts/**/*.sol"
className="form-control"
onChange={change}
value={includeInput}
></input>
</div>
</>
)
}

@ -0,0 +1,32 @@
import React, { useContext } from 'react'
import { SearchContext } from '../context/context'
export const OverWriteCheck = props => {
const { setReplaceWithoutConfirmation } = useContext(SearchContext)
const change = e => {
setReplaceWithoutConfirmation(e.target.checked)
}
return (
<>
<div className="search_plugin_find-part">
<div className="mb-2 remixui_nightlyBuilds custom-control custom-checkbox">
<input
className="mr-2 custom-control-input"
id="confirm_replace"
type="checkbox"
onChange={change}
/>
<label
htmlFor='confirm_replace'
data-id="confirm_replace_label"
className="form-check-label custom-control-label"
>
replace without confirmation
</label>
</div>
</div>
</>
)
}

@ -0,0 +1,25 @@
import React, { useContext, useRef } from 'react'
import { SearchContext } from '../context/context'
export const Replace = props => {
const { setReplace } = useContext(SearchContext)
const timeOutId = useRef(null)
const change = e => {
clearTimeout(timeOutId.current)
timeOutId.current = setTimeout(() => setReplace(e.target.value), 500)
}
return (
<>
<div className="search_plugin_find-part">
<label>replace</label>
<input
id='search_replace'
placeholder="Replace"
className="form-control"
onChange={change}
></input>
</div>
</>
)
}

@ -0,0 +1,29 @@
import React from 'react'
import { SearchProvider } from '../context/context'
import { Find } from './Find'
import { Results } from './results/Results'
import '../search.css'
import { Include } from './Include'
import { Exclude } from './Exclude'
import { Replace } from './Replace'
import { OverWriteCheck } from './OverWriteCheck'
export const SearchTab = props => {
const plugin = props.plugin
return (
<>
<div className="search_plugin_search_tab pl-2 pr-2">
<SearchProvider plugin={plugin}>
<Find></Find>
<Replace></Replace>
<Include></Include>
<Exclude></Exclude>
<OverWriteCheck></OverWriteCheck>
<Results></Results>
</SearchProvider>
</div>
</>
)
}

@ -0,0 +1,27 @@
import React, { useEffect, useState } from 'react'
import { SearchResult } from '../../types'
import { getPathIcon } from '@remix-ui/helper'
import * as path from 'path'
interface ResultItemProps {
file: SearchResult
}
export const ResultFileName = (props: ResultItemProps) => {
const [icon, setIcon] = useState<string>('')
useEffect(() => {
if (props.file && props.file.path) {
setIcon(getPathIcon(props.file.path))
}
}, [props.file])
return (
<>
{icon ? <div className={`${icon} caret caret_tv`}></div> : null}
<div title={props.file.filename} className="search_plugin_search_file_name ml-2">
{path.basename(props.file.path)}
<span className='pl-1 text-muted text-lowercase'>{path.dirname(props.file.path)}</span>
</div>
</>
)
}

@ -0,0 +1,104 @@
import React, { useContext, useEffect, useRef, useState } from 'react'
import { SearchContext } from '../../context/context'
import { SearchResult, SearchResultLine } from '../../types'
import { ResultFileName } from './ResultFileName'
import { ResultSummary } from './ResultSummary'
interface ResultItemProps {
file: SearchResult
}
export const ResultItem = (props: ResultItemProps) => {
const { state, findText, disableForceReload, updateCount } = useContext(
SearchContext
)
const [loading, setLoading] = useState<boolean>(false)
const [lines, setLines] = useState<SearchResultLine[]>([])
const [toggleExpander, setToggleExpander] = useState<boolean>(false)
const reloadTimeOut = useRef(null)
const subscribed = useRef(true)
useEffect(() => {
reload()
}, [props.file.timeStamp])
useEffect(() => {
if (props.file.forceReload) {
clearTimeout(reloadTimeOut.current)
reloadTimeOut.current = setTimeout(() => reload(), 1000)
}
}, [props.file.forceReload])
const toggleClass = () => {
setToggleExpander(!toggleExpander)
}
useEffect(() => {
reload()
}, [state.find])
useEffect(() => {
subscribed.current = true
return () => {
subscribed.current = false
}
}, [])
const reload = () => {
findText(props.file.filename).then(res => {
if (subscribed.current) {
setLines(res)
if (res) {
let count = 0
res.forEach(line => {
count += line.lines.length
})
updateCount(count, props.file.filename)
}
setLoading(false)
disableForceReload(props.file.filename)
}
})
}
return (
<>
{lines && lines.length ? (
<>
<div onClick={toggleClass} className="search_plugin_search_result_item_title">
<button className="btn">
<i
className={`fas ${
toggleExpander ? 'fa-angle-right' : 'fa-angle-down'
}`}
aria-hidden="true"
></i>
</button>{' '}
<ResultFileName file={props.file} />
<div className="search_plugin_result_count">
<div className="search_plugin_result_count_number badge badge-pill badge-secondary">
{props.file.count}
</div>
</div>
</div>
{loading ? <div className="loading">Loading...</div> : null}
{!toggleExpander && !loading ? (
<div className="p-1 search_plugin_wrap_summary">
{lines.map((line, index) => (
<ResultSummary
setLoading={setLoading}
key={index}
searchResult={props.file}
line={line}
/>
))}
</div>
) : null}
</>
) : (
<></>
)}
</>
)
}

@ -0,0 +1,63 @@
import { useDialogDispatchers } from '@remix-ui/app'
import React, { useContext } from 'react'
import { SearchContext } from '../../context/context'
import { SearchResult, SearchResultLine, SearchResultLineLine } from '../../types'
interface ResultSummaryProps {
searchResult: SearchResult
line: SearchResultLine
setLoading: (value: boolean) => void
}
export const ResultSummary = (props: ResultSummaryProps) => {
const { hightLightInPath, replaceText, state } = useContext(SearchContext)
const { modal } = useDialogDispatchers()
const selectLine = async (line: SearchResultLineLine) => {
await hightLightInPath(props.searchResult, line)
}
const confirmReplace = async (line: SearchResultLineLine) => {
props.setLoading(true)
try{
await replaceText(props.searchResult, line)
}catch(e){
props.setLoading(false)
}
}
const replace = async (line: SearchResultLineLine) => {
if(state.replaceWithOutConfirmation){
confirmReplace(line)
}else{
modal({ id: 'confirmreplace', title: 'Replace', message: `Are you sure you want to replace '${line.center}' by '${state.replace}' in ${props.searchResult.filename}?`, okLabel: 'Yes', okFn: confirmReplace, cancelLabel: 'No', cancelFn: ()=>{}, data: line })
}
}
return (
<>
{props.line.lines.map((lineItem, index) => (
<div className='search_plugin_search_line_container' key={index}>
<div
onClick={async () => {
selectLine(lineItem)
}}
data-id={`${props.searchResult.filename}-${lineItem.position.start.line}-${lineItem.position.start.column}`}
key={props.searchResult.filename}
className='search_plugin_search_line pb-1'
>
<div className='search_plugin_summary_left'>{lineItem.left.substring(lineItem.left.length - 20).trimStart()}</div>
<mark className={`search_plugin_summary_center ${state.replace? 'search_plugin_replace_strike':''}`}>{lineItem.center}</mark>
{state.replace? <mark className='search_plugin_replacement'>{state.replace}</mark>:<></>}
<div className='search_plugin_summary_right'>{lineItem.right.substring(0, 100)}</div>
</div>
<div className='search_plugin_search_control'>
<div title="Replace" data-id={`replace-${props.searchResult.filename}-${lineItem.position.start.line}-${lineItem.position.start.column}`} onClick={async () => {
replace(lineItem)
}} className="codicon codicon-find-replace" role="button" aria-label="Replace" aria-disabled="false"></div>
</div>
</div>
))}
</>
)
}

@ -0,0 +1,17 @@
import React, { useContext, useEffect } from 'react'
import { SearchContext } from '../../context/context'
import { ResultItem } from './ResultItem'
export const Results = () => {
const { state } = useContext(SearchContext)
return (
<div data-id='search_results'>
{state.find ? <div className='search_plugin_result_count_number badge badge-pill badge-secondary'>{state.count} results</div>: null}
{state.count < state.maxResults && state.searchResults &&
state.searchResults.map((result, index) => {
return <ResultItem key={index} file={result} />
})}
{state.find && state.count >= state.maxResults? <div className='alert alert-warning mt-1'>Too many results to display.<br></br>Please narrow your search.</div>: null}
</div>
)
}

@ -0,0 +1,108 @@
import { EOL } from 'os'
import { SearchResultLineLine } from '../../types'
export const getDirectory = async (dir: string, plugin: any) => {
let result = []
const files = await plugin.call('fileManager', 'readdir', dir)
const fileArray = normalize(files)
for (const fi of fileArray) {
if (fi) {
const type = fi.data.isDirectory
if (type === true) {
result = [...result, ...(await getDirectory(`${fi.filename}`, plugin))]
} else {
result = [...result, fi.filename]
}
}
}
return result
}
const normalize = filesList => {
const folders = []
const files = []
Object.keys(filesList || {}).forEach(key => {
if (filesList[key].isDirectory) {
folders.push({
filename: key,
data: filesList[key]
})
} else {
files.push({
filename: key,
data: filesList[key]
})
}
})
return [...folders, ...files]
}
export const findLinesInStringWithMatch = (str: string, re: RegExp) => {
return str
.split(/\r?\n/)
.map(function (line, i) {
const matchResult = matchesInString(line, re)
if (matchResult.length) {
return {
lines: splitLines(matchResult, i),
}
}
})
.filter(Boolean)
}
const matchesInString = (str: string, re: RegExp) => {
let a: RegExpExecArray
const results:RegExpExecArray[] = [];
while ((a = re.exec(str || '')) !== null) {
results.push(a);
}
return results
}
const splitLines = (matchResult: RegExpExecArray[], lineNumber: number) => {
return matchResult.map((matchResultPart, i) => {
const result:SearchResultLineLine = {
left: matchResultPart.input.substring(0, matchResultPart.index),
right: matchResultPart.input.substring(matchResultPart.index + matchResultPart[0].length),
center: matchResultPart[0],
position : {
start: {
line: lineNumber,
column: matchResultPart.index,
},
end: {
line: lineNumber,
column: matchResultPart.index + matchResultPart[0].length,
},
},
}
return result
})
}
function getEOL(text) {
const m = text.match(/\r\n|\n/g);
const u = m && m.filter(a => a === '\n').length;
const w = m && m.length - u;
if (u === w) {
return EOL; // use the OS default
}
return u > w ? '\n' : '\r\n';
}
export const replaceTextInLine = (str: string, searchResultLine: SearchResultLineLine, newText: string) => {
return str
.split(/\r?\n/)
.map(function (line, i) {
if (i === searchResultLine.position.start.line) {
return searchResultLine.left + newText + searchResultLine.right
}
return line
}).join(getEOL(str))
}

@ -0,0 +1,239 @@
import React, { useEffect, useRef } from 'react'
import { createContext, useReducer } from 'react'
import {
findLinesInStringWithMatch,
getDirectory,
replaceTextInLine
} from '../components/results/SearchHelper'
import { SearchReducer } from '../reducers/Reducer'
import {
SearchState,
SearchResult,
SearchResultLine,
SearchResultLineLine,
SearchingInitialState
} from '../types'
import { filePathFilter } from '@jsdevtools/file-path-filter'
import { escapeRegExp } from 'lodash'
export interface SearchingStateInterface {
state: SearchState
setFind: (value: string) => void
setReplace: (value: string) => void
setInclude: (value: string) => void
setExclude: (value: string) => void
setCaseSensitive: (value: boolean) => void
setRegex: (value: boolean) => void
setWholeWord: (value: boolean) => void
setSearchResults: (value: SearchResult[]) => void
findText: (path: string) => Promise<SearchResultLine[]>
hightLightInPath: (result: SearchResult, line: SearchResultLineLine) => void
replaceText: (result: SearchResult, line: SearchResultLineLine) => void
reloadFile: (file: string) => void
toggleCaseSensitive: () => void
toggleMatchWholeWord: () => void
toggleUseRegex: () => void
setReplaceWithoutConfirmation: (value: boolean) => void
disableForceReload: (file: string) => void
updateCount: (count: number, file: string) => void
}
export const SearchContext = createContext<SearchingStateInterface>(null)
export const SearchProvider = ({
children = [],
reducer = SearchReducer,
initialState = SearchingInitialState,
plugin = undefined
} = {}) => {
const [state, dispatch] = useReducer(reducer, initialState)
const reloadTimeOut = useRef(null)
const value = {
state,
setFind: (value: string) => {
dispatch({
type: 'SET_FIND',
payload: value
})
},
setReplace: (value: string) => {
dispatch({
type: 'SET_REPLACE',
payload: value
})
},
setInclude: (value: string) => {
dispatch({
type: 'SET_INCLUDE',
payload: value
})
},
setExclude(value: string) {
dispatch({
type: 'SET_EXCLUDE',
payload: value
})
},
setCaseSensitive(value: boolean) {
dispatch({
type: 'SET_CASE_SENSITIVE',
payload: value
})
},
setWholeWord(value: boolean) {
dispatch({
type: 'SET_WHOLE_WORD',
payload: value
})
},
setRegex(value: boolean) {
dispatch({
type: 'SET_REGEX',
payload: value
})
},
setSearchResults(value: SearchResult[]) {
dispatch({
type: 'SET_SEARCH_RESULTS',
payload: value
})
},
reloadFile: async (file: string) => {
dispatch({
type: 'RELOAD_FILE',
payload: file
})
},
toggleUseRegex: () => {
dispatch({
type: 'TOGGLE_USE_REGEX',
payload: undefined
})
},
toggleCaseSensitive: () => {
dispatch({
type: 'TOGGLE_CASE_SENSITIVE',
payload: undefined
})
},
toggleMatchWholeWord: () => {
dispatch({
type: 'TOGGLE_MATCH_WHOLE_WORD',
payload: undefined
})
},
setReplaceWithoutConfirmation: (value: boolean) => {
dispatch({
type: 'SET_REPLACE_WITHOUT_CONFIRMATION',
payload: value
})
},
disableForceReload: (file: string) => {
dispatch({
type: 'DISABLE_FORCE_RELOAD',
payload: file
})
},
updateCount: (count: number, file: string) => {
dispatch({
type: 'UPDATE_COUNT',
payload: {count, file}
})
},
findText: async (path: string) => {
if (!plugin) return
try {
if (state.find.length < 3) return
const text = await plugin.call('fileManager', 'readFile', path)
let flags = 'g'
let find = state.find
if (!state.casesensitive) flags += 'i'
if (!state.useRegExp) find = escapeRegExp(find)
if (state.matchWord) find = `\\b${find}\\b`
const re = new RegExp(find, flags)
const result: SearchResultLine[] = findLinesInStringWithMatch(text, re)
return result
} catch (e) {}
},
hightLightInPath: async (
result: SearchResult,
line: SearchResultLineLine
) => {
await plugin.call('editor', 'discardHighlight')
await plugin.call('editor', 'highlight', line.position, result.path)
},
replaceText: async (result: SearchResult, line: SearchResultLineLine) => {
try {
await plugin.call('editor', 'discardHighlight')
await plugin.call('editor', 'highlight', line.position, result.path)
const content = await plugin.call(
'fileManager',
'readFile',
result.path
)
await plugin.call(
'fileManager',
'setFile',
result.path,
replaceTextInLine(content, line, state.replace)
)
} catch (e) {
throw new Error(e)
}
}
}
const reloadStateForFile = async (file: string) => {
await value.reloadFile(file)
}
useEffect(() => {
plugin.on('filePanel', 'setWorkspace', () => {
value.setSearchResults(null)
})
plugin.on('fileManager', 'fileSaved', async file => {
await reloadStateForFile(file)
})
return () => {
plugin.off('fileManager', 'fileChanged')
plugin.off('filePanel', 'setWorkspace')
}
}, [])
useEffect(() => {
if (state.find) {
(async () => {
const files = await getDirectory('/', plugin)
const pathFilter: any = {}
if (state.include){
const includeWithGlobalExpression = state.include.replaceAll(/(?<!\/)(\*\.)/g, '**/*.')
pathFilter.include = includeWithGlobalExpression.split(',').map(i => i.trim())
}
if (state.exclude){
const excludeWithGlobalExpression = state.exclude.replaceAll(/(?<!\/)(\*\.)/g, '**/*.')
pathFilter.exclude = excludeWithGlobalExpression.split(',').map(i => i.trim())
}
const filteredFiles = files.filter(filePathFilter(pathFilter)).map(file => {
const r: SearchResult = {
filename: file,
lines: [],
path: file,
timeStamp: Date.now(),
forceReload: false,
count: 0
}
return r
})
value.setSearchResults(filteredFiles)
})()
}
}, [state.timeStamp])
return (
<>
<SearchContext.Provider value={value}>{children}</SearchContext.Provider>
</>
)
}

@ -0,0 +1,101 @@
import { Action, SearchingInitialState, SearchState } from "../types"
export const SearchReducer = (state: SearchState = SearchingInitialState, action: Action) => {
switch (action.type) {
case 'SET_FIND':
return {
...state,
find: action.payload,
timeStamp: Date.now()
}
case 'SET_REPLACE':
return {
...state,
replace: action.payload,
}
case 'SET_INCLUDE':
return {
...state,
include: action.payload,
timeStamp: Date.now()
}
case 'SET_EXCLUDE':
return {
...state,
exclude: action.payload,
timeStamp: Date.now()
}
case 'SET_SEARCH_RESULTS':
return {
...state,
searchResults: action.payload,
count: 0
}
case 'UPDATE_COUNT':
if (state.searchResults) {
const findFile = state.searchResults.find(file => file.filename === action.payload.file)
let count = 0
if (findFile) {
findFile.count = action.payload.count
}
state.searchResults.forEach(file => {
if (file.count) {
count += file.count
}
})
return {
...state,
count: count
}
} else {
return state
}
case 'TOGGLE_CASE_SENSITIVE':
return {
...state,
casesensitive: !state.casesensitive,
timeStamp: Date.now()
}
case 'TOGGLE_USE_REGEX':
return {
...state,
useRegExp: !state.useRegExp,
timeStamp: Date.now()
}
case 'TOGGLE_MATCH_WHOLE_WORD':
return {
...state,
matchWord: !state.matchWord,
timeStamp: Date.now()
}
case 'SET_REPLACE_WITHOUT_CONFIRMATION':
return {
...state,
replaceWithOutConfirmation: action.payload,
}
case 'DISABLE_FORCE_RELOAD':
if (state.searchResults) {
const findFile = state.searchResults.find(file => file.filename === action.payload)
if (findFile) findFile.forceReload = false
}
return {
...state,
}
case 'RELOAD_FILE':
if (state.searchResults) {
const findFile = state.searchResults.find(file => file.filename === action.payload)
if (findFile) findFile.forceReload = true
}
return {
...state,
}
default:
return {
...state,
}
}
}

@ -0,0 +1,106 @@
.search_plugin_search_result_item_title {
display: flex;
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10+/Edge */
user-select: none; /* Standard */
cursor: pointer;
align-items: center;
}
.search_plugin_wrap_summary {
overflow: hidden;
white-space: nowrap;
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* IE10+/Edge */
user-select: none; /* Standard */
cursor: pointer;
}
.search_plugin_find-part {
display: flex;
flex-direction: column;
padding: 2px;
}
.search_plugin_controls {
display: flex;
}
.search_plugin_search_tab .search_plugin_search_line_container {
display: flex;
flex-direction: row;
position: relative;
}
.search_plugin_search_tab .search_plugin_search_line {
width: 100%;
overflow: hidden;
display: flex;
}
.search_plugin_search_tab .search_plugin_search_control {
flex-grow: 0;
position: absolute;
right: 0px;
top: 0px;
}
.search_plugin_summary_right {
min-width: 0;
white-space: pre;
text-overflow: ellipsis;
overflow: hidden;
}
.search_plugin_search_tab .search_plugin_replace_strike {
text-decoration: line-through;
}
.search_plugin_summary_left {
white-space: pre;
}
.search_plugin_search_tab mark {
padding: 0;
}
.search_plugin_search_tab .search_plugin_search_line_container .search_plugin_search_control {
display: none;
}
.search_plugin_search_tab .search_plugin_search_line_container:hover .search_plugin_search_control {
display: block;
}
.search_plugin_search_tab .search_plugin_search_line_container:hover .search_plugin_search_line {
width: 93%;
}
.search_plugin_search-input {
display: flex;
flex-direction: row;
align-items: center;
}
.search_plugin_search_tab .checked {
background-color: var(--secondary);
}
.search_plugin_search_tab .search_plugin_search_file_name {
text-overflow: ellipsis;
overflow: hidden;
text-transform: uppercase;
}
.search_plugin_search_tab .search_plugin_result_count {
flex-grow: 1;
text-align: right;
display: flex;
justify-content: flex-end;
}
.search_plugin_search_tab .search_plugin_result_count_number {
font-size: x-small;
}

@ -0,0 +1,66 @@
import { count } from "console";
export interface Action {
type: string
payload: any
}
interface position {
start: {
line: number
column: number
},
end: {
line: number
column: number
}
}
export interface SearchResultLineLine {
left: any,
center: any,
right: any,
position: position
}
export interface SearchResultLine {
lines: SearchResultLineLine[]
}
export interface SearchResult {
filename: string,
path: string,
lines: SearchResultLine[],
timeStamp: number,
forceReload: boolean,
count: number
}
export interface SearchState {
find: string,
searchResults: SearchResult[],
replace: string,
include: string,
exclude: string,
casesensitive: boolean,
matchWord: boolean,
replaceWithOutConfirmation: boolean,
useRegExp: boolean,
timeStamp: number,
count: number,
maxResults: number
}
export const SearchingInitialState: SearchState = {
find: '',
replace: '',
include: '',
exclude: '',
searchResults: [],
casesensitive: false,
matchWord: false,
useRegExp: false,
replaceWithOutConfirmation: false,
timeStamp: 0,
count: 0,
maxResults: 500
}

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

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

@ -139,6 +139,9 @@
"remix-ui-vertical-icons-panel": {
"tags": []
},
"remix-ui-search": {
"tags": []
},
"remix-ui-home-tab": {
"tags": []
},

@ -45,7 +45,7 @@
"workspace-schematic": "nx workspace-schematic",
"dep-graph": "nx dep-graph",
"help": "nx help",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-helper,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs,remix-ui-panel,remix-ui-run-tab,remix-ui-permission-handler",
"lint:libs": "nx run-many --target=lint --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd,remix-ui-tree-view,remix-ui-modal-dialog,remix-ui-toaster,remix-ui-helper,remix-ui-debugger-ui,remix-ui-workspace,remix-ui-static-analyser,remix-ui-checkbox,remix-ui-settings,remix-core-plugin,remix-ui-renderer,remix-ui-publish-to-storage,remix-ui-solidity-compiler,solidity-unit-testing,remix-ui-plugin-manager,remix-ui-terminal,remix-ui-editor,remix-ui-app,remix-ui-tabs,remix-ui-panel,remix-ui-run-tab,remix-ui-permission-handler,remix-ui-search",
"build:libs": "nx run-many --target=build --parallel=false --with-deps=true --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"test:libs": "nx run-many --target=test --projects=remix-analyzer,remix-astwalker,remix-debug,remix-lib,remix-simulator,remix-solidity,remix-tests,remix-url-resolver,remixd",
"publish:libs": "npm run build:libs && lerna publish --skip-git && npm run bumpVersion:libs",
@ -97,6 +97,7 @@
"nightwatch_local_pluginApi": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/plugin_api_*.js --env=chrome",
"nightwatch_local_migrate_filesystem": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/migrateFileSystem.test.js --env=chrome",
"nightwatch_local_stress_editor": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/stress.editor.js --env=chromeDesktop",
"nightwatch_local_search": "npm run build:e2e && nightwatch --config dist/apps/remix-ide-e2e/nightwatch.js dist/apps/remix-ide-e2e/src/tests/search.test.js --env=chromeDesktop",
"onchange": "onchange apps/remix-ide/build/app.js -- npm-run-all lint",
"remixd": "nx build remixd && chmod +x dist/libs/remixd/src/bin/remixd.js && dist/libs/remixd/src/bin/remixd.js -s ./apps/remix-ide/contracts --remix-ide http://127.0.0.1:8080",
"selenium": "selenium-standalone start",
@ -172,6 +173,7 @@
"ethers": "^5.4.2",
"ethjs-util": "^0.1.6",
"express-ws": "^4.0.0",
"file-path-filter": "^3.0.2",
"file-saver": "^2.0.5",
"form-data": "^4.0.0",
"fs-extra": "^3.0.1",

@ -40,6 +40,7 @@
],
"@remix-project/remixd": ["dist/libs/remixd/index.js"],
"@remix-ui/tree-view": ["libs/remix-ui/tree-view/src/index.ts"],
"@remix-ui/search": ["libs/remix-ui/search/src/index.ts"],
"@remix-ui/debugger-ui": ["libs/remix-ui/debugger-ui/src/index.ts"],
"@remix-ui/utils": ["libs/remix-ui/utils/src/index.ts"],
"@remix-ui/clipboard": ["libs/remix-ui/clipboard/src/index.ts"],

@ -1108,6 +1108,21 @@
}
}
},
"remix-ui-search": {
"root": "libs/remix-ui/search",
"sourceRoot": "libs/remix-ui/search/src",
"projectType": "library",
"architect": {
"lint": {
"builder": "@nrwl/linter:lint",
"options": {
"linter": "eslint",
"tsConfig": ["libs/remix-ui/search/tsconfig.lib.json"],
"exclude": ["**/node_modules/**", "!libs/remix-ui/search/**/*"]
}
}
}
},
"remix-ui-panel": {
"root": "libs/remix-ui/panel",
"sourceRoot": "libs/remix-ui/panel/src",

Loading…
Cancel
Save