- {state.find ?
{state.count} results
: null}
- {state.find && state.count >= state.maxResults?
The result set only contains a subset of all matches
Please narrow down your search.
: null}
+ {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.searchResults &&
state.searchResults.map((result, index) => {
return index
: null
diff --git a/libs/remix-ui/search/src/lib/components/results/SearchHelper.ts b/libs/remix-ui/search/src/lib/components/results/SearchHelper.ts
index 88ae6b1de1..fb6b096df2 100644
--- a/libs/remix-ui/search/src/lib/components/results/SearchHelper.ts
+++ b/libs/remix-ui/search/src/lib/components/results/SearchHelper.ts
@@ -92,6 +92,9 @@ function getEOL(text) {
return u > w ? '\n' : '\r\n';
}
+export const replaceAllInFile = (string: string, re:RegExp, newText: string) => {
+ return string.replace(re, newText)
+}
export const replaceTextInLine = (str: string, searchResultLine: SearchResultLineLine, newText: string) => {
return str
diff --git a/libs/remix-ui/search/src/lib/context/context.tsx b/libs/remix-ui/search/src/lib/context/context.tsx
index 267fa2a92e..9d131ba620 100644
--- a/libs/remix-ui/search/src/lib/context/context.tsx
+++ b/libs/remix-ui/search/src/lib/context/context.tsx
@@ -3,6 +3,7 @@ import { createContext, useReducer } from 'react'
import {
findLinesInStringWithMatch,
getDirectory,
+ replaceAllInFile,
replaceTextInLine
} from '../components/results/SearchHelper'
import { SearchReducer } from '../reducers/Reducer'
@@ -11,7 +12,8 @@ import {
SearchResult,
SearchResultLine,
SearchResultLineLine,
- SearchingInitialState
+ SearchingInitialState,
+ undoBufferRecord
} from '../types'
import { filePathFilter } from '@jsdevtools/file-path-filter'
import { escapeRegExp } from 'lodash'
@@ -29,7 +31,7 @@ export interface SearchingStateInterface {
setSearchResults: (value: SearchResult[]) => void
findText: (path: string) => Promise
hightLightInPath: (result: SearchResult, line: SearchResultLineLine) => void
- replaceText: (result: SearchResult, line: SearchResultLineLine) => void
+ replaceText: (result: SearchResult, line: SearchResultLineLine) => Promise
reloadFile: (file: string) => void
toggleCaseSensitive: () => void
toggleMatchWholeWord: () => void
@@ -37,6 +39,9 @@ export interface SearchingStateInterface {
setReplaceWithoutConfirmation: (value: boolean) => void
disableForceReload: (file: string) => void
updateCount: (count: number, file: string) => void
+ replaceAllInFile: (result: SearchResult) => Promise
+ undoReplace: (buffer: undoBufferRecord) => Promise
+ clearUndo: () => void
}
export const SearchContext = createContext(null)
@@ -145,7 +150,7 @@ export const SearchProvider = ({
updateCount: (count: number, file: string) => {
dispatch({
type: 'UPDATE_COUNT',
- payload: {count, file}
+ payload: { count, file }
})
},
findText: async (path: string) => {
@@ -153,15 +158,9 @@ export const SearchProvider = ({
try {
if (state.find.length < 1) return
const text = await plugin.call('fileManager', 'readFile', path)
- let flags = 'g'
- let find = state.find
- if (!state.casesensitive) flags += 'i'
- if (!state.useRegExp) find = escapeRegExp(find)
- if (state.matchWord) find = `\\b${find}\\b`
- const re = new RegExp(find, flags)
- const result: SearchResultLine[] = findLinesInStringWithMatch(text, re)
+ const result: SearchResultLine[] = findLinesInStringWithMatch(text, createRegExFromFind())
return result
- } catch (e) {}
+ } catch (e) { }
},
hightLightInPath: async (
result: SearchResult,
@@ -180,26 +179,81 @@ export const SearchProvider = ({
'readFile',
result.path
)
-
+ const replaced = replaceTextInLine(content, line, state.replace)
await plugin.call(
'fileManager',
'setFile',
result.path,
- replaceTextInLine(content, line, state.replace)
+ replaced
)
+ setUndoState(content, replaced, result.path)
} catch (e) {
throw new Error(e)
}
+ },
+ 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
+ )
+ setUndoState(content, replaced, result.path)
+ },
+ undoReplace: async (buffer: undoBufferRecord) => {
+ const content = await plugin.call(
+ 'fileManager',
+ 'readFile',
+ buffer.path
+ )
+ if (buffer.newContent !== content) {
+ value.clearUndo()
+ throw new Error('Can not undo replace, file has been changed.')
+
+ }
+ await plugin.call(
+ 'fileManager',
+ 'setFile',
+ buffer.path,
+ buffer.oldContent
+ )
+ await plugin.call(
+ 'fileManager',
+ 'open',
+ buffer.path
+ )
+ value.clearUndo()
+ },
+ clearUndo: () => {
+ dispatch ({
+ type: 'CLEAR_UNDO',
+ payload: undefined
+ })
}
}
+
+
const reloadStateForFile = async (file: string) => {
- await value.reloadFile(file)
+ await value.reloadFile(file)
}
useEffect(() => {
plugin.on('filePanel', 'setWorkspace', () => {
value.setSearchResults(null)
+ value.clearUndo()
})
plugin.on('fileManager', 'fileSaved', async file => {
await reloadStateForFile(file)
@@ -215,22 +269,46 @@ export const SearchProvider = ({
const results = []
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.startsWith('*.')) path = path.replace(/(\*\.)/g, '**/*.')
+ if (path.endsWith('/*') && !path.endsWith('/**/*')) path = path.replace(/(\*)/g, '**/*.*')
results.push(path)
})
return results
}
+ const setUndoState = async (oldContent: string, newContent: string, path: string) => {
+ const workspace = await plugin.call('filePanel', 'getCurrentWorkspace')
+ const undo = {
+ oldContent,
+ newContent,
+ path,
+ workspace
+ }
+ dispatch({
+ type: 'SET_UNDO',
+ payload: undo
+ })
+ }
+
+ const createRegExFromFind = () => {
+ let flags = 'g'
+ let find = state.find
+ if (!state.casesensitive) flags += 'i'
+ if (!state.useRegExp) find = escapeRegExp(find)
+ if (state.matchWord) find = `\\b${find}\\b`
+ const re = new RegExp(find, flags)
+ return re
+ }
+
useEffect(() => {
if (state.find) {
(async () => {
const files = await getDirectory('/', plugin)
const pathFilter: any = {}
- if (state.include){
+ if (state.include) {
pathFilter.include = setGlobalExpression(state.include)
}
- if (state.exclude){
+ if (state.exclude) {
pathFilter.exclude = setGlobalExpression(state.exclude)
}
const filteredFiles = files.filter(filePathFilter(pathFilter)).map(file => {
diff --git a/libs/remix-ui/search/src/lib/reducers/Reducer.ts b/libs/remix-ui/search/src/lib/reducers/Reducer.ts
index 47b9cef21f..b40d8ab4c6 100644
--- a/libs/remix-ui/search/src/lib/reducers/Reducer.ts
+++ b/libs/remix-ui/search/src/lib/reducers/Reducer.ts
@@ -1,4 +1,4 @@
-import { Action, SearchingInitialState, SearchState } from "../types"
+import { Action, SearchingInitialState, SearchState, undoBufferRecord } from "../types"
export const SearchReducer = (state: SearchState = SearchingInitialState, action: Action) => {
switch (action.type) {
@@ -41,21 +41,50 @@ export const SearchReducer = (state: SearchState = SearchingInitialState, action
searchResults: action.payload,
count: 0
}
+ case 'SET_UNDO': {
+ const undoState = {
+ newContent : action.payload.newContent,
+ oldContent: action.payload.oldContent,
+ path: action.payload.path,
+ workspace: action.payload.workspace,
+ timeStamp: Date.now()
+ }
+ state.undoBuffer = [undoState]
+ return {
+ ...state,
+ }
+ }
+ case 'CLEAR_UNDO': {
+ state.undoBuffer = []
+ return {
+ ...state,
+ }
+ }
case 'UPDATE_COUNT':
if (state.searchResults) {
const findFile = state.searchResults.find(file => file.filename === action.payload.file)
let count = 0
+ let fileCount = 0
+ let clipped = false
if (findFile) {
findFile.count = action.payload.count
}
state.searchResults.forEach(file => {
- if (file.count) {
- count += file.count
+ if (file.count) {
+ if(file.count > state.maxLines) {
+ clipped = true
+ count += state.maxLines
+ }else{
+ count += file.count
+ }
+ fileCount++
}
})
return {
...state,
- count: count
+ count: count,
+ fileCount,
+ clipped
}
} else {
return state
diff --git a/libs/remix-ui/search/src/lib/types/index.ts b/libs/remix-ui/search/src/lib/types/index.ts
index 05f2200b1d..aa11435099 100644
--- a/libs/remix-ui/search/src/lib/types/index.ts
+++ b/libs/remix-ui/search/src/lib/types/index.ts
@@ -35,6 +35,14 @@ export interface SearchResult {
count: number
}
+export interface undoBufferRecord {
+ workspace: string,
+ path: string,
+ newContent: string,
+ timeStamp: number,
+ oldContent: string
+}
+
export interface SearchState {
find: string,
searchResults: SearchResult[],
@@ -48,9 +56,11 @@ export interface SearchState {
useRegExp: boolean,
timeStamp: number,
count: number,
- maxResults: number
+ fileCount: number,
maxFiles: number,
maxLines: number
+ clipped: boolean,
+ undoBuffer: undoBufferRecord[],
}
export const SearchingInitialState: SearchState = {
@@ -66,7 +76,9 @@ export const SearchingInitialState: SearchState = {
replaceWithOutConfirmation: false,
timeStamp: 0,
count: 0,
- maxResults: 1500,
- maxFiles: 100,
- maxLines: 200
+ fileCount: 0,
+ maxFiles: 5000,
+ maxLines: 5000,
+ clipped: false,
+ undoBuffer: null
}
\ No newline at end of file