pull/2170/head
filip mertens 3 years ago
parent d5ba20621f
commit 79ac970b69
  1. 2
      apps/remix-ide-e2e/src/tests/search.test.ts
  2. 16
      apps/remix-ide/src/app/editor/editor.js
  3. 2
      apps/remix-ide/src/app/tabs/search.tsx
  4. 37
      libs/remix-ui/editor/src/lib/actions/editor.ts
  5. 4
      libs/remix-ui/search/src/lib/components/Exclude.tsx
  6. 1
      libs/remix-ui/search/src/lib/components/Find.tsx
  7. 33
      libs/remix-ui/search/src/lib/components/FindContainer.tsx
  8. 4
      libs/remix-ui/search/src/lib/components/Include.tsx
  9. 36
      libs/remix-ui/search/src/lib/components/OverWriteCheck.tsx
  10. 2
      libs/remix-ui/search/src/lib/components/Replace.tsx
  11. 4
      libs/remix-ui/search/src/lib/components/Search.tsx
  12. 3
      libs/remix-ui/search/src/lib/components/results/ResultItem.tsx
  13. 7
      libs/remix-ui/search/src/lib/components/results/ResultSummary.tsx
  14. 8
      libs/remix-ui/search/src/lib/components/results/Results.tsx
  15. 10
      libs/remix-ui/search/src/lib/context/context.tsx
  16. 6
      libs/remix-ui/search/src/lib/reducers/Reducer.ts
  17. 17
      libs/remix-ui/search/src/lib/search.css
  18. 8
      libs/remix-ui/search/src/lib/types/index.ts

@ -67,6 +67,8 @@ module.exports = {
}, },
'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('*[id="search_replace"]')
.setValue('*[id="search_replace"]', 'replacing').pause(1000) .setValue('*[id="search_replace"]', 'replacing').pause(1000)
.waitForElementVisible('*[data-id="contracts/2_Owner.sol-30-71"]') .waitForElementVisible('*[data-id="contracts/2_Owner.sol-30-71"]')
.moveToElement('*[data-id="contracts/2_Owner.sol-30-71"]', 10, 10) .moveToElement('*[data-id="contracts/2_Owner.sol-30-71"]', 10, 10)

