tools ready to test

pull/2089/head
filip mertens 3 years ago
parent 286b02a845
commit 658cff4810
  1. 2
      apps/remix-ide/contracts/app/ethereum/constitution.sol
  2. 10
      libs/remix-ui/search/src/lib/components/Exclude.tsx
  3. 62
      libs/remix-ui/search/src/lib/components/Find.tsx
  4. 1
      libs/remix-ui/search/src/lib/components/Include.tsx
  5. 1
      libs/remix-ui/search/src/lib/components/Replace.tsx
  6. 6
      libs/remix-ui/search/src/lib/components/Search.tsx
  7. 8
      libs/remix-ui/search/src/lib/components/results/ResultFileName.tsx
  8. 40
      libs/remix-ui/search/src/lib/components/results/ResultItem.tsx
  9. 38
      libs/remix-ui/search/src/lib/components/results/ResultSummary.tsx
  10. 78
      libs/remix-ui/search/src/lib/components/results/Results.tsx
  11. 65
      libs/remix-ui/search/src/lib/components/results/SearchHelper.ts
  12. 94
      libs/remix-ui/search/src/lib/context/context.tsx
  13. 77
      libs/remix-ui/search/src/lib/reducers/Reducer.ts
  14. 62
      libs/remix-ui/search/src/lib/search.css
  15. 55
      libs/remix-ui/search/src/lib/types/index.ts

