Merge branch 'master' into mg-compile-btn-text

pull/2257/head
Miladinho 3 years ago committed by GitHub
commit 5e9f0a8541
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 105
      apps/remix-ide-e2e/src/tests/search.test.ts
  2. 6
      apps/remix-ide/src/app.js
  3. 2
      apps/remix-ide/src/remixAppManager.js
  4. 23
      libs/remix-ui/search/src/lib/components/Exclude.tsx
  5. 38
      libs/remix-ui/search/src/lib/components/Find.tsx
  6. 27
      libs/remix-ui/search/src/lib/components/Include.tsx
  7. 11
      libs/remix-ui/search/src/lib/components/Search.tsx
  8. 13
      libs/remix-ui/search/src/lib/components/StopSearch.tsx
  9. 36
      libs/remix-ui/search/src/lib/components/results/ResultItem.tsx
  10. 24
      libs/remix-ui/search/src/lib/components/results/Results.tsx
  11. 178
      libs/remix-ui/search/src/lib/context/context.tsx
  12. 47
      libs/remix-ui/search/src/lib/reducers/Reducer.ts
  13. 8
      libs/remix-ui/search/src/lib/search.css
  14. 8
      libs/remix-ui/search/src/lib/types/index.ts
  15. 2
      libs/remix-ui/workspace/src/lib/components/file-label.tsx