@ -12,7 +12,7 @@ const profile = {
name: 'editor', name: 'editor',
description: 'service - editor', description: 'service - editor',
version: packageJson.version, version: packageJson.version,
methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addAnnotation', 'gotoLine', 'getCursorPosition'] methods: ['highlight', 'discardHighlight', 'clearAnnotations', 'addAnnotation', 'gotoLine', 'revealRange', 'getCursorPosition']
} }
class Editor extends Plugin { class Editor extends Plugin {
@ -390,6 +390,20 @@ class Editor extends Plugin {
this.emit('revealLine', line + 1, col) this.emit('revealLine', line + 1, col)
} }
/**
* Reveals the range in the editor.
* @param {number} startLineNumber
* @param {number} startColumn
* @param {number} endLineNumber
* @param {number} endColumn
*/
revealRange (startLineNumber, startColumn, endLineNumber, endColumn) {
if (!this.activated) return
this.emit('focus')
console.log(startLineNumber, startColumn, endLineNumber, endColumn)
this.emit('revealRange', startLineNumber, startColumn, endLineNumber, endColumn)
}
/** /**
* Scrolls to a line. If center is true, it puts the line in middle of screen (or attempts to). * Scrolls to a line. If center is true, it puts the line in middle of screen (or attempts to).
* @param {number} line The line to scroll to * @param {number} line The line to scroll to

@ -4,7 +4,7 @@ import React from 'react' // eslint-disable-line
import { SearchTab } from '@remix-ui/search' import { SearchTab } from '@remix-ui/search'
const profile = { const profile = {
name: 'search', name: 'search',
displayName: 'Search', displayName: 'Search in files',
methods: [''], methods: [''],
events: [], events: [],
icon: 'assets/img/Search_Icon.svg', icon: 'assets/img/Search_Icon.svg',

@ -1,3 +1,5 @@
import { IRange } from "monaco-editor";
export interface Action { export interface Action {
type: string; type: string;
payload: Record<string, any> payload: Record<string, any>
@ -49,6 +51,27 @@ export const reducerActions = (models = initialState, action: Action) => {
editor.setPosition({ column, lineNumber: line }) editor.setPosition({ column, lineNumber: line })
return models return models
} }
case 'REVEAL_RANGE': {
if (!editor) return models
const range: IRange = {
startLineNumber: action.payload.startLineNumber +1,
startColumn: action.payload.startColumn,
endLineNumber: action.payload.endLineNumber + 1,
endColumn: action.payload.endColumn
}
// reset to start of line
if(action.payload.startColumn < 100){
editor.revealRange({
startLineNumber: range.startLineNumber,
startColumn: 1,
endLineNumber: range.endLineNumber,
endColumn: 1
})
}else{
editor.revealRangeInCenter(range)
}
return models
}
case 'FOCUS': { case 'FOCUS': {
if (!editor) return models if (!editor) return models
editor.focus() editor.focus()
@ -106,6 +129,20 @@ export const reducerListener = (plugin, dispatch, monaco, editor, events) => {
}) })
}) })
plugin.on('editor', 'revealRange', (startLineNumber, startColumn, endLineNumber, endColumn) => {
dispatch({
type: 'REVEAL_RANGE',
payload: {
startLineNumber,
startColumn,
endLineNumber,
endColumn
},
monaco,
editor
})
})
plugin.on('editor', 'focus', () => { plugin.on('editor', 'focus', () => {
dispatch({ dispatch({
type: 'FOCUS', type: 'FOCUS',

@ -17,8 +17,8 @@ export const Exclude = props => {
return ( return (
<> <>
<div className="search_plugin_find-part"> <div className="search_plugin_find-part pl-3">
<label>exclude</label> <label>files to exclude</label>
<input <input
id='search_exclude' id='search_exclude'
placeholder="Exclude ie .git/**/*" placeholder="Exclude ie .git/**/*"

@ -18,7 +18,6 @@ export const Find = () => {
return ( return (
<> <>
<div className="search_plugin_find-part"> <div className="search_plugin_find-part">
<label>search</label>
<div className="search_plugin_search-input"> <div className="search_plugin_search-input">
<input <input
id='search_input' id='search_input'

@ -0,0 +1,33 @@
import React, { useContext, useEffect, useState } from 'react'
import { SearchContext } from '../context/context'
import { Find } from './Find'
import { Replace } from './Replace'
export const FindContainer = props => {
const { setReplaceEnabled } = useContext(SearchContext)
const [expanded, setExpanded] = useState<boolean>(false)
const toggleExpand = () => setExpanded(!expanded)
useEffect(() => {
setReplaceEnabled(expanded)
}, [expanded])
return (
<div className="search_plugin_find_container">
<div
title="Toggle Replace"
data-id="toggle_replace"
className={`codicon codicon-find-${
expanded ? 'expanded' : 'collapsed'
} search_plugin_find_container_arrow`}
role="button"
onClick={toggleExpand}
aria-label="Toggle Replace"
aria-expanded="true"
aria-disabled="false"
></div>
<div className="search_plugin_find_container_internal">
<Find></Find>
{expanded ? <Replace></Replace> : null}
</div>
</div>
)
}

@ -13,8 +13,8 @@ export const Include = props => {
return ( return (
<> <>
<div className="search_plugin_find-part"> <div className="search_plugin_find-part pl-3">
<label>include</label> <label>files to include</label>
<input <input
id='search_include' id='search_include'
placeholder="Include ie contracts/**/*.sol" placeholder="Include ie contracts/**/*.sol"

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

@ -12,7 +12,7 @@ export const Replace = props => {
return ( return (
<> <>
<div className="search_plugin_find-part"> <div className="search_plugin_find-part">
<label>replace</label> <label>replace in files</label>
<input <input
id='search_replace' id='search_replace'
placeholder="Replace" placeholder="Replace"

@ -7,6 +7,7 @@ import { Include } from './Include'
import { Exclude } from './Exclude' import { Exclude } from './Exclude'
import { Replace } from './Replace' import { Replace } from './Replace'
import { OverWriteCheck } from './OverWriteCheck' import { OverWriteCheck } from './OverWriteCheck'
import { FindContainer } from './FindContainer'
export const SearchTab = props => { export const SearchTab = props => {
@ -16,8 +17,7 @@ return (
<> <>
<div className="search_plugin_search_tab pl-2 pr-2"> <div className="search_plugin_search_tab pl-2 pr-2">
<SearchProvider plugin={plugin}> <SearchProvider plugin={plugin}>
<Find></Find> <FindContainer></FindContainer>
<Replace></Replace>
<Include></Include> <Include></Include>
<Exclude></Exclude> <Exclude></Exclude>
<OverWriteCheck></OverWriteCheck> <OverWriteCheck></OverWriteCheck>

@ -86,12 +86,13 @@ export const ResultItem = (props: ResultItemProps) => {
{!toggleExpander && !loading ? ( {!toggleExpander && !loading ? (
<div className="p-1 search_plugin_wrap_summary"> <div className="p-1 search_plugin_wrap_summary">
{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}

@ -47,15 +47,16 @@ export const ResultSummary = (props: ResultSummaryProps) => {
className='search_plugin_search_line pb-1' className='search_plugin_search_line pb-1'
> >
<div className='search_plugin_summary_left'>{lineItem.left.substring(lineItem.left.length - 20).trimStart()}</div> <div className='search_plugin_summary_left'>{lineItem.left.substring(lineItem.left.length - 20).trimStart()}</div>
<mark className={`search_plugin_summary_center ${state.replace? 'search_plugin_replace_strike':''}`}>{lineItem.center}</mark> <mark className={`search_plugin_summary_center ${state.replace && state.replaceEnabled? 'search_plugin_replace_strike':''}`}>{lineItem.center}</mark>
{state.replace? <mark className='search_plugin_replacement'>{state.replace}</mark>:<></>} {state.replace && state.replaceEnabled? <mark className='search_plugin_replacement'>{state.replace}</mark>:<></>}
<div className='search_plugin_summary_right'>{lineItem.right.substring(0, 100)}</div> <div className='search_plugin_summary_right'>{lineItem.right.substring(0, 100)}</div>
</div> </div>
{state.replaceEnabled?
<div className='search_plugin_search_control'> <div className='search_plugin_search_control'>
<div title="Replace" data-id={`replace-${props.searchResult.filename}-${lineItem.position.start.line}-${lineItem.position.start.column}`} onClick={async () => { <div title="Replace" data-id={`replace-${props.searchResult.filename}-${lineItem.position.start.line}-${lineItem.position.start.column}`} onClick={async () => {
replace(lineItem) replace(lineItem)
}} className="codicon codicon-find-replace" role="button" aria-label="Replace" aria-disabled="false"></div> }} className="codicon codicon-find-replace" role="button" aria-label="Replace" aria-disabled="false"></div>
</div> </div>:null}
</div> </div>
))} ))}
</> </>

@ -5,13 +5,13 @@ 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'> <div data-id='search_results' className='mt-2'>
{state.find ? <div className='search_plugin_result_count_number badge badge-pill badge-secondary'>{state.count} results</div>: null} {state.find ? <div className='search_plugin_result_count_number badge badge-pill badge-secondary'>{state.count} results</div>: null}
{state.count < state.maxResults && state.searchResults && {state.find && state.count >= state.maxResults? <div className='alert alert-warning mt-1'>The result set only contains a subset of all matches<br></br>Please narrow down your search.</div>: null}
{state.searchResults &&
state.searchResults.map((result, index) => { state.searchResults.map((result, index) => {
return <ResultItem key={index} file={result} /> return index <state.maxFiles? <ResultItem key={index} file={result} />: null
})} })}
{state.find && state.count >= state.maxResults? <div className='alert alert-warning mt-1'>Too many results to display.<br></br>Please narrow your search.</div>: null}
</div> </div>
) )
} }

@ -20,6 +20,7 @@ export interface SearchingStateInterface {
state: SearchState state: SearchState
setFind: (value: string) => void setFind: (value: string) => void
setReplace: (value: string) => void setReplace: (value: string) => void
setReplaceEnabled: (value: boolean) => void
setInclude: (value: string) => void setInclude: (value: string) => void
setExclude: (value: string) => void setExclude: (value: string) => void
setCaseSensitive: (value: boolean) => void setCaseSensitive: (value: boolean) => void
@ -63,6 +64,12 @@ export const SearchProvider = ({
payload: value payload: value
}) })
}, },
setReplaceEnabled: (value: boolean) => {
dispatch({
type: 'SET_REPLACE_ENABLED',
payload: value
})
},
setInclude: (value: string) => { setInclude: (value: string) => {
dispatch({ dispatch({
type: 'SET_INCLUDE', type: 'SET_INCLUDE',
@ -144,7 +151,7 @@ export const SearchProvider = ({
findText: async (path: string) => { findText: async (path: string) => {
if (!plugin) return if (!plugin) return
try { try {
if (state.find.length < 3) return if (state.find.length < 1) return
const text = await plugin.call('fileManager', 'readFile', path) const text = await plugin.call('fileManager', 'readFile', path)
let flags = 'g' let flags = 'g'
let find = state.find let find = state.find
@ -162,6 +169,7 @@ 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)
}, },
replaceText: async (result: SearchResult, line: SearchResultLineLine) => { replaceText: async (result: SearchResult, line: SearchResultLineLine) => {
try { try {

@ -15,6 +15,12 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
replace: action.payload, replace: action.payload,
} }
case 'SET_REPLACE_ENABLED':
return {
...state,
replaceEnabled: action.payload,
}
case 'SET_INCLUDE': case 'SET_INCLUDE':
return { return {
...state, ...state,

@ -103,4 +103,21 @@
.search_plugin_search_tab .search_plugin_result_count_number { .search_plugin_search_tab .search_plugin_result_count_number {
font-size: x-small; font-size: x-small;
}
.search_plugin_find_container {
display: flex;
flex-direction: row;
}
.search_plugin_find_container_internal {
display: flex;
flex-direction: column;
flex-grow: 1;
}
.search_plugin_find_container_arrow {
display: flex !important;
align-items: center;
cursor: pointer !important;
} }

@ -39,6 +39,7 @@ export interface SearchState {
find: string, find: string,
searchResults: SearchResult[], searchResults: SearchResult[],
replace: string, replace: string,
replaceEnabled: boolean,
include: string, include: string,
exclude: string, exclude: string,
casesensitive: boolean, casesensitive: boolean,
@ -48,6 +49,8 @@ export interface SearchState {
timeStamp: number, timeStamp: number,
count: number, count: number,
maxResults: number maxResults: number
maxFiles: number,
maxLines: number
} }
export const SearchingInitialState: SearchState = { export const SearchingInitialState: SearchState = {
@ -55,6 +58,7 @@ export const SearchingInitialState: SearchState = {
replace: '', replace: '',
include: '', include: '',
exclude: '', exclude: '',
replaceEnabled: false,
searchResults: [], searchResults: [],
casesensitive: false, casesensitive: false,
matchWord: false, matchWord: false,
@ -62,5 +66,7 @@ export const SearchingInitialState: SearchState = {
replaceWithOutConfirmation: false, replaceWithOutConfirmation: false,
timeStamp: 0, timeStamp: 0,
count: 0, count: 0,
maxResults: 500 maxResults: 1500,
maxFiles: 100,
maxLines: 200
} }
Loading…
Cancel
Save