diff --git a/app/src/history/history.ts b/app/src/history/history.ts index 134d3a65e..2f2c76c5d 100644 --- a/app/src/history/history.ts +++ b/app/src/history/history.ts @@ -266,6 +266,7 @@ export const openHistory = () => { delete format sync + replace diff --git a/kernel/model/history.go b/kernel/model/history.go index 520cd3e9a..b6cebd444 100644 --- a/kernel/model/history.go +++ b/kernel/model/history.go @@ -547,15 +547,20 @@ func (box *Box) recentModifiedDocs() (ret []string) { } const ( - HistoryOpClean = "clean" - HistoryOpUpdate = "update" - HistoryOpDelete = "delete" - HistoryOpFormat = "format" - HistoryOpSync = "sync" + HistoryOpClean = "clean" + HistoryOpUpdate = "update" + HistoryOpDelete = "delete" + HistoryOpFormat = "format" + HistoryOpSync = "sync" + HistoryOpReplace = "replace" ) func GetHistoryDir(suffix string) (ret string, err error) { - ret = filepath.Join(util.HistoryDir, time.Now().Format("2006-01-02-150405")+"-"+suffix) + return getHistoryDir(suffix, time.Now()) +} + +func getHistoryDir(suffix string, t time.Time) (ret string, err error) { + ret = filepath.Join(util.HistoryDir, t.Format("2006-01-02-150405")+"-"+suffix) if err = os.MkdirAll(ret, 0755); nil != err { logging.LogErrorf("make history dir failed: %s", err) return @@ -584,7 +589,7 @@ func ReindexHistory() (err error) { return } -var validOps = []string{HistoryOpClean, HistoryOpUpdate, HistoryOpDelete, HistoryOpFormat, HistoryOpSync} +var validOps = []string{HistoryOpClean, HistoryOpUpdate, HistoryOpDelete, HistoryOpFormat, HistoryOpSync, HistoryOpReplace} const ( HistoryTypeDocName = 0 diff --git a/kernel/model/search.go b/kernel/model/search.go index 1e7b56b00..7d77053b3 100644 --- a/kernel/model/search.go +++ b/kernel/model/search.go @@ -20,8 +20,9 @@ import ( "bytes" "errors" "fmt" - "github.com/siyuan-note/siyuan/kernel/task" + "os" "path" + "path/filepath" "regexp" "sort" "strconv" @@ -35,10 +36,12 @@ import ( "github.com/88250/lute/lex" "github.com/88250/lute/parse" "github.com/jinzhu/copier" + "github.com/siyuan-note/filelock" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/conf" "github.com/siyuan-note/siyuan/kernel/search" "github.com/siyuan-note/siyuan/kernel/sql" + "github.com/siyuan-note/siyuan/kernel/task" "github.com/siyuan-note/siyuan/kernel/treenode" "github.com/siyuan-note/siyuan/kernel/util" "github.com/xrash/smetrics" @@ -215,16 +218,42 @@ func FindReplace(keyword, replacement string, ids []string, method int) (err err ids = gulu.Str.RemoveDuplicatedElem(ids) var renameRoots []*ast.Node renameRootTitles := map[string]string{} + cachedTrees := map[string]*parse.Tree{} + now := time.Now() for _, id := range ids { - var tree *parse.Tree - tree, err = loadTreeByBlockID(id) - if nil != err { - return + bt := treenode.GetBlockTree(id) + if nil == bt { + continue + } + + tree := cachedTrees[bt.RootID] + if nil != tree { + continue + } + + tree, _ = loadTreeByBlockID(id) + if nil == tree { + continue + } + + generateReplaceHistory(tree, now) + cachedTrees[bt.RootID] = tree + } + + for _, id := range ids { + bt := treenode.GetBlockTree(id) + if nil == bt { + continue + } + + tree := cachedTrees[bt.RootID] + if nil == tree { + continue } node := treenode.GetNodeInTree(tree, id) if nil == node { - return + continue } ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus { @@ -338,6 +367,33 @@ func FindReplace(keyword, replacement string, ids []string, method int) (err err return } +func generateReplaceHistory(tree *parse.Tree, t time.Time) { + historyDir, err := getHistoryDir(HistoryOpReplace, t) + if nil != err { + logging.LogErrorf("get history dir failed: %s", err) + return + } + + historyPath := filepath.Join(historyDir, tree.Box, tree.Path) + if err = os.MkdirAll(filepath.Dir(historyPath), 0755); nil != err { + logging.LogErrorf("generate history failed: %s", err) + return + } + + var data []byte + if data, err = filelock.ReadFile(filepath.Join(util.DataDir, tree.Box, tree.Path)); err != nil { + logging.LogErrorf("generate history failed: %s", err) + return + } + + if err = gulu.File.WriteFileSafer(historyPath, data, 0644); err != nil { + logging.LogErrorf("generate history failed: %s", err) + return + } + + indexHistoryDir(filepath.Base(historyDir), util.NewLute()) +} + // FullTextSearchBlock 搜索内容块。 // // method:0:关键字,1:查询语法,2:SQL,3:正则表达式