Skip to content

Commit

Permalink
breaking(ish): pane.get_current_working_dir now returns Url
Browse files Browse the repository at this point in the history
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: #4157
refs: #4000
  • Loading branch information
wez committed Aug 25, 2023
1 parent 5478250 commit b904ed7
Show file tree
Hide file tree
Showing 17 changed files with 223 additions and 19 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ dhat-heap.json
/docs/config/lua/wezterm.procinfo/index.md
/docs/config/lua/wezterm.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/window-events/index.md
/docs/config/lua/window/index.md
Expand Down
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions ci/generate-docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,10 @@ def render(self, output, depth=0, mode="mdbook"):
"module: wezterm.time",
"config/lua/wezterm.time",
),
Gen(
"module: wezterm.url",
"config/lua/wezterm.url",
),
Gen(
"enum: KeyAssignment",
"config/lua/keyassignment",
Expand Down
8 changes: 8 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ As features stabilize some brief notes about them will accumulate here.

#### Changed
* 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
their own new Short Codes section.
* CharSelect now shows emoji variations such as skin tones
Expand Down
10 changes: 10 additions & 0 deletions docs/config/lua/pane/get_current_working_dir.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,13 @@ working directory using operating system dependent code:
If the current working directory is not known then this method returns `nil`.
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.

32 changes: 32 additions & 0 deletions docs/config/lua/wezterm.url/Url.md
Original file line number Diff line number Diff line change
@@ -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')
```

10 changes: 10 additions & 0 deletions docs/config/lua/wezterm.url/index.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# `wezterm.url` module

{{since('nightly')}}

The `wezterm.url` module exposes functions that allow working
with URLs.

## Available functions and objects


7 changes: 7 additions & 0 deletions docs/config/lua/wezterm.url/parse.md
Original file line number Diff line number Diff line change
@@ -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.

41 changes: 29 additions & 12 deletions docs/config/lua/window/set_right_status.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,40 @@ wezterm.on('update-right-status', function(window, pane)
-- shell is using OSC 7 on the remote host.
local cwd_uri = pane:get_current_working_dir()
if cwd_uri then
cwd_uri = cwd_uri:sub(8)
local slash = cwd_uri:find '/'
local cwd = ''
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)

if type(cwd_uri) == 'userdata' then
-- 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
-- and extract the cwd from the uri
cwd = cwd_uri:sub(slash)
end

table.insert(cells, cwd)
table.insert(cells, hostname)
-- 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

-- I like my date/time in this style: "Wed Mar 3 08:14"
Expand Down
1 change: 1 addition & 0 deletions env-bootstrap/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ share-data = { path = "../lua-api-crates/share-data" }
ssh-funcs = { path = "../lua-api-crates/ssh-funcs" }
spawn-funcs = { path = "../lua-api-crates/spawn-funcs" }
time-funcs = { path = "../lua-api-crates/time-funcs" }
url-funcs = { path = "../lua-api-crates/url-funcs" }
wezterm-version = { path = "../wezterm-version" }

[target."cfg(windows)".dependencies]
Expand Down
1 change: 1 addition & 0 deletions env-bootstrap/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ fn register_lua_modules() {
spawn_funcs::register,
share_data::register,
time_funcs::register,
url_funcs::register,
] {
config::lua::add_context_setup_func(func);
}
Expand Down
1 change: 1 addition & 0 deletions lua-api-crates/mux/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ portable-pty = { path = "../../pty" }
smol = "1.2"
termwiz = { path = "../../termwiz" }
mux = { path = "../../mux" }
url-funcs = { path = "../url-funcs" }
3 changes: 2 additions & 1 deletion lua-api-crates/mux/src/pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use mlua::Value;
use std::cmp::Ordering;
use std::sync::Arc;
use termwiz::cell::SemanticType;
use url_funcs::Url;
use wezterm_term::{SemanticZone, StableRowIndex};

#[derive(Clone, Copy, Debug)]
Expand Down Expand Up @@ -145,7 +146,7 @@ impl UserData for MuxPane {
methods.add_method("get_current_working_dir", |_, this, _: ()| {
let mux = get_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, _: ()| {
Expand Down
14 changes: 14 additions & 0 deletions lua-api-crates/url-funcs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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" }
83 changes: 83 additions & 0 deletions lua-api-crates/url-funcs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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)
}
});
}
}
1 change: 1 addition & 0 deletions wezterm-gui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ unicode-normalization = "0.1"
unicode-segmentation = "1.8"
unicode-width = "0.1"
url = "2"
url-funcs = { path = "../lua-api-crates/url-funcs" }
walkdir = "2"
wezterm-bidi = { path = "../bidi" }
wezterm-blob-leases = { path = "../wezterm-blob-leases", version="0.1", features=["simple_tempdir"] }
Expand Down
10 changes: 4 additions & 6 deletions wezterm-gui/src/termwindow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,16 +300,14 @@ impl UserData for PaneInformation {
Ok(name)
});
fields.add_field_method_get("current_working_dir", |_, this| {
let mut name = None;
if let Some(mux) = Mux::try_get() {
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 {
Some(name) => Ok(name),
None => Ok("".to_string()),
}
Ok(None)
});
fields.add_field_method_get("domain_name", |_, this| {
let mut name = None;
Expand Down

0 comments on commit b904ed7

Please sign in to comment.