pull/2086/head
filip mertens 3 years ago
parent c9dd9fb9ab
commit fe21ad5b64
  1. 26
      libs/remix-ui/search/src/lib/components/Exclude.tsx
  2. 49
      libs/remix-ui/search/src/lib/components/Find.tsx
  3. 22
      libs/remix-ui/search/src/lib/components/Include.tsx
  4. 16
      libs/remix-ui/search/src/lib/components/Replace.tsx
  5. 18
      libs/remix-ui/search/src/lib/components/Search.tsx
  6. 20
      libs/remix-ui/search/src/lib/components/results/ResultFileName.tsx
  7. 64
      libs/remix-ui/search/src/lib/components/results/ResultItem.tsx
  8. 34
      libs/remix-ui/search/src/lib/components/results/ResultSummary.tsx
  9. 149
      libs/remix-ui/search/src/lib/components/results/Results.tsx
  10. 45
      libs/remix-ui/search/src/lib/components/results/SearchHelper.ts
  11. 25
      libs/remix-ui/search/src/lib/context/context.tsx
  12. 31
      libs/remix-ui/search/src/lib/reducers/Reducer.ts
  13. 26
      libs/remix-ui/search/src/lib/search.css
  14. 1
      package.json

@ -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 (
<>
<div className="find-part">
<input
placeholder="Exclude ie .git/**"
className="form-control"
onChange={change}
></input>
</div>
</>
)
}

@ -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(<>
<input placeholder="Search" className="form-control" onChange={change}></input>
</>)
return (
<>
<div className="find-part">
<input
placeholder="Search"
className="form-control"
onChange={change}
></input>
{/* <div className="controls">
<div
title="Match Case (⌥⌘C)"
className="monaco-custom-checkbox codicon codicon-case-sensitive"
role="checkbox"
aria-checked="false"
aria-label="Match Case (⌥⌘C)"
aria-disabled="false"
></div>
<div
title="Match Whole Word"
className="monaco-custom-checkbox codicon codicon-whole-word"
role="checkbox"
aria-checked="false"
aria-label="Match Whole Word"
aria-disabled="false"
></div>
</div> */}
</div>
</>
)
}

@ -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 (
<>
<div className="find-part">
<input
placeholder="Include ie contracts/**/*.sol"
className="form-control"
onChange={change}
></input>
</div>
</>
)
}

@ -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 (
<>
<input placeholder='Replace' className="form-control" onChange={change}></input>
<div className="find-part">
<input
placeholder="Replace"
className="form-control"
onChange={change}
></input>
</div>
</>
)
}

@ -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 (
<>
<SearchProvider>
<SearchProvider plugin={plugin}>
<Find></Find>
<Replace></Replace>
<Include></Include>
<Exclude></Exclude>
<Results plugin={plugin}></Results>
</SearchProvider>
</>

@ -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 (
<div className="input-group udapp_nameNbuts">
<div className="udapp_titleText input-group-prepend">
<span className="input-group-text udapp_spanTitleText">
{props.file.filename}
</span>
</div>
</div>
)
}

@ -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<SearchResultLine[]>([])
const [toggleExpander, setToggleExpander] = useState<boolean>(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 ? (
<>
<div onClick={toggleClass} className="search_result_item_title">
<button className="btn" >
<i
className={`fas ${
toggleExpander ? 'fa-angle-right' : 'fa-angle-down'
}`}
aria-hidden="true"
></i>
</button>{' '}
<ResultFileName file={props.file} />
</div>
{!toggleExpander ? (
<ul>
{lines.map((line, index) => (
<ResultSummary key={index} searchResult={props.file} line={line} />
))}
</ul>
) : null}
</>
) : (
<></>
)}
</>
)
}

@ -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 (
<li className="p-1 wrap_summary">
{props.line.lines.map((lineItem, index) => (
<div
onClick={async () => {
selectLine(lineItem)
}}
key={index}
>
{lineItem.left.substring(lineItem.left.length - 20)}
<mark>{lineItem.center}</mark>
{lineItem.right.substring(0, 100)}
</div>
))}
</li>
)
}

@ -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(() => {
console.log(state.searchResults)
},[state.searchResults])
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])
return(<></>)
return (
<>
{alertText ? (
<div className="alert alert-success mt-1" role="alert">
{alertText}
</div>
) : null}
{state.searchResults &&
state.searchResults.map((result, index) => {
return <ResultItem key={index} file={result} />
})}
</>
)
}

@ -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
})
}

@ -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<SearchResultLine[]>,
hightLightInPath: (path:SearchResult, line:SearchResultLineLine) => void,
}
export const SearchContext = createContext<SearchingStateInterface>(null)
@ -24,13 +29,15 @@ export const SearchContext = createContext<SearchingStateInterface>(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 (

@ -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:
}
}

@ -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;
}

@ -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",

Loading…
Cancel
Save