Summary
When Fresh runs on Windows and connects to a Linux remote via fresh ssh://user@host/path, no
language server ever starts for remote files. A POSIX remote path can't be converted to a file:// URI by a
Windows-host build, so didOpen is never sent and the server is never spawned.
Repro
- On Windows,
fresh ssh://user@linux-host/home/user/project
- Open a file with a configured LSP (any language pack with
fresh.lsp).
- No server starts.
ShowLspStatus reports nothing running.
Log:
WARN fresh::app::file_operations: No URI in metadata for file: /home/user/project/main.rs (failed to compute absolute path)
Cause
Window::notify_lsp_file_opened returns early when the buffer has no URI:
let Some(uri) = metadata.file_uri().cloned() else {
tracing::warn!("No URI in metadata for file: {} (failed to compute absolute path)", path.display());
return;
};
crates/fresh-editor/src/app/file_operations.rs:1448
The URI is computed in BufferMetadata::with_file → LspUri::from_host_path →
fresh_core::file_uri::path_to_file_uri, which starts with:
if !path.is_absolute() { return None; }
crates/fresh-core/src/file_uri.rs:26
On Windows, Path::new("/home/user/project/main.rs").is_absolute() is false Windows
absoluteness requires a Prefix component (drive letter or UNC); a rooted-but-prefixless path has
has_root() == true but is_absolute() == false. Even without that guard, the #[cfg(windows)]
branch matches on Component::Prefix and returns None when prefix_kind is None
(file_uri.rs:58-61).
So the remote POSIX path is being interpreted with host path semantics. LspUri's
PathTranslation escape hatch doesn't help, because it's only populated for container authorities —
per the doc comment at crates/fresh-editor/src/app/types/buffer_meta.rs:172, it is "None for
local/SSH authorities". SSH assumes host and remote share path semantics, which holds Linux → Linux
and breaks Windows → Linux.
I also verified Linux → Linux, it works fine.
Suggested fix
Make URI computation path-style-aware rather than host-platform-aware:
- Add
path_to_file_uri_posix / file_uri_to_path_posix in fresh-core/src/file_uri.rs that build
and parse using /-splitting instead of Path::components(), so they behave identically
regardless of the platform Fresh was compiled for.
- Carry a path style (posix / windows) on the
Authority, set from the transport — SSH and
kubectl-exec remotes are posix.
- Have
LspUri::from_host_path / to_host_path and the root_uri construction in LspManager
select the builder from that style instead of #[cfg].
The canonicalize() calls in display_name_for_path (buffer_meta.rs:317) already fall back
gracefully on failure, so they shouldn't need changes for this.
Broader note: there are ~39 is_absolute() call sites in fresh-editor/fresh-core that interpret
possibly-remote paths with host semantics. Most degrade gracefully; the URI path is the one that
hard-fails. A path-style abstraction would be the systematic fix, but the three steps above are the
contained change that restores remote LSP.
Environment
- Fresh host: Windows
- Remote: Linux (Termux/Android in my case, but the path semantics issue is generic)
- Repro'd against
b5b3dc7855c36b8ce6fcbbe3c0652d050f6f7cd0
Summary
When Fresh runs on Windows and connects to a Linux remote via
fresh ssh://user@host/path, nolanguage server ever starts for remote files. A POSIX remote path can't be converted to a
file://URI by aWindows-host build, so
didOpenis never sent and the server is never spawned.Repro
fresh ssh://user@linux-host/home/user/projectfresh.lsp).ShowLspStatusreports nothing running.Log:
Cause
Window::notify_lsp_file_openedreturns early when the buffer has no URI:crates/fresh-editor/src/app/file_operations.rs:1448The URI is computed in
BufferMetadata::with_file→LspUri::from_host_path→fresh_core::file_uri::path_to_file_uri, which starts with:crates/fresh-core/src/file_uri.rs:26On Windows,
Path::new("/home/user/project/main.rs").is_absolute()is false Windowsabsoluteness requires a
Prefixcomponent (drive letter or UNC); a rooted-but-prefixless path hashas_root() == truebutis_absolute() == false. Even without that guard, the#[cfg(windows)]branch matches on
Component::Prefixand returnsNonewhenprefix_kindisNone(
file_uri.rs:58-61).So the remote POSIX path is being interpreted with host path semantics.
LspUri'sPathTranslationescape hatch doesn't help, because it's only populated for container authorities —per the doc comment at
crates/fresh-editor/src/app/types/buffer_meta.rs:172, it is "Noneforlocal/SSH authorities". SSH assumes host and remote share path semantics, which holds Linux → Linux
and breaks Windows → Linux.
I also verified Linux → Linux, it works fine.
Suggested fix
Make URI computation path-style-aware rather than host-platform-aware:
path_to_file_uri_posix/file_uri_to_path_posixinfresh-core/src/file_uri.rsthat buildand parse using
/-splitting instead ofPath::components(), so they behave identicallyregardless of the platform Fresh was compiled for.
Authority, set from the transport — SSH andkubectl-exec remotes are posix.
LspUri::from_host_path/to_host_pathand theroot_uriconstruction inLspManagerselect the builder from that style instead of
#[cfg].The
canonicalize()calls indisplay_name_for_path(buffer_meta.rs:317) already fall backgracefully on failure, so they shouldn't need changes for this.
Broader note: there are ~39
is_absolute()call sites infresh-editor/fresh-corethat interpretpossibly-remote paths with host semantics. Most degrade gracefully; the URI path is the one that
hard-fails. A path-style abstraction would be the systematic fix, but the three steps above are the
contained change that restores remote LSP.
Environment
b5b3dc7855c36b8ce6fcbbe3c0652d050f6f7cd0