Skip to content

Commit

Permalink
fix filetree cut/paste item issue; update EntryNavItem style
Browse files Browse the repository at this point in the history
  • Loading branch information
oligo committed Nov 20, 2024
1 parent 5950b2c commit aa708b0
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 18 deletions.
102 changes: 102 additions & 0 deletions explorer/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,13 @@ func (n *EntryNode) Move(nodePath string) error {
return err
}

// if nodePath is a descendant of the root tree, refresh its parent to clean dirty nodes.
parent := findNodeInTree(n, filepath.Dir(nodePath))
if parent != nil && parent != n {
log.Println("hhhh rereshed: ", parent.Path)
parent.Refresh(hiddenFileFilter)
}

return n.Refresh(hiddenFileFilter)
}

Expand All @@ -287,6 +294,8 @@ func (n *EntryNode) UpdateName(newName string) error {
newPath := filepath.Join(filepath.Dir(n.Path), newName)
defer func() {
n.Path = filepath.Clean(newPath)
st, _ := os.Stat(n.Path)
n.FileInfo = st
}()

return os.Rename(n.Path, newPath)
Expand Down Expand Up @@ -497,3 +506,96 @@ func copySymLink(src, dst string) error {
}
return os.Symlink(link, dst)
}

// find longest common path for path1 and path2. path1 and path2 must be
// absolute paths.
func longestCommonPath(path1, path2 string) string {
if path1 == path2 {
return path1
}

lastSeq := -1
idx := 0
for i := 0; i < min(len(path1), len(path2)); i++ {
if path1[i] != path2[i] {
break
}

idx = i
if path1[i] == os.PathSeparator {
lastSeq = i
}
}

if lastSeq < 0 {
return ""
}

if idx > lastSeq && idx == min(len(path1), len(path2))-1 {
if len(path1) > len(path2) && path1[idx+1] == os.PathSeparator {
return path1[:idx+1]

} else if len(path2) > len(path1) && path2[idx+1] == os.PathSeparator {
return path2[:idx+1]
}
}

// without the trailing seqarator.
return path1[:lastSeq]

}

// find the node that has its Path equals path.
// The node is an artitary node of a tree.
func findNodeInTree(node *EntryNode, path string) *EntryNode {
log.Println("running findNodeInTree: ", node.Path, path)

if path == node.Path {
return node
}

commonPath := longestCommonPath(node.Path, path)
if commonPath == "" {
return nil
}

commonNode := node
for {
if commonNode.Path == commonPath {
break
}

commonNode = commonNode.Parent
if commonNode == nil {
return nil
}
}

if commonNode.Path == path {
return commonNode
}

// search decencents
children := commonNode.children
LOOP:
for len(children) > 0 {
for _, child := range children {
if child.Path == path {
return child
}
if child.Path == node.Path {
continue
}
if len(child.children) > 0 {
children = child.children
log.Println("loop: ", child.Path)

goto LOOP
}
}
break
}

return nil

}
62 changes: 45 additions & 17 deletions explorer/tree_style.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"log"
"slices"
"strings"
"unsafe"

"gioui.org/gesture"
"gioui.org/io/clipboard"
Expand Down Expand Up @@ -95,6 +96,14 @@ func (eitem *EntryNavItem) OnSelect() {
eitem.expaned = !eitem.expaned
if eitem.expaned {
eitem.needSync = true

for _, child := range eitem.children {
child := child.(*EntryNavItem)
if child.isCut {
child.isCut = false
}
}

}

if eitem.state.Kind() == FileNode && eitem.onSelectFunc != nil {
Expand All @@ -112,7 +121,7 @@ func (eitem *EntryNavItem) Layout(gtx layout.Context, th *theme.Theme, textColor
defer pointer.PassOp{}.Push(gtx.Ops).Pop()
defer clip.Rect(image.Rectangle{Max: dims.Size}).Push(gtx.Ops).Pop()
if eitem.isCut {
defer paint.PushOpacity(gtx.Ops, 0.7).Pop()
defer paint.PushOpacity(gtx.Ops, 0.6).Pop()
}
event.Op(gtx.Ops, eitem)
call.Add(gtx.Ops)
Expand Down Expand Up @@ -144,9 +153,8 @@ func (eitem *EntryNavItem) layout(gtx layout.Context, th *theme.Theme, textColor
})
}),
layout.Flexed(1, func(gtx C) D {
return layout.W.Layout(gtx, func(gtx C) D {
return eitem.label.Layout(gtx, th)
})
gtx.Constraints.Min.X = gtx.Constraints.Max.X
return eitem.label.Layout(gtx, th)
}),
)

Expand Down Expand Up @@ -292,31 +300,46 @@ func (eitem *EntryNavItem) Kind() NodeKind {
}

