diff --git a/libs/remix-ui/search/src/lib/components/Exclude.tsx b/libs/remix-ui/search/src/lib/components/Exclude.tsx index e69de29bb2..e755f1370e 100644 --- a/libs/remix-ui/search/src/lib/components/Exclude.tsx +++ b/libs/remix-ui/search/src/lib/components/Exclude.tsx @@ -0,0 +1,26 @@ +import React, { useContext, useEffect } from 'react' +import { SearchContext } from '../context/context' + +export const Exclude = props => { + const { setExclude } = useContext(SearchContext) + const change = e => { + const timeOutId = setTimeout(() => setExclude(e.target.value), 500) + return () => clearTimeout(timeOutId) + } + + useEffect(() => { + setExclude('.git/**') + }, []) + + return ( + <> +
+ +
+ + ) +} diff --git a/libs/remix-ui/search/src/lib/components/Find.tsx b/libs/remix-ui/search/src/lib/components/Find.tsx index db8cb94463..ef15cb1c81 100644 --- a/libs/remix-ui/search/src/lib/components/Find.tsx +++ b/libs/remix-ui/search/src/lib/components/Find.tsx @@ -1,17 +1,40 @@ -import React, { useContext, useReducer } from "react" -import { SearchContext } from "../context/context" -import { SearchingInitialState, SearchReducer } from "../reducers/Reducer" - - +import React, { useContext } from 'react' +import { SearchContext } from '../context/context' export const Find = props => { + const { setFind } = useContext(SearchContext) + const change = e => { + const timeOutId = setTimeout(() => setFind(e.target.value), 500) + return () => clearTimeout(timeOutId) + } - const { setFind } = useContext(SearchContext) - const change = (e) => { - setFind(e.target.value) - } - - return(<> - - ) -} \ No newline at end of file + return ( + <> +
+ + {/*
+
+
+
*/} +
+ + ) +} diff --git a/libs/remix-ui/search/src/lib/components/Include.tsx b/libs/remix-ui/search/src/lib/components/Include.tsx index e69de29bb2..bea364a6c9 100644 --- a/libs/remix-ui/search/src/lib/components/Include.tsx +++ b/libs/remix-ui/search/src/lib/components/Include.tsx @@ -0,0 +1,22 @@ +import React, { useContext } from 'react' +import { SearchContext } from '../context/context' + +export const Include = props => { + const { setInclude } = useContext(SearchContext) + const change = e => { + const timeOutId = setTimeout(() => setInclude(e.target.value), 500) + return () => clearTimeout(timeOutId) + } + + return ( + <> +
+ +
+ + ) +} diff --git a/libs/remix-ui/search/src/lib/components/Replace.tsx b/libs/remix-ui/search/src/lib/components/Replace.tsx index fc60d3d73a..28712774ca 100644 --- a/libs/remix-ui/search/src/lib/components/Replace.tsx +++ b/libs/remix-ui/search/src/lib/components/Replace.tsx @@ -1,16 +1,22 @@ -import React, { useContext, useEffect } from 'react' +import React, { useContext } from 'react' import { SearchContext } from '../context/context' export const Replace = props => { - const { state, setReplace } = useContext(SearchContext) - + const { setReplace } = useContext(SearchContext) const change = e => { - setReplace(e.target.value) + const timeOutId = setTimeout(() => setReplace(e.target.value), 500) + return () => clearTimeout(timeOutId) } return ( <> - +
+ +
) } diff --git a/libs/remix-ui/search/src/lib/components/Search.tsx b/libs/remix-ui/search/src/lib/components/Search.tsx index e9d2df1097..913e00eab2 100644 --- a/libs/remix-ui/search/src/lib/components/Search.tsx +++ b/libs/remix-ui/search/src/lib/components/Search.tsx @@ -1,23 +1,21 @@ -import React, { useContext, useEffect, useReducer } from 'react' -import { SearchContext, SearchProvider } from '../context/context' -import { SearchingInitialState, SearchReducer } from '../reducers/Reducer' +import React from 'react' +import { SearchProvider } from '../context/context' import { Find } from './Find' -import { Replace } from './Replace' import { Results } from './results/Results' +import '../search.css' +import { Include } from './Include' +import { Exclude } from './Exclude' export const SearchTab = props => { const plugin = props.plugin -useEffect(() => { - console.log (plugin) -},[]) - return ( <> - + - + + diff --git a/libs/remix-ui/search/src/lib/components/results/ResultFileName.tsx b/libs/remix-ui/search/src/lib/components/results/ResultFileName.tsx index e69de29bb2..2d297a078a 100644 --- a/libs/remix-ui/search/src/lib/components/results/ResultFileName.tsx +++ b/libs/remix-ui/search/src/lib/components/results/ResultFileName.tsx @@ -0,0 +1,20 @@ +import { ViewPlugin } from '@remixproject/engine-web' +import React, { useContext, useEffect } from 'react' +import { SearchContext } from '../../context/context' +import { SearchResult } from '../../reducers/Reducer' + +interface ResultItemProps { + file: SearchResult +} + +export const ResultFileName = (props: ResultItemProps) => { + return ( +
+
+ + {props.file.filename} + +
+
+ ) +} 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 e69de29bb2..54785a714d 100644 --- a/libs/remix-ui/search/src/lib/components/results/ResultItem.tsx +++ b/libs/remix-ui/search/src/lib/components/results/ResultItem.tsx @@ -0,0 +1,64 @@ +import { ViewPlugin } from '@remixproject/engine-web' +import React, { useContext, useEffect, useState } from 'react' +import { SearchContext } from '../../context/context' +import { SearchResult, SearchResultLine } from '../../reducers/Reducer' +import { ResultFileName } from './ResultFileName' +import { ResultSummary } from './ResultSummary' + +interface ResultItemProps { + file: SearchResult +} + +export const ResultItem = (props: ResultItemProps) => { + const { state, findText } = useContext(SearchContext) + + const [lines, setLines] = useState([]) + const [toggleExpander, setToggleExpander] = useState(false) + + useEffect(() => { + if (!props.file.searchComplete) { + // console.log('searching for: ' + props.file.filename) + } + }, [props.file.searchComplete]) + + const toggleClass = () => { + setToggleExpander(!toggleExpander) + } + + useEffect(() => { + if (!props.file.searchComplete) { + findText(props.file.filename).then(res => { + setLines(res) + }) + } + }, [state.find]) + + return ( + <> + {lines && lines.length ? ( + <> +
+ {' '} + +
+ {!toggleExpander ? ( +
    + {lines.map((line, index) => ( + + ))} +
+ ) : null} + + ) : ( + <> + )} + + ) +} diff --git a/libs/remix-ui/search/src/lib/components/results/ResultSummary.tsx b/libs/remix-ui/search/src/lib/components/results/ResultSummary.tsx index e69de29bb2..f86d842212 100644 --- a/libs/remix-ui/search/src/lib/components/results/ResultSummary.tsx +++ b/libs/remix-ui/search/src/lib/components/results/ResultSummary.tsx @@ -0,0 +1,34 @@ +import { ViewPlugin } from '@remixproject/engine-web' +import React, { useContext, useEffect, useState } from 'react' +import { SearchContext } from '../../context/context' +import { SearchResult, SearchResultLine, SearchResultLineLine } from '../../reducers/Reducer' +import { ResultFileName } from './ResultFileName' + +interface ResultSummaryProps { + searchResult: SearchResult + line: SearchResultLine +} + +export const ResultSummary = (props: ResultSummaryProps) => { + const { hightLightInPath } = useContext(SearchContext) + const selectLine = async (line: SearchResultLineLine) => { + console.log(line, props.searchResult) + await hightLightInPath(props.searchResult, line) + } + return ( +
  • + {props.line.lines.map((lineItem, index) => ( +
    { + selectLine(lineItem) + }} + key={index} + > + {lineItem.left.substring(lineItem.left.length - 20)} + {lineItem.center} + {lineItem.right.substring(0, 100)} +
    + ))} +
  • + ) +} 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 7514c51e98..ca109573fb 100644 --- a/libs/remix-ui/search/src/lib/components/results/Results.tsx +++ b/libs/remix-ui/search/src/lib/components/results/Results.tsx @@ -1,81 +1,98 @@ -import { ViewPlugin } from "@remixproject/engine-web" -import React, { useContext, useEffect } from "react" -import { SearchContext } from "../../context/context" -import { SearchResult } from "../../reducers/Reducer" +import { ViewPlugin } from '@remixproject/engine-web' +import { matches } from 'lodash' +import React, { useContext, useEffect, useState } from 'react' +import { SearchContext } from '../../context/context' +import { SearchResult, SearchResultLine } from '../../reducers/Reducer' +import { ResultItem } from './ResultItem' +import { findLinesInStringWithMatch } from './SearchHelper' +const filePathFilter = require('@jsdevtools/file-path-filter') interface ResultsProps { - plugin: ViewPlugin + plugin: ViewPlugin } export const Results = (props: ResultsProps) => { + const { state, setSearchResults, setFind } = useContext(SearchContext) + const [ alertText, setAlertText ] = useState('') + const { plugin } = props - const { state, setSearchResults } = useContext(SearchContext) - - const { plugin } = props - - const getDirectory = async (dir) => { - let result = [] - const files = await plugin.call('fileManager', 'readdir', dir) - const fileArray = normalize(files) - for (const fi of fileArray) { - if (fi) { - const type = fi.data.isDirectory - if (type === true) { - result = [ - ...result, - ...(await getDirectory( - `${fi.filename}` - )) - ] - } else { - result = [...result, fi.filename] - } - } + const getDirectory = async (dir: string) => { + let result = [] + const files = await plugin.call('fileManager', 'readdir', dir) + const fileArray = normalize(files) + for (const fi of fileArray) { + if (fi) { + const type = fi.data.isDirectory + if (type === true) { + result = [...result, ...(await getDirectory(`${fi.filename}`))] + } else { + result = [...result, fi.filename] } - return result + } } + return result + } - const normalize = (filesList) => { - const folders = [] - const files = [] - Object.keys(filesList || {}).forEach(key => { - if (filesList[key].isDirectory) { - folders.push({ - filename: key, - data: filesList[key] - }) - } else { - files.push({ - filename: key, - data: filesList[key] - }) - } + const normalize = filesList => { + const folders = [] + const files = [] + Object.keys(filesList || {}).forEach(key => { + if (filesList[key].isDirectory) { + folders.push({ + filename: key, + data: filesList[key] + }) + } else { + files.push({ + filename: key, + data: filesList[key] }) - return [...folders, ...files] } + }) + return [...folders, ...files] + } - useEffect(() => { - if (state.find) { - getDirectory('/').then(res => { - const ob = res.map(file => { - const r:SearchResult = { - filename: file, - lines: [], - path: file, - searchComplete: false - } - return r - }) - console.log(ob) - setSearchResults(ob) - }) - } - },[state.find]) + useEffect(() => { + plugin.on('filePanel', 'setWorkspace', () => { + setSearchResults(null) + }) + return () => plugin.off('filePanel', 'setWorkspace') + }, []) + useEffect(() => { + if (state.find) { + (async () => { + const res = await getDirectory('/') + const pathFilter: any = {} + if (state.include) + pathFilter.include = state.include.split(',').map(i => i.trim()) + if (state.exclude) + pathFilter.exclude = state.exclude.split(',').map(i => i.trim()) + const ob = res.filter(filePathFilter(pathFilter)).map(file => { + const r: SearchResult = { + filename: file, + lines: [], + path: file, + searchComplete: false + } + return r + }) + setSearchResults(ob) + })() + } + }, [state.find, state.replace, state.include, state.exclude]) - useEffect(() => { - console.log(state.searchResults) - },[state.searchResults]) - - return(<>) -} \ No newline at end of file + return ( + <> + {alertText ? ( +
    + {alertText} +
    + ) : null} + {state.searchResults && + state.searchResults.map((result, index) => { + return + })} + + ) +} diff --git a/libs/remix-ui/search/src/lib/components/results/SearchHelper.ts b/libs/remix-ui/search/src/lib/components/results/SearchHelper.ts new file mode 100644 index 0000000000..3e41e418ca --- /dev/null +++ b/libs/remix-ui/search/src/lib/components/results/SearchHelper.ts @@ -0,0 +1,45 @@ +import { SearchResultLineLine } from "../../reducers/Reducer" + +export const findLinesInStringWithMatch = (str: string, re: RegExp) => { + return str + .split(/\r?\n/) + .map(function (line, i) { + const matchResult = matchesInString(line, re) + if (matchResult.length) { + return { + lines: splitLines(matchResult, i), + } + } + }) + .filter(Boolean) +} + +const matchesInString = (str: string, re: RegExp) => { + let a: RegExpExecArray + let results:RegExpExecArray[] = []; + while ((a = re.exec(str || '')) !== null) { + results.push(a); + } + return results +} + +const splitLines = (matchResult: RegExpExecArray[], lineNumber: number) => { + return matchResult.map((matchResultPart, i) => { + const result:SearchResultLineLine = { + left: matchResultPart.input.substring(0, matchResultPart.index), + right: matchResultPart.input.substring(matchResultPart.index + matchResultPart[0].length), + center: matchResultPart[0], + position : { + start: { + line: lineNumber, + column: matchResultPart.index, + }, + end: { + line: lineNumber, + column: matchResultPart.index + matchResultPart[0].length, + }, + }, + } + return result + }) +} diff --git a/libs/remix-ui/search/src/lib/context/context.tsx b/libs/remix-ui/search/src/lib/context/context.tsx index 4b696f948e..1762d7c367 100644 --- a/libs/remix-ui/search/src/lib/context/context.tsx +++ b/libs/remix-ui/search/src/lib/context/context.tsx @@ -1,9 +1,12 @@ import React from 'react' import { createContext, useReducer } from 'react' +import { findLinesInStringWithMatch } from '../components/results/SearchHelper' import { SearchingInitialState, SearchReducer, SearchResult, + SearchResultLine, + SearchResultLineLine, SearchState } from '../reducers/Reducer' @@ -17,6 +20,8 @@ export interface SearchingStateInterface { setRegex: (value: boolean) => void, setWholeWord: (value: boolean) => void, setSearchResults: (value: SearchResult[]) => void, + findText: (path: string) => Promise, + hightLightInPath: (path:SearchResult, line:SearchResultLineLine) => void, } export const SearchContext = createContext(null) @@ -24,13 +29,15 @@ export const SearchContext = createContext(null) export const SearchProvider = ({ children = [], reducer = SearchReducer, - initialState = SearchingInitialState + initialState = SearchingInitialState, + plugin = undefined } = {}) => { const [state, dispatch] = useReducer(reducer, initialState) const value = { state, setFind: (value: string) => { + console.log('setFind: ' + value) dispatch({ type: 'SET_FIND', payload: value @@ -77,7 +84,23 @@ export const SearchProvider = ({ type: 'SET_SEARCH_RESULTS', payload: value }) - } + }, + findText : async (path: string) => { + if(!plugin) return + try { + if(state.find.length < 3) return + const text = await plugin.call('fileManager', 'readFile', path) + const re = new RegExp(state.find, 'gi') + const result: SearchResultLine[] = findLinesInStringWithMatch(text, re) + // console.log(result, path) + return result + } catch (e) {} + }, + hightLightInPath: async(result: SearchResult, line: SearchResultLineLine) => { + await plugin.call('editor', 'discardHighlight') + await plugin.call('editor', 'highlight', line.position, result.path) + } + } return ( diff --git a/libs/remix-ui/search/src/lib/reducers/Reducer.ts b/libs/remix-ui/search/src/lib/reducers/Reducer.ts index e0cc2a4b0c..c4a6742a08 100644 --- a/libs/remix-ui/search/src/lib/reducers/Reducer.ts +++ b/libs/remix-ui/search/src/lib/reducers/Reducer.ts @@ -4,9 +4,25 @@ interface Action { payload: any } +interface position { + start: { + line: number + column: number + }, + end: { + line: number + column: number + } + } + +export interface SearchResultLineLine { + left: any, + center: any, + right: any, + position: position +} export interface SearchResultLine { - lineNumber: number - text: string + lines: SearchResultLineLine[] } export interface SearchResult { @@ -33,38 +49,37 @@ export const SearchingInitialState: SearchState = { } export const SearchReducer = (state: SearchState = SearchingInitialState, action: Action) => { - console.log(state) switch (action.type) { case 'SET_FIND': return { ...state, find: action.payload, } - break; + case 'SET_REPLACE': return { ...state, replace: action.payload, } - break; + case 'SET_INCLUDE': return { ...state, include: action.payload, } - break; + case 'SET_EXCLUDE': return { ...state, exclude: action.payload, } - break; + case 'SET_SEARCH_RESULTS': return { ...state, searchResults: action.payload, } - break; + default: } } \ No newline at end of file diff --git a/libs/remix-ui/search/src/lib/search.css b/libs/remix-ui/search/src/lib/search.css new file mode 100644 index 0000000000..a7b9326785 --- /dev/null +++ b/libs/remix-ui/search/src/lib/search.css @@ -0,0 +1,26 @@ +.search_result_item_title { + display: flex; + -webkit-user-select: none; /* Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+/Edge */ + user-select: none; /* Standard */ + cursor: pointer; +} + +.wrap_summary { + overflow: hidden; + white-space: nowrap; + -webkit-user-select: none; /* Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+/Edge */ + user-select: none; /* Standard */ + cursor: pointer; +} + +.find-part { + display: flex; +} + +.controls { + display: flex; +} \ No newline at end of file diff --git a/package.json b/package.json index 3585888453..a38866094a 100644 --- a/package.json +++ b/package.json @@ -171,6 +171,7 @@ "ethers": "^5.4.2", "ethjs-util": "^0.1.6", "express-ws": "^4.0.0", + "file-path-filter": "^3.0.2", "file-saver": "^2.0.5", "form-data": "^4.0.0", "fs-extra": "^3.0.1",