diff --git a/libs/remix-ui/search/src/lib/components/CancelSearch.tsx b/libs/remix-ui/search/src/lib/components/CancelSearch.tsx new file mode 100644 index 0000000000..5461c5ebec --- /dev/null +++ b/libs/remix-ui/search/src/lib/components/CancelSearch.tsx @@ -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 ( + await cancel()}>stop + ) +} \ No newline at end of file diff --git a/libs/remix-ui/search/src/lib/components/Exclude.tsx b/libs/remix-ui/search/src/lib/components/Exclude.tsx index 5385783324..cd9aaf07bc 100644 --- a/libs/remix-ui/search/src/lib/components/Exclude.tsx +++ b/libs/remix-ui/search/src/lib/components/Exclude.tsx @@ -2,12 +2,13 @@ import React, { useContext, useEffect, useRef, useState } from 'react' import { SearchContext } from '../context/context' export const Exclude = props => { - const { setExclude, state } = useContext(SearchContext) - const [excludeInput, setExcludeInput] = useState('.git/**/*,.deps/**/*') + const { setExclude, cancelSearch } = useContext(SearchContext) + const [excludeInput, setExcludeInput] = useState('.*/**/*') const timeOutId = useRef(null) - const change = e => { + const change = async e => { setExcludeInput(e.target.value) clearTimeout(timeOutId.current) + await cancelSearch() timeOutId.current = setTimeout(() => setExclude(e.target.value), 500) } @@ -23,7 +24,7 @@ export const Exclude = props => { id='search_exclude' placeholder="Exclude ie .git/**/*" className="form-control" - onChange={change} + onChange={async(e) => change(e)} value={excludeInput} > diff --git a/libs/remix-ui/search/src/lib/components/Include.tsx b/libs/remix-ui/search/src/lib/components/Include.tsx index 1d3f8faf96..c0284d1b24 100644 --- a/libs/remix-ui/search/src/lib/components/Include.tsx +++ b/libs/remix-ui/search/src/lib/components/Include.tsx @@ -2,12 +2,13 @@ import React, { useContext, useRef, useState } from 'react' import { SearchContext } from '../context/context' export const Include = props => { - const { setInclude } = useContext(SearchContext) + const { setInclude, cancelSearch } = useContext(SearchContext) const [includeInput, setIncludeInput] = useState('') const timeOutId = useRef(null) - const change = e => { + const change = async e => { setIncludeInput(e.target.value) clearTimeout(timeOutId.current) + await cancelSearch() timeOutId.current = setTimeout(() => setInclude(e.target.value), 500) } @@ -19,7 +20,7 @@ export const Include = props => { id='search_include' placeholder="Include ie contracts/**/*.sol" className="form-control" - onChange={change} + onChange={async(e) => change(e)} value={includeInput} > diff --git a/libs/remix-ui/search/src/lib/components/Search.tsx b/libs/remix-ui/search/src/lib/components/Search.tsx index 7f6d62aa5e..3dee1711e8 100644 --- a/libs/remix-ui/search/src/lib/components/Search.tsx +++ b/libs/remix-ui/search/src/lib/components/Search.tsx @@ -1,30 +1,28 @@ -import React from 'react' +import React, { useContext } from 'react' import { SearchProvider } from '../context/context' -import { Find } from './Find' import { Results } from './results/Results' import '../search.css' import { Include } from './Include' import { Exclude } from './Exclude' -import { Replace } from './Replace' -import { OverWriteCheck } from './OverWriteCheck' import { FindContainer } from './FindContainer' import { Undo } from './Undo' +import { CacncelSearch } from './CancelSearch' export const SearchTab = props => { -const plugin = props.plugin + const plugin = props.plugin -return ( + return ( <> -
- - - - - - - -
+
+ + + + + + + +
) } diff --git a/libs/remix-ui/search/src/lib/components/results/ResultItem.tsx b/libs/remix-ui/search/src/lib/components/results/ResultItem.tsx index 91fb1c6042..0c36c057a7 100644 --- a/libs/remix-ui/search/src/lib/components/results/ResultItem.tsx +++ b/libs/remix-ui/search/src/lib/components/results/ResultItem.tsx @@ -7,6 +7,7 @@ import { ResultSummary } from './ResultSummary' interface ResultItemProps { file: SearchResult + index: number } export const ResultItem = (props: ResultItemProps) => { @@ -17,6 +18,7 @@ export const ResultItem = (props: ResultItemProps) => { const [lines, setLines] = useState([]) const [toggleExpander, setToggleExpander] = useState(false) const reloadTimeOut = useRef(null) + const loadTimeout = useRef(null) const subscribed = useRef(true) const { modal } = useDialogDispatchers() @@ -27,6 +29,7 @@ export const ResultItem = (props: ResultItemProps) => { useEffect(() => { if (props.file.forceReload) { clearTimeout(reloadTimeOut.current) + clearTimeout(loadTimeout.current) reloadTimeOut.current = setTimeout(() => reload(), 1000) } }, [props.file.forceReload]) @@ -35,14 +38,11 @@ export const ResultItem = (props: ResultItemProps) => { setToggleExpander(!toggleExpander) } - useEffect(() => { - reload() - }, [state.find]) - useEffect(() => { subscribed.current = true return () => { - updateCount(0, props.file.filename) + clearTimeout(reloadTimeOut.current) + clearTimeout(loadTimeout.current) subscribed.current = false } }, []) @@ -64,7 +64,7 @@ export const ResultItem = (props: ResultItemProps) => { } } - const reload = () => { + const doLoad = () => { findText(props.file.filename).then(res => { if (subscribed.current) { setLines(res) @@ -78,9 +78,15 @@ export const ResultItem = (props: ResultItemProps) => { setLoading(false) disableForceReload(props.file.filename) } + }).catch((e) => { + console.error(e) }) } + const reload = () => { + loadTimeout.current = setTimeout(doLoad, 150 * props.index) + } + return ( <> {lines && lines.length ? ( @@ -110,13 +116,12 @@ export const ResultItem = (props: ResultItemProps) => { :null} {lines.map((line, index) => ( - index < state.maxLines ? : null + /> ))} ) : null} diff --git a/libs/remix-ui/search/src/lib/components/results/Results.tsx b/libs/remix-ui/search/src/lib/components/results/Results.tsx index 3e5472915a..4d16f05235 100644 --- a/libs/remix-ui/search/src/lib/components/results/Results.tsx +++ b/libs/remix-ui/search/src/lib/components/results/Results.tsx @@ -5,12 +5,26 @@ import { ResultItem } from './ResultItem' export const Results = () => { const { state } = useContext(SearchContext) return ( -
- {state.find ?
showing {state.count} results {state.fileCount} in files
: null} - {state.find && state.clipped?
The result set only shows a subset of all matches

Please narrow down your search.
: null} +
+
+ {' '} + {state.searching && !state.clipped + ? `searching in ${state.searching}` + : null} +
+ {state.find && !state.clipped ? ( +
+ showing {state.count} results {state.fileCount} in files +
+ ) : null} + {state.find && state.clipped ? ( +
+ Too many resuls to display...

Please narrow down your search. +
+ ) : null} {state.searchResults && state.searchResults.map((result, index) => { - return index : null + return })}
) diff --git a/libs/remix-ui/search/src/lib/context/context.tsx b/libs/remix-ui/search/src/lib/context/context.tsx index 6d270ba1b2..a8d3a7fab0 100644 --- a/libs/remix-ui/search/src/lib/context/context.tsx +++ b/libs/remix-ui/search/src/lib/context/context.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { createContext, useReducer } from 'react' import { findLinesInStringWithMatch, @@ -31,7 +31,10 @@ export interface SearchingStateInterface { setSearchResults: (value: SearchResult[]) => void findText: (path: string) => Promise hightLightInPath: (result: SearchResult, line: SearchResultLineLine) => void - replaceText: (result: SearchResult, line: SearchResultLineLine) => Promise + replaceText: ( + result: SearchResult, + line: SearchResultLineLine + ) => Promise reloadFile: (file: string) => void toggleCaseSensitive: () => void toggleMatchWholeWord: () => void @@ -39,9 +42,10 @@ export interface SearchingStateInterface { setReplaceWithoutConfirmation: (value: boolean) => void disableForceReload: (file: string) => void updateCount: (count: number, file: string) => void - replaceAllInFile: (result: SearchResult) => Promise + replaceAllInFile: (result: SearchResult) => Promise undoReplace: (buffer: undoBufferRecord) => Promise clearUndo: () => void + cancelSearch: () => Promise } export const SearchContext = createContext(null) @@ -53,11 +57,12 @@ export const SearchProvider = ({ plugin = undefined } = {}) => { const [state, dispatch] = useReducer(reducer, initialState) - - const reloadTimeOut = useRef(null) + const [files, setFiles] = useState([]) + const clearSearchingTimeout = useRef(null) const value = { state, setFind: (value: string) => { + plugin.cancel('fileManager') dispatch({ type: 'SET_FIND', payload: value @@ -165,14 +170,30 @@ export const SearchProvider = ({ payload: { count, file } }) }, + setSearching(file: string) { + dispatch({ + type: 'SET_SEARCHING', + payload: file + }) + }, + findText: async (path: string) => { if (!plugin) return try { if (state.find.length < 1) return + value.setSearching(path) const text = await plugin.call('fileManager', 'readFile', path) - const result: SearchResultLine[] = findLinesInStringWithMatch(text, createRegExFromFind()) + const result: SearchResultLine[] = findLinesInStringWithMatch( + text, + createRegExFromFind() + ) + clearTimeout(clearSearchingTimeout.current) + clearSearchingTimeout.current = setTimeout(() => value.setSearching(null), 500) return result - } catch (e) { } + } catch (e) { + value.setSearching(null) + // do nothing + } }, hightLightInPath: async ( result: SearchResult, @@ -180,7 +201,14 @@ export const SearchProvider = ({ ) => { await plugin.call('editor', 'discardHighlight') await plugin.call('editor', 'highlight', line.position, result.path) - await plugin.call('editor', 'revealRange', line.position.start.line, line.position.start.column, line.position.end.line, line.position.end.column) + await plugin.call( + 'editor', + 'revealRange', + line.position.start.line, + line.position.start.column, + line.position.end.line, + line.position.end.column + ) }, replaceText: async (result: SearchResult, line: SearchResultLineLine) => { try { @@ -192,12 +220,7 @@ export const SearchProvider = ({ result.path ) const replaced = replaceTextInLine(content, line, state.replace) - await plugin.call( - 'fileManager', - 'setFile', - result.path, - replaced - ) + await plugin.call('fileManager', 'setFile', result.path, replaced) setUndoState(content, replaced, result.path) } catch (e) { throw new Error(e) @@ -205,26 +228,17 @@ export const SearchProvider = ({ }, replaceAllInFile: async (result: SearchResult) => { await plugin.call('editor', 'discardHighlight') - const content = await plugin.call( - 'fileManager', - 'readFile', - result.path - ) - const replaced = replaceAllInFile(content, createRegExFromFind(), state.replace) - await plugin.call( - 'fileManager', - 'setFile', - result.path, - replaced - ) - await plugin.call( - 'fileManager', - 'open', - result.path + const content = await plugin.call('fileManager', 'readFile', result.path) + const replaced = replaceAllInFile( + content, + createRegExFromFind(), + state.replace ) + await plugin.call('fileManager', 'setFile', result.path, replaced) + await plugin.call('fileManager', 'open', result.path) setUndoState(content, replaced, result.path) }, - setUndoEnabled: (path:string, workspace: string, content: string) => { + setUndoEnabled: (path: string, workspace: string, content: string) => { dispatch({ type: 'SET_UNDO_ENABLED', payload: { @@ -235,11 +249,7 @@ export const SearchProvider = ({ }) }, undoReplace: async (buffer: undoBufferRecord) => { - const content = await plugin.call( - 'fileManager', - 'readFile', - buffer.path - ) + const content = await plugin.call('fileManager', 'readFile', buffer.path) if (buffer.newContent !== content) { throw new Error('Can not undo replace, file has been changed.') } @@ -249,41 +259,58 @@ export const SearchProvider = ({ buffer.path, buffer.oldContent ) - await plugin.call( - 'fileManager', - 'open', - buffer.path - ) + await plugin.call('fileManager', 'open', buffer.path) }, clearUndo: () => { - dispatch ({ + dispatch({ type: 'CLEAR_UNDO', payload: undefined }) - } - } + }, + clearStats: () => { + 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) => { await value.reloadFile(file) } useEffect(() => { - plugin.on('filePanel', 'setWorkspace', async (workspace) => { + plugin.on('filePanel', 'setWorkspace', async workspace => { value.setSearchResults(null) value.clearUndo() value.setCurrentWorkspace(workspace.name) + setFiles(await getDirectory('/', plugin)) }) plugin.on('fileManager', 'fileSaved', async file => { await reloadStateForFile(file) await checkUndoState(file) }) + plugin.on('fileManager', 'fileAdded', async file => { + setFiles(await getDirectory('/', plugin)) + }) plugin.on('fileManager', 'currentFileChanged', async file => { value.setCurrentFile(file) await checkUndoState(file) }) - + return () => { plugin.off('fileManager', 'fileChanged') plugin.off('filePanel', 'setWorkspace') @@ -296,7 +323,8 @@ export const SearchProvider = ({ paths.split(',').forEach(path => { path = path.trim() if (path.startsWith('*.')) path = path.replace(/(\*\.)/g, '**/*.') - if (path.endsWith('/*') && !path.endsWith('/**/*')) path = path.replace(/(\*)/g, '**/*.*') + if (path.endsWith('/*') && !path.endsWith('/**/*')) + path = path.replace(/(\*)/g, '**/*.*') results.push(path) }) return results @@ -305,19 +333,19 @@ export const SearchProvider = ({ const checkUndoState = async (path: string) => { if (!plugin) return try { - const content = await plugin.call( - 'fileManager', - 'readFile', - path - ) + const content = await plugin.call('fileManager', 'readFile', path) const workspace = await plugin.call('filePanel', 'getCurrentWorkspace') value.setUndoEnabled(path, workspace.name, content) - } catch (e) { + } catch (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 undo = { oldContent, @@ -341,29 +369,41 @@ export const SearchProvider = ({ return re } + useEffect(() => { + if(state.count>500) { + value.setClipped(true) + value.cancelSearch() + } + }, [state.count]) + useEffect(() => { if (state.find) { (async () => { - const files = await getDirectory('/', plugin) - const pathFilter: any = {} - if (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 + try { + const pathFilter: any = {} + if (state.include) { + pathFilter.include = setGlobalExpression(state.include) + } + if (state.exclude) { + pathFilter.exclude = setGlobalExpression(state.exclude) } - return r - }) - 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]) diff --git a/libs/remix-ui/search/src/lib/reducers/Reducer.ts b/libs/remix-ui/search/src/lib/reducers/Reducer.ts index 3e188f61fb..1455dab61e 100644 --- a/libs/remix-ui/search/src/lib/reducers/Reducer.ts +++ b/libs/remix-ui/search/src/lib/reducers/Reducer.ts @@ -5,6 +5,7 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action case 'SET_FIND': return { ...state, + searchResults: null, find: action.payload, timeStamp: Date.now() } @@ -64,6 +65,21 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action ...state, } } + case 'CLEAR_STATS': + return { + ...state, + count: 0, + fileCount: 0, + searchResults: null, + searching: null + } + + case 'SET_SEARCHING': + return { + ...state, + searching: action.payload, + } + case 'CLEAR_UNDO': { state.undoBuffer = [] return { @@ -81,12 +97,7 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action } state.searchResults.forEach(file => { if (file.count) { - if(file.count > state.maxLines) { - clipped = true - count += state.maxLines - }else{ - count += file.count - } + count += file.count fileCount++ } }) @@ -99,6 +110,13 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action } else { return state } + + case 'SET_CLIPPED': + return { + ...state, + clipped: action.payload + } + case 'TOGGLE_CASE_SENSITIVE': return { ...state, diff --git a/libs/remix-ui/search/src/lib/search.css b/libs/remix-ui/search/src/lib/search.css index acc789d51c..6e4a21ef89 100644 --- a/libs/remix-ui/search/src/lib/search.css +++ b/libs/remix-ui/search/src/lib/search.css @@ -133,4 +133,9 @@ white-space: pre; text-overflow: ellipsis; overflow: hidden; +} +.search_plugin_search_indicator{ + white-space: pre; + text-overflow: ellipsis; + overflow: hidden; } \ No newline at end of file diff --git a/libs/remix-ui/search/src/lib/types/index.ts b/libs/remix-ui/search/src/lib/types/index.ts index 231c4ebf93..3e32c6a974 100644 --- a/libs/remix-ui/search/src/lib/types/index.ts +++ b/libs/remix-ui/search/src/lib/types/index.ts @@ -63,7 +63,8 @@ export interface SearchState { clipped: boolean, undoBuffer: Record[], currentFile: string, - workspace: string + workspace: string, + searching: string | null, } export const SearchingInitialState: SearchState = { @@ -85,5 +86,6 @@ export const SearchingInitialState: SearchState = { clipped: false, undoBuffer: null, currentFile: '', - workspace: '' + workspace: '', + searching: null } \ No newline at end of file