kitty/tools/utils/paths.go
Kovid Goyal 3c29ce936b
Dont recurse for file completion
We could potentially end up recursing over the entire file system. And
for completion we only present the candidates in the immediate directory
anyway.
2022-11-14 15:41:57 +05:30

166 lines
3.8 KiB
Go

// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
package utils
import (
"io/fs"
"os"
"os/user"
"path/filepath"
"runtime"
"strings"
)
var Sep = string(os.PathSeparator)
func Expanduser(path string) string {
if !strings.HasPrefix(path, "~") {
return path
}
home, err := os.UserHomeDir()
if err != nil {
usr, err := user.Current()
if err == nil {
home = usr.HomeDir
}
}
if err != nil || home == "" {
return path
}
if path == "~" {
return home
}
path = strings.ReplaceAll(path, Sep, "/")
parts := strings.Split(path, "/")
if parts[0] == "~" {
parts[0] = home
} else {
uname := parts[0][1:]
if uname != "" {
u, err := user.Lookup(uname)
if err == nil && u.HomeDir != "" {
parts[0] = u.HomeDir
}
}
}
return strings.Join(parts, Sep)
}
func Abspath(path string) string {
q, err := filepath.Abs(path)
if err == nil {
return q
}
return path
}
var config_dir string
func ConfigDir() string {
if config_dir != "" {
return config_dir
}
if os.Getenv("KITTY_CONFIG_DIRECTORY") != "" {
config_dir = Abspath(Expanduser(os.Getenv("KITTY_CONFIG_DIRECTORY")))
} else {
var locations []string
if os.Getenv("XDG_CONFIG_HOME") != "" {
locations = append(locations, os.Getenv("XDG_CACHE_HOME"))
}
locations = append(locations, Expanduser("~/.config"))
if runtime.GOOS == "darwin" {
locations = append(locations, Expanduser("~/Library/Preferences"))
}
for _, loc := range locations {
if loc != "" {
q := filepath.Join(loc, "kitty")
if _, err := os.Stat(filepath.Join(q, "kitty.conf")); err == nil {
config_dir = q
break
}
}
}
for _, loc := range locations {
if loc != "" {
config_dir = filepath.Join(loc, "kitty")
break
}
}
}
return config_dir
}
type Walk_callback func(path, abspath string, d fs.DirEntry, err error) error
func transform_symlink(path string) string {
if q, err := filepath.EvalSymlinks(path); err == nil {
return q
}
return path
}
func needs_symlink_recurse(path string, d fs.DirEntry) bool {
if d.Type()&os.ModeSymlink == os.ModeSymlink {
if s, serr := os.Stat(path); serr == nil && s.IsDir() {
return true
}
}
return false
}
type transformed_walker struct {
seen map[string]bool
real_callback Walk_callback
transform_func func(string) string
needs_recurse_func func(string, fs.DirEntry) bool
}
func (self *transformed_walker) walk(dirpath string) error {
resolved_path := self.transform_func(dirpath)
if self.seen[resolved_path] {
return nil
}
self.seen[resolved_path] = true
c := func(path string, d fs.DirEntry, err error) error {
if err != nil {
// Happens if ReadDir on d failed, skip it in that case
return fs.SkipDir
}
rpath, err := filepath.Rel(resolved_path, path)
if err != nil {
return err
}
// we cant use filepath.Join here as it calls Clean() which can alter dirpath if it contains .. or . etc.
path_based_on_original_dir := dirpath
if !strings.HasSuffix(dirpath, Sep) && dirpath != "" {
path_based_on_original_dir += Sep
}
path_based_on_original_dir += rpath
if self.needs_recurse_func(path, d) {
err = self.walk(path_based_on_original_dir)
} else {
err = self.real_callback(path_based_on_original_dir, path, d, err)
}
return err
}
return filepath.WalkDir(resolved_path, c)
}
// Walk, recursing into symlinks that point to directories. Ignores directories
// that could not be read.
func WalkWithSymlink(dirpath string, callback Walk_callback, transformers ...func(string) string) error {
transform := func(path string) string {
for _, t := range transformers {
path = t(path)
}
return transform_symlink(path)
}
sw := transformed_walker{
seen: make(map[string]bool), real_callback: callback, transform_func: transform, needs_recurse_func: needs_symlink_recurse}
return sw.walk(dirpath)
}