pull/5370/head
filip mertens 3 years ago
parent 1f255d2008
commit a8d46a0d43
  1. 131
      apps/remix-ide-e2e/src/tests/search.test.ts
  2. 2
      apps/remix-ide/src/app/tabs/search.tsx
  3. 77
      apps/remix-ide/src/assets/img/Search_Icon.svg
  4. BIN
      apps/remix-ide/src/assets/img/search_icon.webp
  5. 2
      libs/remix-ui/search/src/lib/components/OverWriteCheck.tsx
  6. 10
      libs/remix-ui/search/src/lib/components/Undo.tsx
  7. 6
      libs/remix-ui/search/src/lib/components/results/ResultItem.tsx
  8. 51
      libs/remix-ui/search/src/lib/context/context.tsx
  9. 24
      libs/remix-ui/search/src/lib/reducers/Reducer.ts
  10. 12
      libs/remix-ui/search/src/lib/search.css
  11. 13
      libs/remix-ui/search/src/lib/types/index.ts

@ -8,7 +8,7 @@ module.exports = {
before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', true)
},
'Should find text': function (browser: NightwatchBrowser) {
'Should find text #group3': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.click('*[plugin="search"]').waitForElementVisible('*[id="search_input"]')
.setValue('*[id="search_input"]', 'read').pause(1000)
@ -24,7 +24,7 @@ module.exports = {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 6)
})
},
'Should find regex': function (browser: NightwatchBrowser) {
'Should find regex #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="search_use_regex"]').click('*[data-id="search_use_regex"]')
.waitForElementVisible('*[id="search_input"]')
@ -39,7 +39,7 @@ module.exports = {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 4)
})
},
'Should find matchcase': function (browser: NightwatchBrowser) {
'Should find matchcase #group3': 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"]')
@ -55,7 +55,7 @@ module.exports = {
.waitForElementContainsText('*[data-id="search_results"]', 'DEPLOY_WEB3.JS', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'scripts', 60000)
},
'Should find matchword': function (browser: NightwatchBrowser) {
'Should find matchword #group3': 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"]')
@ -65,7 +65,7 @@ module.exports = {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 27)
})
},
'Should replace text': function (browser: NightwatchBrowser) {
'Should replace text #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[data-id="toggle_replace"]').click('*[data-id="toggle_replace"]')
.waitForElementVisible('*[id="search_replace"]')
@ -79,7 +79,7 @@ module.exports = {
browser.assert.ok(content.includes('replacing deployer for a constructor'), 'should replace text ok')
})
},
'Should replace text without confirmation': function (browser: NightwatchBrowser) {
'Should replace text without confirmation #group3': function (browser: NightwatchBrowser) {
browser.click('*[data-id="confirm_replace_label"]').pause(500)
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'replacing').pause(1000)
@ -92,7 +92,120 @@ module.exports = {
browser.assert.ok(content.includes('replacing2 deployer for a constructor'), 'should replace text ok')
})
},
'Should find text with include': function (browser: NightwatchBrowser) {
'Should replace all & undo #group3': function (browser: NightwatchBrowser) {
browser
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'storage')
.clearValue('*[id="search_replace"]')
.setValue('*[id="search_replace"]', '123test').pause(1000)
.waitForElementVisible('*[data-id="replace-all-contracts/1_Storage.sol"]')
.click('*[data-id="replace-all-contracts/1_Storage.sol"]').pause(2000)
.getEditorValue((content) => {
browser.assert.ok(content.includes('contract 123test'), 'should replace text ok')
browser.assert.ok(content.includes('title 123test'), 'should replace text ok')
})
.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')
})
},
'Should replace all & undo & switch between files #group3': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[id="search_input"]')
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'storage')
.clearValue('*[id="search_replace"]')
.setValue('*[id="search_replace"]', '123test').pause(1000)
.waitForElementVisible('*[data-id="replace-all-contracts/1_Storage.sol"]')
.click('*[data-id="replace-all-contracts/1_Storage.sol"]').pause(2000)
.getEditorValue((content) => {
browser.assert.ok(content.includes('contract 123test'), 'should replace text ok')
browser.assert.ok(content.includes('title 123test'), 'should replace text ok')
})
.waitForElementVisible('*[data-id="undo-replace-contracts/1_Storage.sol"]')
.openFile('README.txt')
.click('*[plugin="search"]').pause(2000)
.waitForElementNotPresent('*[data-id="undo-replace-contracts/1_Storage.sol"]')
.waitForElementVisible('*[data-id="replace-all-README.txt"]')
.click('*[data-id="replace-all-README.txt"]').pause(2000)
.getEditorValue((content) => {
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)
.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)
.waitForElementVisible('*[data-id="undo-replace-README.txt"]')
.click('*[data-id="undo-replace-README.txt"]').pause(2000)
.getEditorValue((content) => {
browser.assert.ok(content.includes("Storage' contract"), 'should replace text ok')
})
},
'Should hide button when edited content is the same #group3': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.addFile('test.sol', { content: '123'})
.click('*[plugin="search"]').waitForElementVisible('*[id="search_input"]')
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', '123')
.clearValue('*[id="search_replace"]')
.setValue('*[id="search_replace"]', '456').pause(1000)
.waitForElementVisible('*[data-id="replace-all-test.sol"]')
.click('*[data-id="replace-all-test.sol"]').pause(2000)
.getEditorValue((content) => {
browser.assert.ok(content.includes('456'), 'should replace text ok')
}
)
.setEditorValue('123')
.getEditorValue((content) => {
browser.assert.ok(content.includes('123'), 'should have text ok')
}
).pause(1000)
.waitForElementNotPresent('*[data-id="undo-replace-test.sol"]')
},
'Should disable/enable button when edited content changed #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[id="search_input"]')
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', '123')
.clearValue('*[id="search_replace"]')
.setValue('*[id="search_replace"]', 'replaced').pause(1000)
.waitForElementVisible('*[data-id="replace-all-test.sol"]')
.click('*[data-id="replace-all-test.sol"]').pause(2000)
.getEditorValue((content) => {
browser.assert.ok(content.includes('replaced'), 'should replace text ok')
}
)
.setEditorValue('changed')
.getEditorValue((content) => {
browser.assert.ok(content.includes('changed'), 'should have text ok')
}
).pause(1000)
.waitForElementVisible('*[data-id="undo-replace-test.sol"]')
.getAttribute('[data-id="undo-replace-test.sol"]', 'disabled', (result) => {
browser.assert.equal(result.value, 'true', 'should be disabled')
})
.setEditorValue('replaced')
.getEditorValue((content) => {
browser.assert.ok(content.includes('replaced'), 'should have text ok')
}
).pause(1000)
.waitForElementVisible('*[data-id="undo-replace-test.sol"]')
.getAttribute('[data-id="undo-replace-test.sol"]', 'disabled', (result) => {
browser.assert.equal(result.value, null, 'should not be disabled')
})
.click('*[data-id="undo-replace-test.sol"]').pause(2000)
.getEditorValue((content) => {
browser.assert.ok(content.includes('123'), 'should have text ok')
})
.waitForElementNotPresent('*[data-id="undo-replace-test.sol"]')
},
'Should find text with include #group3': function (browser: NightwatchBrowser) {
browser
.clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'contract').pause(1000)
@ -101,7 +214,7 @@ module.exports = {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 4)
})
},
'Should find text with exclude': function (browser: NightwatchBrowser) {
'Should find text with exclude #group3': function (browser: NightwatchBrowser) {
browser
.clearValue('*[id="search_include"]').pause(2000)
.setValue('*[id="search_include"]', '**').pause(2000)
@ -113,7 +226,7 @@ module.exports = {
Array.isArray(res.value) && browser.assert.equal(res.value.length, 22)
})
},
'should clear search': function (browser: NightwatchBrowser) {
'should clear search #group3': function (browser: NightwatchBrowser) {
browser
.waitForElementVisible('*[id="search_input"]')
.setValue('*[id="search_input"]', 'nodata').pause(1000)

@ -7,7 +7,7 @@ const profile = {
displayName: 'Search in files',
methods: [''],
events: [],
icon: 'assets/img/Search_Icon.svg',
icon: 'assets/img/search_icon.webp',
description: '',
kind: '',
location: 'sidePanel',

@ -1,77 +0,0 @@
<?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>

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

@ -11,7 +11,7 @@ export const OverWriteCheck = props => {
return (
<>
{state.replaceEnabled ? (
<div className="search_plugin_find-part pl-3">
<div className="search_plugin_find-part">
<div className="mb-2 remixui_nightlyBuilds custom-control custom-checkbox">
<input
className="mr-2 custom-control-input"

@ -2,6 +2,7 @@ import { useDialogDispatchers } from "@remix-ui/app"
import React from "react"
import { useContext } from "react"
import { SearchContext } from "../context/context"
import * as path from 'path'
export const Undo = () => {
const {
@ -10,20 +11,19 @@ export const Undo = () => {
} = useContext(SearchContext)
const { alert } = useDialogDispatchers()
const undo = async () => {
try{
await undoReplace(state.undoBuffer[0])
await undoReplace(state.undoBuffer[`${state.workspace}/${state.currentFile}`])
}catch(e){
alert({ id: 'undo_error', title: 'Cannot undo this change', message: e.message })
}
}
return (<>
{state.undoBuffer && state.undoBuffer.length > 0 ?
<button onClick={async() => await undo()} className="btn btn-secondary btn-block">
{state.undoBuffer && state.undoBuffer[`${state.workspace}/${state.currentFile}`] && state.undoBuffer[`${state.workspace}/${state.currentFile}`].visible ?
<button data-id={`undo-replace-${state.currentFile}`} disabled={!state.undoBuffer[`${state.workspace}/${state.currentFile}`].enabled} onClick={async() => await undo()} className="undo-button btn btn-secondary btn-block my-3">
<div className="fas fa-undo mr-2"></div>
Undo changes to {state.undoBuffer[0].path}
Undo changes to {path.basename(state.undoBuffer[`${state.workspace}/${state.currentFile}`].path)}
</button> : null}
</>)
}

@ -104,7 +104,11 @@ export const ResultItem = (props: ResultItemProps) => {
{loading ? <div className="loading">Loading...</div> : null}
{!toggleExpander && !loading ? (
<div className="search_plugin_wrap_summary">
{state.replaceEnabled? <div onClick={async() => replace()} className='btn btn-secondary btn-block mb-2 btn-sm'>Replace all</div>:null}
{state.replaceEnabled?
<div className="search_plugin_wrap_summary_replace">
<div data-id={`replace-all-${props.file.filename}`} onClick={async() => replace()} className='btn btn-secondary mb-2 btn-sm'>Replace all</div>
</div>
:null}
{lines.map((line, index) => (
index < state.maxLines ?
<ResultSummary

@ -147,6 +147,18 @@ export const SearchProvider = ({
payload: file
})
},
setCurrentFile: (file: string) => {
dispatch({
type: 'SET_CURRENT_FILE',
payload: file
})
},
setCurrentWorkspace: (workspace: any) => {
dispatch({
type: 'SET_CURRENT_WORKSPACE',
payload: workspace
})
},
updateCount: (count: number, file: string) => {
dispatch({
type: 'UPDATE_COUNT',
@ -212,6 +224,16 @@ export const SearchProvider = ({
)
setUndoState(content, replaced, result.path)
},
setUndoEnabled: (path:string, workspace: string, content: string) => {
dispatch({
type: 'SET_UNDO_ENABLED',
payload: {
path,
workspace,
content
}
})
},
undoReplace: async (buffer: undoBufferRecord) => {
const content = await plugin.call(
'fileManager',
@ -219,9 +241,7 @@ export const SearchProvider = ({
buffer.path
)
if (buffer.newContent !== content) {
value.clearUndo()
throw new Error('Can not undo replace, file has been changed.')
}
await plugin.call(
'fileManager',
@ -234,7 +254,6 @@ export const SearchProvider = ({
'open',
buffer.path
)
value.clearUndo()
},
clearUndo: () => {
dispatch ({
@ -251,13 +270,20 @@ export const SearchProvider = ({
}
useEffect(() => {
plugin.on('filePanel', 'setWorkspace', () => {
plugin.on('filePanel', 'setWorkspace', async (workspace) => {
value.setSearchResults(null)
value.clearUndo()
value.setCurrentWorkspace(workspace.name)
})
plugin.on('fileManager', 'fileSaved', async file => {
await reloadStateForFile(file)
await checkUndoState(file)
})
plugin.on('fileManager', 'currentFileChanged', async file => {
value.setCurrentFile(file)
await checkUndoState(file)
})
return () => {
plugin.off('fileManager', 'fileChanged')
plugin.off('filePanel', 'setWorkspace')
@ -276,13 +302,28 @@ export const SearchProvider = ({
return results
}
const checkUndoState = async (path: string) => {
if (!plugin) return
try {
const content = await plugin.call(
'fileManager',
'readFile',
path
)
const workspace = await plugin.call('filePanel', 'getCurrentWorkspace')
value.setUndoEnabled(path, workspace.name, content)
} catch (e) {
console.log(e)
}
}
const setUndoState = async (oldContent: string, newContent: string, path: string) => {
const workspace = await plugin.call('filePanel', 'getCurrentWorkspace')
const undo = {
oldContent,
newContent,
path,
workspace
workspace: workspace.name
}
dispatch({
type: 'SET_UNDO',

@ -41,15 +41,25 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
searchResults: action.payload,
count: 0
}
case 'SET_UNDO_ENABLED':
if(state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`]){
state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`].enabled = (action.payload.content === state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`].newContent)
state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`].visible = (action.payload.content !== state.undoBuffer[`${action.payload.workspace}/${action.payload.path}`].oldContent)
}
return {
...state,
}
case 'SET_UNDO': {
const undoState = {
newContent : action.payload.newContent,
oldContent: action.payload.oldContent,
path: action.payload.path,
workspace: action.payload.workspace,
timeStamp: Date.now()
timeStamp: Date.now(),
enabled: true,
visible: true
}
state.undoBuffer = [undoState]
state.undoBuffer[`${undoState.workspace}/${undoState.path}`] = undoState
return {
...state,
}
@ -120,6 +130,16 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
return {
...state,
}
case 'SET_CURRENT_FILE':
return {
...state,
currentFile: action.payload,
}
case 'SET_CURRENT_WORKSPACE':
return {
...state,
workspace: action.payload,
}
case 'RELOAD_FILE':
if (state.searchResults) {
const findFile = state.searchResults.find(file => file.filename === action.payload)

@ -121,4 +121,16 @@
display: flex !important;
align-items: center;
cursor: pointer !important;
}
.search_plugin_wrap_summary_replace {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.undo-button {
white-space: pre;
text-overflow: ellipsis;
overflow: hidden;
}

@ -40,9 +40,10 @@ export interface undoBufferRecord {
path: string,
newContent: string,
timeStamp: number,
oldContent: string
oldContent: string,
enabled: boolean,
visible: boolean
}
export interface SearchState {
find: string,
searchResults: SearchResult[],
@ -60,7 +61,9 @@ export interface SearchState {
maxFiles: number,
maxLines: number
clipped: boolean,
undoBuffer: undoBufferRecord[],
undoBuffer: Record<string, undoBufferRecord>[],
currentFile: string,
workspace: string
}
export const SearchingInitialState: SearchState = {
@ -80,5 +83,7 @@ export const SearchingInitialState: SearchState = {
maxFiles: 5000,
maxLines: 5000,
clipped: false,
undoBuffer: null
undoBuffer: null,
currentFile: '',
workspace: ''
}
Loading…
Cancel
Save