Commits

Thibaut Colar committed 66a7199

Added powerful replace function to find command (multiple files & slect/deslect items to replace)
Small fix to Goto selection
bumped to version 1.0.6

Comments (0)

Files changed (6)

src/brie/build.fan

                `fan/command/`,
                `fan/widget/`]
     resDirs = [`res/`]
-    version = Version("1.0.5")
+    version = Version("1.0.6")
     meta    =  ["license.name"   : "Academic License",
                 "vcs.uri"   : "https://bitbucket.org/tcolar/camembert"]
     docSrc  = true

src/brie/fan/command/NavigationCommands.fan

         selected = matches.items.getSafe(table.selected.first ?: -1)
         dialog.close(ok)
       }
+      onSelect.add |e|
+      {
+        selected = matches.items.getSafe(table.selected.first ?: -1)
+      }
     }
 
     // open dialog
 }
 
 **************************************************************************
-** FindCmd
+** FindCmd / Repace
 **************************************************************************
 
 internal const class FindCmd : Cmd
     prompt := Text { }
     path := Text { text = FileUtil.pathDis(file) }
     matchCase := Button { mode = ButtonMode.check; text = "Match case"; selected = lastMatchCase.val }
+    replace := Button { mode = ButtonMode.check; text = "Replace (with preview)"; selected = false }
 
     selection := frame.curView?.curSelection ?: ""
     if (!selection.isEmpty && !selection.contains("\n"))
       ConstraintPane { minw=300; maxw=300; add(prompt) },
       Label { text="File" },
       ConstraintPane { minw=300; maxw=300; add(path) },
-      Label {}, // spacer
       matchCase,
+      replace,
     }
     dlg := Dialog(frame)
     {
       findMatches(matches, file, str, matchCase.selected)
     if (matches.isEmpty) { Dialog.openInfo(frame, "No matches: $str.toCode"); return }
 
-      // open in console
-    console.show(matches)
-    frame.goto(matches.first)
+    if(replace.selected)
+    {
+      // deal with replace
+      replaceAll(str, matches)
+    }
+    else
+    {
+      // show results in console
+      console.show(matches)
+      frame.goto(matches.first)
+    }
+  }
+
+  ** Replace dialog & action on matches
+  Void replaceAll(Str search, Item[] items)
+  {
+    font :=  ((Sys)Service.find(Sys#)).theme.font
+    matches := GotoMatchModel { itemFont = font; width = 800;}
+    matches.items = items
+    table := Table
+    {
+      it.headerVisible = false
+      it.model = matches
+      multi = true
+      selected = (0 .. items.size-1).toList // select all
+    }
+    newText := Text {it.text = search}
+    dialog := Dialog(frame)
+    {
+      title = "Replace All"
+      commands = [ok, cancel]
+      body = EdgePane
+      {
+        top = InsetPane(0, 0, 10, 0)
+        {
+          GridPane
+          {
+            numCols = 2
+            Label{it.text = "Replace with:"},
+            newText,
+          },
+        }
+        bottom = ConstraintPane
+        {
+          minw = maxw = matches.width+10
+          minh = maxh = 500
+          table,
+        }
+      }
+    }
+
+    // open dialog
+    if (dialog.open != Dialog.ok) return
+
+    // do replace
+    selectedItems := items.findAll |item, index| {table.selected.contains(index)}
+    FileUtil.replaceAll(selectedItems, search, newText.text, "\n")
   }
 
   Void findMatches(Item[] matches, File f, Str str, Bool matchCase)
       col := chars.index(str)
       while (col != null)
       {
-        dis := "$f.name(${linei+1}): $line.trim"
         span := Span(linei, col, linei, col+str.size)
+        dis := "$f.name(${linei+1}) [${col+1}-${col+1+str.size}]: $line.trim"
         matches.add(Item(f)
           {
             it.line = linei
       if (cs is FileSpace) dir = ((FileSpace)cs).dir
       if (dir != null) ((FindCmd)sys.commands.find).find(dir)
     }
-}
+}
+

src/brie/fan/space/FileSpace.fan

 
     // if path is file, make view for it
     Widget? view := null
-    echo(x.uri)
     if (!x.isDir) view = View.makeBest(frame, x)
 
     return EdgePane

src/brie/fan/util/FileUtil.fan

     return false
   }
 
+  ** Replace found items with new text
+  static Void replaceAll(Item[] items, Str oldText, Str newText, Str delimiter)
+  {
+    // file currently worked on
+    File? curFile
+    //  text lines of file currently worked on
+    Str[]? lines
+    // Index of current line we are working on
+    Int curLine
+    // current offset in line (because item spans are "off" after mmultiple replaces in same line)
+    offset := 0
+    step := newText.size - oldText.size
+
+    items.each |item|
+    {
+      if(item.file != curFile)
+      {
+        if(curFile != null)
+          saveLines(curFile, lines, delimiter)
+        curFile = item.file
+        try
+          lines = curFile.readAllLines
+        catch(Err e) {e.trace; lines=null}
+        offset = 0
+      }
+      if(item.span.start.line != item.span.end.line)
+        throw Err("Replace only supported within a single line.")
+      line := item.span.start.line
+      if(line != curLine)
+      {
+        offset = 0
+        curLine = line
+      }
+      if(lines != null)
+      {
+        l := lines[line]
+        start :=  item.span.start.col + offset
+        end := item.span.end.col + offset
+        lines[line] = l[0 ..< start] + newText + l[end .. -1]
+        offset += step
+      }
+    }
+    // last file
+    if(curFile!=null)
+      saveLines(curFile, lines, delimiter)
+  }
+
+  static internal Void saveLines(File file, Str[] lines, Str delimiter)
+  {
+    lastLine := lines.size-1
+    out := file.out
+    try
+    {
+      lines.each |Str line, Int i|
+      {
+        out.print(line)
+        if (i != lines.size-1 || line.isEmpty)
+          out.print(delimiter)
+      }
+    }
+    catch(Err e)
+      e.trace
+    finally
+      out.close
+  }
 }

src/brie/fan/widget/Item.fan

     {
       MenuItem
       {
-        it.text = "Find in \"$dis\""
+        it.text = "Find in \"$file.name\""
         it.onAction.add |e|
           { (frame.sys.commands.find as FindCmd).find(file) }
       },
-
-find/replace .. local / global
-
-Bocce: Ctrl+A -> select all
-
 Bug: When going in the text area, the first keystroke is lost (gains focus but do not act on the keystroke)