@ -1,4 +1,4 @@
contract Constitution {
Constitution {
function Found(uint8 _numProposals) {
proposals.length = _numProposals;

@ -1,9 +1,11 @@
import React, { useContext, useEffect } from 'react'
import React, { useContext, useEffect, useState } from 'react'
import { SearchContext } from '../context/context'
export const Exclude = props => {
const { setExclude } = useContext(SearchContext)
const { setExclude, state } = useContext(SearchContext)
const [str, setStr] = useState<string>('.git/**/*,.deps/**/*')
const change = e => {
setStr(e.target.value)
const timeOutId = setTimeout(() => setExclude(e.target.value), 500)
return () => clearTimeout(timeOutId)
}
@ -15,10 +17,12 @@ export const Exclude = props => {
return (
<>
<div className="find-part">
<label>exclude</label>
<input
placeholder="Exclude ie .git/**"
placeholder="Exclude ie .git/**/*"
className="form-control"
onChange={change}
value={str}
></input>
</div>
</>

@ -2,38 +2,48 @@ import React, { useContext } from 'react'
import { SearchContext } from '../context/context'
export const Find = props => {
const { setFind } = useContext(SearchContext)
const { setFind, state, toggleCaseSensitive, toggleMatchWholeWord } = useContext(SearchContext)
let timeOutId: any = null
const change = e => {
const timeOutId = setTimeout(() => setFind(e.target.value), 500)
return () => clearTimeout(timeOutId)
clearTimeout(timeOutId)
timeOutId = setTimeout(() => setFind(e.target.value), 500)
}
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> */}
<label>search</label>
<div className="search-input">
<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 ${state.casesensitive ? 'checked' : ''}`}
role="checkbox"
aria-checked="false"
aria-label="Match Case (⌥⌘C)"
aria-disabled="false"
onClick={() => {
toggleCaseSensitive()
}}
></div>
<div
title="Match Whole Word"
className={`monaco-custom-checkbox codicon codicon-whole-word ${state.matchWord ? 'checked' : ''}`}
role="checkbox"
aria-checked="false"
aria-label="Match Whole Word"
aria-disabled="false"
onClick={() => {
toggleMatchWholeWord()
}}
></div>
</div>
</div>
</div>
</>
)

@ -11,6 +11,7 @@ export const Include = props => {
return (
<>
<div className="find-part">
<label>include</label>
<input
placeholder="Include ie contracts/**/*.sol"
className="form-control"

@ -11,6 +11,7 @@ export const Replace = props => {
return (
<>
<div className="find-part">
<label>replace</label>
<input
placeholder="Replace"
className="form-control"

@ -5,6 +5,7 @@ import { Results } from './results/Results'
import '../search.css'
import { Include } from './Include'
import { Exclude } from './Exclude'
import { Replace } from './Replace'
export const SearchTab = props => {
@ -12,12 +13,15 @@ const plugin = props.plugin
return (
<>
<div className="search_tab">
<SearchProvider plugin={plugin}>
<Find></Find>
<Replace></Replace>
<Include></Include>
<Exclude></Exclude>
<Results plugin={plugin}></Results>
<Results></Results>
</SearchProvider>
</div>
</>
)
}

@ -1,7 +1,5 @@
import { ViewPlugin } from '@remixproject/engine-web'
import React, { useContext, useEffect } from 'react'
import { SearchContext } from '../../context/context'
import { SearchResult } from '../../reducers/Reducer'
import React from 'react'
import { SearchResult } from '../../types'
interface ResultItemProps {
file: SearchResult
@ -9,7 +7,7 @@ interface ResultItemProps {
export const ResultFileName = (props: ResultItemProps) => {
return (
<div className="input-group udapp_nameNbuts">
<div title={props.file.filename} className="input-group udapp_nameNbuts">
<div className="udapp_titleText input-group-prepend">
<span className="input-group-text udapp_spanTitleText">
{props.file.filename}

@ -1,7 +1,6 @@
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 { SearchResult, SearchResultLine } from '../../types'
import { ResultFileName } from './ResultFileName'
import { ResultSummary } from './ResultSummary'
@ -11,34 +10,35 @@ interface ResultItemProps {
export const ResultItem = (props: ResultItemProps) => {
const { state, findText } = useContext(SearchContext)
const [loading, setLoading] = useState<boolean>(false)
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])
reload()
}, [props.file.timeStamp])
const toggleClass = () => {
setToggleExpander(!toggleExpander)
}
useEffect(() => {
if (!props.file.searchComplete) {
findText(props.file.filename).then(res => {
setLines(res)
})
}
reload()
}, [state.find])
const reload = () => {
findText(props.file.filename).then(res => {
setLines(res)
setLoading(false)
})
}
return (
<>
{lines && lines.length ? (
<>
<div onClick={toggleClass} className="search_result_item_title">
<button className="btn" >
<button className="btn">
<i
className={`fas ${
toggleExpander ? 'fa-angle-right' : 'fa-angle-down'
@ -48,12 +48,18 @@ export const ResultItem = (props: ResultItemProps) => {
</button>{' '}
<ResultFileName file={props.file} />
</div>
{!toggleExpander ? (
<ul>
{loading ? <div className="loading">Loading...</div> : null}
{!toggleExpander && !loading ? (
<div className="p-1 wrap_summary">
{lines.map((line, index) => (
<ResultSummary key={index} searchResult={props.file} line={line} />
<ResultSummary
setLoading={setLoading}
key={index}
searchResult={props.file}
line={line}
/>
))}
</ul>
</div>
) : null}
</>
) : (

@ -1,34 +1,48 @@
import { ViewPlugin } from '@remixproject/engine-web'
import React, { useContext, useEffect, useState } from 'react'
import React, { useContext } from 'react'
import { SearchContext } from '../../context/context'
import { SearchResult, SearchResultLine, SearchResultLineLine } from '../../reducers/Reducer'
import { ResultFileName } from './ResultFileName'
import { SearchResult, SearchResultLine, SearchResultLineLine } from '../../types'
interface ResultSummaryProps {
searchResult: SearchResult
line: SearchResultLine
setLoading: (value: boolean) => void
}
export const ResultSummary = (props: ResultSummaryProps) => {
const { hightLightInPath } = useContext(SearchContext)
const { hightLightInPath, replaceText, state } = useContext(SearchContext)
const selectLine = async (line: SearchResultLineLine) => {
console.log(line, props.searchResult)
await hightLightInPath(props.searchResult, line)
}
const replace = async (line: SearchResultLineLine) => {
props.setLoading(true)
await replaceText(props.searchResult, line)
}
return (
<li className="p-1 wrap_summary">
<>
{props.line.lines.map((lineItem, index) => (
<div className='search_line_container' key={index}>
<div
onClick={async () => {
selectLine(lineItem)
}}
key={index}
key={props.searchResult.filename}
className='search_line pb-1'
>
{lineItem.left.substring(lineItem.left.length - 20)}
<mark>{lineItem.center}</mark>
{lineItem.right.substring(0, 100)}
<div className='summary_left'>{lineItem.left.substring(lineItem.left.length - 20)}</div>
<mark className={`summary_center ${state.replace? 'replace_strike':''}`}>{lineItem.center}</mark>
{state.replace? <mark className='replacement'>{state.replace}</mark>:<></>}
<div className='summary_right'>{lineItem.right.substring(0, 100)}</div>
</div>
<div className='search_control'>
<div title="Replace" onClick={async () => {
replace(lineItem)
}} className="codicon codicon-find-replace" role="button" aria-label="Replace" aria-disabled="false"></div>
</div>
</div>
))}
</li>
</>
)
}

@ -1,86 +1,12 @@
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
}
export const Results = (props: ResultsProps) => {
const { state, setSearchResults, setFind } = useContext(SearchContext)
const [ alertText, setAlertText ] = useState('')
const { plugin } = props
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
}
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]
}
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])
const { state} = useContext(SearchContext)
const [alertText, setAlertText] = useState('')
return (
<>

@ -1,4 +1,42 @@
import { SearchResultLineLine } from "../../reducers/Reducer"
import { EOL } from 'os'
import { SearchResultLineLine } from '../../types'
export const getDirectory = async (dir: string, plugin: any) => {
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}`, plugin))]
} else {
result = [...result, fi.filename]
}
}
}
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]
})
}
})
return [...folders, ...files]
}
export const findLinesInStringWithMatch = (str: string, re: RegExp) => {
return str
@ -43,3 +81,28 @@ const splitLines = (matchResult: RegExpExecArray[], lineNumber: number) => {
return result
})
}
function getEOL(text) {
const m = text.match(/\r\n|\n/g);
const u = m && m.filter(a => a === '\n').length;
const w = m && m.length - u;
if (u === w) {
return EOL; // use the OS default
}
return u > w ? '\n' : '\r\n';
}
export const replaceTextInLine = (str: string, searchResultLine: SearchResultLineLine, newText: string) => {
return str
.split(/\r?\n/)
.map(function (line, i) {
if (i === searchResultLine.position.start.line) {
return searchResultLine.left + newText + searchResultLine.right
}
return line
}).join(getEOL(str))
}

@ -1,14 +1,11 @@
import React from 'react'
import React, { useEffect } from 'react'
import { createContext, useReducer } from 'react'
import { findLinesInStringWithMatch } from '../components/results/SearchHelper'
import { findLinesInStringWithMatch, getDirectory, replaceTextInLine } from '../components/results/SearchHelper'
import {
SearchingInitialState,
SearchReducer,
SearchResult,
SearchResultLine,
SearchResultLineLine,
SearchState
} from '../reducers/Reducer'
import { SearchState, SearchResult, SearchResultLine, SearchResultLineLine, SearchingInitialState } from '../types'
const filePathFilter = require('@jsdevtools/file-path-filter')
export interface SearchingStateInterface {
state: SearchState
@ -21,7 +18,11 @@ export interface SearchingStateInterface {
setWholeWord: (value: boolean) => void,
setSearchResults: (value: SearchResult[]) => void,
findText: (path: string) => Promise<SearchResultLine[]>,
hightLightInPath: (path:SearchResult, line:SearchResultLineLine) => void,
hightLightInPath: (result:SearchResult, line:SearchResultLineLine) => void,
replaceText: (result: SearchResult, line: SearchResultLineLine) => void,
reloadFile: (file:string) => void,
toggleCaseSensitive: () => void,
toggleMatchWholeWord: () => void,
}
export const SearchContext = createContext<SearchingStateInterface>(null)
@ -33,11 +34,11 @@ export const SearchProvider = ({
plugin = undefined
} = {}) => {
const [state, dispatch] = useReducer(reducer, initialState)
let reloadTimeOut: any = null
const value = {
state,
setFind: (value: string) => {
console.log('setFind: ' + value)
dispatch({
type: 'SET_FIND',
payload: value
@ -85,24 +86,95 @@ export const SearchProvider = ({
payload: value
})
},
reloadFile: async (file:string) => {
dispatch({
type: 'RELOAD_FILE',
payload: file
})
},
toggleCaseSensitive: () => {
dispatch({
type: 'TOGGLE_CASE_SENSITIVE',
payload: undefined
})
},
toggleMatchWholeWord: () => {
dispatch({
type: 'TOGGLE_MATCH_WHOLE_WORD',
payload: undefined
})
},
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')
let flags = 'g'
let find = state.find
if(!state.casesensitive) flags += 'i'
if(state.matchWord) find = `\\b${find}\\b`
const re = new RegExp(find, flags)
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)
},
replaceText: async(result: SearchResult, line: SearchResultLineLine) => {
await plugin.call('editor', 'discardHighlight')
await plugin.call('editor', 'highlight', line.position, result.path)
let content = await plugin.call('fileManager', 'readFile', result.path)
await plugin.call('fileManager','setFile', result.path, replaceTextInLine(content, line, state.replace))
}
}
const reloadStateForFile = async (file: string) => {
clearTimeout(reloadTimeOut)
reloadTimeOut = setTimeout(async () => {
await value.reloadFile(file)
}, 1000)
}
useEffect(() => {
plugin.on('filePanel', 'setWorkspace', () => {
value.setSearchResults(null)
})
plugin.on('fileManager', 'fileSaved', async file => {
await reloadStateForFile(file)
})
return () => {
plugin.off('fileManager', 'fileChanged')
plugin.off('filePanel', 'setWorkspace')
}
}, [])
useEffect(() => {
if (state.find) {
(async () => {
const res = await getDirectory('/', plugin)
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,
timeStamp: Date.now()
}
return r
})
value.setSearchResults(ob)
})()
}
}, [state.timeStamp])
return (
<>
<SearchContext.Provider value={value}>{children}</SearchContext.Provider>

@ -1,52 +1,4 @@
interface Action {
type: string
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 {
lines: SearchResultLineLine[]
}
export interface SearchResult {
filename: string,
path: string,
lines: SearchResultLine[],
searchComplete: boolean
}
export interface SearchState {
find: string,
searchResults: SearchResult[],
replace: string,
include: string,
exclude: string,
}
export const SearchingInitialState: SearchState = {
find: '',
replace: '',
include: '',
exclude: '',
searchResults: []
}
import { Action, SearchingInitialState, SearchState } from "../types"
export const SearchReducer = (state: SearchState = SearchingInitialState, action: Action) => {
switch (action.type) {
@ -54,6 +6,7 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
return {
...state,
find: action.payload,
timeStamp: Date.now()
}
case 'SET_REPLACE':
@ -66,12 +19,14 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
return {
...state,
include: action.payload,
timeStamp: Date.now()
}
case 'SET_EXCLUDE':
return {
...state,
exclude: action.payload,
timeStamp: Date.now()
}
case 'SET_SEARCH_RESULTS':
@ -79,7 +34,29 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
...state,
searchResults: action.payload,
}
case 'TOGGLE_CASE_SENSITIVE':
return {
...state,
casesensitive: !state.casesensitive,
timeStamp: Date.now()
}
case 'TOGGLE_MATCH_WHOLE_WORD':
return {
...state,
matchWord: !state.matchWord,
timeStamp: Date.now()
}
case 'RELOAD_FILE':
if (state.searchResults) {
const findFile = state.searchResults.find(file => file.filename === action.payload)
if (findFile) findFile.timeStamp = Date.now()
}
return {
...state,
}
default:
return {
...state,
}
}
}

@ -19,8 +19,70 @@
.find-part {
display: flex;
flex-direction: column;
padding: 2px;
}
.controls {
display: flex;
}
.search_tab .search_line_container {
display: flex;
flex-direction: row;
position: relative;
}
.search_tab .search_line {
width: 100%;
overflow: hidden;
display: flex;
}
.search_tab .search_control {
flex-grow: 0;
position: absolute;
right: 0px;
top: 0px;
}
.summary_right {
min-width: 0;
white-space: pre;
text-overflow: ellipsis;
overflow: hidden;
}
.search_tab .replace_strike {
text-decoration: line-through;
}
.summary_left {
white-space: pre;
}
.search_tab mark {
padding: 0;
}
.search_tab .search_line_container .search_control {
display: none;
}
.search_tab .search_line_container:hover .search_control {
display: block;
}
.search_tab .search_line_container:hover .search_line {
width: 93%;
}
.search-input {
display: flex;
flex-direction: row;
align-items: center;
}
.search_tab .checked {
background-color: var(--secondary);
}

@ -0,0 +1,55 @@
export interface Action {
type: string
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 {
lines: SearchResultLineLine[]
}
export interface SearchResult {
filename: string,
path: string,
lines: SearchResultLine[],
timeStamp: number
}
export interface SearchState {
find: string,
searchResults: SearchResult[],
replace: string,
include: string,
exclude: string,
casesensitive: boolean,
matchWord: boolean,
timeStamp: number
}
export const SearchingInitialState: SearchState = {
find: '',
replace: '',
include: '',
exclude: '',
searchResults: [],
casesensitive: false,
matchWord: false,
timeStamp: 0
}
Loading…
Cancel
Save