performance

pull/5370/head
filip mertens 3 years ago committed by yann300
parent 4e85142e35
commit e9260ecc06
  1. 13
      libs/remix-ui/search/src/lib/components/CancelSearch.tsx
  2. 9
      libs/remix-ui/search/src/lib/components/Exclude.tsx
  3. 7
      libs/remix-ui/search/src/lib/components/Include.tsx
  4. 28
      libs/remix-ui/search/src/lib/components/Search.tsx
  5. 21
      libs/remix-ui/search/src/lib/components/results/ResultItem.tsx
  6. 22
      libs/remix-ui/search/src/lib/components/results/Results.tsx
  7. 186
      libs/remix-ui/search/src/lib/context/context.tsx
  8. 30
      libs/remix-ui/search/src/lib/reducers/Reducer.ts
  9. 5
      libs/remix-ui/search/src/lib/search.css
  10. 6
      libs/remix-ui/search/src/lib/types/index.ts

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

@ -2,12 +2,13 @@ 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 } = useContext(SearchContext)
const [excludeInput, setExcludeInput] = useState<string>('.git/**/*,.deps/**/*') const [excludeInput, setExcludeInput] = useState<string>('.*/**/*')
const timeOutId = useRef(null) const timeOutId = useRef(null)
const change = e => { const change = async e => {
setExcludeInput(e.target.value) setExcludeInput(e.target.value)
clearTimeout(timeOutId.current) clearTimeout(timeOutId.current)
await cancelSearch()
timeOutId.current = setTimeout(() => setExclude(e.target.value), 500) timeOutId.current = setTimeout(() => setExclude(e.target.value), 500)
} }
@ -23,7 +24,7 @@ export const Exclude = props => {
id='search_exclude' id='search_exclude'
placeholder="Exclude ie .git/**/*" placeholder="Exclude ie .git/**/*"
className="form-control" className="form-control"
onChange={change} onChange={async(e) => change(e)}
value={excludeInput} value={excludeInput}
></input> ></input>
</div> </div>

@ -2,12 +2,13 @@ import React, { useContext, 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 } = useContext(SearchContext)
const [includeInput, setIncludeInput] = useState<string>('') const [includeInput, setIncludeInput] = useState<string>('')
const timeOutId = useRef(null) const timeOutId = useRef(null)
const change = e => { const change = async e => {
setIncludeInput(e.target.value) setIncludeInput(e.target.value)
clearTimeout(timeOutId.current) clearTimeout(timeOutId.current)
await cancelSearch()
timeOutId.current = setTimeout(() => setInclude(e.target.value), 500) timeOutId.current = setTimeout(() => setInclude(e.target.value), 500)
} }
@ -19,7 +20,7 @@ export const Include = props => {
id='search_include' id='search_include'
placeholder="Include ie contracts/**/*.sol" placeholder="Include ie contracts/**/*.sol"
className="form-control" className="form-control"
onChange={change} onChange={async(e) => change(e)}
value={includeInput} value={includeInput}
></input> ></input>
</div> </div>

@ -1,30 +1,28 @@
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'
import { CacncelSearch } from './CancelSearch'
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">
<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>
</> </>
) )
} }