// read data from clipboard.
func (eitem *EntryNavItem) OnPaste(data string, removeOld bool) error {
func (eitem *EntryNavItem) OnPaste(data string, removeOld bool, src *EntryNavItem) error {
// when paste destination is a normal file node, use its parent dir to ease the CUT/COPY operations.
dest := eitem
if !eitem.IsDir() && eitem.parent != nil {
dest = eitem.parent.(*EntryNavItem)
}

pathes := strings.Split(string(data), "\n")
if removeOld {
for _, p := range pathes {
err := eitem.state.Move(p)
err := dest.state.Move(p)
if err != nil {
return err
}

if src != nil && src.parent != nil {
log.Println("onPaste src is :", src.Path())
parent := src.parent.(*EntryNavItem)
parent.children = slices.DeleteFunc(parent.children, func(chd navi.NavItem) bool {
entry := chd.(*EntryNavItem)
return entry.Path() == p
})
}
}
} else {
for _, p := range pathes {
err := eitem.state.Copy(p)
err := dest.state.Copy(p)
if err != nil {
return err
}
}
}

eitem.needSync = true
eitem.expaned = true
dest.needSync = true
dest.expaned = true
return nil
}

func (eitem *EntryNavItem) OnCopyOrCut(gtx C, isCut bool) {
gtx.Execute(clipboard.WriteCmd{Type: mimeText, Data: io.NopCloser(asPayload(eitem.Path(), isCut))})
gtx.Execute(clipboard.WriteCmd{Type: mimeText, Data: io.NopCloser(asPayload(eitem, eitem.Path(), isCut))})
eitem.isCut = isCut
}

Expand All @@ -338,7 +361,7 @@ func (eitem *EntryNavItem) Update(gtx C) error {

switch event := ke.(type) {
case key.Event:
if !event.Modifiers.Contain(key.ModShortcut) || event.State != key.Press {
if !event.Modifiers.Contain(key.ModShortcut) {
break
}

Expand All @@ -360,13 +383,13 @@ func (eitem *EntryNavItem) Update(gtx C) error {
if event.Type == mimeText {
p, err := toPayload(event.Open())
if err == nil {
if err := eitem.OnPaste(p.Data, p.IsCut); err != nil {
if err := eitem.OnPaste(p.Data, p.IsCut, p.GetSrc()); err != nil {
return err
}
} else {
content, err := io.ReadAll(event.Open())
if err == nil {
if err := eitem.OnPaste(string(content), false); err != nil {
if err := eitem.OnPaste(string(content), false, nil); err != nil {
return err
}
}
Expand All @@ -389,12 +412,17 @@ const (
)

type payload struct {
IsCut bool `json:"isCut"`
Data string `json:"data"`
IsCut bool `json:"isCut"`
Data string `json:"data"`
Src uintptr `json:"src"`
}

func (p *payload) GetSrc() *EntryNavItem {
return (*EntryNavItem)(unsafe.Pointer(p.Src))
}

func asPayload(data string, isCut bool) io.Reader {
p := payload{Data: data, IsCut: isCut}
func asPayload(src *EntryNavItem, data string, isCut bool) io.Reader {
p := payload{Data: data, IsCut: isCut, Src: uintptr(unsafe.Pointer(src))}
var buf bytes.Buffer
err := json.NewEncoder(&buf).Encode(p)
if err != nil {
Expand Down
105 changes: 105 additions & 0 deletions explorer/tree_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package explorer

import "testing"

func TestFindNodeInTree(t *testing.T) {
root := &EntryNode{Path: "/a"}
root.children = append(root.children, &EntryNode{Path: "/a/b", Parent: root})

ab := root.children[0]
ab.children = []*EntryNode{
{Path: "/a/b/c", Parent: ab},
{Path: "/a/b/d", Parent: ab},
{Path: "/a/b/e", Parent: ab},
}

abe := ab.children[2]
abe.children = []*EntryNode{
{Path: "/a/b/e/k", Parent: abe},
{Path: "/a/b/e/l", Parent: abe},
{Path: "/a/b/e/m", Parent: abe},
}

cases := []struct {
current *EntryNode
path string
want string
}{
{current: root, path: "/a/b/c", want: "/a/b/c"},
{current: root.children[0].children[0], path: "/a/b/e/k", want: "/a/b/e/k"},
{current: root.children[0].children[2], path: "/a/b", want: "/a/b"},
{current: root.children[0], path: "/out", want: ""},
}

for _, tc := range cases {
t.Run(tc.want, func(t *testing.T) {
found := findNodeInTree(tc.current, tc.path)
if found == nil {
if tc.want != "" {
t.Fail()
}
} else {
if tc.want != found.Path {
t.Fail()
}
}
})

}

}

func TestLongestCommonPath(t *testing.T) {
cases := []struct {
pair []string
want string
}{
{
pair: []string{
"/home/User/Desktop/gfg/test",
"/home/User/Desktop/gfg/file",
"/home/User/Desktop/geeks/folders",
},
want: "/home/User/Desktop/gfg",
},
{
pair: []string{
"/home/User/Desktop/gfg/test",
"/home/User/Desktop/gfg/test",
},
want: "/home/User/Desktop/gfg/test",
},
{
pair: []string{
"/home/User/Desktop/abc/test",
"/home/User/Desktop/gfg/test",
},
want: "/home/User/Desktop",
},
{
pair: []string{
"/home/User/Desktop/abc/test1",
"/home/User/Desktop/abc/test",
},
want: "/home/User/Desktop/abc",
},
{
pair: []string{
"/home/User/Desktop/abc/test/sudir",
"/home/User/Desktop/abc/test",
},
want: "/home/User/Desktop/abc/test",
},
}

for _, tc := range cases {
t.Run(tc.want, func(t *testing.T) {
cp := longestCommonPath(tc.pair[0], tc.pair[1])
if cp != tc.want {
t.Logf("actual: %s, wanted: %s", cp, tc.want)
t.Fail()
}
})
}

}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
golang.org/x/exp v0.0.0-20240707233637-46b078467d37
golang.org/x/exp/shiny v0.0.0-20240707233637-46b078467d37
golang.org/x/image v0.18.0
golang.org/x/sys v0.25.0
golang.org/x/text v0.16.0
)

Expand All @@ -21,5 +22,4 @@ require (
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/sys v0.25.0 // indirect
)

0 comments on commit aa708b0

Please sign in to comment.