1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-23 05:12:40 +03:00

breaking(ish): pane.get_current_working_dir now returns Url

Previously we'd return the Url string.  Now we provide a Url
object that provides access to the various elements of the Url.

This will cause slightly breakage for folks that were treating
it as a string in their status event handlers, for example.

The docs have been updated to show how to run with both this
new Url object and also continue to run on older versions of
wezterm.

They now also show how to manually percent decode the url
for older versions of wezterm.

refs: https://github.com/wez/wezterm/discussions/4157
refs: https://github.com/wez/wezterm/issues/4000
This commit is contained in:
Wez Furlong 2023-08-25 06:46:44 -07:00
parent 547825072c
commit b904ed7677
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
17 changed files with 224 additions and 20 deletions

1
.gitignore vendored
View File

@ -39,6 +39,7 @@ dhat-heap.json
/docs/config/lua/wezterm.procinfo/index.md /docs/config/lua/wezterm.procinfo/index.md
/docs/config/lua/wezterm.time/index.md /docs/config/lua/wezterm.time/index.md
/docs/config/lua/wezterm.time/Time/index.md /docs/config/lua/wezterm.time/Time/index.md
/docs/config/lua/wezterm.url/index.md
/docs/config/lua/wezterm/index.md /docs/config/lua/wezterm/index.md
/docs/config/lua/window-events/index.md /docs/config/lua/window-events/index.md
/docs/config/lua/window/index.md /docs/config/lua/window/index.md

15
Cargo.lock generated
View File

