Skip to content

Commit

Permalink
fix(path): improve path cleaning, normalization and parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
lewis-yeung authored and JanDeDobbeleer committed Aug 29, 2024
1 parent 476bfd1 commit abd6676
Show file tree
Hide file tree
Showing 6 changed files with 499 additions and 366 deletions.
24 changes: 14 additions & 10 deletions src/prompt/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ func (e *Engine) getTitleTemplateText() string {
}

func (e *Engine) renderBlock(block *config.Block, cancelNewline bool) bool {
defer e.patchPowerShellBleed()
defer e.applyPowerShellBleedPatch()

// This is deprecated but we leave it in to not break configs
// It is encouraged to use "newline": true on block level
Expand Down Expand Up @@ -267,7 +267,7 @@ func (e *Engine) renderBlock(block *config.Block, cancelNewline bool) bool {
return true
}

func (e *Engine) patchPowerShellBleed() {
func (e *Engine) applyPowerShellBleedPatch() {
// when in PowerShell, we need to clear the line after the prompt
// to avoid the background being printed on the next line
// when at the end of the buffer.
Expand Down Expand Up @@ -514,10 +514,6 @@ func New(flags *runtime.Flags) *Engine {
env.Init()
cfg := config.Load(env)

if cfg.PatchPwshBleed {
patchPowerShellBleed(env.Shell(), flags)
}

env.Var = cfg.Var
flags.HasTransient = cfg.TransientPrompt != nil

Expand All @@ -532,10 +528,16 @@ func New(flags *runtime.Flags) *Engine {
Plain: flags.Plain,
}

if cfg.PatchPwshBleed {
eng.patchPowerShellBleed()
}

return eng
}

func patchPowerShellBleed(sh string, flags *runtime.Flags) {
func (e *Engine) patchPowerShellBleed() {
sh := e.Env.Shell()

// when in PowerShell, and force patching the bleed bug
// we need to reduce the terminal width by 1 so the last
// character isn't cut off by the ANSI escape sequences
Expand All @@ -544,10 +546,12 @@ func patchPowerShellBleed(sh string, flags *runtime.Flags) {
return
}

// only do this when relevant
if flags.TerminalWidth <= 0 {
// Since the terminal width may not be given by the CLI flag, we should always call this here.
_, err := e.Env.TerminalWidth()
if err != nil {
// Skip when we're unable to determine the terminal width.
return
}

flags.TerminalWidth--
e.Env.Flags().TerminalWidth--
}
68 changes: 57 additions & 11 deletions src/runtime/terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,25 +173,20 @@ func (term *Terminal) Pwd() string {
if term.cwd != "" {
return term.cwd
}
correctPath := func(pwd string) string {
if term.GOOS() != WINDOWS {
return pwd
}
// on Windows, and being case sensitive and not consistent and all, this gives silly issues
driveLetter := regex.GetCompiledRegex(`^[a-z]:`)
return driveLetter.ReplaceAllStringFunc(pwd, strings.ToUpper)
}

if term.CmdFlags != nil && term.CmdFlags.PWD != "" {
term.cwd = correctPath(term.CmdFlags.PWD)
term.cwd = CleanPath(term, term.CmdFlags.PWD)
term.Debug(term.cwd)
return term.cwd
}

dir, err := os.Getwd()
if err != nil {
term.Error(err)
return ""
}
term.cwd = correctPath(dir)

term.cwd = CleanPath(term, dir)
term.Debug(term.cwd)
return term.cwd
}
Expand Down Expand Up @@ -321,7 +316,10 @@ func (term *Terminal) LsDir(path string) []fs.DirEntry {

func (term *Terminal) PathSeparator() string {
defer term.Trace(time.Now())
return string(os.PathSeparator)
if term.GOOS() == WINDOWS {
return `\`
}
return "/"
}

func (term *Terminal) User() string {
Expand Down Expand Up @@ -882,6 +880,54 @@ func Base(env Environment, path string) string {
return path
}

func CleanPath(env Environment, path string) string {
if len(path) == 0 {
return path
}

cleaned := path
separator := env.PathSeparator()

// The prefix can be empty for a relative path.
var prefix string
if IsPathSeparator(env, cleaned[0]) {
prefix = separator
}

if env.GOOS() == WINDOWS {
// Normalize (forward) slashes to backslashes on Windows.
cleaned = strings.ReplaceAll(cleaned, "/", `\`)

// Clean the prefix for a UNC path, if any.
if regex.MatchString(`^\\{2}[^\\]+`, cleaned) {
cleaned = strings.TrimPrefix(cleaned, `\\.\UNC\`)
if len(cleaned) == 0 {
return cleaned
}
prefix = `\\`
}

// Always use an uppercase drive letter on Windows.
driveLetter := regex.GetCompiledRegex(`^[a-z]:`)
cleaned = driveLetter.ReplaceAllStringFunc(cleaned, strings.ToUpper)
}

sb := new(strings.Builder)
sb.WriteString(prefix)

// Clean slashes.
matches := regex.FindAllNamedRegexMatch(fmt.Sprintf(`(?P<element>[^\%s]+)`, separator), cleaned)
n := len(matches) - 1
for i, m := range matches {
sb.WriteString(m["element"])
if i != n {
sb.WriteString(separator)
}
}

return sb.String()
}

func ReplaceTildePrefixWithHomeDir(env Environment, path string) string {
if !strings.HasPrefix(path, "~") {
return path
Expand Down
Loading

0 comments on commit abd6676

Please sign in to comment.