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. 385
      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. 27
      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. 212
      libs/remix-ui/search/src/lib/context/context.tsx
  12. 49
      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,235 +4,230 @@ import { NightwatchBrowser } from 'nightwatch'
import init from '../helpers/init' import init from '../helpers/init'
module.exports = { module.exports = {
'@disabled': true,
before: function (browser: NightwatchBrowser, done: VoidFunction) { before: function (browser: NightwatchBrowser, done: VoidFunction) {
init(browser, done, 'http://127.0.0.1:8080', true) init(browser, done, 'http://127.0.0.1:8080', true)
}, },
'Should find text': function (browser: NightwatchBrowser) { 'Should find text': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]') browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.click('*[plugin="search"]').waitForElementVisible('*[id="search_input"]') .click('*[plugin="search"]').waitForElementVisible('*[id="search_input"]')
.setValue('*[id="search_input"]', 'read').pause(1000) .waitForElementVisible('*[id="search_include"]')
.waitForElementContainsText('*[data-id="search_results"]', '3_BALLOT.SOL', 60000) .setValue('*[id="search_include"]', ', *.txt').pause(2000)
.waitForElementContainsText('*[data-id="search_results"]', 'contracts', 60000) .setValue('*[id="search_input"]', 'read').sendKeys('*[id="search_input"]', browser.Keys.ENTER)
.waitForElementContainsText('*[data-id="search_results"]', 'README.TXT', 60000) .pause(1000)
.waitForElementContainsText('*[data-id="search_results"]', 'file must') .waitForElementContainsText('*[data-id="search_results"]', '3_BALLOT.SOL', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'be compiled') .waitForElementContainsText('*[data-id="search_results"]', 'contracts', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'that person al') .waitForElementContainsText('*[data-id="search_results"]', 'README.TXT', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'sender.voted') .waitForElementContainsText('*[data-id="search_results"]', 'file must')
.waitForElementContainsText('*[data-id="search_results"]', 'read') .waitForElementContainsText('*[data-id="search_results"]', 'be compiled')
.elements('css selector','.search_plugin_search_line', (res) => { .waitForElementContainsText('*[data-id="search_results"]', 'that person al')
Array.isArray(res.value) && browser.assert.equal(res.value.length, 6) .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 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) { 'Should find regex': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="search_use_regex"]').click('*[data-id="search_use_regex"]') .waitForElementVisible('*[data-id="search_use_regex"]').click('*[data-id="search_use_regex"]')
.waitForElementVisible('*[id="search_input"]') .waitForElementVisible('*[id="search_input"]')
.clearValue('*[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"]', '3_BALLOT.SOL', 60000)
.waitForElementContainsText('*[data-id="search_results"]', '2_OWNER.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"]', '1_STORAGE.SOL', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'BALLOT_TEST.SOL', 60000) .waitForElementContainsText('*[data-id="search_results"]', 'BALLOT_TEST.SOL', 60000)
.waitForElementContainsText('*[data-id="search_results"]', 'tests', 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) Array.isArray(res.value) && browser.assert.equal(res.value.length, 4)
}) })
}, },
'Should find matchcase': function (browser: NightwatchBrowser) { 'Should find matchcase': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="search_use_regex"]').click('*[data-id="search_use_regex"]') .waitForElementVisible('*[data-id="search_use_regex"]').click('*[data-id="search_use_regex"]')
.waitForElementVisible('*[data-id="search_case_sensitive"]').click('*[data-id="search_case_sensitive"]') .waitForElementVisible('*[data-id="search_case_sensitive"]').click('*[data-id="search_case_sensitive"]').pause(4000)
.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, 0) Array.isArray(res.value) && browser.assert.equal(res.value.length, 0)
}) })
.clearValue('*[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).pause(3000)
.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, 9) 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"]', 'STORAGE.TEST.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) { 'Should find matchword': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="search_case_sensitive"]').click('*[data-id="search_case_sensitive"]') .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"]') .clearValue('*[id="search_input"]')
.setValue('*[id="search_input"]', 'contract').pause(1000) .setValue('*[id="search_input"]', 'contract').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(4000)
.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, 29) Array.isArray(res.value) && browser.assert.equal(res.value.length, 13)
}) })
}, },
'Should replace text': function (browser: NightwatchBrowser) { 'Should replace text': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[data-id="toggle_replace"]').click('*[data-id="toggle_replace"]') .waitForElementVisible('*[data-id="toggle_replace"]').click('*[data-id="toggle_replace"]')
.waitForElementVisible('*[id="search_replace"]') .waitForElementVisible('*[id="search_replace"]')
.setValue('*[id="search_replace"]', 'replacing').pause(1000) .setValue('*[id="search_replace"]', 'replacing').sendKeys('*[id="search_replace"]', browser.Keys.ENTER).pause(1000)
.waitForElementVisible('*[data-id="contracts/2_Owner.sol-30-71"]') .waitForElementVisible('*[data-id="contracts/2_Owner.sol-33-71"]')
.moveToElement('*[data-id="contracts/2_Owner.sol-30-71"]', 10, 10) .moveToElement('*[data-id="contracts/2_Owner.sol-33-71"]', 10, 10)
.waitForElementVisible('*[data-id="replace-contracts/2_Owner.sol-30-71"]') .waitForElementVisible('*[data-id="replace-contracts/2_Owner.sol-33-71"]')
.click('*[data-id="replace-contracts/2_Owner.sol-30-71"]').pause(2000). .click('*[data-id="replace-contracts/2_Owner.sol-33-71"]').pause(2000).
modalFooterOKClick('confirmreplace').pause(2000). modalFooterOKClick('confirmreplace').pause(2000).
getEditorValue((content) => { getEditorValue((content) => {
browser.assert.ok(content.includes('replacing deployer for a constructor'), 'should replace text ok') 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': function (browser: NightwatchBrowser) {
browser.click('*[data-id="confirm_replace_label"]').pause(500) browser.click('*[data-id="confirm_replace_label"]').pause(500)
.clearValue('*[id="search_input"]') .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) .setValue('*[id="search_replace"]', '2').pause(1000)
.waitForElementVisible('*[data-id="contracts/2_Owner.sol-30-71"]') .waitForElementVisible('*[data-id="contracts/2_Owner.sol-33-71"]')
.moveToElement('*[data-id="contracts/2_Owner.sol-30-71"]', 10, 10) .moveToElement('*[data-id="contracts/2_Owner.sol-33-71"]', 10, 10)
.waitForElementVisible('*[data-id="replace-contracts/2_Owner.sol-30-71"]') .waitForElementVisible('*[data-id="replace-contracts/2_Owner.sol-33-71"]')
.click('*[data-id="replace-contracts/2_Owner.sol-30-71"]').pause(2000). .click('*[data-id="replace-contracts/2_Owner.sol-33-71"]').pause(2000).
getEditorValue((content) => { getEditorValue((content) => {
browser.assert.ok(content.includes('replacing2 deployer for a constructor'), 'should replace text ok') browser.assert.ok(content.includes('replacing2 deployer for a constructor'), 'should replace text ok')
}) })
}, },
'Should replace all & undo': function (browser: NightwatchBrowser) { 'Should replace all & undo': function (browser: NightwatchBrowser) {
browser browser
.clearValue('*[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"]') .clearValue('*[id="search_replace"]')
.setValue('*[id="search_replace"]', '123test').pause(1000) .setValue('*[id="search_replace"]', '123test').pause(1000)
.waitForElementVisible('*[data-id="replace-all-contracts/1_Storage.sol"]') .waitForElementVisible('*[data-id="replace-all-contracts/1_Storage.sol"]')
.click('*[data-id="replace-all-contracts/1_Storage.sol"]').pause(2000) .click('*[data-id="replace-all-contracts/1_Storage.sol"]').pause(2000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.includes('contract 123test'), 'should replace text ok') browser.assert.ok(content.includes('contract 123test'), 'should replace text ok')
browser.assert.ok(content.includes('title 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"]') .waitForElementVisible('*[data-id="undo-replace-contracts/1_Storage.sol"]')
.click('*[data-id="undo-replace-contracts/1_Storage.sol"]').pause(2000) .click('*[data-id="undo-replace-contracts/1_Storage.sol"]').pause(2000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.includes('contract Storage'), 'should undo text ok') browser.assert.ok(content.includes('contract Storage'), 'should undo text ok')
browser.assert.ok(content.includes('title Storage'), 'should undo text ok') browser.assert.ok(content.includes('title Storage'), 'should undo text ok')
}) })
}, },
'Should replace all & undo & switch between files': function (browser: NightwatchBrowser) { 'Should replace all & undo & switch between files': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[id="search_input"]') browser.waitForElementVisible('*[id="search_input"]')
.clearValue('*[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"]') .clearValue('*[id="search_replace"]')
.setValue('*[id="search_replace"]', '123test').pause(1000) .setValue('*[id="search_replace"]', '123test').pause(1000)
.waitForElementVisible('*[data-id="replace-all-contracts/1_Storage.sol"]') .waitForElementVisible('*[data-id="replace-all-contracts/1_Storage.sol"]')
.click('*[data-id="replace-all-contracts/1_Storage.sol"]').pause(2000) .click('*[data-id="replace-all-contracts/1_Storage.sol"]').pause(2000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.includes('contract 123test'), 'should replace text ok') browser.assert.ok(content.includes('contract 123test'), 'should replace text ok')
browser.assert.ok(content.includes('title 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"]') .waitForElementVisible('*[data-id="undo-replace-contracts/1_Storage.sol"]')
.openFile('README.txt') .openFile('README.txt')
.click('*[plugin="search"]').pause(2000) .click('*[plugin="search"]').pause(2000)
.waitForElementNotPresent('*[data-id="undo-replace-contracts/1_Storage.sol"]') .waitForElementNotPresent('*[data-id="undo-replace-contracts/1_Storage.sol"]')
.waitForElementVisible('*[data-id="replace-all-README.txt"]') .waitForElementVisible('*[data-id="replace-all-README.txt"]')
.click('*[data-id="replace-all-README.txt"]').pause(2000) .click('*[data-id="replace-all-README.txt"]').pause(2000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.includes("123test' contract"), 'should replace text ok') browser.assert.ok(content.includes("123test' contract"), 'should replace text ok')
}) })
.waitForElementVisible('*[data-id="undo-replace-README.txt"]') .waitForElementVisible('*[data-id="undo-replace-README.txt"]')
.click('div[title="default_workspace/contracts/1_Storage.sol"]').pause(2000) .click('div[title="default_workspace/contracts/1_Storage.sol"]').pause(2000)
.waitForElementVisible('*[data-id="undo-replace-contracts/1_Storage.sol"]') .waitForElementVisible('*[data-id="undo-replace-contracts/1_Storage.sol"]')
.click('*[data-id="undo-replace-contracts/1_Storage.sol"]').pause(2000) .click('*[data-id="undo-replace-contracts/1_Storage.sol"]').pause(2000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.includes('contract Storage'), 'should undo text ok') browser.assert.ok(content.includes('contract Storage'), 'should undo text ok')
browser.assert.ok(content.includes('title Storage'), 'should undo text ok') browser.assert.ok(content.includes('title Storage'), 'should undo text ok')
}) })
.click('div[title="default_workspace/README.txt"]').pause(2000) .click('div[title="default_workspace/README.txt"]').pause(2000)
.waitForElementVisible('*[data-id="undo-replace-README.txt"]') .waitForElementVisible('*[data-id="undo-replace-README.txt"]')
.click('*[data-id="undo-replace-README.txt"]').pause(2000) .click('*[data-id="undo-replace-README.txt"]').pause(2000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.includes("Storage' contract"), 'should replace text ok') browser.assert.ok(content.includes("Storage' contract"), 'should replace text ok')
}) })
}, },
'Should hide button when edited content is the same': function (browser: NightwatchBrowser) { 'Should hide button when edited content is the same': function (browser: NightwatchBrowser) {
browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]') browser.waitForElementVisible('*[data-id="remixIdeSidePanel"]')
.addFile('test.sol', { content: '123'}) .addFile('test.sol', { content: '123' })
.click('*[plugin="search"]').waitForElementVisible('*[id="search_input"]') .click('*[plugin="search"]').waitForElementVisible('*[id="search_input"]')
.clearValue('*[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"]') .clearValue('*[id="search_replace"]')
.setValue('*[id="search_replace"]', '456').pause(1000) .setValue('*[id="search_replace"]', '456').pause(1000)
.waitForElementVisible('*[data-id="replace-all-test.sol"]') .waitForElementVisible('*[data-id="replace-all-test.sol"]')
.click('*[data-id="replace-all-test.sol"]').pause(2000) .click('*[data-id="replace-all-test.sol"]').pause(2000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.includes('456'), 'should replace text ok') browser.assert.ok(content.includes('456'), 'should replace text ok')
} }
) )
.setEditorValue('123') .setEditorValue('123')
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.includes('123'), 'should have text ok') browser.assert.ok(content.includes('123'), 'should have text ok')
} }
).pause(1000) ).pause(1000)
.waitForElementNotPresent('*[data-id="undo-replace-test.sol"]') .waitForElementNotPresent('*[data-id="undo-replace-test.sol"]')
}, },
'Should disable/enable button when edited content changed': function (browser: NightwatchBrowser) { 'Should disable/enable button when edited content changed': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[id="search_input"]') .waitForElementVisible('*[id="search_input"]')
.clearValue('*[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"]') .clearValue('*[id="search_replace"]')
.setValue('*[id="search_replace"]', 'replaced').pause(1000) .setValue('*[id="search_replace"]', 'replaced').pause(1000)
.waitForElementVisible('*[data-id="replace-all-test.sol"]') .waitForElementVisible('*[data-id="replace-all-test.sol"]')
.click('*[data-id="replace-all-test.sol"]').pause(2000) .click('*[data-id="replace-all-test.sol"]').pause(2000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.includes('replaced'), 'should replace text ok') browser.assert.ok(content.includes('replaced'), 'should replace text ok')
} }
) )
.setEditorValue('changed') .setEditorValue('changed')
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.includes('changed'), 'should have text ok') browser.assert.ok(content.includes('changed'), 'should have text ok')
} }
).pause(1000) ).pause(1000)
.waitForElementVisible('*[data-id="undo-replace-test.sol"]') .waitForElementVisible('*[data-id="undo-replace-test.sol"]')
.getAttribute('[data-id="undo-replace-test.sol"]', 'disabled', (result) => { .getAttribute('[data-id="undo-replace-test.sol"]', 'disabled', (result) => {
browser.assert.equal(result.value, 'true', 'should be disabled') browser.assert.equal(result.value, 'true', 'should be disabled')
}) })
.setEditorValue('replaced') .setEditorValue('replaced')
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.includes('replaced'), 'should have text ok') browser.assert.ok(content.includes('replaced'), 'should have text ok')
} }
).pause(1000) ).pause(1000)
.waitForElementVisible('*[data-id="undo-replace-test.sol"]') .waitForElementVisible('*[data-id="undo-replace-test.sol"]')
.getAttribute('[data-id="undo-replace-test.sol"]', 'disabled', (result) => { .getAttribute('[data-id="undo-replace-test.sol"]', 'disabled', (result) => {
browser.assert.equal(result.value, null, 'should not be disabled') browser.assert.equal(result.value, null, 'should not be disabled')
}) })
.click('*[data-id="undo-replace-test.sol"]').pause(2000) .click('*[data-id="undo-replace-test.sol"]').pause(2000)
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.includes('123'), 'should have text ok') browser.assert.ok(content.includes('123'), 'should have text ok')
}) })
.waitForElementNotPresent('*[data-id="undo-replace-test.sol"]') .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) { 'should clear search': function (browser: NightwatchBrowser) {
browser browser
.waitForElementVisible('*[id="search_input"]') .waitForElementVisible('*[id="search_input"]')
.setValue('*[id="search_input"]', 'nodata').pause(1000) .setValue('*[id="search_input"]', 'nodata').sendKeys('*[id="search_input"]', browser.Keys.ENTER).pause(1000)
.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, 0) 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') const remixLib = require('@remix-project/remix-lib')
import { QueryParams } from '@remix-project/remix-lib' import { QueryParams } from '@remix-project/remix-lib'
import { SearchPlugin } from './app/tabs/search'
const Storage = remixLib.Storage const Storage = remixLib.Storage
const RemixDProvider = require('./app/files/remixDProvider') const RemixDProvider = require('./app/files/remixDProvider')
@ -152,7 +153,7 @@ class AppComponent {
const storagePlugin = new StoragePlugin() const storagePlugin = new StoragePlugin()
//----- search //----- search
// const search = new SearchPlugin() const search = new SearchPlugin()
// ----------------- import content service ------------------------ // ----------------- import content service ------------------------
const contentImport = new CompilerImports() const contentImport = new CompilerImports()
@ -233,6 +234,7 @@ class AppComponent {
hardhatProvider, hardhatProvider,
ganacheProvider, ganacheProvider,
this.walkthroughService, this.walkthroughService,
search
]) ])
// LAYOUT & SYSTEM VIEWS // LAYOUT & SYSTEM VIEWS
@ -350,7 +352,7 @@ class AppComponent {
await this.appManager.activatePlugin(['settings', 'config']) await this.appManager.activatePlugin(['settings', 'config'])
await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler']) await this.appManager.activatePlugin(['hiddenPanel', 'pluginManager', 'contextualListener', 'terminal', 'blockchain', 'fetchAndCompile', 'contentImport', 'gistHandler'])
await this.appManager.activatePlugin(['settings']) await this.appManager.activatePlugin(['settings'])
await this.appManager.activatePlugin(['walkthrough','storage', 'intelligentScriptExecutor']) await this.appManager.activatePlugin(['walkthrough','storage', 'search','intelligentScriptExecutor'])
this.appManager.on( this.appManager.on(
'filePanel', 'filePanel',

@ -8,7 +8,7 @@ const requiredModules = [ // services + layout views + system views
'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme',
'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons', 'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity-logic', 'gistHandler', 'layout', '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) 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' import { SearchContext } from '../context/context'
export const Exclude = props => { export const Exclude = props => {
const { setExclude, state } = useContext(SearchContext) const { setExclude, cancelSearch, startSearch } = useContext(SearchContext)
const [excludeInput, setExcludeInput] = useState<string>('.git/**/*,.deps/**/*') const [excludeInput, setExcludeInput] = useState<string>('.*/**/*')
const timeOutId = useRef(null)
const change = e => { const change = async e => {
setExcludeInput(e.target.value) setExcludeInput(e.target.value)
clearTimeout(timeOutId.current) await cancelSearch()
timeOutId.current = setTimeout(() => setExclude(e.target.value), 500) }
const handleKeypress = async e => {
await setExclude(excludeInput)
if (e.charCode === 13 || e.keyCode === 13) {
startSearch()
}
} }
useEffect(() => { useEffect(() => {
@ -21,9 +27,10 @@ export const Exclude = props => {
<label className='mt-2'>Files to exclude</label> <label className='mt-2'>Files to exclude</label>
<input <input
id='search_exclude' id='search_exclude'
placeholder="Exclude ie .git/**/*" placeholder="Exclude ie .git/**/* ( Enter to exclude )"
className="form-control" className="form-control"
onChange={change} onKeyUp={handleKeypress}
onChange={async (e) => change(e)}
value={excludeInput} value={excludeInput}
></input> ></input>
</div> </div>

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

@ -1,30 +1,27 @@
import React from 'react' import React, { useContext } from 'react'
import { SearchProvider } from '../context/context' import { SearchProvider } from '../context/context'
import { Find } from './Find'
import { Results } from './results/Results' import { Results } from './results/Results'
import '../search.css' import '../search.css'
import { Include } from './Include' import { Include } from './Include'
import { Exclude } from './Exclude' import { Exclude } from './Exclude'
import { Replace } from './Replace'
import { OverWriteCheck } from './OverWriteCheck'
import { FindContainer } from './FindContainer' import { FindContainer } from './FindContainer'
import { Undo } from './Undo' import { Undo } from './Undo'
export const SearchTab = props => { 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}> <SearchProvider plugin={plugin}>
<FindContainer></FindContainer> <FindContainer></FindContainer>
<Include></Include> <Include></Include>
<Exclude></Exclude> <Exclude></Exclude>
<Undo></Undo> <Undo></Undo>
<Results></Results> <Results></Results>
</SearchProvider> </SearchProvider>
</div> </div>
</> </>
) )
} }

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

@ -1,16 +1,32 @@
import React, { useContext, useEffect } from 'react' import React, { useContext, useEffect } from 'react'
import { SearchContext } from '../../context/context' import { SearchContext } from '../../context/context'
import { StopSearch } from '../StopSearch'
import { ResultItem } from './ResultItem' import { ResultItem } from './ResultItem'
export const Results = () => { export const Results = () => {
const { state } = useContext(SearchContext) const { state } = useContext(SearchContext)
return ( return (
<div data-id='search_results' className='mt-2'> <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} <div className="search_plugin_search_indicator py-1">
{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} {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 &&
state.searchResults.map((result, index) => { state.searchResults.map((result, index) => {
return index <state.maxFiles? <ResultItem key={index} file={result} />: null return <ResultItem index={index} key={index} file={result} />
})} })}
</div> </div>
) )

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

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

@ -134,3 +134,11 @@
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; 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, clipped: boolean,
undoBuffer: Record<string, undoBufferRecord>[], undoBuffer: Record<string, undoBufferRecord>[],
currentFile: string, currentFile: string,
workspace: string workspace: string,
searching: string | null,
run: boolean,
} }
export const SearchingInitialState: SearchState = { export const SearchingInitialState: SearchState = {
@ -85,5 +87,7 @@ export const SearchingInitialState: SearchState = {
clipped: false, clipped: false,
undoBuffer: null, undoBuffer: null,
currentFile: '', currentFile: '',
workspace: '' workspace: '',
searching: null,
run: false,
} }

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

Loading…
Cancel
Save