@ -1506,6 +1506,7 @@ dependencies = [
"termwiz", "termwiz",
"termwiz-funcs", "termwiz-funcs",
"time-funcs", "time-funcs",
"url-funcs",
"wezterm-version", "wezterm-version",
"winapi", "winapi",
] ]
@ -3266,6 +3267,7 @@ dependencies = [
"portable-pty", "portable-pty",
"smol", "smol",
"termwiz", "termwiz",
"url-funcs",
"wezterm-dynamic", "wezterm-dynamic",
"wezterm-term", "wezterm-term",
] ]
@ -5671,6 +5673,18 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "url-funcs"
version = "0.1.0"
dependencies = [
"anyhow",
"config",
"luahelper",
"percent-encoding",
"url",
"wezterm-dynamic",
]
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.1" version = "0.2.1"
@ -6185,6 +6199,7 @@ dependencies = [
"unicode-segmentation", "unicode-segmentation",
"unicode-width", "unicode-width",
"url", "url",
"url-funcs",
"walkdir", "walkdir",
"wezterm-bidi", "wezterm-bidi",
"wezterm-blob-leases", "wezterm-blob-leases",

View File

@ -398,6 +398,10 @@ TOC = [
"module: wezterm.time", "module: wezterm.time",
"config/lua/wezterm.time", "config/lua/wezterm.time",
), ),
Gen(
"module: wezterm.url",
"config/lua/wezterm.url",
),
Gen( Gen(
"enum: KeyAssignment", "enum: KeyAssignment",
"config/lua/keyassignment", "config/lua/keyassignment",

View File

@ -23,6 +23,14 @@ As features stabilize some brief notes about them will accumulate here.
#### Changed #### Changed
* The default for [front_end](config/lua/config/front_end.md) is now `WebGpu`. * The default for [front_end](config/lua/config/front_end.md) is now `WebGpu`.
* The return type of
[pane.get_current_working_dir](config/lua/pane/get_current_working_dir.md)
and [PaneInformation.current_working_dir](config/lua/PaneInformation.md)
has changed to the new [Url](config/lua/wezterm.url/Url.md) object, which
makes it easier to handle things like percent-encoding for paths with spaces
or non-ASCII characters. Please see the revised example on
[set_right_status](config/lua/window/set_right_status.md) for example usage
with backwards compatibility in mind. #4000
* Added split out github short codes from the various charselect sections into * Added split out github short codes from the various charselect sections into
their own new Short Codes section. their own new Short Codes section.
* CharSelect now shows emoji variations such as skin tones * CharSelect now shows emoji variations such as skin tones

View File

@ -24,3 +24,13 @@ working directory using operating system dependent code:
If the current working directory is not known then this method returns `nil`. If the current working directory is not known then this method returns `nil`.
Otherwise, it returns the current working directory as a URI string. Otherwise, it returns the current working directory as a URI string.
Note that while the current working directory is usually a file path,
it is possible for an application to set it to an FTP URL or some
other kind of URL, which is why this method doesn't simply return
a file path string.
{{since('nightly')}}
This method now returns a [Url](../wezterm.url/Url.md) object which
provides a convenient way to decode and operate on the URL.

View File

@ -0,0 +1,32 @@
# Url object
{{since('nightly')}}
The `Url` object represents a parsed Url. It has the following fields:
* `scheme` - the URL scheme such as `"file"`, or `"https"`
* `file_path` - decodes the `path` field and interprets it as a file path
* `username` - the username portion of the URL, or an empty string if none is specified
* `password` - the password portion of the URL, or `nil` if none is specified
* `host` - the hostname portion of the URL, with IDNA decoded to UTF-8
* `path` - the path portion of the URL, complete with percent encoding
* `fragment` - the fragment portion of the URL
* `query` - the query portion of the URL
```lua
local wezterm = require 'wezterm'
local url = wezterm.url.parse 'file://myhost/some/path%20with%20spaces'
assert(url.scheme == 'file')
assert(url.file_path == '/some/path with spaces')
local url =
wezterm.url.parse 'https://github.com/rust-lang/rust/issues?labels=E-easy&state=open'
assert(url.scheme == 'https')
assert(url.username == '')
assert(url.password == nil)
assert(url.host == 'github.com')
assert(url.path == '/rust-lang/rust/issues')
assert(url.query == 'labels=E-easy&state=open')
```

View File

@ -0,0 +1,10 @@
# `wezterm.url` module
{{since('nightly')}}
The `wezterm.url` module exposes functions that allow working
with URLs.
## Available functions and objects

View File

@ -0,0 +1,7 @@
# `wezterm.url.parse(URL_STRING)`
{{since('nightly')}}
Attempts to parse the provided *URL_STRING* as a URL.
If success, returns a [Url](Url.md) object representing that URL.

View File

@ -51,23 +51,40 @@ wezterm.on('update-right-status', function(window, pane)
-- shell is using OSC 7 on the remote host. -- shell is using OSC 7 on the remote host.
local cwd_uri = pane:get_current_working_dir() local cwd_uri = pane:get_current_working_dir()
if cwd_uri then if cwd_uri then
cwd_uri = cwd_uri:sub(8)
local slash = cwd_uri:find '/'
local cwd = '' local cwd = ''
local hostname = '' local hostname = ''
if slash then
hostname = cwd_uri:sub(1, slash - 1)
-- Remove the domain name portion of the hostname
local dot = hostname:find '[.]'
if dot then
hostname = hostname:sub(1, dot - 1)
end
-- and extract the cwd from the uri
cwd = cwd_uri:sub(slash)
table.insert(cells, cwd) if type(cwd_uri) == 'userdata' then
table.insert(cells, hostname) -- Running on a newer version of wezterm and we have
-- a URL object here, making this simple!
cwd = cwd_uri.file_path
hostname = cwd_uri.host or wezterm.hostname()
else
-- an older version of wezterm, 20230712-072601-f4abf8fd or earlier,
-- which doesn't have the Url object
cwd_uri = cwd_uri:sub(8)
local slash = cwd_uri:find '/'
if slash then
hostname = cwd_uri:sub(1, slash - 1)
-- and extract the cwd from the uri, decoding %-encoding
cwd = cwd_uri:sub(slash):gsub('%%(%x%x)', function(hex)
return string.char(tonumber(hex, 16))
end)
end
end end
-- Remove the domain name portion of the hostname
local dot = hostname:find '[.]'
if dot then
hostname = hostname:sub(1, dot - 1)
end
if hostname == '' then
hostname = wezterm.hostname()
end
table.insert(cells, cwd)
table.insert(cells, hostname)
end end
-- I like my date/time in this style: "Wed Mar 3 08:14" -- I like my date/time in this style: "Wed Mar 3 08:14"

View File

@ -29,6 +29,7 @@ share-data = { path = "../lua-api-crates/share-data" }
ssh-funcs = { path = "../lua-api-crates/ssh-funcs" } ssh-funcs = { path = "../lua-api-crates/ssh-funcs" }
spawn-funcs = { path = "../lua-api-crates/spawn-funcs" } spawn-funcs = { path = "../lua-api-crates/spawn-funcs" }
time-funcs = { path = "../lua-api-crates/time-funcs" } time-funcs = { path = "../lua-api-crates/time-funcs" }
url-funcs = { path = "../lua-api-crates/url-funcs" }
wezterm-version = { path = "../wezterm-version" } wezterm-version = { path = "../wezterm-version" }
[target."cfg(windows)".dependencies] [target."cfg(windows)".dependencies]

View File

@ -205,6 +205,7 @@ fn register_lua_modules() {
spawn_funcs::register, spawn_funcs::register,
share_data::register, share_data::register,
time_funcs::register, time_funcs::register,
url_funcs::register,
] { ] {
config::lua::add_context_setup_func(func); config::lua::add_context_setup_func(func);
} }

View File

@ -18,3 +18,4 @@ portable-pty = { path = "../../pty" }
smol = "1.2" smol = "1.2"
termwiz = { path = "../../termwiz" } termwiz = { path = "../../termwiz" }
mux = { path = "../../mux" } mux = { path = "../../mux" }
url-funcs = { path = "../url-funcs" }

View File

@ -4,6 +4,7 @@ use mlua::Value;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::sync::Arc; use std::sync::Arc;
use termwiz::cell::SemanticType; use termwiz::cell::SemanticType;
use url_funcs::Url;
use wezterm_term::{SemanticZone, StableRowIndex}; use wezterm_term::{SemanticZone, StableRowIndex};
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@ -145,7 +146,7 @@ impl UserData for MuxPane {
methods.add_method("get_current_working_dir", |_, this, _: ()| { methods.add_method("get_current_working_dir", |_, this, _: ()| {
let mux = get_mux()?; let mux = get_mux()?;
let pane = this.resolve(&mux)?; let pane = this.resolve(&mux)?;
Ok(pane.get_current_working_dir().map(|u| u.to_string())) Ok(pane.get_current_working_dir().map(|url| Url { url }))
}); });
methods.add_method("get_metadata", |lua, this, _: ()| { methods.add_method("get_metadata", |lua, this, _: ()| {

View File

@ -0,0 +1,14 @@
[package]
name = "url-funcs"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0"
config = { path = "../../config" }
luahelper = { path = "../../luahelper" }
percent-encoding = "2.3"
url = "2"
wezterm-dynamic = { path = "../../wezterm-dynamic" }

View File

@ -0,0 +1,83 @@
use crate::mlua::UserDataFields;
use config::lua::get_or_create_sub_module;
use config::lua::mlua::{self, Lua, MetaMethod, UserData, UserDataMethods};
use percent_encoding::percent_decode;
pub fn register(lua: &Lua) -> anyhow::Result<()> {
let url_mod = get_or_create_sub_module(lua, "url")?;
url_mod.set(
"parse",
lua.create_function(|_, s: String| {
let url = url::Url::parse(&s).map_err(|err| {
mlua::Error::external(format!("{err:#} while parsing {s} as URL"))
})?;
Ok(Url { url })
})?,
)?;
Ok(())
}
#[derive(Clone, Debug)]
pub struct Url {
pub url: url::Url,
}
impl std::ops::Deref for Url {
type Target = url::Url;
fn deref(&self) -> &url::Url {
&self.url
}
}
impl std::ops::DerefMut for Url {
fn deref_mut(&mut self) -> &mut url::Url {
&mut self.url
}
}
impl UserData for Url {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::ToString, |_, this, _: ()| {
Ok(this.url.as_str().to_string())
});
}
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("scheme", |_, this| Ok(this.scheme().to_string()));
fields.add_field_method_get("username", |_, this| Ok(this.username().to_string()));
fields.add_field_method_get("password", |_, this| {
Ok(this.password().map(|s| s.to_string()))
});
fields.add_field_method_get("host", |_, this| Ok(this.host_str().map(|s| s.to_string())));
fields.add_field_method_get("port", |_, this| Ok(this.port()));
fields.add_field_method_get("query", |_, this| Ok(this.query().map(|s| s.to_string())));
fields.add_field_method_get("fragment", |_, this| {
Ok(this.fragment().map(|s| s.to_string()))
});
fields.add_field_method_get("path", |_, this| Ok(this.path().to_string()));
fields.add_field_method_get("file_path", |lua, this| {
if let Some(segments) = this.path_segments() {
let mut bytes = vec![];
for segment in segments {
bytes.push(b'/');
bytes.extend(percent_decode(segment.as_bytes()));
}
// A windows drive letter must end with a slash.
if bytes.len() > 2
&& bytes[bytes.len() - 2].is_ascii_alphabetic()
&& matches!(bytes[bytes.len() - 1], b':' | b'|')
{
bytes.push(b'/');
}
let s = lua.create_string(bytes)?;
Ok(Some(s))
} else {
Ok(None)
}
});
}
}

View File

@ -92,6 +92,7 @@ unicode-normalization = "0.1"
unicode-segmentation = "1.8" unicode-segmentation = "1.8"
unicode-width = "0.1" unicode-width = "0.1"
url = "2" url = "2"
url-funcs = { path = "../lua-api-crates/url-funcs" }
walkdir = "2" walkdir = "2"
wezterm-bidi = { path = "../bidi" } wezterm-bidi = { path = "../bidi" }
wezterm-blob-leases = { path = "../wezterm-blob-leases", version="0.1", features=["simple_tempdir"] } wezterm-blob-leases = { path = "../wezterm-blob-leases", version="0.1", features=["simple_tempdir"] }

View File

@ -300,16 +300,14 @@ impl UserData for PaneInformation {
Ok(name) Ok(name)
}); });
fields.add_field_method_get("current_working_dir", |_, this| { fields.add_field_method_get("current_working_dir", |_, this| {
let mut name = None;
if let Some(mux) = Mux::try_get() { if let Some(mux) = Mux::try_get() {
if let Some(pane) = mux.get_pane(this.pane_id) { if let Some(pane) = mux.get_pane(this.pane_id) {
name = pane.get_current_working_dir().map(|u| u.to_string()); return Ok(pane
.get_current_working_dir()
.map(|url| url_funcs::Url { url }));
} }
} }
match name { Ok(None)
Some(name) => Ok(name),
None => Ok("".to_string()),
}
}); });
fields.add_field_method_get("domain_name", |_, this| { fields.add_field_method_get("domain_name", |_, this| {
let mut name = None; let mut name = None;