Repository: sxyazi/yazi (language: rust)

Issue:
`cwd` gets messed up when using `g<Space>` with `../`
### What system are you running Yazi on?

Linux Wayland

### What terminal are you running Yazi in?

kitty 0.35.2

### `yazi --debug` output

<details>

```sh
##### ↓↓↓ Paste the output here: ↓↓↓ #####

Yazi
    Version: 0.3.0 (d2ebadb6 2024-08-03)
    Debug  : false
    OS     : linux-x86_64 (unix)

Ya
    Version: 0.3.0

Emulator
    Emulator.via_env: ("xterm-kitty", "")
    Emulator.via_csi: Ok(Kitty)
    Emulator.detect : Kitty

Adapter
    Adapter.matches: Kitty

Desktop
    XDG_SESSION_TYPE: Some("wayland")
    WAYLAND_DISPLAY : Some("wayland-0")
    DISPLAY         : Some(":0")

SSH
    shared.in_ssh_connection: false

WSL
    /proc/sys/fs/binfmt_misc/WSLInterop: false

Variables
    SHELL              : Some("/usr/bin/fish")
    EDITOR             : Some("nvim")
    YAZI_FILE_ONE      : None
    YAZI_CONFIG_HOME   : None
    ZELLIJ_SESSION_NAME: None

Text Opener
    default: Some(Opener { run: "${EDITOR:-vi} \"$@\"", block: true, orphan: false, desc: "$EDITOR", for_: None, spread: true })
    block  : Some(Opener { run: "${EDITOR:-vi} \"$@\"", block: true, orphan: false, desc: "$EDITOR", for_: None, spread: true })

tmux
    TMUX   : false
    Version: 3.4

Dependencies
    file             : 5.45
    ueberzugpp       : 2.9.6
    ffmpegthumbnailer: 2.2.2
    magick           : 7.1.1-36
    fzf              : 0.54.3
    fd               : 10.1.0
    rg               : 14.1.0
    chafa            : 1.14.1
    zoxide           : 0.9.4
    7z               : 17.05
    7zz              : No such file or directory (os error 2)
    jq               : 1.7.1


--------------------------------------------------
When reporting a bug, please also upload the `yazi.log` log file - only upload the most recent content by time.
You can find it in the "/home/tlss/.local/state/yazi" directory.
```

</details>


### Did you try the latest nightly build to see if the problem got fixed?

Yes, and I updated the debug information above (`yazi --debug`) to the nightly that I tried

### Describe the bug

When using `g<Space>` with `../xxx/xxx`, the `cwd` in the header is appended directly.

![screenshot_20240806_010056](https://github.com/user-attachments/assets/18af8a0b-31b2-435e-acff-a668e16f1760)

![screenshot_20240806_010858](https://github.com/user-attachments/assets/867b6099-4ccc-4157-930f-3fa1cd95061d)


### Minimal reproducer

1. Open yazi
2. Press `g<Space>`
3. Input `../` then press `<Enter>`
4. Check the `cwd` in the header

### Anything else?

_No response_


Discussion / hints:
I noticed that when we use `g<Space>`, the new URL is generated by concatenating the current cwd and the user input

https://github.com/sxyazi/yazi/blob/f8659104814f183a1f580bd30cb481136105bf94/yazi-shared/src/fs/path.rs#L46

This action does not seem to resolve the relative path correctly. I will open a PR to fix that.

Fix patch:
diff --git a/yazi-shared/src/fs/path.rs b/yazi-shared/src/fs/path.rs
index 4aa318e69..ddaebda8b 100644
--- a/yazi-shared/src/fs/path.rs
+++ b/yazi-shared/src/fs/path.rs
@@ -1,9 +1,8 @@
-use std::{borrow::Cow, env, ffi::OsString, path::{Component, Path, PathBuf, MAIN_SEPARATOR}};
+use std::{borrow::Cow, env, ffi::OsString, path::{Component, Path, PathBuf}};
 
 use super::maybe_exists;
 use crate::fs::Url;
 
-#[inline]
 pub fn current_cwd() -> Option<PathBuf> {
 	env::var_os("PWD")
 		.map(PathBuf::from)
@@ -11,6 +10,29 @@ pub fn current_cwd() -> Option<PathBuf> {
 		.or_else(|| env::current_dir().ok())
 }
 
+pub fn clean_path(path: &Path) -> PathBuf {
+	let mut out = vec![];
+	for c in path.components() {
+		match c {
+			Component::CurDir => {}
+			Component::ParentDir => match out.last() {
+				Some(Component::RootDir) => {}
+				Some(Component::Normal(_)) => _ = out.pop(),
+				None
+				| Some(Component::CurDir)
+				| Some(Component::ParentDir)
+				| Some(Component::Prefix(_)) => out.push(c),
+			},
+			c => out.push(c),
+		}
+	}
+
+	if out.is_empty() { PathBuf::from(".") } else { out.iter().collect() }
+}
+
+#[inline]
+pub fn expand_path(p: impl AsRef<Path>) -> PathBuf { _expand_path(p.as_ref()) }
+
 fn _expand_path(p: &Path) -> PathBuf {
 	// ${HOME} or $HOME
 	#[cfg(unix)]
@@ -35,24 +57,18 @@ fn _expand_path(p: &Path) -> PathBuf {
 		}
 	}
 
-	let p = Path::new(s.as_ref());
+	let p = clean_path(Path::new(s.as_ref()));
 	if let Ok(rest) = p.strip_prefix("~") {
 		return dirs::home_dir().unwrap_or_default().join(rest);
 	}
 
 	if p.is_absolute() {
-		return p.to_path_buf();
+		p
+	} else if let Some(cwd) = current_cwd() {
+		cwd.join(&p)
+	} else {
+		p
 	}
-	current_cwd().map_or_else(|| p.to_path_buf(), |c| c.join(p))
-}
-
-#[inline]
-pub fn expand_path(p: impl AsRef<Path>) -> PathBuf { _expand_path(p.as_ref()) }
-
-#[inline]
-pub fn ends_with_slash(p: &Path) -> bool {
-	let b = p.as_os_str().as_encoded_bytes();
-	if let [.., last] = b { *last == MAIN_SEPARATOR as u8 } else { false }
 }
 
 // FIXME: should return a `std::io::Result` to handle errors such as


Test patch: