mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2025-02-21 02:55:37 -08:00
fix(path): parse path correctly
This commit is contained in:
parent
049f9d4f94
commit
5b6a3470d1
|
@ -22,7 +22,7 @@ const (
|
|||
Prompt BlockType = "prompt"
|
||||
// LineBreak creates a line break in the prompt
|
||||
LineBreak BlockType = "newline"
|
||||
// RPrompt a right aligned prompt in ZSH and Powershell
|
||||
// RPrompt is a right aligned prompt
|
||||
RPrompt BlockType = "rprompt"
|
||||
// Left aligns left
|
||||
Left BlockAlignment = "left"
|
||||
|
|
|
@ -130,7 +130,7 @@ func (e *Engine) shouldFill(block *Block, length int) (string, bool) {
|
|||
|
||||
func (e *Engine) renderBlock(block *Block) {
|
||||
defer func() {
|
||||
// Due to a bug in Powershell, the end of the line needs to be cleared.
|
||||
// Due to a bug in PowerShell, the end of the line needs to be cleared.
|
||||
// If this doesn't happen, the portion after the prompt gets colored in the background
|
||||
// color of the line above the new input line. Clearing the line fixes this,
|
||||
// but can hopefully one day be removed when this is resolved natively.
|
||||
|
|
|
@ -337,11 +337,16 @@ func (env *ShellEnvironment) Pwd() string {
|
|||
}
|
||||
correctPath := func(pwd string) string {
|
||||
// on Windows, and being case sensitive and not consistent and all, this gives silly issues
|
||||
if env.GOOS() == WINDOWS {
|
||||
driveLetter := regex.GetCompiledRegex(`^[a-z]:`)
|
||||
return driveLetter.ReplaceAllStringFunc(pwd, strings.ToUpper)
|
||||
}
|
||||
return pwd
|
||||
}
|
||||
if env.CmdFlags != nil && env.CmdFlags.PWD != "" {
|
||||
env.cwd = correctPath(env.CmdFlags.PWD)
|
||||
// ensure a clean path
|
||||
root, path := ParsePath(env, correctPath(env.CmdFlags.PWD))
|
||||
env.cwd = root + path
|
||||
return env.cwd
|
||||
}
|
||||
dir, err := os.Getwd()
|
||||
|
@ -735,6 +740,9 @@ func (env *ShellEnvironment) TemplateCache() *TemplateCache {
|
|||
pwd := env.Pwd()
|
||||
tmplCache.PWD = ReplaceHomeDirPrefixWithTilde(env, pwd)
|
||||
tmplCache.Folder = Base(env, pwd)
|
||||
if env.GOOS() == WINDOWS && strings.HasSuffix(tmplCache.Folder, ":") {
|
||||
tmplCache.Folder += `\`
|
||||
}
|
||||
tmplCache.UserName = env.User()
|
||||
if host, err := env.Host(); err == nil {
|
||||
tmplCache.HostName = host
|
||||
|
@ -766,19 +774,21 @@ func (env *ShellEnvironment) DirMatchesOneOf(dir string, regexes []string) (matc
|
|||
}
|
||||
|
||||
func dirMatchesOneOf(dir, home, goos string, regexes []string) bool {
|
||||
normalizedCwd := strings.ReplaceAll(dir, "\\", "/")
|
||||
normalizedHomeDir := strings.ReplaceAll(home, "\\", "/")
|
||||
if goos == WINDOWS {
|
||||
dir = strings.ReplaceAll(dir, "\\", "/")
|
||||
home = strings.ReplaceAll(home, "\\", "/")
|
||||
}
|
||||
|
||||
for _, element := range regexes {
|
||||
normalizedElement := strings.ReplaceAll(element, "\\\\", "/")
|
||||
if strings.HasPrefix(normalizedElement, "~") {
|
||||
normalizedElement = strings.Replace(normalizedElement, "~", normalizedHomeDir, 1)
|
||||
normalizedElement = strings.Replace(normalizedElement, "~", home, 1)
|
||||
}
|
||||
pattern := fmt.Sprintf("^%s$", normalizedElement)
|
||||
if goos == WINDOWS || goos == DARWIN {
|
||||
pattern = "(?i)" + pattern
|
||||
}
|
||||
matched := regex.MatchString(pattern, normalizedCwd)
|
||||
matched := regex.MatchString(pattern, dir)
|
||||
if matched {
|
||||
return true
|
||||
}
|
||||
|
@ -786,7 +796,7 @@ func dirMatchesOneOf(dir, home, goos string, regexes []string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func isPathSeparator(env Environment, c uint8) bool {
|
||||
func IsPathSeparator(env Environment, c uint8) bool {
|
||||
if c == '/' {
|
||||
return true
|
||||
}
|
||||
|
@ -800,14 +810,14 @@ func isPathSeparator(env Environment, c uint8) bool {
|
|||
// Trailing path separators are removed before extracting the last element.
|
||||
// If the path consists entirely of separators, Base returns a single separator.
|
||||
func Base(env Environment, path string) string {
|
||||
if path == "/" {
|
||||
return path
|
||||
}
|
||||
volumeName := filepath.VolumeName(path)
|
||||
// Strip trailing slashes.
|
||||
for len(path) > 0 && isPathSeparator(env, path[len(path)-1]) {
|
||||
for len(path) > 0 && IsPathSeparator(env, path[len(path)-1]) {
|
||||
path = path[0 : len(path)-1]
|
||||
}
|
||||
if len(path) == 0 {
|
||||
return env.PathSeparator()
|
||||
}
|
||||
if volumeName == path {
|
||||
return path
|
||||
}
|
||||
|
@ -815,22 +825,69 @@ func Base(env Environment, path string) string {
|
|||
path = path[len(filepath.VolumeName(path)):]
|
||||
// Find the last element
|
||||
i := len(path) - 1
|
||||
for i >= 0 && !isPathSeparator(env, path[i]) {
|
||||
for i >= 0 && !IsPathSeparator(env, path[i]) {
|
||||
i--
|
||||
}
|
||||
if i >= 0 {
|
||||
path = path[i+1:]
|
||||
}
|
||||
// If empty now, it had only slashes.
|
||||
if path == "" {
|
||||
if len(path) == 0 {
|
||||
return env.PathSeparator()
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// ParsePath parses an input path and returns a clean root and a clean path.
|
||||
func ParsePath(env Environment, inputPath string) (root, path string) {
|
||||
if len(inputPath) == 0 {
|
||||
return
|
||||
}
|
||||
separator := env.PathSeparator()
|
||||
clean := func(path string) string {
|
||||
matches := regex.FindAllNamedRegexMatch(fmt.Sprintf(`(?P<element>[^\%s]+)`, separator), path)
|
||||
n := len(matches) - 1
|
||||
s := new(strings.Builder)
|
||||
for i, m := range matches {
|
||||
s.WriteString(m["element"])
|
||||
if i != n {
|
||||
s.WriteString(separator)
|
||||
}
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
if env.GOOS() == WINDOWS {
|
||||
inputPath = strings.ReplaceAll(inputPath, "/", `\`)
|
||||
// for a UNC path, extract \\hostname\sharename as the root
|
||||
matches := regex.FindNamedRegexMatch(`^\\\\(?P<hostname>[^\\]+)\\+(?P<sharename>[^\\]+)\\*(?P<path>[\s\S]*)$`, inputPath)
|
||||
if len(matches) > 0 {
|
||||
root = `\\` + matches["hostname"] + `\` + matches["sharename"] + `\`
|
||||
path = clean(matches["path"])
|
||||
return
|
||||
}
|
||||
}
|
||||
s := strings.SplitAfterN(inputPath, separator, 2)
|
||||
root = s[0]
|
||||
if !strings.HasSuffix(root, separator) {
|
||||
// a root should end with a separator
|
||||
root += separator
|
||||
}
|
||||
if len(s) == 2 {
|
||||
path = clean(s[1])
|
||||
}
|
||||
return root, path
|
||||
}
|
||||
|
||||
func ReplaceHomeDirPrefixWithTilde(env Environment, path string) string {
|
||||
if strings.HasPrefix(path, env.Home()) {
|
||||
return strings.Replace(path, env.Home(), "~", 1)
|
||||
home := env.Home()
|
||||
// match Home directory exactly
|
||||
if !strings.HasPrefix(path, home) {
|
||||
return path
|
||||
}
|
||||
rem := path[len(home):]
|
||||
if len(rem) == 0 || IsPathSeparator(env, rem[0]) {
|
||||
return "~" + rem
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
|
|
@ -22,29 +22,6 @@ func TestHostNameWithLan(t *testing.T) {
|
|||
assert.Equal(t, "hello", cleanHostName)
|
||||
}
|
||||
|
||||
func TestWindowsPathWithDriveLetter(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
CWD string
|
||||
Expected string
|
||||
}{
|
||||
{Case: "C drive", CWD: `C:\Windows\`, Expected: `C:\Windows\`},
|
||||
{Case: "C drive lower case", CWD: `c:\Windows\`, Expected: `C:\Windows\`},
|
||||
{Case: "P drive lower case", CWD: `p:\some\`, Expected: `P:\some\`},
|
||||
{Case: "some drive lower case", CWD: `some:\some\`, Expected: `some:\some\`},
|
||||
{Case: "drive ending in c:", CWD: `src:\source\`, Expected: `src:\source\`},
|
||||
{Case: "registry drive", CWD: `HKLM:\SOFTWARE\magnetic:test\`, Expected: `HKLM:\SOFTWARE\magnetic:test\`},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
env := &ShellEnvironment{
|
||||
CmdFlags: &Flags{
|
||||
PWD: tc.CWD,
|
||||
},
|
||||
}
|
||||
assert.Equal(t, env.Pwd(), tc.Expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirMatchesOneOf(t *testing.T) {
|
||||
cases := []struct {
|
||||
GOOS string
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
"oh-my-posh/environment"
|
||||
"oh-my-posh/properties"
|
||||
"oh-my-posh/regex"
|
||||
"oh-my-posh/shell"
|
||||
"oh-my-posh/template"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
@ -45,7 +45,7 @@ const (
|
|||
Full string = "full"
|
||||
// Folder displays the current folder
|
||||
Folder string = "folder"
|
||||
// Mixed like agnoster, but if the path is short it displays it
|
||||
// Mixed like agnoster, but if a folder name is short enough, it is displayed as-is
|
||||
Mixed string = "mixed"
|
||||
// Letter like agnoster, but with the first letter of each folder name
|
||||
Letter string = "letter"
|
||||
|
@ -70,64 +70,33 @@ func (pt *Path) Template() string {
|
|||
}
|
||||
|
||||
func (pt *Path) Enabled() bool {
|
||||
pt.pwd = pt.env.Pwd()
|
||||
switch style := pt.props.GetString(properties.Style, Agnoster); style {
|
||||
case Agnoster:
|
||||
pt.Path = pt.getAgnosterPath()
|
||||
case AgnosterFull:
|
||||
pt.Path = pt.getAgnosterFullPath()
|
||||
case AgnosterShort:
|
||||
pt.Path = pt.getAgnosterShortPath()
|
||||
case Mixed:
|
||||
pt.Path = pt.getMixedPath()
|
||||
case Letter:
|
||||
pt.Path = pt.getLetterPath()
|
||||
case Unique:
|
||||
pt.Path = pt.getUniqueLettersPath()
|
||||
case AgnosterLeft:
|
||||
pt.Path = pt.getAgnosterLeftPath()
|
||||
case Short:
|
||||
// "short" is a duplicate of "full", just here for backwards compatibility
|
||||
fallthrough
|
||||
case Full:
|
||||
pt.Path = pt.getFullPath()
|
||||
case Folder:
|
||||
pt.Path = pt.getFolderPath()
|
||||
default:
|
||||
pt.Path = fmt.Sprintf("Path style: %s is not available", style)
|
||||
}
|
||||
pt.Path = pt.formatWindowsDrive(pt.Path)
|
||||
pt.setPath()
|
||||
if pt.env.IsWsl() {
|
||||
pt.Location, _ = pt.env.RunCommand("wslpath", "-m", pt.pwd)
|
||||
} else {
|
||||
pt.Location = pt.pwd
|
||||
}
|
||||
|
||||
pt.StackCount = pt.env.StackCount()
|
||||
pt.Writable = pt.env.DirIsWritable(pt.pwd)
|
||||
return true
|
||||
}
|
||||
|
||||
func (pt *Path) Parent() string {
|
||||
if pt.pwd == pt.env.Home() {
|
||||
pwd := pt.getPwd()
|
||||
if len(pwd) == 0 {
|
||||
return ""
|
||||
}
|
||||
parent := filepath.Dir(pt.pwd)
|
||||
if pt.pwd == parent {
|
||||
root, path := environment.ParsePath(pt.env, pwd)
|
||||
if len(path) == 0 {
|
||||
// a root path has no parent
|
||||
return ""
|
||||
}
|
||||
separator := pt.env.PathSeparator()
|
||||
if parent == pt.rootLocation() || parent == separator {
|
||||
separator = ""
|
||||
base := environment.Base(pt.env, path)
|
||||
path = pt.replaceFolderSeparators(path[:len(path)-len(base)])
|
||||
if root != pt.env.PathSeparator() {
|
||||
root = root[:len(root)-1] + pt.getFolderSeparator()
|
||||
}
|
||||
return pt.replaceMappedLocations(parent) + separator
|
||||
}
|
||||
|
||||
func (pt *Path) formatWindowsDrive(pwd string) string {
|
||||
if pt.env.GOOS() != environment.WINDOWS || !strings.HasSuffix(pwd, ":") {
|
||||
return pwd
|
||||
}
|
||||
return pwd + "\\"
|
||||
return root + path
|
||||
}
|
||||
|
||||
func (pt *Path) Init(props properties.Properties, env environment.Environment) {
|
||||
|
@ -135,10 +104,52 @@ func (pt *Path) Init(props properties.Properties, env environment.Environment) {
|
|||
pt.env = env
|
||||
}
|
||||
|
||||
func (pt *Path) setPath() {
|
||||
pwd := pt.getPwd()
|
||||
if len(pwd) == 0 {
|
||||
return
|
||||
}
|
||||
root, path := environment.ParsePath(pt.env, pwd)
|
||||
if len(path) == 0 {
|
||||
pt.Path = pt.formatRoot(root)
|
||||
return
|
||||
}
|
||||
switch style := pt.props.GetString(properties.Style, Agnoster); style {
|
||||
case Agnoster:
|
||||
pt.Path = pt.getAgnosterPath(root, path)
|
||||
case AgnosterFull:
|
||||
pt.Path = pt.getAgnosterFullPath(root, path)
|
||||
case AgnosterShort:
|
||||
pt.Path = pt.getAgnosterShortPath(root, path)
|
||||
case Mixed:
|
||||
pt.Path = pt.getMixedPath(root, path)
|
||||
case Letter:
|
||||
pt.Path = pt.getLetterPath(root, path)
|
||||
case Unique:
|
||||
pt.Path = pt.getUniqueLettersPath(root, path)
|
||||
case AgnosterLeft:
|
||||
pt.Path = pt.getAgnosterLeftPath(root, path)
|
||||
case Short:
|
||||
// "short" is a duplicate of "full", just here for backwards compatibility
|
||||
fallthrough
|
||||
case Full:
|
||||
pt.Path = pt.getFullPath(root, path)
|
||||
case Folder:
|
||||
pt.Path = pt.getFolderPath(path)
|
||||
default:
|
||||
pt.Path = fmt.Sprintf("Path style: %s is not available", style)
|
||||
}
|
||||
}
|
||||
|
||||
func (pt *Path) getFolderSeparator() string {
|
||||
separatorTemplate := pt.props.GetString(FolderSeparatorTemplate, "")
|
||||
if len(separatorTemplate) == 0 {
|
||||
return pt.props.GetString(FolderSeparatorIcon, pt.env.PathSeparator())
|
||||
separator := pt.props.GetString(FolderSeparatorIcon, pt.env.PathSeparator())
|
||||
// if empty, use the default separator
|
||||
if len(separator) == 0 {
|
||||
return pt.env.PathSeparator()
|
||||
}
|
||||
return separator
|
||||
}
|
||||
tmpl := &template.Text{
|
||||
Template: separatorTemplate,
|
||||
|
@ -149,67 +160,66 @@ func (pt *Path) getFolderSeparator() string {
|
|||
if err != nil {
|
||||
pt.env.Log(environment.Error, "getFolderSeparator", err.Error())
|
||||
}
|
||||
if len(text) == 0 {
|
||||
return pt.env.PathSeparator()
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func (pt *Path) getMixedPath() string {
|
||||
func (pt *Path) getMixedPath(root, path string) string {
|
||||
var buffer strings.Builder
|
||||
pwd := pt.getPwd()
|
||||
splitted := strings.Split(pwd, pt.env.PathSeparator())
|
||||
threshold := int(pt.props.GetFloat64(MixedThreshold, 4))
|
||||
for i, part := range splitted {
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
folder := part
|
||||
if len(part) > threshold && i != 0 && i != len(splitted)-1 {
|
||||
folder = pt.props.GetString(FolderIcon, "..")
|
||||
}
|
||||
folderIcon := pt.props.GetString(FolderIcon, "..")
|
||||
separator := pt.getFolderSeparator()
|
||||
if i == 0 {
|
||||
separator = ""
|
||||
elements := strings.Split(path, pt.env.PathSeparator())
|
||||
if root != pt.env.PathSeparator() {
|
||||
elements = append([]string{root[:len(root)-1]}, elements...)
|
||||
}
|
||||
n := len(elements)
|
||||
buffer.WriteString(elements[0])
|
||||
for i := 1; i < n; i++ {
|
||||
folder := elements[i]
|
||||
if len(folder) > threshold && i != n-1 {
|
||||
folder = folderIcon
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("%s%s", separator, folder))
|
||||
}
|
||||
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (pt *Path) getAgnosterPath() string {
|
||||
func (pt *Path) getAgnosterPath(root, path string) string {
|
||||
var buffer strings.Builder
|
||||
pwd := pt.getPwd()
|
||||
buffer.WriteString(pt.rootLocation())
|
||||
pathDepth := pt.pathDepth(pwd)
|
||||
folderIcon := pt.props.GetString(FolderIcon, "..")
|
||||
separator := pt.getFolderSeparator()
|
||||
for i := 1; i < pathDepth; i++ {
|
||||
elements := strings.Split(path, pt.env.PathSeparator())
|
||||
if root != pt.env.PathSeparator() {
|
||||
elements = append([]string{root[:len(root)-1]}, elements...)
|
||||
}
|
||||
n := len(elements)
|
||||
buffer.WriteString(elements[0])
|
||||
for i := 2; i < n; i++ {
|
||||
buffer.WriteString(fmt.Sprintf("%s%s", separator, folderIcon))
|
||||
}
|
||||
if pathDepth > 0 {
|
||||
buffer.WriteString(fmt.Sprintf("%s%s", separator, environment.Base(pt.env, pwd)))
|
||||
if n > 1 {
|
||||
buffer.WriteString(fmt.Sprintf("%s%s", separator, elements[n-1]))
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (pt *Path) getAgnosterLeftPath() string {
|
||||
pwd := pt.getPwd()
|
||||
separator := pt.env.PathSeparator()
|
||||
pwd = strings.Trim(pwd, separator)
|
||||
splitted := strings.Split(pwd, separator)
|
||||
folderIcon := pt.props.GetString(FolderIcon, "..")
|
||||
separator = pt.getFolderSeparator()
|
||||
switch len(splitted) {
|
||||
case 0:
|
||||
return ""
|
||||
case 1:
|
||||
return splitted[0]
|
||||
case 2:
|
||||
return fmt.Sprintf("%s%s%s", splitted[0], separator, splitted[1])
|
||||
}
|
||||
func (pt *Path) getAgnosterLeftPath(root, path string) string {
|
||||
var buffer strings.Builder
|
||||
buffer.WriteString(fmt.Sprintf("%s%s%s", splitted[0], separator, splitted[1]))
|
||||
for i := 2; i < len(splitted); i++ {
|
||||
folderIcon := pt.props.GetString(FolderIcon, "..")
|
||||
separator := pt.getFolderSeparator()
|
||||
elements := strings.Split(path, pt.env.PathSeparator())
|
||||
if root != pt.env.PathSeparator() {
|
||||
elements = append([]string{root[:len(root)-1]}, elements...)
|
||||
}
|
||||
n := len(elements)
|
||||
buffer.WriteString(elements[0])
|
||||
if n > 1 {
|
||||
buffer.WriteString(fmt.Sprintf("%s%s", separator, elements[1]))
|
||||
}
|
||||
for i := 2; i < n; i++ {
|
||||
buffer.WriteString(fmt.Sprintf("%s%s", separator, folderIcon))
|
||||
}
|
||||
return buffer.String()
|
||||
|
@ -218,7 +228,7 @@ func (pt *Path) getAgnosterLeftPath() string {
|
|||
func (pt *Path) getRelevantLetter(folder string) string {
|
||||
// check if there is at least a letter we can use
|
||||
matches := regex.FindNamedRegexMatch(`(?P<letter>[\p{L}0-9]).*`, folder)
|
||||
if matches == nil || matches["letter"] == "" {
|
||||
if matches == nil || len(matches["letter"]) == 0 {
|
||||
// no letter found, keep the folder unchanged
|
||||
return folder
|
||||
}
|
||||
|
@ -228,36 +238,36 @@ func (pt *Path) getRelevantLetter(folder string) string {
|
|||
return letter
|
||||
}
|
||||
|
||||
func (pt *Path) getLetterPath() string {
|
||||
func (pt *Path) getLetterPath(root, path string) string {
|
||||
var buffer strings.Builder
|
||||
pwd := pt.getPwd()
|
||||
splitted := strings.Split(pwd, pt.env.PathSeparator())
|
||||
separator := pt.getFolderSeparator()
|
||||
for i := 0; i < len(splitted)-1; i++ {
|
||||
folder := splitted[i]
|
||||
if len(folder) == 0 {
|
||||
continue
|
||||
elements := strings.Split(path, pt.env.PathSeparator())
|
||||
if root != pt.env.PathSeparator() {
|
||||
elements = append([]string{root[:len(root)-1]}, elements...)
|
||||
}
|
||||
letter := pt.getRelevantLetter(folder)
|
||||
buffer.WriteString(fmt.Sprintf("%s%s", letter, separator))
|
||||
n := len(elements)
|
||||
for i := 0; i < n-1; i++ {
|
||||
letter := pt.getRelevantLetter(elements[i])
|
||||
if i != 0 {
|
||||
buffer.WriteString(separator)
|
||||
}
|
||||
if len(splitted) > 0 {
|
||||
buffer.WriteString(splitted[len(splitted)-1])
|
||||
buffer.WriteString(letter)
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("%s%s", separator, elements[n-1]))
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (pt *Path) getUniqueLettersPath() string {
|
||||
func (pt *Path) getUniqueLettersPath(root, path string) string {
|
||||
var buffer strings.Builder
|
||||
pwd := pt.getPwd()
|
||||
splitted := strings.Split(pwd, pt.env.PathSeparator())
|
||||
separator := pt.getFolderSeparator()
|
||||
letters := make(map[string]bool, len(splitted))
|
||||
for i := 0; i < len(splitted)-1; i++ {
|
||||
folder := splitted[i]
|
||||
if len(folder) == 0 {
|
||||
continue
|
||||
elements := strings.Split(path, pt.env.PathSeparator())
|
||||
if root != pt.env.PathSeparator() {
|
||||
elements = append([]string{root[:len(root)-1]}, elements...)
|
||||
}
|
||||
n := len(elements)
|
||||
letters := make(map[string]bool)
|
||||
for i := 0; i < n-1; i++ {
|
||||
folder := elements[i]
|
||||
letter := pt.getRelevantLetter(folder)
|
||||
for letters[letter] {
|
||||
if letter == folder {
|
||||
|
@ -266,102 +276,126 @@ func (pt *Path) getUniqueLettersPath() string {
|
|||
letter += folder[len(letter) : len(letter)+1]
|
||||
}
|
||||
letters[letter] = true
|
||||
buffer.WriteString(fmt.Sprintf("%s%s", letter, separator))
|
||||
if i != 0 {
|
||||
buffer.WriteString(separator)
|
||||
}
|
||||
if len(splitted) > 0 {
|
||||
buffer.WriteString(splitted[len(splitted)-1])
|
||||
buffer.WriteString(letter)
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("%s%s", separator, elements[n-1]))
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (pt *Path) getAgnosterFullPath() string {
|
||||
pwd := pt.getPwd()
|
||||
for len(pwd) > 1 && string(pwd[0]) == pt.env.PathSeparator() {
|
||||
pwd = pwd[1:]
|
||||
func (pt *Path) getAgnosterFullPath(root, path string) string {
|
||||
path = pt.replaceFolderSeparators(path)
|
||||
if root == pt.env.PathSeparator() {
|
||||
return path
|
||||
}
|
||||
return pt.replaceFolderSeparators(pwd)
|
||||
root = root[:len(root)-1] + pt.getFolderSeparator()
|
||||
return root + path
|
||||
}
|
||||
|
||||
func (pt *Path) getAgnosterShortPath() string {
|
||||
pwd := pt.getPwd()
|
||||
pathDepth := pt.pathDepth(pwd)
|
||||
func (pt *Path) getAgnosterShortPath(root, path string) string {
|
||||
elements := strings.Split(path, pt.env.PathSeparator())
|
||||
if root != pt.env.PathSeparator() {
|
||||
elements = append([]string{root[:len(root)-1]}, elements...)
|
||||
}
|
||||
depth := len(elements)
|
||||
maxDepth := pt.props.GetInt(MaxDepth, 1)
|
||||
if maxDepth < 1 {
|
||||
maxDepth = 1
|
||||
}
|
||||
hideRootLocation := pt.props.GetBool(HideRootLocation, false)
|
||||
if hideRootLocation {
|
||||
// 1-indexing to avoid showing the root location when exceeding the max depth
|
||||
pathDepth++
|
||||
if !hideRootLocation {
|
||||
maxDepth++
|
||||
}
|
||||
if pathDepth <= maxDepth {
|
||||
return pt.getAgnosterFullPath()
|
||||
if depth <= maxDepth {
|
||||
return pt.getAgnosterFullPath(root, path)
|
||||
}
|
||||
folderSeparator := pt.getFolderSeparator()
|
||||
pathSeparator := pt.env.PathSeparator()
|
||||
splitted := strings.Split(pwd, pathSeparator)
|
||||
fullPathDepth := len(splitted)
|
||||
splitPos := fullPathDepth - maxDepth
|
||||
var buffer strings.Builder
|
||||
if hideRootLocation {
|
||||
buffer.WriteString(splitted[splitPos])
|
||||
splitPos++
|
||||
} else {
|
||||
separator := pt.getFolderSeparator()
|
||||
folderIcon := pt.props.GetString(FolderIcon, "..")
|
||||
root := pt.rootLocation()
|
||||
buffer.WriteString(fmt.Sprintf("%s%s%s", root, folderSeparator, folderIcon))
|
||||
var buffer strings.Builder
|
||||
if !hideRootLocation {
|
||||
buffer.WriteString(fmt.Sprintf("%s%s", elements[0], separator))
|
||||
maxDepth--
|
||||
}
|
||||
splitPos := depth - maxDepth
|
||||
if splitPos != 1 {
|
||||
buffer.WriteString(fmt.Sprintf("%s%s", folderIcon, separator))
|
||||
}
|
||||
for i := splitPos; i < depth; i++ {
|
||||
buffer.WriteString(elements[i])
|
||||
if i != depth-1 {
|
||||
buffer.WriteString(separator)
|
||||
}
|
||||
for i := splitPos; i < fullPathDepth; i++ {
|
||||
buffer.WriteString(fmt.Sprintf("%s%s", folderSeparator, splitted[i]))
|
||||
}
|
||||
return buffer.String()
|
||||
}
|
||||
|
||||
func (pt *Path) getFullPath() string {
|
||||
pwd := pt.getPwd()
|
||||
return pt.replaceFolderSeparators(pwd)
|
||||
func (pt *Path) getFullPath(root, path string) string {
|
||||
if root != pt.env.PathSeparator() {
|
||||
root = root[:len(root)-1] + pt.getFolderSeparator()
|
||||
}
|
||||
path = pt.replaceFolderSeparators(path)
|
||||
return root + path
|
||||
}
|
||||
|
||||
func (pt *Path) getFolderPath() string {
|
||||
pwd := pt.getPwd()
|
||||
pwd = environment.Base(pt.env, pwd)
|
||||
return pt.replaceFolderSeparators(pwd)
|
||||
func (pt *Path) getFolderPath(path string) string {
|
||||
return environment.Base(pt.env, path)
|
||||
}
|
||||
|
||||
func (pt *Path) setPwd() {
|
||||
if len(pt.pwd) > 0 {
|
||||
return
|
||||
}
|
||||
if pt.env.Shell() == shell.PWSH || pt.env.Shell() == shell.PWSH5 {
|
||||
pt.pwd = pt.env.Flags().PSWD
|
||||
}
|
||||
if len(pt.pwd) == 0 {
|
||||
pt.pwd = pt.env.Pwd()
|
||||
return
|
||||
}
|
||||
// ensure a clean path
|
||||
root, path := environment.ParsePath(pt.env, pt.pwd)
|
||||
pt.pwd = root + path
|
||||
}
|
||||
|
||||
func (pt *Path) getPwd() string {
|
||||
pwd := pt.env.Flags().PSWD
|
||||
if pwd == "" {
|
||||
pwd = pt.env.Pwd()
|
||||
pt.setPwd()
|
||||
return pt.replaceMappedLocations(pt.pwd)
|
||||
}
|
||||
|
||||
func (pt *Path) formatRoot(root string) string {
|
||||
n := len(root)
|
||||
// trim the trailing separator first
|
||||
root = root[:n-1]
|
||||
// only preserve the trailing separator for a Unix/Windows/PSDrive root
|
||||
if len(root) == 0 || (strings.HasPrefix(pt.pwd, root) && strings.HasSuffix(root, ":")) {
|
||||
return root + pt.env.PathSeparator()
|
||||
}
|
||||
pwd = pt.replaceMappedLocations(pwd)
|
||||
return pwd
|
||||
return root
|
||||
}
|
||||
|
||||
func (pt *Path) normalize(inputPath string) string {
|
||||
normalized := inputPath
|
||||
if strings.HasPrefix(inputPath, "~") {
|
||||
if strings.HasPrefix(normalized, "~") && (len(normalized) == 1 || environment.IsPathSeparator(pt.env, normalized[1])) {
|
||||
normalized = pt.env.Home() + normalized[1:]
|
||||
}
|
||||
normalized = strings.ReplaceAll(normalized, "\\", "/")
|
||||
goos := pt.env.GOOS()
|
||||
if goos == environment.WINDOWS || goos == environment.DARWIN {
|
||||
switch pt.env.GOOS() {
|
||||
case environment.WINDOWS:
|
||||
normalized = strings.ReplaceAll(normalized, "/", `\`)
|
||||
fallthrough
|
||||
case environment.DARWIN:
|
||||
normalized = strings.ToLower(normalized)
|
||||
}
|
||||
if !strings.HasSuffix(normalized, "/") {
|
||||
normalized += "/"
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
func (pt *Path) replaceMappedLocations(pwd string) string {
|
||||
if strings.HasPrefix(pwd, "Microsoft.PowerShell.Core\\FileSystem::") {
|
||||
pwd = strings.Replace(pwd, "Microsoft.PowerShell.Core\\FileSystem::", "", 1)
|
||||
}
|
||||
|
||||
mappedLocations := map[string]string{}
|
||||
// predefined mapped locations, can be disabled
|
||||
if pt.props.GetBool(MappedLocationsEnabled, true) {
|
||||
mappedLocations[pt.normalize("hkcu:")] = pt.props.GetString(WindowsRegistryIcon, "\uF013")
|
||||
mappedLocations[pt.normalize("hklm:")] = pt.props.GetString(WindowsRegistryIcon, "\uF013")
|
||||
mappedLocations["hkcu:"] = pt.props.GetString(WindowsRegistryIcon, "\uF013")
|
||||
mappedLocations["hklm:"] = pt.props.GetString(WindowsRegistryIcon, "\uF013")
|
||||
mappedLocations[pt.normalize(pt.env.Home())] = pt.props.GetString(HomeIcon, "~")
|
||||
}
|
||||
|
||||
|
@ -369,37 +403,48 @@ func (pt *Path) replaceMappedLocations(pwd string) string {
|
|||
// mapped locations can override predefined locations
|
||||
keyValues := pt.props.GetKeyValueMap(MappedLocations, make(map[string]string))
|
||||
for key, val := range keyValues {
|
||||
if key != "" {
|
||||
mappedLocations[pt.normalize(key)] = val
|
||||
}
|
||||
}
|
||||
|
||||
// sort map keys in reverse order
|
||||
// fixes case when a subfoder and its parent are mapped
|
||||
// ex /users/test and /users/test/dev
|
||||
keys := make([]string, len(mappedLocations))
|
||||
i := 0
|
||||
keys := make([]string, 0, len(mappedLocations))
|
||||
for k := range mappedLocations {
|
||||
keys[i] = k
|
||||
i++
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Sort(sort.Reverse(sort.StringSlice(keys)))
|
||||
|
||||
normalizedPwd := pt.normalize(pwd)
|
||||
cleanPwdRoot, cleanPwdPath := environment.ParsePath(pt.env, pwd)
|
||||
pwdRoot := pt.normalize(cleanPwdRoot)
|
||||
pwdPath := pt.normalize(cleanPwdPath)
|
||||
for _, key := range keys {
|
||||
if strings.HasPrefix(normalizedPwd, key) {
|
||||
replacement := mappedLocations[key]
|
||||
// -1 as we want to ignore the trailing slash
|
||||
// set by the normalize function
|
||||
return replacement + pwd[len(key)-1:]
|
||||
keyRoot, keyPath := environment.ParsePath(pt.env, key)
|
||||
if keyRoot != pwdRoot || !strings.HasPrefix(pwdPath, keyPath) {
|
||||
continue
|
||||
}
|
||||
value := mappedLocations[key]
|
||||
rem := cleanPwdPath[len(keyPath):]
|
||||
if len(rem) == 0 {
|
||||
// exactly match the full path
|
||||
return value
|
||||
}
|
||||
if len(keyPath) == 0 {
|
||||
// only match the root
|
||||
return value + pt.env.PathSeparator() + cleanPwdPath
|
||||
}
|
||||
// match several prefix elements
|
||||
if rem[0:1] == pt.env.PathSeparator() {
|
||||
return value + rem
|
||||
}
|
||||
}
|
||||
return pwd
|
||||
return cleanPwdRoot + cleanPwdPath
|
||||
}
|
||||
|
||||
func (pt *Path) replaceFolderSeparators(pwd string) string {
|
||||
defaultSeparator := pt.env.PathSeparator()
|
||||
if pwd == defaultSeparator {
|
||||
return pwd
|
||||
}
|
||||
folderSeparator := pt.getFolderSeparator()
|
||||
if folderSeparator == defaultSeparator {
|
||||
return pwd
|
||||
|
@ -408,22 +453,3 @@ func (pt *Path) replaceFolderSeparators(pwd string) string {
|
|||
pwd = strings.ReplaceAll(pwd, defaultSeparator, folderSeparator)
|
||||
return pwd
|
||||
}
|
||||
|
||||
func (pt *Path) rootLocation() string {
|
||||
pwd := pt.getPwd()
|
||||
pwd = strings.TrimPrefix(pwd, pt.env.PathSeparator())
|
||||
splitted := strings.Split(pwd, pt.env.PathSeparator())
|
||||
rootLocation := splitted[0]
|
||||
return rootLocation
|
||||
}
|
||||
|
||||
func (pt *Path) pathDepth(pwd string) int {
|
||||
splitted := strings.Split(pwd, pt.env.PathSeparator())
|
||||
depth := 0
|
||||
for _, part := range splitted {
|
||||
if part != "" {
|
||||
depth++
|
||||
}
|
||||
}
|
||||
return depth - 1
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -100,22 +100,22 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
|
|||
|
||||
function Set-PoshContext {}
|
||||
|
||||
function Get-PoshContext {
|
||||
$cleanPWD = $PWD.ProviderPath
|
||||
$cleanPSWD = $PWD.ToString()
|
||||
$cleanPWD = $cleanPWD.TrimEnd('\')
|
||||
$cleanPSWD = $cleanPSWD.TrimEnd('\')
|
||||
return $cleanPWD, $cleanPSWD
|
||||
function Get-CleanPSWD {
|
||||
$pswd = $PWD.ToString()
|
||||
if ($pswd -ne '/') {
|
||||
return $pswd.TrimEnd('\') -replace '^Microsoft\.PowerShell\.Core\\FileSystem::', ''
|
||||
}
|
||||
return $pswd
|
||||
}
|
||||
|
||||
if (("::TOOLTIPS::" -eq "true") -and ($ExecutionContext.SessionState.LanguageMode -ne "ConstrainedLanguage")) {
|
||||
Set-PSReadLineKeyHandler -Key Spacebar -BriefDescription 'OhMyPoshSpaceKeyHandler' -ScriptBlock {
|
||||
[Microsoft.PowerShell.PSConsoleReadLine]::Insert(' ')
|
||||
$position = $host.UI.RawUI.CursorPosition
|
||||
$cleanPWD, $cleanPSWD = Get-PoshContext
|
||||
$cleanPSWD = Get-CleanPSWD
|
||||
$command = $null
|
||||
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$command, [ref]$null)
|
||||
$standardOut = @(Start-Utf8Process $script:OMPExecutable @("print", "tooltip", "--error=$script:ErrorCode", "--pwd=$cleanPWD", "--shell=$script:ShellName", "--pswd=$cleanPSWD", "--config=$env:POSH_THEME", "--command=$command", "--shell-version=$script:PSVersion"))
|
||||
$standardOut = @(Start-Utf8Process $script:OMPExecutable @("print", "tooltip", "--error=$script:ErrorCode", "--shell=$script:ShellName", "--pswd=$cleanPSWD", "--config=$env:POSH_THEME", "--command=$command", "--shell-version=$script:PSVersion"))
|
||||
Write-Host $standardOut -NoNewline
|
||||
$host.UI.RawUI.CursorPosition = $position
|
||||
# we need this workaround to prevent the text after cursor from disappearing when the tooltip is rendered
|
||||
|
@ -259,9 +259,10 @@ New-Module -Name "oh-my-posh-core" -ScriptBlock {
|
|||
if ($List -eq $true) {
|
||||
$themes | Select-Object @{ Name = 'hyperlink'; Expression = { Get-FileHyperlink -uri $_.FullName } } | Format-Table -HideTableHeaders
|
||||
} else {
|
||||
$cleanPSWD = Get-CleanPSWD
|
||||
$themes | ForEach-Object -Process {
|
||||
Write-Host "Theme: $(Get-FileHyperlink -uri $_.FullName -Name ($_.BaseName -replace '\.omp$', ''))`n"
|
||||
@(Start-Utf8Process $script:OMPExecutable @("print", "primary", "--config=$($_.FullName)", "--pwd=$PWD", "--shell=$script:ShellName"))
|
||||
@(Start-Utf8Process $script:OMPExecutable @("print", "primary", "--config=$($_.FullName)", "--pswd=$cleanPSWD", "--shell=$script:ShellName"))
|
||||
Write-Host "`n"
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +332,7 @@ Example:
|
|||
if ($script:PromptType -ne 'transient') {
|
||||
Update-PoshErrorCode
|
||||
}
|
||||
$cleanPWD, $cleanPSWD = Get-PoshContext
|
||||
$cleanPSWD = Get-CleanPSWD
|
||||
$stackCount = global:Get-PoshStackCount
|
||||
Set-PoshContext
|
||||
$terminalWidth = $Host.UI.RawUI.WindowSize.Width
|
||||
|
@ -339,7 +340,7 @@ Example:
|
|||
if (-not $terminalWidth) {
|
||||
$terminalWidth = 0
|
||||
}
|
||||
$standardOut = @(Start-Utf8Process $script:OMPExecutable @("print", $script:PromptType, "--error=$script:ErrorCode", "--pwd=$cleanPWD", "--pswd=$cleanPSWD", "--execution-time=$script:ExecutionTime", "--stack-count=$stackCount", "--config=$env:POSH_THEME", "--shell-version=$script:PSVersion", "--terminal-width=$terminalWidth", "--shell=$script:ShellName"))
|
||||
$standardOut = @(Start-Utf8Process $script:OMPExecutable @("print", $script:PromptType, "--error=$script:ErrorCode", "--pswd=$cleanPSWD", "--execution-time=$script:ExecutionTime", "--stack-count=$stackCount", "--config=$env:POSH_THEME", "--shell-version=$script:PSVersion", "--terminal-width=$terminalWidth", "--shell=$script:ShellName"))
|
||||
# make sure PSReadLine knows if we have a multiline prompt
|
||||
Set-PSReadLineOption -ExtraPromptLineCount (($standardOut | Measure-Object -Line).Lines - 1)
|
||||
# the output can be multiline, joining these ensures proper rendering by adding line breaks with `n
|
||||
|
|
|
@ -42,9 +42,9 @@ Display the current path.
|
|||
|
||||
## Mapped Locations
|
||||
|
||||
Allows you to override a location with an icon. It validates if the current path **starts with** the value and replaces
|
||||
it with the icon if there's a match. To avoid issues with nested overrides, Oh My Posh will sort the list of mapped
|
||||
locations before doing a replacement.
|
||||
Allows you to override a location with an icon/string.
|
||||
It validates if the current path **starts with the specific elements** and replaces it with the icon/string if there's a match.
|
||||
To avoid issues with nested overrides, Oh My Posh will sort the list of mapped locations before doing a replacement.
|
||||
|
||||
| Name | Type | Description |
|
||||
| -------------------------- | --------- | -------------------------------------------------------------------------------------------------------- |
|
||||
|
@ -63,8 +63,8 @@ For example, to swap out `C:\Users\Leet\GitHub` with a GitHub icon, you can do t
|
|||
|
||||
### Notes
|
||||
|
||||
- Oh My Posh will accept both `/` and `\` as path separators for a mapped location and will match regardless of which
|
||||
is used by the current operating system.
|
||||
- To make mapped Locations work cross-platform, you should use `/` as the path separator, Oh My Posh will
|
||||
automatically match effective separators based on the running operating system.
|
||||
- The character `~` at the start of a mapped location will match the user's home directory.
|
||||
- The match is case-insensitive on Windows and macOS, but case-sensitive on other operating systems.
|
||||
|
||||
|
@ -87,8 +87,8 @@ Style sets the way the path is displayed. Based on previous experience and popul
|
|||
|
||||
### Agnoster
|
||||
|
||||
Renders each folder as the `folder_icon` separated by the `folder_separator_icon`.
|
||||
Only the current folder name is displayed at the end.
|
||||
Renders each intermediate folder as the `folder_icon` separated by the `folder_separator_icon`.
|
||||
The first and the last (current) folder name are always displayed as-is.
|
||||
|
||||
### Agnoster Full
|
||||
|
||||
|
@ -96,17 +96,18 @@ Renders each folder name separated by the `folder_separator_icon`.
|
|||
|
||||
### Agnoster Short
|
||||
|
||||
When more than `max_depth` levels deep, it renders one `folder_icon` (if `hide_root_location` is `false`) followed by
|
||||
the names of the last `max_depth` folders, separated by the `folder_separator_icon`.
|
||||
When more than `max_depth` levels deep, it renders one `folder_icon` (if `hide_root_location` is `false`,
|
||||
which means the root folder does not count for depth) followed by the names of the last `max_depth` folders,
|
||||
separated by the `folder_separator_icon`.
|
||||
|
||||
### Agnoster Left
|
||||
|
||||
Renders each folder as the `folder_icon` separated by the `folder_separator_icon`.
|
||||
Only the root folder name and it's child are displayed in full.
|
||||
Only the first folder name and its child are displayed in full.
|
||||
|
||||
### Full
|
||||
|
||||
Display `$PWD` as a string.
|
||||
Display the current working directory as a full string with each folder separated by the `folder_separator_icon`.
|
||||
|
||||
### Folder
|
||||
|
||||
|
@ -114,13 +115,13 @@ Display the name of the current folder.
|
|||
|
||||
### Mixed
|
||||
|
||||
Works like `Agnoster Full`, but for any middle folder short enough it will display its name instead. The maximum length
|
||||
for the folders to display is governed by the `mixed_threshold` property.
|
||||
Works like `Agnoster`, but for any intermediate folder name that is short enough, it will be displayed as-is.
|
||||
The maximum length for the folders to display is governed by the `mixed_threshold` property.
|
||||
|
||||
### Letter
|
||||
|
||||
Works like `Full`, but will write every subfolder name using the first letter only, except when the folder name
|
||||
starts with a symbol or icon.
|
||||
Works like `Agnoster Full`, but will write every folder name using the first letter only, except when the folder name
|
||||
starts with a symbol or icon. Specially, the last (current) folder name is always displayed in full.
|
||||
|
||||
- `folder` will be shortened to `f`
|
||||
- `.config` will be shortened to `.c`
|
||||
|
@ -132,9 +133,9 @@ starts with a symbol or icon.
|
|||
Works like `Letter`, but will make sure every folder name is the shortest unique value.
|
||||
|
||||
The uniqueness refers to the displayed path, so `C:\dev\dev\dev\development` will be displayed as
|
||||
`C:\d\de\dev\development` (instead of `C:\d\d\d\development` for `Letter`). Uniqueness does _not_ refer to other
|
||||
`C\d\de\dev\development` (instead of `C\d\d\d\development` for `Letter`). Uniqueness does **not** refer to other
|
||||
folders at the same level, so if `C:\projectA\dev` and `C:\projectB\dev` exist, then both will be displayed as
|
||||
`C:\p\dev`.
|
||||
`C\p\dev`.
|
||||
|
||||
## Template ([info][templates])
|
||||
|
||||
|
@ -151,7 +152,7 @@ folders at the same level, so if `C:\projectA\dev` and `C:\projectB\dev` exist,
|
|||
| Name | Type | Description |
|
||||
| ------------- | --------- | ---------------------------------------------------------------------------- |
|
||||
| `.Path` | `string` | the current directory (based on the `style` property) |
|
||||
| `.Parent` | `string` | the current directory's parent folder (designed for use with style `folder`) |
|
||||
| `.Parent` | `string` | the current directory's parent folder which ends with a path separator (designed for use with style `folder`, it is empty if `.Path` contains only one single element) |
|
||||
| `.Location` | `string` | the current directory (raw value) |
|
||||
| `.StackCount` | `int` | the stack count |
|
||||
| `.Writable` | `boolean` | is the current directory writable by the user or not |
|
||||
|
|
Loading…
Reference in a new issue