@ -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()
@ -27,6 +29,7 @@ export const ResultItem = (props: ResultItemProps) => {
useEffect(() => { useEffect(() => {
if (props.file.forceReload) { if (props.file.forceReload) {
clearTimeout(reloadTimeOut.current) clearTimeout(reloadTimeOut.current)
clearTimeout(loadTimeout.current)
reloadTimeOut.current = setTimeout(() => reload(), 1000) reloadTimeOut.current = setTimeout(() => reload(), 1000)
} }
}, [props.file.forceReload]) }, [props.file.forceReload])
@ -35,14 +38,11 @@ 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
} }
}, []) }, [])
@ -64,7 +64,7 @@ export const ResultItem = (props: ResultItemProps) => {
} }
} }
const reload = () => { const doLoad = () => {
findText(props.file.filename).then(res => { findText(props.file.filename).then(res => {
if (subscribed.current) { if (subscribed.current) {
setLines(res) setLines(res)
@ -78,9 +78,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 = () => {
loadTimeout.current = setTimeout(doLoad, 150 * props.index)
}
return ( return (
<> <>
{lines && lines.length ? ( {lines && lines.length ? (
@ -110,13 +116,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}

@ -5,12 +5,26 @@ 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
? `searching in ${state.searching}`
: null}
</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
@ -39,9 +42,10 @@ export interface SearchingStateInterface {
setReplaceWithoutConfirmation: (value: boolean) => void setReplaceWithoutConfirmation: (value: boolean) => void
disableForceReload: (file: string) => void disableForceReload: (file: string) => void
updateCount: (count: number, file: string) => void updateCount: (count: number, file: string) => void
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: () => Promise<void>
} }
export const SearchContext = createContext<SearchingStateInterface>(null) export const SearchContext = createContext<SearchingStateInterface>(null)
@ -53,11 +57,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 +170,30 @@ export const SearchProvider = ({
payload: { count, file } payload: { count, file }
}) })
}, },
setSearching(file: string) {
dispatch({
type: 'SET_SEARCHING',
payload: file
})
},
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) {
value.setSearching(null)
// do nothing
}
}, },
hightLightInPath: async ( hightLightInPath: async (
result: SearchResult, result: SearchResult,
@ -180,7 +201,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 +220,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 +228,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 +249,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,41 +259,58 @@ 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: () => {
dispatch({
type: 'CLEAR_STATS',
payload: undefined
})
},
cancelSearch: async () => {
plugin.cancel('fileManager')
value.clearStats()
},
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', 'fileAdded', async file => {
setFiles(await getDirectory('/', plugin))
})
plugin.on('fileManager', 'currentFileChanged', async file => { plugin.on('fileManager', 'currentFileChanged', async file => {
value.setCurrentFile(file) value.setCurrentFile(file)
await checkUndoState(file) await checkUndoState(file)
}) })
return () => { return () => {
plugin.off('fileManager', 'fileChanged') plugin.off('fileManager', 'fileChanged')
plugin.off('filePanel', 'setWorkspace') plugin.off('filePanel', 'setWorkspace')
@ -296,7 +323,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,19 +333,19 @@ 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) {
console.log(e) console.log(e)
} }
} }
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 +369,41 @@ export const SearchProvider = ({
return re return re
} }
useEffect(() => {
if(state.count>500) {
value.setClipped(true)
value.cancelSearch()
}
}, [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) { if (state.exclude) {
pathFilter.exclude = setGlobalExpression(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 const filteredFiles = files
}) .filter(filePathFilter(pathFilter))
value.setSearchResults(filteredFiles) .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])

@ -5,6 +5,7 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
case 'SET_FIND': case 'SET_FIND':
return { return {
...state, ...state,
searchResults: null,
find: action.payload, find: action.payload,
timeStamp: Date.now() timeStamp: Date.now()
} }
@ -64,6 +65,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 {
@ -81,12 +97,7 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
} }
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 +110,13 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
} else { } else {
return state return state
} }
case 'SET_CLIPPED':
return {
...state,
clipped: action.payload
}
case 'TOGGLE_CASE_SENSITIVE': case 'TOGGLE_CASE_SENSITIVE':
return { return {
...state, ...state,

@ -133,4 +133,9 @@
white-space: pre; white-space: pre;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
}
.search_plugin_search_indicator{
white-space: pre;
text-overflow: ellipsis;
overflow: hidden;
} }

@ -63,7 +63,8 @@ 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,
} }
export const SearchingInitialState: SearchState = { export const SearchingInitialState: SearchState = {
@ -85,5 +86,6 @@ export const SearchingInitialState: SearchState = {
clipped: false, clipped: false,
undoBuffer: null, undoBuffer: null,
currentFile: '', currentFile: '',
workspace: '' workspace: '',
searching: null
} }
Loading…
Cancel
Save