@ -4,14 +4,16 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init'
module.exports = {
'@disabled': true,
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)
.waitForElementVisible('*[id="search_include"]')
.setValue('*[id="search_include"]', ', *.txt').pause(2000)
.setValue('*[id="search_input"]', 'read').sendKeys('*[id="search_input"]', browser.Keys.ENTER)
.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)
@ -20,61 +22,74 @@ module.exports = {
.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) => {
.elements('css selector', '.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 6)
})
},
'Should find text with exclude': function (browser: NightwatchBrowser) {
browser
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'contract').pause(1000)
.clearValue('*[id="search_include"]').pause(2000)
.setValue('*[id="search_include"]', '**').sendKeys('*[id="search_include"]', browser.Keys.ENTER).pause(4000)
.elements('css selector', '.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 44)
})
.setValue('*[id="search_exclude"]', ',contracts/**').sendKeys('*[id="search_exclude"]', browser.Keys.ENTER).pause(4000)
.elements('css selector', '.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 38)
})
.clearValue('*[id="search_include"]').setValue('*[id="search_include"]', '*.sol, *.js, *.txt')
.clearValue('*[id="search_exclude"]').setValue('*[id="search_exclude"]', '.*/**/*')
},
'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)
.setValue('*[id="search_input"]', '^contract').sendKeys('*[id="search_input"]', browser.Keys.ENTER)
.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"]', 'BALLOT_TEST.SOL', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'tests', 60000)
.elements('css selector','.search_plugin_search_line', (res) => {
.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) => {
.waitForElementVisible('*[data-id="search_case_sensitive"]').click('*[data-id="search_case_sensitive"]').pause(4000)
.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, 9)
.setValue('*[id="search_input"]', 'Contract').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(3000)
.elements('css selector', '.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 3)
})
.waitForElementContainsText('*[data-id="search_results"]', 'DEPLOY_ETHERS.JS', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'DEPLOY_WEB3.JS', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'STORAGE.TEST.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"]')
.waitForElementVisible('*[data-id="search_whole_word"]').click('*[data-id="search_whole_word"]').pause(2000)
.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, 29)
.setValue('*[id="search_input"]', 'contract').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(4000)
.elements('css selector', '.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 13)
})
},
'Should replace text': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="toggle_replace"]').click('*[data-id="toggle_replace"]')
.waitForElementVisible('*[id="search_replace"]')
.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).
.setValue('*[id="search_replace"]', 'replacing').sendKeys('*[id="search_replace"]', browser.Keys.ENTER).pause(1000)
.waitForElementVisible('*[data-id="contracts/2_Owner.sol-33-71"]')
.moveToElement('*[data-id="contracts/2_Owner.sol-33-71"]', 10, 10)
.waitForElementVisible('*[data-id="replace-contracts/2_Owner.sol-33-71"]')
.click('*[data-id="replace-contracts/2_Owner.sol-33-71"]').pause(2000).
modalFooterOKClick('confirmreplace').pause(2000).
getEditorValue((content) => {
browser.assert.ok(content.includes('replacing deployer for a constructor'), 'should replace text ok')
@ -83,12 +98,12 @@ module.exports = {
'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_input"]', 'replacing').sendKeys('*[id="search_input"]', browser.Keys.ENTER).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).
.waitForElementVisible('*[data-id="contracts/2_Owner.sol-33-71"]')
.moveToElement('*[data-id="contracts/2_Owner.sol-33-71"]', 10, 10)
.waitForElementVisible('*[data-id="replace-contracts/2_Owner.sol-33-71"]')
.click('*[data-id="replace-contracts/2_Owner.sol-33-71"]').pause(2000).
getEditorValue((content) => {
browser.assert.ok(content.includes('replacing2 deployer for a constructor'), 'should replace text ok')
})
@ -96,7 +111,7 @@ module.exports = {
'Should replace all & undo': function (browser: NightwatchBrowser) {
browser
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'storage')
.setValue('*[id="search_input"]', 'storage').sendKeys('*[id="search_input"]', browser.Keys.ENTER)
.clearValue('*[id="search_replace"]')
.setValue('*[id="search_replace"]', '123test').pause(1000)
.waitForElementVisible('*[data-id="replace-all-contracts/1_Storage.sol"]')
@ -115,7 +130,7 @@ module.exports = {
'Should replace all & undo & switch between files': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[id="search_input"]')
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'storage')
.setValue('*[id="search_input"]', 'storage').sendKeys('*[id="search_input"]', browser.Keys.ENTER)
.clearValue('*[id="search_replace"]')
.setValue('*[id="search_replace"]', '123test').pause(1000)
.waitForElementVisible('*[data-id="replace-all-contracts/1_Storage.sol"]')
@ -150,10 +165,10 @@ module.exports = {
},
'Should hide button when edited content is the same': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.addFile('test.sol', { content: '123'})
.addFile('test.sol', { content: '123' })
.click('*[plugin="search"]').waitForElementVisible('*[id="search_input"]')
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', '123')
.setValue('*[id="search_input"]', '123').sendKeys('*[id="search_input"]', browser.Keys.ENTER)
.clearValue('*[id="search_replace"]')
.setValue('*[id="search_replace"]', '456').pause(1000)
.waitForElementVisible('*[data-id="replace-all-test.sol"]')
@ -173,7 +188,7 @@ module.exports = {
browser
.waitForElementVisible('*[id="search_input"]')
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', '123')
.setValue('*[id="search_input"]', '123').sendKeys('*[id="search_input"]', browser.Keys.ENTER)
.clearValue('*[id="search_replace"]')
.setValue('*[id="search_replace"]', 'replaced').pause(1000)
.waitForElementVisible('*[data-id="replace-all-test.sol"]')
@ -206,32 +221,12 @@ module.exports = {
})
.waitForElementNotPresent('*[data-id="undo-replace-test.sol"]')
},
'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, 28)
})
.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, 24)
})
},
'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) => {
.setValue('*[id="search_input"]', 'nodata').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(1000)
.elements('css selector', '.search_plugin_search_line', (res) => {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 0)
})
}

