diff --git a/completions/bash/g-completion.bash b/completions/bash/g-completion.bash index 7c8c49b..98442f3 100644 --- a/completions/bash/g-completion.bash +++ b/completions/bash/g-completion.bash @@ -98,7 +98,8 @@ _g() { --literal -O --no-owner -Q --quote-name -g -l - --long -o" + --long -o + --extended -@" COMPREPLY=($(compgen -W "${opts}" -- ${cur})) return 0 diff --git a/completions/fish/g.fish b/completions/fish/g.fish index a51a33b..92d48af 100644 --- a/completions/fish/g.fish +++ b/completions/fish/g.fish @@ -101,4 +101,5 @@ complete -c g -s Q -l quote-name -d "enclose entry names in double quotes" complete -c g -s g -d "like -all, but do not list owner" complete -c g -s l -l long -d "use a long listing format" complete -c g -s o -d "like -all, but do not list group information" +complete -c g -s @ -l extended -d "list each file's extended attributes and sizes in long listing" complete -c g -f -a "(__fish_complete_path)" diff --git a/completions/zsh/_g b/completions/zsh/_g index 0a702e0..224a483 100644 --- a/completions/zsh/_g +++ b/completions/zsh/_g @@ -125,6 +125,8 @@ _g() { '-l[use a long listing format]' '--long[use a long listing format]' '-o[like -all, but do not list group information]' + '-@[list each file's extended attributes and sizes in long listing]' + '--extended[list each file's extended attributes and sizes in long listing]' '*:filename:_files' ) diff --git a/go.mod b/go.mod index ee26314..946e01f 100644 --- a/go.mod +++ b/go.mod @@ -87,4 +87,5 @@ require ( golang.org/x/net v0.25.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect + howett.net/plist v1.0.1 // indirect ) diff --git a/go.sum b/go.sum index f2b424d..ee2ce76 100644 --- a/go.sum +++ b/go.sum @@ -99,6 +99,7 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOl github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedib0t/go-pretty/v6 v6.5.9 h1:ACteMBRrrmm1gMsXe9PSTOClQ63IXDUt03H5U+UV8OU= github.com/jedib0t/go-pretty/v6 v6.5.9/go.mod h1:zbn98qrYlh95FIhwwsbIip0LYpwSG8SUOScs+v9/t0E= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/junegunn/fzf v0.0.0-20240109033114-e4d0f7acd516 h1:QVNsMfKwLPfv0Ajq6LlBgS/dchHP9c/i3lTQRyhkxRs= @@ -298,6 +299,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -305,3 +307,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= +howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= diff --git a/internal/cli/g.go b/internal/cli/g.go index 4be0d66..4206815 100644 --- a/internal/cli/g.go +++ b/internal/cli/g.go @@ -58,6 +58,7 @@ var ( groupEnabler = contents.NewGroupEnabler() gitEnabler = contents.NewGitEnabler() gitRepoEnabler = contents.NewGitRepoEnabler() + extendedEnabler = contents.ExtendedEnabler{} nameToDisplay = contents.NewNameEnabler() flagsEnabler = contents.NewFlagsEnabler() depthLimitMap map[string]int @@ -202,8 +203,8 @@ func dive( continue } // store its parent and level/depth - info.Cache["parent"] = []byte(parent) - info.Cache["level"] = []byte(strconv.Itoa(depth)) + info.Cache["parent"] = parent + info.Cache["level"] = depth infos.AppendTo(info) if f.IsDir() { wg.Add(1) @@ -523,6 +524,11 @@ var logic = func(context *cli.Context) error { contentFunc = append(contentFunc, gitEnabler.Enable(r)) } + extended := context.Bool("extended") + if extended { + noOutputFunc = append(noOutputFunc, extendedEnabler.Enable(r)) + } + gitBranch := context.Bool("git-repo-branch") if gitBranch { contentFunc = append(contentFunc, gitRepoEnabler.Enable(r)) @@ -619,7 +625,7 @@ var logic = func(context *cli.Context) error { case "never": case "always": nameToDisplay.SetHyperlink() - display.IncludeHyperlink = true + global.IncludeHyperlink = true default: fallthrough case "auto": @@ -629,7 +635,7 @@ var logic = func(context *cli.Context) error { case *display.JsonPrinter: default: nameToDisplay.SetHyperlink() - display.IncludeHyperlink = true + global.IncludeHyperlink = true } } } @@ -800,7 +806,7 @@ var logic = func(context *cli.Context) error { infos = itemFilter.Filter(infos...) if tree { - infos[0].Cache["level"] = []byte("0") + infos[0].Cache["level"] = 0 } goto final } @@ -815,7 +821,7 @@ var logic = func(context *cli.Context) error { infos = append( infos, info, ) - infos[0].Cache["level"] = []byte("0") + infos[0].Cache["level"] = 0 if depth >= 1 || depth < 0 { wg := sync.WaitGroup{} infoSlice := util.NewSlice[*item.FileInfo](10) @@ -981,7 +987,7 @@ var logic = func(context *cli.Context) error { for _, part := range allPart { content, ok := it.Get(part) if ok && part != contents.NameName { - l := display.WidthNoHyperLinkLen(content.String()) + l := util.WidthNoHyperLinkLen(content.String()) if l > longestEachPart[part] { longestEachPart[part] = l } @@ -994,7 +1000,7 @@ var logic = func(context *cli.Context) error { for _, part := range allPart { if part != contents.NameName { content, _ := it.Get(part) - l := display.WidthNoHyperLinkLen(content.String()) + l := util.WidthNoHyperLinkLen(content.String()) if l < longestEachPart[part] { expand := content.SetPrefix if align.IsLeft(part) { diff --git a/internal/cli/view.go b/internal/cli/view.go index 6711bd1..03482bb 100644 --- a/internal/cli/view.go +++ b/internal/cli/view.go @@ -837,6 +837,13 @@ var viewFlag = []cli.Flag{ DisableDefaultText: true, Category: "VIEW", }, + &cli.BoolFlag{ + Name: "extended", + Aliases: []string{"@"}, + Usage: `list each file's extended attributes and sizes in long listing`, + DisableDefaultText: true, + Category: "VIEW", + }, } func setLimit(context *cli.Context) error { diff --git a/internal/content/charset.go b/internal/content/charset.go index ab0c303..54ecbd4 100644 --- a/internal/content/charset.go +++ b/internal/content/charset.go @@ -27,7 +27,7 @@ func (c *CharsetEnabler) Enable(renderer *render.Renderer) ContentOption { return func(info *item.FileInfo) (string, string) { res, returnName := func() (string, string) { if c, ok := info.Cache[Charset]; ok { - return string(c), Charset + return c.(string), Charset } // only text file has charset if info.IsDir() { @@ -63,7 +63,7 @@ func (c *CharsetEnabler) Enable(renderer *render.Renderer) ContentOption { return "failed_to_detect", Charset } charset = best.Charset - info.Cache[Charset] = []byte(charset) + info.Cache[Charset] = charset } return charset, Charset } diff --git a/internal/content/duplicate.go b/internal/content/duplicate.go index b9e827a..4550b4f 100644 --- a/internal/content/duplicate.go +++ b/internal/content/duplicate.go @@ -116,7 +116,7 @@ func fileHash(fileInfo *item.FileInfo, isThorough bool) (string, error) { var fileReadErr error if isThorough || fileInfo.Size() <= thresholdFileSize { if content, ok := fileInfo.Cache["content"]; ok { - bytes = content + bytes = content.([]byte) } else { bytes, fileReadErr = os.ReadFile(fileInfo.FullPath) if fileReadErr != nil { diff --git a/internal/content/extended.go b/internal/content/extended.go new file mode 100644 index 0000000..f57fad6 --- /dev/null +++ b/internal/content/extended.go @@ -0,0 +1,69 @@ +package content + +import ( + "encoding/xml" + "fmt" + "github.com/Equationzhao/g/internal/global" + "github.com/Equationzhao/g/internal/item" + "github.com/Equationzhao/g/internal/render" + "github.com/pkg/xattr" + "github.com/valyala/bytebufferpool" + "howett.net/plist" +) + +type ExtendedEnabler struct{} + +const Extended = global.NameOfExtended + +func formatBytes(bytes []byte) string { + res := bytebufferpool.Get() + defer bytebufferpool.Put(res) + _ = res.WriteByte('[') + for i, b := range bytes { + if i > 0 { + _, _ = res.WriteString(", ") + } + _, _ = res.WriteString(fmt.Sprintf("%02x", b)) + } + _ = res.WriteByte(']') + return res.String() +} + +// formatXattrValue attempts to parse the xattr value and returns a human-readable string. +func formatXattrValue(value []byte) string { + if len(value) == 0 { + return "" + } + + // Check if the value is a binary plist + var plistData any + if _, err := plist.Unmarshal(value, &plistData); err == nil { + xmlOut, err := xml.MarshalIndent(plistData, "", " ") + if err == nil { + return string(xmlOut) + } + } + // Default to hex encoding + return formatBytes(value) +} + +func (e ExtendedEnabler) Enable(renderer *render.Renderer) NoOutputOption { + return func(info *item.FileInfo) { + var list any + ok := false + if list, ok = info.Cache[Extended]; !ok { + list, _ = xattr.LList(info.FullPath) + info.Cache[Extended] = list + } + if lists, ok := list.([]string); ok && len(lists) > 0 { + for j, key := range lists { + val, _ := xattr.LGet(info.FullPath, key) + if j == len(lists)-1 { + info.AfterLines = append(info.AfterLines, renderer.Extended(fmt.Sprintf("└── %s: %s", key, formatXattrValue(val)))) + } else { + info.AfterLines = append(info.AfterLines, renderer.Extended(fmt.Sprintf("├── %s: %s", key, formatXattrValue(val)))) + } + } + } + } +} diff --git a/internal/content/inode.go b/internal/content/inode.go index 456cb8f..a585b95 100644 --- a/internal/content/inode.go +++ b/internal/content/inode.go @@ -21,7 +21,7 @@ func (i *InodeEnabler) Enable(renderer *render.Renderer) ContentOption { return func(info *item.FileInfo) (string, string) { i := "" if m, ok := info.Cache[Inode]; ok { - i = string(m) + i = m.(string) } else { i = osbased.Inode(info) } diff --git a/internal/content/mimetype.go b/internal/content/mimetype.go index d77de1c..b6fa73c 100644 --- a/internal/content/mimetype.go +++ b/internal/content/mimetype.go @@ -38,7 +38,7 @@ func (e *MimeFileTypeEnabler) Enable(renderer *render.Renderer) ContentOption { returnName = ParentMimeTypeName } if c, ok := info.Cache[MimeTypeName]; ok { - tn = string(c) + tn = c.(string) } else { if info.IsDir() { tn = "directory" @@ -58,7 +58,7 @@ func (e *MimeFileTypeEnabler) Enable(renderer *render.Renderer) ContentOption { } if m, ok := info.Cache[MimeTypeName]; ok { info.Cache[Charset] = m - return string(m), returnName + return m.(string), returnName } file, err := os.Open(info.FullPath) @@ -83,7 +83,7 @@ func (e *MimeFileTypeEnabler) Enable(renderer *render.Renderer) ContentOption { s := strings.SplitN(tn, ";", 2) tn = s[0] charset := strings.SplitN(s[1], "=", 2)[1] - info.Cache[Charset] = []byte(charset) + info.Cache[Charset] = charset } return tn, returnName diff --git a/internal/content/permission.go b/internal/content/permission.go index 2df82b7..cdaf636 100644 --- a/internal/content/permission.go +++ b/internal/content/permission.go @@ -4,20 +4,24 @@ import ( "strconv" "github.com/Equationzhao/g/internal/align" - constval "github.com/Equationzhao/g/internal/global" + "github.com/Equationzhao/g/internal/global" "github.com/Equationzhao/g/internal/item" "github.com/Equationzhao/g/internal/render" "github.com/pkg/xattr" ) -const Permissions = constval.NameOfPermission +const Permissions = global.NameOfPermission // EnableFileMode return file mode like -rwxrwxrwx/drwxrwxrwx func EnableFileMode(renderer *render.Renderer) ContentOption { align.Register(Permissions) return func(info *item.FileInfo) (string, string) { perm := renderer.FileMode(info.Mode().String()) + if info.Cache[Extended] != nil { + perm += "@" + } list, _ := xattr.LList(info.FullPath) + info.Cache[Extended] = list if len(list) != 0 { perm += "@" } diff --git a/internal/content/size.go b/internal/content/size.go index d0511cb..53e4f90 100644 --- a/internal/content/size.go +++ b/internal/content/size.go @@ -273,7 +273,7 @@ func (s *SizeEnabler) EnableSize(size SizeUnit, renderer *render.Renderer) Conte if s.recursive != nil { if r, ok := info.Cache[RecursiveSizeName]; ok { // convert []byte to int64 - v, _ = strconv.ParseInt(string(r), 10, 64) + v = r.(int64) } else { v = util.RecursivelySizeOf(info, s.recursive.depth) } diff --git a/internal/content/sum.go b/internal/content/sum.go index 1bf5d19..4b61d25 100644 --- a/internal/content/sum.go +++ b/internal/content/sum.go @@ -41,7 +41,7 @@ func (s SumEnabler) EnableSum(renderer *render.Renderer, sumTypes ...SumType) [] var content []byte if content_, ok := info.Cache["content"]; ok { - content = content_ + content = content_.([]byte) } else { file, err := os.Open(info.FullPath) if err != nil { diff --git a/internal/display/header.go b/internal/display/header.go index a22a487..9dae1ba 100644 --- a/internal/display/header.go +++ b/internal/display/header.go @@ -4,6 +4,8 @@ import ( "fmt" "strings" + "github.com/Equationzhao/g/internal/util" + "github.com/Equationzhao/g/internal/align" constval "github.com/Equationzhao/g/internal/global" "github.com/Equationzhao/g/internal/item" @@ -52,7 +54,7 @@ func (h HeaderMaker) Make(p Printer, Items ...*item.FileInfo) { for _, it := range Items { content, _ := it.Get(s) if s != constval.NameOfName { - toAddNum := len(s) - WidthNoHyperLinkLen(content.String()) + toAddNum := len(s) - util.WidthNoHyperLinkLen(content.String()) if align.IsLeft(s) { content.AddSuffix(strings.Repeat(" ", toAddNum)) } else { diff --git a/internal/display/printer.go b/internal/display/printer.go index b1a7fa9..44db5a5 100644 --- a/internal/display/printer.go +++ b/internal/display/printer.go @@ -8,7 +8,6 @@ import ( "fmt" "io" "os" - "regexp" "slices" "strconv" "strings" @@ -18,9 +17,7 @@ import ( "github.com/Equationzhao/g/internal/global" "github.com/Equationzhao/g/internal/item" "github.com/Equationzhao/g/internal/util" - "github.com/acarl005/stripansi" "github.com/jedib0t/go-pretty/v6/table" - "github.com/mattn/go-runewidth" "github.com/olekukonko/ts" "github.com/valyala/bytebufferpool" orderedmap "github.com/wk8/go-ordered-map/v2" @@ -128,6 +125,19 @@ func (b *Byline) Print(i ...*item.FileInfo) { for _, v := range i { _, _ = b.WriteString(v.OrderedContent(" ")) _ = b.WriteByte('\n') // byline means a new line :) + if len(v.AfterLines) != 0 { + items := v.ValuesByOrdered() + res := bytebufferpool.Get() + for j, it := range items[:len(items)-1] { + _, _ = res.WriteString(it.String()) + if j != len(items)-1 { + _, _ = res.WriteString(" ") + } + } + space := util.WidthNoHyperLinkLen(res.String()) + bytebufferpool.Put(res) + _, _ = b.WriteString(strings.Join(append(v.Extended(space), ""), "\n")) + } } if !b.disableAfter { fire(b.AfterPrint, b, i...) @@ -172,7 +182,7 @@ func (f *FitTerminal) printColumns(stringsArray []string, space int) { maxWidth := 0 widths := make([]int, n) for i, s := range stringsArray { - width := WidthLen(s) + width := util.WidthLen(s) widths[i] = width if width > maxWidth { maxWidth = width @@ -225,42 +235,6 @@ func (f *FitTerminal) printColumns(stringsArray []string, space int) { } } -var IncludeHyperlink = false - -func parseLink(link string) (name, other string, ok bool) { - re := regexp.MustCompile(`\033]8;;(.*?)\033\\(.*?)\033]8;;\033\\`) - matches := re.FindStringSubmatch(link) - - if len(matches) == 3 { - // if matches, get the other content and the link - other = strings.Replace(link, matches[0], "", 1) - return matches[2], other, true - } - return "", "", false -} - -func WidthLen(str string) int { - if IncludeHyperlink { - name, other, ok := parseLink(str) - if ok { - str = other + name - } - } - colorless := stripansi.Strip(str) - // len() is not proper here, as it counts emojis as 4 characters each - length := runewidth.StringWidth(colorless) - - return length -} - -func WidthNoHyperLinkLen(str string) int { - colorless := stripansi.Strip(str) - // len() is insufficient here, as it counts emojis as 4 characters each - length := runewidth.StringWidth(colorless) - - return length -} - var ( getTermWidthOnce util.Once size ts.Size @@ -369,12 +343,12 @@ func (a *Across) printRowWithNoSpace(strs []string) { maxLength := 0 for _, str := range strs { - maxLength += WidthLen(str) + maxLength += util.WidthLen(str) if maxLength <= width { _, _ = a.WriteString(str) } else { _, _ = a.WriteString("\n" + str) - maxLength = WidthLen(str) + maxLength = util.WidthLen(str) } } _ = a.WriteByte('\n') @@ -388,7 +362,7 @@ func (a *Across) printRow(stringsArray []string, space int) { maxWidth := 0 widths := make([]int, n) for i, s := range stringsArray { - width := WidthLen(s) + width := util.WidthLen(s) widths[i] = width if width > maxWidth { maxWidth = width @@ -716,9 +690,9 @@ func (t *TreePrinter) Print(s ...*item.FileInfo) { total := len(s) buildTree := tree.NewTree(tree.WithCap(total / 2)) - level := make(map[string][]*item.FileInfo) + level := make(map[int][]*item.FileInfo) for _, v := range s { - level[string(v.Cache["level"])] = append(level[string(v.Cache["level"])], v) + level[(v.Cache["level"].(int))] = append(level[(v.Cache["level"].(int))], v) } prefixAndName := func(info *item.FileInfo) (prefix string, name string) { @@ -740,13 +714,13 @@ func (t *TreePrinter) Print(s ...*item.FileInfo) { l := len(level) nodeMap := make(map[string]*tree.Node, l) - root := level["0"][0] + root := level[0][0] buildTree.Root.Meta = root nodeMap[root.FullPath] = buildTree.Root for i := 1; i < l; i++ { - for _, v := range level[strconv.Itoa(i)] { - node := nodeMap[string(v.Cache["parent"])] + for _, v := range level[i] { + node := nodeMap[v.Cache["parent"].(string)] c := &tree.Node{ Parent: node, Child: make([]*tree.Node, 0, 10), diff --git a/internal/filter/itemfliter.go b/internal/filter/itemfliter.go index acac65d..3778603 100644 --- a/internal/filter/itemfliter.go +++ b/internal/filter/itemfliter.go @@ -225,7 +225,7 @@ func MimeTypeOnly(fileTypes ...string) ItemFilterFunc { s = strings.SplitN(s, ";", 2)[0] } if isOrIsSonOf(s, fileTypes[i]) { - e.Cache[MimeTypeName] = []byte(s) + e.Cache[MimeTypeName] = s return keep } } diff --git a/internal/global/const.go b/internal/global/const.go index de411e8..531b5cd 100644 --- a/internal/global/const.go +++ b/internal/global/const.go @@ -78,5 +78,6 @@ const ( NameOfTimeCreated = "Created" NameOfTimeAccessed = "Accessed" NameOfTimeBirth = "Birth" + NameOfExtended = "Extended" NameOfFlags = "Flags" ) diff --git a/internal/global/var.go b/internal/global/var.go index ee68793..5d48a45 100644 --- a/internal/global/var.go +++ b/internal/global/var.go @@ -3,3 +3,5 @@ package global import "github.com/spf13/afero" var Fs = afero.NewOsFs() + +var IncludeHyperlink = false diff --git a/internal/item/fileinfo.go b/internal/item/fileinfo.go index 4e61e41..399e0d3 100644 --- a/internal/item/fileinfo.go +++ b/internal/item/fileinfo.go @@ -5,6 +5,7 @@ import ( "os" "path/filepath" "slices" + "strings" "github.com/Equationzhao/g/internal/cached" "github.com/valyala/bytebufferpool" @@ -12,9 +13,10 @@ import ( type FileInfo struct { os.FileInfo - FullPath string - Meta *cached.Map[string, Item] - Cache map[string][]byte + FullPath string + Meta *cached.Map[string, Item] + Cache map[string]any + AfterLines []string } type Option = func(info *FileInfo) error @@ -66,7 +68,7 @@ func NewFileInfoWithOption(opts ...Option) (*FileInfo, error) { f.Meta = cached.NewCacheMap[string, Item](20) } if f.Cache == nil { - f.Cache = make(map[string][]byte) + f.Cache = make(map[string]any) } return f, errSum } @@ -86,7 +88,7 @@ func NewFileInfo(name string) (*FileInfo, error) { FileInfo: info, FullPath: abs, Meta: cached.NewCacheMap[string, Item](8), - Cache: make(map[string][]byte), + Cache: make(map[string]any), }, nil } @@ -160,3 +162,13 @@ func (i *FileInfo) OrderedContent(delimiter string) string { } return res.String() } + +func (i *FileInfo) Extended(space int) []string { + res := make([]string, 0, len(i.AfterLines)) + // space is the length of sum i.Meta + for _, line := range i.AfterLines { + // add spaces to both + res = append(res, strings.Repeat(" ", space)+(line)) + } + return res +} diff --git a/internal/render/renderer.go b/internal/render/renderer.go index 69ce90d..8a06c96 100644 --- a/internal/render/renderer.go +++ b/internal/render/renderer.go @@ -567,6 +567,18 @@ func (rd *Renderer) Colorend() string { return rd.theme.InfoTheme["reset"].Color } +func (rd *Renderer) Extended(extended string) string { + bb := bytebufferpool.Get() + defer bytebufferpool.Put(bb) + style := rd.theme.Special["extended"] + _, _ = bb.WriteString(style.Color) + checkStyle(&style, bb) + _, _ = bb.WriteString(style.Icon) + _, _ = bb.WriteString(extended) + _, _ = bb.WriteString(rd.Colorend()) + return bb.String() +} + func checkStyle(style *theme.Style, bb *bytebufferpool.ByteBuffer) { if style.Underline { _, _ = bb.WriteString(global.Underline) diff --git a/internal/sorter/sort.go b/internal/sorter/sort.go index c3cec43..c8040f3 100644 --- a/internal/sorter/sort.go +++ b/internal/sorter/sort.go @@ -9,7 +9,7 @@ import ( "strings" "github.com/Equationzhao/g/internal/content" - constval "github.com/Equationzhao/g/internal/global" + "github.com/Equationzhao/g/internal/global" "github.com/Equationzhao/g/internal/item" "github.com/Equationzhao/g/internal/osbased" "github.com/Equationzhao/g/internal/util" @@ -63,8 +63,8 @@ func ByNameWithoutALeadingDotCaseSensitiveAscend(a, b *item.FileInfo) int { func byInode(a *item.FileInfo, b *item.FileInfo) (int, int) { inodeA, _ := strconv.Atoi(osbased.Inode(a)) inodeB, _ := strconv.Atoi(osbased.Inode(b)) - a.Cache["Inode"] = []byte(strconv.Itoa(inodeA)) - b.Cache["Inode"] = []byte(strconv.Itoa(inodeB)) + a.Cache[global.NameOfInode] = []byte(strconv.Itoa(inodeA)) + b.Cache[global.NameOfInode] = []byte(strconv.Itoa(inodeB)) return inodeA, inodeB } @@ -208,12 +208,12 @@ func byMimeType(a, b *item.FileInfo, ascend bool) int { return cmp.Compare(mimeBstr, mimeAstr) } -const MimeTypeName = constval.NameOfMIME +const MimeTypeName = global.NameOfMIME func getMimeName(a *item.FileInfo, b *item.FileInfo) (string, string) { mimeAstr, mimeBstr := "", "" if c, ok := a.Cache[MimeTypeName]; ok { - mimeAstr = string(c) + mimeAstr = c.(string) } else { mimeA, err := mt.DetectFile(a.FullPath) if err != nil { @@ -229,11 +229,11 @@ func getMimeName(a *item.FileInfo, b *item.FileInfo) (string, string) { } else { mimeAstr = mimeA.String() } - a.Cache[MimeTypeName] = []byte(mimeAstr) + a.Cache[MimeTypeName] = mimeAstr } if c, ok := b.Cache[MimeTypeName]; ok { - mimeBstr = string(c) + mimeBstr = c.(string) } else { mimeB, err := mt.DetectFile(b.FullPath) if err != nil { @@ -274,23 +274,21 @@ func byMimeTypeParent(a, b *item.FileInfo, ascend bool) int { const RecursiveSizeName = content.RecursiveSizeName func byRecursiveSize(a, b *item.FileInfo, depth int, ascend bool) int { - var sa []byte - var sb []byte + var sa any + var sb any exist := false sai, sbi := int64(0), int64(0) if sa, exist = a.Cache[RecursiveSizeName]; !exist { sai = util.RecursivelySizeOf(a, depth) - sa = []byte(strconv.FormatInt(sai, 10)) - a.Cache[RecursiveSizeName] = sa + a.Cache[RecursiveSizeName] = sai } else { - sai, _ = strconv.ParseInt(string(sa), 10, 64) + sai = sa.(int64) } if sb, exist = b.Cache[RecursiveSizeName]; !exist { sbi = util.RecursivelySizeOf(b, depth) - sb = []byte(strconv.FormatInt(sbi, 10)) - b.Cache[RecursiveSizeName] = sb + b.Cache[RecursiveSizeName] = sbi } else { - sbi, _ = strconv.ParseInt(string(sb), 10, 64) + sbi = sb.(int64) } if ascend { diff --git a/internal/theme/default.go b/internal/theme/default.go index 7ba83fe..9ca6393 100644 --- a/internal/theme/default.go +++ b/internal/theme/default.go @@ -1108,6 +1108,9 @@ var Special = map[string]Style{ "mounts": { Color: global.BrightBlack, }, + "extended": { + Color: global.BrightBlack, + }, } var Name = map[string]Style{ diff --git a/internal/util/util.go b/internal/util/util.go index 918ebbb..aa05787 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -3,9 +3,13 @@ package util import ( "fmt" "path/filepath" + "regexp" "strconv" "strings" + "github.com/acarl005/stripansi" + "github.com/mattn/go-runewidth" + "github.com/Equationzhao/g/internal/global" ) @@ -65,3 +69,36 @@ func SplitNumberAndUnit(input string) (float64, string) { return number, unit } + +func parseLink(link string) (name, other string, ok bool) { + re := regexp.MustCompile(`\033]8;;(.*?)\033\\(.*?)\033]8;;\033\\`) + matches := re.FindStringSubmatch(link) + + if len(matches) == 3 { + // if matches, get the other content and the link + other = strings.Replace(link, matches[0], "", 1) + return matches[2], other, true + } + return "", "", false +} + +func WidthLen(str string) int { + if global.IncludeHyperlink { + name, other, ok := parseLink(str) + if ok { + str = other + name + } + } + colorless := stripansi.Strip(str) + // len() is not proper here, as it counts emojis as 4 characters each + length := runewidth.StringWidth(colorless) + + return length +} + +func WidthNoHyperLinkLen(str string) int { + colorless := stripansi.Strip(str) + // len() is insufficient here, as it counts emojis as 4 characters each + length := runewidth.StringWidth(colorless) + return length +}