regex support

pull/5370/head
filip mertens 3 years ago
parent 6e35fc6fca
commit 57814f7522
  1. 33
      libs/remix-ui/search/src/lib/components/Find.tsx
  2. 2
      libs/remix-ui/search/src/lib/components/Search.tsx
  3. 23
      libs/remix-ui/search/src/lib/components/results/ResultFileName.tsx
  4. 6
      libs/remix-ui/search/src/lib/components/results/ResultSummary.tsx
  5. 3
      libs/remix-ui/search/src/lib/components/results/SearchHelper.ts
  6. 92
      libs/remix-ui/search/src/lib/context/context.tsx
  7. 6
      libs/remix-ui/search/src/lib/reducers/Reducer.ts
  8. 7
      libs/remix-ui/search/src/lib/search.css
  9. 2
      libs/remix-ui/search/src/lib/types/index.ts

@ -2,7 +2,13 @@ import React, { useContext } from 'react'
import { SearchContext } from '../context/context' import { SearchContext } from '../context/context'
export const Find = props => { export const Find = props => {
const { setFind, state, toggleCaseSensitive, toggleMatchWholeWord } = useContext(SearchContext) const {
setFind,
state,
toggleCaseSensitive,
toggleMatchWholeWord,
toggleUseRegex
} = useContext(SearchContext)
let timeOutId: any = null let timeOutId: any = null
const change = e => { const change = e => {
clearTimeout(timeOutId) clearTimeout(timeOutId)
@ -21,11 +27,13 @@ export const Find = props => {
></input> ></input>
<div className="controls"> <div className="controls">
<div <div
title="Match Case (⌥⌘C)" title="Match Case"
className={`monaco-custom-checkbox codicon codicon-case-sensitive ${state.casesensitive ? 'checked' : ''}`} className={`monaco-custom-checkbox codicon codicon-case-sensitive ${
state.casesensitive ? 'checked' : ''
}`}
role="checkbox" role="checkbox"
aria-checked="false" aria-checked="false"
aria-label="Match Case (⌥⌘C)" aria-label="Match Case"
aria-disabled="false" aria-disabled="false"
onClick={() => { onClick={() => {
toggleCaseSensitive() toggleCaseSensitive()
@ -33,7 +41,9 @@ export const Find = props => {
></div> ></div>
<div <div
title="Match Whole Word" title="Match Whole Word"
className={`monaco-custom-checkbox codicon codicon-whole-word ${state.matchWord ? 'checked' : ''}`} className={`monaco-custom-checkbox codicon codicon-whole-word ${
state.matchWord ? 'checked' : ''
}`}
role="checkbox" role="checkbox"
aria-checked="false" aria-checked="false"
aria-label="Match Whole Word" aria-label="Match Whole Word"
@ -42,6 +52,19 @@ export const Find = props => {
toggleMatchWholeWord() toggleMatchWholeWord()
}} }}
></div> ></div>
<div
title="Use Regular Expression"
className={`monaco-custom-checkbox codicon codicon-regex ${
state.useRegExp ? 'checked' : ''
}`}
role="checkbox"
aria-checked="false"
aria-label="Use Regular Expression"
aria-disabled="false"
onClick={() => {
toggleUseRegex()
}}
></div>
</div> </div>
</div> </div>
</div> </div>

@ -13,7 +13,7 @@ const plugin = props.plugin
return ( return (
<> <>
<div className="search_tab"> <div className="search_tab pl-2 pr-2">
<SearchProvider plugin={plugin}> <SearchProvider plugin={plugin}>
<Find></Find> <Find></Find>
<Replace></Replace> <Replace></Replace>

@ -1,18 +1,25 @@
import React from 'react' import React, { useEffect, useState } from 'react'
import { SearchResult } from '../../types' import { SearchResult } from '../../types'
import { getPathIcon } from '@remix-ui/helper'
interface ResultItemProps { interface ResultItemProps {
file: SearchResult file: SearchResult
} }
export const ResultFileName = (props: ResultItemProps) => { export const ResultFileName = (props: ResultItemProps) => {
const [icon, setIcon] = useState<string>('')
useEffect(() => {
if (props.file && props.file.path) {
setIcon(getPathIcon(props.file.path))
}
}, [props.file])
return ( return (
<div title={props.file.filename} className="input-group udapp_nameNbuts"> <>
<div className="udapp_titleText input-group-prepend"> {icon ? <div className={`${icon} caret caret_tv`}></div> : null}
<span className="input-group-text udapp_spanTitleText"> <div className="search_file_name ml-2">
{props.file.filename} {props.file.filename}
</span>
</div> </div>
</div> </>
) )
} }

@ -17,7 +17,11 @@ export const ResultSummary = (props: ResultSummaryProps) => {
const replace = async (line: SearchResultLineLine) => { const replace = async (line: SearchResultLineLine) => {
props.setLoading(true) props.setLoading(true)
await replaceText(props.searchResult, line) try{
await replaceText(props.searchResult, line)
}catch(e){
props.setLoading(false)
}
} }
return ( return (

@ -104,5 +104,8 @@ export const replaceTextInLine = (str: string, searchResultLine: SearchResultLin
}).join(getEOL(str)) }).join(getEOL(str))
} }
export function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}

@ -1,28 +1,38 @@
import React, { useEffect } from 'react' import React, { useEffect } from 'react'
import { createContext, useReducer } from 'react' import { createContext, useReducer } from 'react'
import { findLinesInStringWithMatch, getDirectory, replaceTextInLine } from '../components/results/SearchHelper'
import { import {
SearchReducer, findLinesInStringWithMatch,
} from '../reducers/Reducer' getDirectory,
import { SearchState, SearchResult, SearchResultLine, SearchResultLineLine, SearchingInitialState } from '../types' replaceTextInLine
} from '../components/results/SearchHelper'
import { SearchReducer } from '../reducers/Reducer'
import {
SearchState,
SearchResult,
SearchResultLine,
SearchResultLineLine,
SearchingInitialState
} from '../types'
import { filePathFilter } from '@jsdevtools/file-path-filter' import { filePathFilter } from '@jsdevtools/file-path-filter'
import { escapeRegExp } from 'lodash'
export interface SearchingStateInterface { export interface SearchingStateInterface {
state: SearchState state: SearchState
setFind: (value: string) => void setFind: (value: string) => void
setReplace: (value: string) => void setReplace: (value: string) => void
setInclude: (value: string) => void, setInclude: (value: string) => void
setExclude: (value: string) => void, setExclude: (value: string) => void
setCaseSensitive: (value: boolean) => void, setCaseSensitive: (value: boolean) => void
setRegex: (value: boolean) => void, setRegex: (value: boolean) => void
setWholeWord: (value: boolean) => void, setWholeWord: (value: boolean) => void
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) => void, replaceText: (result: SearchResult, line: SearchResultLineLine) => void
reloadFile: (file:string) => void, reloadFile: (file: string) => void
toggleCaseSensitive: () => void, toggleCaseSensitive: () => void
toggleMatchWholeWord: () => void, toggleMatchWholeWord: () => void
toggleUseRegex: () => void
} }
export const SearchContext = createContext<SearchingStateInterface>(null) export const SearchContext = createContext<SearchingStateInterface>(null)
@ -86,12 +96,18 @@ export const SearchProvider = ({
payload: value payload: value
}) })
}, },
reloadFile: async (file:string) => { reloadFile: async (file: string) => {
dispatch({ dispatch({
type: 'RELOAD_FILE', type: 'RELOAD_FILE',
payload: file payload: file
}) })
}, },
toggleUseRegex: () => {
dispatch({
type: 'TOGGLE_USE_REGEX',
payload: undefined
})
},
toggleCaseSensitive: () => { toggleCaseSensitive: () => {
dispatch({ dispatch({
type: 'TOGGLE_CASE_SENSITIVE', type: 'TOGGLE_CASE_SENSITIVE',
@ -104,29 +120,47 @@ export const SearchProvider = ({
payload: undefined payload: undefined
}) })
}, },
findText : async (path: string) => { findText: async (path: string) => {
if(!plugin) return if (!plugin) return
try { try {
if(state.find.length < 3) return if (state.find.length < 3) return
const text = await plugin.call('fileManager', 'readFile', path) const text = await plugin.call('fileManager', 'readFile', path)
let flags = 'g' let flags = 'g'
let find = state.find let find = state.find
if(!state.casesensitive) flags += 'i' if (!state.casesensitive) flags += 'i'
if(state.matchWord) find = `\\b${find}\\b` if (state.matchWord) find = `\\b${find}\\b`
if (!state.useRegExp) find = escapeRegExp(find)
const re = new RegExp(find, flags) const re = new RegExp(find, flags)
const result: SearchResultLine[] = findLinesInStringWithMatch(text, re) const result: SearchResultLine[] = findLinesInStringWithMatch(text, re)
return result return result
} catch (e) {} } catch (e) {}
}, },
hightLightInPath: async(result: SearchResult, line: SearchResultLineLine) => { hightLightInPath: async (
result: SearchResult,
line: SearchResultLineLine
) => {
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)
}, },
replaceText: async(result: SearchResult, line: SearchResultLineLine) => { replaceText: async (result: SearchResult, line: SearchResultLineLine) => {
await plugin.call('editor', 'discardHighlight') try {
await plugin.call('editor', 'highlight', line.position, result.path) await plugin.call('editor', 'discardHighlight')
const content = await plugin.call('fileManager', 'readFile', result.path) await plugin.call('editor', 'highlight', line.position, result.path)
await plugin.call('fileManager','setFile', result.path, replaceTextInLine(content, line, state.replace)) const content = await plugin.call(
'fileManager',
'readFile',
result.path
)
await plugin.call(
'fileManager',
'setFile',
result.path,
replaceTextInLine(content, line, state.replace)
)
} catch (e) {
throw new Error(e)
}
} }
} }
@ -173,8 +207,6 @@ export const SearchProvider = ({
} }
}, [state.timeStamp]) }, [state.timeStamp])
return ( return (
<> <>
<SearchContext.Provider value={value}>{children}</SearchContext.Provider> <SearchContext.Provider value={value}>{children}</SearchContext.Provider>

@ -40,6 +40,12 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
casesensitive: !state.casesensitive, casesensitive: !state.casesensitive,
timeStamp: Date.now() timeStamp: Date.now()
} }
case 'TOGGLE_USE_REGEX':
return {
...state,
useRegExp: !state.useRegExp,
timeStamp: Date.now()
}
case 'TOGGLE_MATCH_WHOLE_WORD': case 'TOGGLE_MATCH_WHOLE_WORD':
return { return {
...state, ...state,

@ -5,6 +5,7 @@
-ms-user-select: none; /* IE10+/Edge */ -ms-user-select: none; /* IE10+/Edge */
user-select: none; /* Standard */ user-select: none; /* Standard */
cursor: pointer; cursor: pointer;
align-items: center;
} }
.wrap_summary { .wrap_summary {
@ -86,3 +87,9 @@
.search_tab .checked { .search_tab .checked {
background-color: var(--secondary); background-color: var(--secondary);
} }
.search_tab .search_file_name {
text-overflow: ellipsis;
overflow: hidden;
text-transform: uppercase;
}

@ -40,6 +40,7 @@ export interface SearchState {
exclude: string, exclude: string,
casesensitive: boolean, casesensitive: boolean,
matchWord: boolean, matchWord: boolean,
useRegExp: boolean,
timeStamp: number timeStamp: number
} }
@ -51,5 +52,6 @@ export const SearchingInitialState: SearchState = {
searchResults: [], searchResults: [],
casesensitive: false, casesensitive: false,
matchWord: false, matchWord: false,
useRegExp: false,
timeStamp: 0 timeStamp: 0
} }
Loading…
Cancel
Save