@ -33,6 +33,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')
@ -152,7 +153,7 @@ class AppComponent {
const storagePlugin = new StoragePlugin()
//----- search
// const search = new SearchPlugin()
const search = new SearchPlugin()
// ----------------- import content service ------------------------
const contentImport = new CompilerImports()
@ -233,6 +234,7 @@ class AppComponent {
hardhatProvider,
ganacheProvider,
this.walkthroughService,
search
])
// LAYOUT & SYSTEM VIEWS
@ -350,7 +352,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', 'intelligentScriptExecutor'])
await this.appManager.activatePlugin(['walkthrough','storage', 'search','intelligentScriptExecutor'])
this.appManager.on(
'filePanel',

@ -8,7 +8,7 @@ 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', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'hardhat-provider', 'intelligentScriptExecutor']
'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'hardhat-provider', 'intelligentScriptExecutor', 'search']
const dependentModules = ['git', 'hardhat', 'truffle', 'slither'] // module which shouldn't be manually activated (e.g git is activated by remixd)

@ -2,13 +2,19 @@ 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 => {
const { setExclude, cancelSearch, startSearch } = useContext(SearchContext)
const [excludeInput, setExcludeInput] = useState<string>('.*/**/*')
const change = async e => {
setExcludeInput(e.target.value)
clearTimeout(timeOutId.current)
timeOutId.current = setTimeout(() => setExclude(e.target.value), 500)
await cancelSearch()
}
const handleKeypress = async e => {
await setExclude(excludeInput)
if (e.charCode === 13 || e.keyCode === 13) {
startSearch()
}
}
useEffect(() => {
@ -21,9 +27,10 @@ export const Exclude = props => {
<label className='mt-2'>Files to exclude</label>
<input
id='search_exclude'
placeholder="Exclude ie .git/**/*"
placeholder="Exclude ie .git/**/* ( Enter to exclude )"
className="form-control"
onChange={change}
onKeyUp={handleKeypress}
onChange={async (e) => change(e)}
value={excludeInput}
></input>
</div>

@ -1,33 +1,49 @@
import React, { useContext, useRef } from 'react'
import React, { useContext, useEffect, useRef, useState } from 'react'
import { SearchContext } from '../context/context'
export const Find = () => {
const {
setFind,
cancelSearch,
startSearch,
state,
toggleCaseSensitive,
toggleMatchWholeWord,
toggleUseRegex
} = useContext(SearchContext)
const timeOutId = useRef(null)
const change = e => {
clearTimeout(timeOutId.current)
timeOutId.current = setTimeout(() => setFind(e.target.value), 500)
const [inputValue, setInputValue] = useState('')
const change = async e => {
setInputValue(e.target.value)
await cancelSearch()
}
const handleKeypress = async e => {
if (e.charCode === 13 || e.keyCode === 13) {
startSearch()
}
await setFind(inputValue)
}
useEffect(() => {
setInputValue('')
}, [state.workspace])
return (
<>
<div className="search_plugin_find-part">
<div className="search_plugin_search-input">
<input
id='search_input'
placeholder="Search"
id="search_input"
placeholder="Search ( Enter to search )"
className="form-control"
onChange={change}
value={inputValue}
onChange={async e => await change(e)}
onKeyUp={handleKeypress}
></input>
<div className="search_plugin_controls">
<div
data-id='search_case_sensitive'
data-id="search_case_sensitive"
title="Match Case"
className={`monaco-custom-checkbox codicon codicon-case-sensitive ${
state.casesensitive ? 'checked' : ''
@ -41,7 +57,7 @@ export const Find = () => {
}}
></div>
<div
data-id='search_whole_word'
data-id="search_whole_word"
title="Match Whole Word"
className={`monaco-custom-checkbox codicon codicon-whole-word ${
state.matchWord ? 'checked' : ''
@ -55,7 +71,7 @@ export const Find = () => {
}}
></div>
<div
data-id='search_use_regex'
data-id="search_use_regex"
title="Use Regular Expression"
className={`monaco-custom-checkbox codicon codicon-regex ${
state.useRegExp ? 'checked' : ''

@ -1,15 +1,23 @@
import React, { useContext, useRef, useState } from 'react'
import React, { useContext, useEffect, 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 => {
const { setInclude, cancelSearch, startSearch } = useContext(SearchContext)
const [includeInput, setIncludeInput] = useState<string>('*.sol, *.js')
const change = async e => {
setIncludeInput(e.target.value)
clearTimeout(timeOutId.current)
timeOutId.current = setTimeout(() => setInclude(e.target.value), 500)
await cancelSearch()
}
const handleKeypress = async e => {
await setInclude(includeInput)
if (e.charCode === 13 || e.keyCode === 13) {
startSearch()
}
}
useEffect(() => {
setInclude(includeInput)
}, [])
return (
<>
@ -17,9 +25,10 @@ export const Include = props => {
<label className='mt-2'>Files to include</label>
<input
id='search_include'
placeholder="Include ie contracts/**/*.sol"
placeholder="Include ie *.sol ( Enter to include )"
className="form-control"
onChange={change}
onChange={async(e) => change(e)}
onKeyUp={handleKeypress}
value={includeInput}
></input>
</div>

@ -1,22 +1,19 @@
import React from 'react'
import React, { useContext } 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'
import { FindContainer } from './FindContainer'
import { Undo } from './Undo'
export const SearchTab = props => {
const plugin = props.plugin
const plugin = props.plugin
return (
return (
<>
<div className="search_plugin_search_tab px-2">
<div className="search_plugin_search_tab px-2 pb-4">
<SearchProvider plugin={plugin}>
<FindContainer></FindContainer>
<Include></Include>

@ -0,0 +1,13 @@
import React from "react"
import { useContext } from "react"
import { SearchContext } from "../context/context"
export const StopSearch = () => {
const { cancelSearch } = useContext(SearchContext)
const cancel = async () => {
await cancelSearch(false)
}
return (
<a className="badge badge-danger search_plugin_stop" onClick={async () => await cancel()}>stop</a>
)
}

@ -7,6 +7,7 @@ import { ResultSummary } from './ResultSummary'
interface ResultItemProps {
file: SearchResult
index: number
}
export const ResultItem = (props: ResultItemProps) => {
@ -17,6 +18,7 @@ export const ResultItem = (props: ResultItemProps) => {
const [lines, setLines] = useState<SearchResultLine[]>([])
const [toggleExpander, setToggleExpander] = useState<boolean>(false)
const reloadTimeOut = useRef(null)
const loadTimeout = useRef(null)
const subscribed = useRef(true)
const { modal } = useDialogDispatchers()
@ -26,8 +28,11 @@ export const ResultItem = (props: ResultItemProps) => {
useEffect(() => {
if (props.file.forceReload) {
console.log('force reload')
clearTimeout(reloadTimeOut.current)
reloadTimeOut.current = setTimeout(() => reload(), 1000)
clearTimeout(loadTimeout.current)
subscribed.current = true
reloadTimeOut.current = setTimeout(() => reload(0), 1000)
}
}, [props.file.forceReload])
@ -35,18 +40,25 @@ export const ResultItem = (props: ResultItemProps) => {
setToggleExpander(!toggleExpander)
}
useEffect(() => {
reload()
}, [state.find])
useEffect(() => {
subscribed.current = true
return () => {
updateCount(0, props.file.filename)
clearTimeout(reloadTimeOut.current)
clearTimeout(loadTimeout.current)
subscribed.current = false
}
}, [])
useEffect((): any => {
if(!state.run){
clearTimeout(reloadTimeOut.current)
clearTimeout(loadTimeout.current)
subscribed.current = false
} else {
subscribed.current = true
}
},[state.run])
const confirmReplace = async () => {
setLoading(true)
try {
@ -64,7 +76,8 @@ export const ResultItem = (props: ResultItemProps) => {
}
}
const reload = () => {
const doLoad = () => {
if(!subscribed.current) return
findText(props.file.filename).then(res => {
if (subscribed.current) {
setLines(res)
@ -78,9 +91,15 @@ export const ResultItem = (props: ResultItemProps) => {
setLoading(false)
disableForceReload(props.file.filename)
}
}).catch((e) => {
console.error(e)
})
}
const reload = (time?: number) => {
loadTimeout.current = setTimeout(doLoad, 150 * (time | props.index))
}
return (
<>
{lines && lines.length ? (
@ -110,13 +129,12 @@ export const ResultItem = (props: ResultItemProps) => {
</div>
:null}
{lines.map((line, index) => (
index < state.maxLines ?
<ResultSummary
setLoading={setLoading}
key={index}
searchResult={props.file}
line={line}
/>: null
/>
))}
</div>
) : null}

@ -1,16 +1,32 @@
import React, { useContext, useEffect } from 'react'
import { SearchContext } from '../../context/context'
import { StopSearch } from '../StopSearch'
import { ResultItem } from './ResultItem'
export const Results = () => {
const { state } = useContext(SearchContext)
return (
<div data-id='search_results' className='mt-2'>
{state.find ? <div className='search_plugin_result_count_number badge badge-pill badge-secondary'>showing {state.count} results {state.fileCount} in files</div>: null}
{state.find && state.clipped? <div className='alert alert-warning mt-1'>The result set only shows a subset of all matches<br></br>Please narrow down your search.</div>: null}
<div data-id="search_results" className="mt-2">
<div className="search_plugin_search_indicator py-1">
{state.searching && !state.clipped ? <StopSearch></StopSearch> : null} {state.searching && !state.clipped
? `searching in ${state.searching}`
: null}<br></br>
</div>
{state.find && !state.clipped ? (
<div className="search_plugin_result_count_number badge badge-pill badge-secondary">
showing {state.count} results {state.fileCount} in files
</div>
) : null}
{state.find && state.clipped ? (
<div className="alert alert-warning mt-1">
Too many resuls to display...<br></br>Please narrow down your search.
</div>
) : null}
{state.searchResults &&
state.searchResults.map((result, index) => {
return index <state.maxFiles? <ResultItem key={index} file={result} />: null
return <ResultItem index={index} key={index} file={result} />
})}
</div>
)

@ -1,4 +1,4 @@
import React, { useEffect, useRef } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import { createContext, useReducer } from 'react'
import {
findLinesInStringWithMatch,
@ -31,7 +31,10 @@ export interface SearchingStateInterface {
setSearchResults: (value: SearchResult[]) => void
findText: (path: string) => Promise<SearchResultLine[]>
hightLightInPath: (result: SearchResult, line: SearchResultLineLine) => void
replaceText: (result: SearchResult, line: SearchResultLineLine) => Promise<void>
replaceText: (
result: SearchResult,
line: SearchResultLineLine
) => Promise<void>
reloadFile: (file: string) => void
toggleCaseSensitive: () => void
toggleMatchWholeWord: () => void
@ -42,6 +45,8 @@ export interface SearchingStateInterface {
replaceAllInFile: (result: SearchResult) => Promise<void>
undoReplace: (buffer: undoBufferRecord) => Promise<void>
clearUndo: () => void
cancelSearch: (clearResults?:boolean) => Promise<void>
startSearch: () => void
}
export const SearchContext = createContext<SearchingStateInterface>(null)
@ -53,11 +58,12 @@ export const SearchProvider = ({
plugin = undefined
} = {}) => {
const [state, dispatch] = useReducer(reducer, initialState)
const reloadTimeOut = useRef(null)
const [files, setFiles] = useState([])
const clearSearchingTimeout = useRef(null)
const value = {
state,
setFind: (value: string) => {
plugin.cancel('fileManager')
dispatch({
type: 'SET_FIND',
payload: value
@ -165,14 +171,45 @@ export const SearchProvider = ({
payload: { count, file }
})
},
setSearching(file: string) {
dispatch({
type: 'SET_SEARCHING',
payload: file
})
},
startSearch: () => {
dispatch({
type: 'START_SEARCH',
payload: undefined
})
},
setRun(value: boolean) {
dispatch({
type: 'SET_RUN',
payload: value
})
},
findText: async (path: string) => {
if (!plugin) return
try {
if (state.find.length < 1) return
value.setSearching(path)
const text = await plugin.call('fileManager', 'readFile', path)
const result: SearchResultLine[] = findLinesInStringWithMatch(text, createRegExFromFind())
const result: SearchResultLine[] = findLinesInStringWithMatch(
text,
createRegExFromFind()
)
clearTimeout(clearSearchingTimeout.current)
clearSearchingTimeout.current = setTimeout(() => value.setSearching(null), 500)
return result
} catch (e) { }
} catch (e) {
console.log(e)
value.setSearching(null)
// do nothing
}
},
hightLightInPath: async (
result: SearchResult,
@ -180,7 +217,14 @@ export const SearchProvider = ({
) => {
await plugin.call('editor', 'discardHighlight')
await plugin.call('editor', 'highlight', line.position, result.path)
await plugin.call('editor', 'revealRange', line.position.start.line, line.position.start.column, line.position.end.line, line.position.end.column)
await plugin.call(
'editor',
'revealRange',
line.position.start.line,
line.position.start.column,
line.position.end.line,
line.position.end.column
)
},
replaceText: async (result: SearchResult, line: SearchResultLineLine) => {
try {
@ -192,12 +236,7 @@ export const SearchProvider = ({
result.path
)
const replaced = replaceTextInLine(content, line, state.replace)
await plugin.call(
'fileManager',
'setFile',
result.path,
replaced
)
await plugin.call('fileManager', 'setFile', result.path, replaced)
setUndoState(content, replaced, result.path)
} catch (e) {
throw new Error(e)
@ -205,26 +244,17 @@ export const SearchProvider = ({
},
replaceAllInFile: async (result: SearchResult) => {
await plugin.call('editor', 'discardHighlight')
const content = await plugin.call(
'fileManager',
'readFile',
result.path
)
const replaced = replaceAllInFile(content, createRegExFromFind(), state.replace)
await plugin.call(
'fileManager',
'setFile',
result.path,
replaced
)
await plugin.call(
'fileManager',
'open',
result.path
const content = await plugin.call('fileManager', 'readFile', result.path)
const replaced = replaceAllInFile(
content,
createRegExFromFind(),
state.replace
)
await plugin.call('fileManager', 'setFile', result.path, replaced)
await plugin.call('fileManager', 'open', result.path)
setUndoState(content, replaced, result.path)
},
setUndoEnabled: (path:string, workspace: string, content: string) => {
setUndoEnabled: (path: string, workspace: string, content: string) => {
dispatch({
type: 'SET_UNDO_ENABLED',
payload: {
@ -235,11 +265,7 @@ export const SearchProvider = ({
})
},
undoReplace: async (buffer: undoBufferRecord) => {
const content = await plugin.call(
'fileManager',
'readFile',
buffer.path
)
const content = await plugin.call('fileManager', 'readFile', buffer.path)
if (buffer.newContent !== content) {
throw new Error('Can not undo replace, file has been changed.')
}
@ -249,40 +275,73 @@ export const SearchProvider = ({
buffer.path,
buffer.oldContent
)
await plugin.call(
'fileManager',
'open',
buffer.path
)
await plugin.call('fileManager', 'open', buffer.path)
},
clearUndo: () => {
dispatch ({
dispatch({
type: 'CLEAR_UNDO',
payload: undefined
})
}
}
},
clearStats: () => {
plugin.call('editor', 'discardHighlight')
dispatch({
type: 'CLEAR_STATS',
payload: undefined
})
},
cancelSearch: async (clearResults = true) => {
plugin.cancel('fileManager')
if(clearResults) value.clearStats()
value.setRun(false)
},
setClipped: (value: boolean) => {
dispatch({
type: 'SET_CLIPPED',
payload: value
})
}
}
const reloadStateForFile = async (file: string) => {
await value.reloadFile(file)
}
useEffect(() => {
plugin.on('filePanel', 'setWorkspace', async (workspace) => {
plugin.on('filePanel', 'setWorkspace', async workspace => {
value.setSearchResults(null)
value.clearUndo()
value.setCurrentWorkspace(workspace.name)
setFiles(await getDirectory('/', plugin))
})
plugin.on('fileManager', 'fileSaved', async file => {
await reloadStateForFile(file)
await checkUndoState(file)
})
plugin.on('fileManager', 'rootFolderChanged', async file => {
const workspace = await plugin.call('filePanel', 'getCurrentWorkspace')
if(workspace) value.setCurrentWorkspace(workspace.name)
setFiles(await getDirectory('/', plugin))
})
plugin.on('fileManager', 'fileAdded', async file => {
setFiles(await getDirectory('/', plugin))
await reloadStateForFile(file)
})
plugin.on('fileManager', 'currentFileChanged', async file => {
value.setCurrentFile(file)
await checkUndoState(file)
})
async function fetchWorkspace() {
const workspace = await plugin.call('filePanel', 'getCurrentWorkspace')
if(workspace) value.setCurrentWorkspace(workspace.name)
setFiles(await getDirectory('/', plugin))
}
fetchWorkspace()
return () => {
plugin.off('fileManager', 'fileChanged')
@ -296,7 +355,8 @@ export const SearchProvider = ({
paths.split(',').forEach(path => {
path = path.trim()
if (path.startsWith('*.')) path = path.replace(/(\*\.)/g, '**/*.')
if (path.endsWith('/*') && !path.endsWith('/**/*')) path = path.replace(/(\*)/g, '**/*.*')
if (path.endsWith('/*') && !path.endsWith('/**/*'))
path = path.replace(/(\*)/g, '**/*.*')
results.push(path)
})
return results
@ -305,11 +365,7 @@ export const SearchProvider = ({
const checkUndoState = async (path: string) => {
if (!plugin) return
try {
const content = await plugin.call(
'fileManager',
'readFile',
path
)
const content = await plugin.call('fileManager', 'readFile', path)
const workspace = await plugin.call('filePanel', 'getCurrentWorkspace')
value.setUndoEnabled(path, workspace.name, content)
} catch (e) {
@ -317,7 +373,11 @@ export const SearchProvider = ({
}
}
const setUndoState = async (oldContent: string, newContent: string, path: string) => {
const setUndoState = async (
oldContent: string,
newContent: string,
path: string
) => {
const workspace = await plugin.call('filePanel', 'getCurrentWorkspace')
const undo = {
oldContent,
@ -341,10 +401,17 @@ export const SearchProvider = ({
return re
}
useEffect(() => {
if(state.count>500) {
value.setClipped(true)
value.cancelSearch(false)
}
}, [state.count])
useEffect(() => {
if (state.find) {
(async () => {
const files = await getDirectory('/', plugin)
try {
const pathFilter: any = {}
if (state.include) {
pathFilter.include = setGlobalExpression(state.include)
@ -352,7 +419,9 @@ export const SearchProvider = ({
if (state.exclude) {
pathFilter.exclude = setGlobalExpression(state.exclude)
}
const filteredFiles = files.filter(filePathFilter(pathFilter)).map(file => {
const filteredFiles = files
.filter(filePathFilter(pathFilter))
.map(file => {
const r: SearchResult = {
filename: file,
lines: [],
@ -364,6 +433,9 @@ export const SearchProvider = ({
return r
})
value.setSearchResults(filteredFiles)
} catch (e) {
console.log(e)
}
})()
}
}, [state.timeStamp])

@ -2,12 +2,17 @@ import { Action, SearchingInitialState, SearchState, undoBufferRecord } from "..
export const SearchReducer = (state: SearchState = SearchingInitialState, action: Action) => {
switch (action.type) {
case 'SET_FIND':
case 'START_SEARCH':
return {
...state,
find: action.payload,
timeStamp: Date.now()
}
case 'SET_FIND':
return {
...state,
searchResults: null,
find: action.payload
}
case 'SET_REPLACE':
return {
@ -25,21 +30,20 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
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
count: 0,
run: true
}
case 'SET_UNDO_ENABLED':
if(state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`]){
@ -64,6 +68,21 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
...state,
}
}
case 'CLEAR_STATS':
return {
...state,
count: 0,
fileCount: 0,
searchResults: null,
searching: null
}
case 'SET_SEARCHING':
return {
...state,
searching: action.payload,
}
case 'CLEAR_UNDO': {
state.undoBuffer = []
return {
@ -75,18 +94,13 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
const findFile = state.searchResults.find(file => file.filename === action.payload.file)
let count = 0
let fileCount = 0
let clipped = false
const clipped = false
if (findFile) {
findFile.count = action.payload.count
}
state.searchResults.forEach(file => {
if (file.count) {
if(file.count > state.maxLines) {
clipped = true
count += state.maxLines
}else{
count += file.count
}
fileCount++
}
})
@ -99,6 +113,17 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
} else {
return state
}
case 'SET_CLIPPED':
return {
...state,
clipped: action.payload
}
case 'SET_RUN':
return {
...state,
run: action.payload
}
case 'TOGGLE_CASE_SENSITIVE':
return {
...state,

@ -134,3 +134,11 @@
text-overflow: ellipsis;
overflow: hidden;
}
.search_plugin_search_indicator{
white-space: pre;
text-overflow: ellipsis;
overflow: hidden;
}
.search_plugin_stop{
cursor: pointer;
}

@ -63,7 +63,9 @@ export interface SearchState {
clipped: boolean,
undoBuffer: Record<string, undoBufferRecord>[],
currentFile: string,
workspace: string
workspace: string,
searching: string | null,
run: boolean,
}
export const SearchingInitialState: SearchState = {
@ -85,5 +87,7 @@ export const SearchingInitialState: SearchState = {
clipped: false,
undoBuffer: null,
currentFile: '',
workspace: ''
workspace: '',
searching: null,
run: false,
}

@ -48,7 +48,7 @@ export const FileLabel = (props: FileLabelProps) => {
return (
<div
className='remixui_items d-inline-block w-100'
className='remixui_items d-inline-block w-100 text-break'
ref={isEditable ? labelRef : null}
suppressContentEditableWarning={true}
contentEditable={isEditable}

Loading…
Cancel
Save