mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
fix: escape control characters in filenames
This commit is contained in:
parent
c6e03e91d2
commit
180b94fb94
14 changed files with 207 additions and 74 deletions
|
|
@ -1,11 +1,11 @@
|
|||
use std::{mem, path::MAIN_SEPARATOR_STR};
|
||||
use std::mem;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_fs::{CWD, path::expand_url, provider::{DirReader, FileHolder}};
|
||||
use yazi_fs::{CWD, path::clean_url, provider::{DirReader, FileHolder}};
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::cmp::{CmpItem, ShowOpt, TriggerOpt};
|
||||
use yazi_proxy::CmpProxy;
|
||||
use yazi_shared::{AnyAsciiChar, data::Data, natsort, path::{PathBufDyn, PathDyn, PathLike}, scheme::{SchemeCow, SchemeLike}, strand::{AsStrand, StrandLike}, url::{UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_shared::{AnyAsciiChar, data::Data, natsort, path::{AsPath, PathBufDyn, PathCow, PathDyn, PathLike}, scheme::{SchemeCow, SchemeLike}, strand::{AsStrand, StrandLike}, url::{UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_vfs::provider;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -74,24 +74,25 @@ impl Trigger {
|
|||
return None; // We don't autocomplete a `~`, but `~/`
|
||||
}
|
||||
|
||||
let cwd = CWD.load();
|
||||
let abs = if !path.is_absolute() && cwd.scheme().covariant(&scheme) {
|
||||
cwd.loc().try_join(&path).ok()?.into()
|
||||
} else {
|
||||
PathCow::from(&path)
|
||||
};
|
||||
|
||||
let sep = if cfg!(windows) {
|
||||
AnyAsciiChar::new(b"/\\").unwrap()
|
||||
} else {
|
||||
AnyAsciiChar::new(b"/").unwrap()
|
||||
};
|
||||
|
||||
Some(match path.rsplit_pred(sep) {
|
||||
Some((p, c)) if p.is_empty() => {
|
||||
let root = PathDyn::with_str(scheme.kind(), MAIN_SEPARATOR_STR);
|
||||
(UrlCow::try_from((scheme, root)).ok()?.into_owned(), c.into())
|
||||
}
|
||||
Some((p, c)) => (expand_url(UrlCow::try_from((scheme, p)).ok()?), c.into()),
|
||||
None if CWD.load().scheme().covariant(&scheme) => (CWD.load().as_ref().clone(), path.into()),
|
||||
None => {
|
||||
let empty = PathDyn::with_str(scheme.kind(), "");
|
||||
(UrlCow::try_from((scheme, empty)).ok()?.into_owned(), path.into())
|
||||
}
|
||||
})
|
||||
let child = path.rsplit_pred(sep).map_or(path.as_path(), |(_, c)| c);
|
||||
let parent =
|
||||
PathDyn::with(scheme.kind(), abs.encoded_bytes().strip_suffix(child.encoded_bytes())?)
|
||||
.ok()?;
|
||||
|
||||
Some((clean_url(UrlCow::try_from((scheme, parent)).ok()?), child.into()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -130,11 +131,11 @@ mod tests {
|
|||
compare("/foo/bar", "/foo/", "bar");
|
||||
compare("///foo/bar", "/foo/", "bar");
|
||||
|
||||
CWD.set(&"sftp://test/".parse::<UrlBuf>().unwrap(), || {});
|
||||
compare("sftp://test/a", "sftp://test/", "a");
|
||||
compare("sftp://test//a", "sftp://test:0//", "a");
|
||||
compare("sftp://test2/a", "sftp://test2/", "a");
|
||||
compare("sftp://test2//a", "sftp://test2:0//", "a");
|
||||
CWD.set(&"sftp://test".parse::<UrlBuf>().unwrap(), || {});
|
||||
compare("sftp://test/a", "sftp://test/.", "a");
|
||||
compare("sftp://test//a", "sftp://test//", "a");
|
||||
compare("sftp://test2/a", "sftp://test2/.", "a");
|
||||
compare("sftp://test2//a", "sftp://test2//", "a");
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use tokio::pin;
|
|||
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{File, FilesOp, path::expand_url};
|
||||
use yazi_fs::{File, FilesOp, path::{clean_url, expand_url}};
|
||||
use yazi_macro::{act, err, render, succ};
|
||||
use yazi_parser::mgr::CdOpt;
|
||||
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy};
|
||||
|
|
@ -71,6 +71,7 @@ impl Cd {
|
|||
Ok(s) => {
|
||||
let Ok(url) = UrlBuf::try_from(s).map(expand_url) else { return };
|
||||
let Ok(url) = provider::absolute(&url).await else { return };
|
||||
let url = clean_url(url);
|
||||
|
||||
let Ok(file) = File::new(&url).await else { return };
|
||||
if file.is_dir() {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ fn clean_path_impl(path: PathDyn, base: usize, trail: usize) -> (PathBufDyn, usi
|
|||
|
||||
macro_rules! push {
|
||||
($i:ident, $c:ident) => {{
|
||||
out.push($c);
|
||||
out.push(($i, $c));
|
||||
if $i >= base {
|
||||
uri_count += 1;
|
||||
}
|
||||
|
|
@ -31,12 +31,25 @@ fn clean_path_impl(path: PathDyn, base: usize, trail: usize) -> (PathBufDyn, usi
|
|||
}};
|
||||
}
|
||||
|
||||
macro_rules! pop {
|
||||
() => {{
|
||||
if let Some((i, _)) = out.pop() {
|
||||
if i >= base {
|
||||
uri_count -= 1;
|
||||
}
|
||||
if i >= trail {
|
||||
urn_count -= 1;
|
||||
}
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
for (i, c) in path.components().enumerate() {
|
||||
match c {
|
||||
CurDir => {}
|
||||
ParentDir => match out.last() {
|
||||
ParentDir => match out.last().map(|(_, c)| c) {
|
||||
Some(RootDir) => {}
|
||||
Some(Normal(_)) => _ = out.pop(),
|
||||
Some(Normal(_)) => pop!(),
|
||||
None | Some(CurDir) | Some(ParentDir) | Some(Prefix(_)) => push!(i, c),
|
||||
},
|
||||
c => push!(i, c),
|
||||
|
|
@ -47,7 +60,8 @@ fn clean_path_impl(path: PathDyn, base: usize, trail: usize) -> (PathBufDyn, usi
|
|||
let path = if out.is_empty() {
|
||||
PathBufDyn::with_str(kind, ".")
|
||||
} else {
|
||||
PathBufDyn::from_components(kind, out).expect("components with same kind")
|
||||
PathBufDyn::from_components(kind, out.into_iter().map(|(_, c)| c))
|
||||
.expect("components with same kind")
|
||||
};
|
||||
|
||||
(path, uri_count, urn_count)
|
||||
|
|
@ -70,10 +84,11 @@ mod tests {
|
|||
("archive://:3:2//../../tmp/test.zip/foo/bar", "archive://:3:2//tmp/test.zip/foo/bar"),
|
||||
("archive://:3:2//tmp/../../test.zip/foo/bar", "archive://:3:2//test.zip/foo/bar"),
|
||||
("archive://:4:2//tmp/test.zip/../../foo/bar", "archive://:2:2//foo/bar"),
|
||||
("archive://:5:2//tmp/test.zip/../../foo/bar", "archive://:3:2//foo/bar"),
|
||||
("archive://:4:4//tmp/test.zip/foo/bar/../../", "archive://:1:1//tmp/test.zip"),
|
||||
("archive://:5:4//tmp/test.zip/foo/bar/../../", "archive://:2:1//tmp/test.zip"),
|
||||
("archive://:5:2//tmp/test.zip/../../foo/bar", "archive://:2:2//foo/bar"),
|
||||
("archive://:4:4//tmp/test.zip/foo/bar/../../", "archive:////tmp/test.zip"),
|
||||
("archive://:5:4//tmp/test.zip/foo/bar/../../", "archive://:1//tmp/test.zip"),
|
||||
("archive://:4:4//tmp/test.zip/foo/bar/../../../", "archive:////tmp"),
|
||||
("sftp://test//root/.config/yazi/../../Downloads", "sftp://test//root/Downloads"),
|
||||
];
|
||||
|
||||
for (input, expected) in cases {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ function Entity:prefix()
|
|||
end
|
||||
|
||||
function Entity:highlights()
|
||||
local name = self._file.name:gsub("\r", "?", 1)
|
||||
local name = ui.printable(self._file.name)
|
||||
local highlights = self._file:highlights()
|
||||
if not highlights or #highlights == 0 then
|
||||
return name
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ function Status:name()
|
|||
return ""
|
||||
end
|
||||
|
||||
return " " .. h.name:gsub("\r", "?", 1)
|
||||
return " " .. ui.printable(h.name)
|
||||
end
|
||||
|
||||
function Status:perm()
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use mlua::{AnyUserData, ExternalError, IntoLua, Lua, ObjectLike, Table, Value};
|
||||
use tracing::error;
|
||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
use yazi_binding::{Composer, ComposerGet, ComposerSet, Permit, PermitRef, elements::{Line, Rect, Span}};
|
||||
use yazi_config::LAYOUT;
|
||||
use yazi_proxy::{AppProxy, HIDER};
|
||||
use yazi_shared::replace_to_printable;
|
||||
|
||||
pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
||||
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
|
||||
match key {
|
||||
b"area" => area(lua)?,
|
||||
b"hide" => hide(lua)?,
|
||||
b"width" => width(lua)?,
|
||||
b"printable" => printable(lua)?,
|
||||
b"redraw" => redraw(lua)?,
|
||||
b"render" => render(lua)?,
|
||||
b"truncate" => truncate(lua)?,
|
||||
b"width" => width(lua)?,
|
||||
_ => return Ok(Value::Nil),
|
||||
}
|
||||
.into_lua(lua)
|
||||
|
|
@ -54,28 +58,12 @@ pub(super) fn hide(lua: &Lua) -> mlua::Result<Value> {
|
|||
f.into_lua(lua)
|
||||
}
|
||||
|
||||
pub(super) fn width(lua: &Lua) -> mlua::Result<Value> {
|
||||
let f = lua.create_function(|_, v: Value| match v {
|
||||
Value::String(s) => {
|
||||
let (mut acc, b) = (0, s.as_bytes());
|
||||
for c in b.utf8_chunks() {
|
||||
acc += c.valid().width();
|
||||
if !c.invalid().is_empty() {
|
||||
acc += 1;
|
||||
}
|
||||
}
|
||||
Ok(acc)
|
||||
pub(super) fn printable(lua: &Lua) -> mlua::Result<Value> {
|
||||
let f = lua.create_function(|lua, s: mlua::String| {
|
||||
match replace_to_printable(&*s.as_bytes(), false, 1, true) {
|
||||
Cow::Borrowed(_) => s.into_lua(lua),
|
||||
Cow::Owned(new) => new.into_lua(lua),
|
||||
}
|
||||
Value::UserData(ud) => {
|
||||
if let Ok(line) = ud.borrow::<Line>() {
|
||||
Ok(line.width())
|
||||
} else if let Ok(span) = ud.borrow::<Span>() {
|
||||
Ok(span.width())
|
||||
} else {
|
||||
Err("expected a string, Line, or Span".into_lua_err())?
|
||||
}
|
||||
}
|
||||
_ => Err("expected a string, Line, or Span".into_lua_err())?,
|
||||
})?;
|
||||
|
||||
f.into_lua(lua)
|
||||
|
|
@ -176,6 +164,33 @@ pub(super) fn truncate(lua: &Lua) -> mlua::Result<Value> {
|
|||
f.into_lua(lua)
|
||||
}
|
||||
|
||||
pub(super) fn width(lua: &Lua) -> mlua::Result<Value> {
|
||||
let f = lua.create_function(|_, v: Value| match v {
|
||||
Value::String(s) => {
|
||||
let (mut acc, b) = (0, s.as_bytes());
|
||||
for c in b.utf8_chunks() {
|
||||
acc += c.valid().width();
|
||||
if !c.invalid().is_empty() {
|
||||
acc += 1;
|
||||
}
|
||||
}
|
||||
Ok(acc)
|
||||
}
|
||||
Value::UserData(ud) => {
|
||||
if let Ok(line) = ud.borrow::<Line>() {
|
||||
Ok(line.width())
|
||||
} else if let Ok(span) = ud.borrow::<Span>() {
|
||||
Ok(span.width())
|
||||
} else {
|
||||
Err("expected a string, Line, or Span".into_lua_err())?
|
||||
}
|
||||
}
|
||||
_ => Err("expected a string, Line, or Span".into_lua_err())?,
|
||||
})?;
|
||||
|
||||
f.into_lua(lua)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use mlua::{Lua, chunk};
|
||||
|
|
|
|||
14
yazi-plugin/src/external/highlighter.rs
vendored
14
yazi-plugin/src/external/highlighter.rs
vendored
|
|
@ -6,7 +6,7 @@ use syntect::{LoadingError, dumps, easy::HighlightLines, highlighting::{self, Th
|
|||
use tokio::io::{AsyncBufReadExt, AsyncSeekExt, BufReader};
|
||||
use yazi_config::{THEME, YAZI, preview::PreviewWrap};
|
||||
use yazi_fs::provider::{Provider, local::Local};
|
||||
use yazi_shared::{Ids, errors::PeekError, replace_to_printable};
|
||||
use yazi_shared::{Ids, errors::PeekError, push_printable_char};
|
||||
|
||||
static INCR: Ids = Ids::new();
|
||||
static SYNTECT: OnceLock<(Theme, SyntaxSet)> = OnceLock::new();
|
||||
|
|
@ -95,7 +95,7 @@ impl Highlighter {
|
|||
}
|
||||
|
||||
Ok(if plain {
|
||||
Text::from(replace_to_printable(&after, YAZI.preview.tab_size))
|
||||
Text::from(Self::merge_highlight_lines(&after, YAZI.preview.tab_size))
|
||||
} else {
|
||||
Self::highlight_with(before, after, syntax.unwrap()).await?
|
||||
})
|
||||
|
|
@ -203,6 +203,16 @@ impl Highlighter {
|
|||
*b = b'\n';
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_highlight_lines(s: &[String], tab_size: u8) -> String {
|
||||
let mut buf = Vec::new();
|
||||
buf.reserve_exact(s.iter().map(|s| s.len()).sum::<usize>() | 15);
|
||||
|
||||
for &b in s.iter().flat_map(|s| s.as_bytes()) {
|
||||
push_printable_char(&mut buf, b, true, tab_size, false);
|
||||
}
|
||||
unsafe { String::from_utf8_unchecked(buf) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Highlighter {
|
||||
|
|
|
|||
|
|
@ -94,26 +94,48 @@ pub fn replace_vec_cow<'a>(v: &'a [u8], from: &[u8], to: &[u8]) -> Cow<'a, [u8]>
|
|||
Cow::Owned(out)
|
||||
}
|
||||
|
||||
pub fn replace_to_printable(s: &[String], tab_size: u8) -> String {
|
||||
let mut buf = Vec::new();
|
||||
buf.try_reserve_exact(s.iter().map(|s| s.len()).sum::<usize>() | 15).unwrap_or_else(|_| panic!());
|
||||
pub fn replace_to_printable(b: &[u8], lf: bool, tab_size: u8, replacement: bool) -> Cow<'_, [u8]> {
|
||||
// Fast path to skip over printable chars at the beginning of the string
|
||||
let printable_len = b.iter().take_while(|&&c| !c.is_ascii_control()).count();
|
||||
if printable_len >= b.len() {
|
||||
return Cow::Borrowed(b);
|
||||
}
|
||||
|
||||
for &b in s.iter().flat_map(|s| s.as_bytes()) {
|
||||
match b {
|
||||
b'\n' => buf.push(b'\n'),
|
||||
b'\t' => {
|
||||
buf.extend((0..tab_size).map(|_| b' '));
|
||||
}
|
||||
b'\0'..=b'\x1F' => {
|
||||
let (printable, rest) = b.split_at(printable_len);
|
||||
|
||||
let mut out = Vec::new();
|
||||
out.reserve_exact(b.len() | 15);
|
||||
out.extend_from_slice(printable);
|
||||
|
||||
for &c in rest {
|
||||
push_printable_char(&mut out, c, lf, tab_size, replacement);
|
||||
}
|
||||
Cow::Owned(out)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn push_printable_char(buf: &mut Vec<u8>, c: u8, lf: bool, tab_size: u8, replacement: bool) {
|
||||
match c {
|
||||
b'\n' if lf => buf.push(b'\n'),
|
||||
b'\t' => {
|
||||
buf.extend((0..tab_size).map(|_| b' '));
|
||||
}
|
||||
b'\0'..=b'\x1F' => {
|
||||
if replacement {
|
||||
buf.extend_from_slice(&[0xef, 0xbf, 0xbd]);
|
||||
} else {
|
||||
buf.push(b'^');
|
||||
buf.push(b + b'@');
|
||||
buf.push(c + b'@');
|
||||
}
|
||||
0x7f => {
|
||||
}
|
||||
0x7f => {
|
||||
if replacement {
|
||||
buf.extend_from_slice(&[0xef, 0xbf, 0xbd]);
|
||||
} else {
|
||||
buf.push(b'^');
|
||||
buf.push(b'?');
|
||||
}
|
||||
_ => buf.push(b),
|
||||
}
|
||||
_ => buf.push(c),
|
||||
}
|
||||
unsafe { String::from_utf8_unchecked(buf) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,7 +107,22 @@ impl<'p> LocAbleImpl<'p> for &'p std::path::Path {
|
|||
where
|
||||
T: AsStrandView<'a, Self::Strand<'a>>,
|
||||
{
|
||||
self.strip_prefix(base.as_strand_view()).ok()
|
||||
use std::path::is_separator;
|
||||
|
||||
let p = self.strip_prefix(base.as_strand_view()).ok()?;
|
||||
let mut b = p.as_encoded_bytes();
|
||||
|
||||
if b.last().is_none_or(|&c| !is_separator(c as char)) || p.parent().is_none() {
|
||||
return Some(p);
|
||||
}
|
||||
|
||||
while let [head @ .., last] = b
|
||||
&& is_separator(*last as char)
|
||||
{
|
||||
b = head;
|
||||
}
|
||||
|
||||
Some(unsafe { Self::from_encoded_bytes_unchecked(b) })
|
||||
}
|
||||
|
||||
fn to_path_buf(self) -> Self::Owned { self.to_path_buf() }
|
||||
|
|
@ -139,7 +154,18 @@ impl<'p> LocAbleImpl<'p> for &'p typed_path::UnixPath {
|
|||
where
|
||||
T: AsStrandView<'a, Self::Strand<'a>>,
|
||||
{
|
||||
self.strip_prefix(base.as_strand_view()).ok()
|
||||
let p = self.strip_prefix(base.as_strand_view()).ok()?;
|
||||
let mut b = p.as_bytes();
|
||||
|
||||
if b.last().is_none_or(|&c| c != b'/') || p.parent().is_none() {
|
||||
return Some(p);
|
||||
}
|
||||
|
||||
while let [head @ .., b'/'] = b {
|
||||
b = head;
|
||||
}
|
||||
|
||||
Some(typed_path::UnixPath::new(b))
|
||||
}
|
||||
|
||||
fn to_path_buf(self) -> Self::Owned { self.to_path_buf() }
|
||||
|
|
|
|||
|
|
@ -85,8 +85,8 @@ where
|
|||
{
|
||||
let path = path.as_path_view();
|
||||
let Some(name) = path.file_name() else {
|
||||
let uri = path.strip_prefix(P::empty()).unwrap().len();
|
||||
return Self { inner: path, uri, urn: 0, _phantom: PhantomData };
|
||||
let p = path.strip_prefix(P::empty()).unwrap();
|
||||
return Self { inner: p, uri: p.len(), urn: 0, _phantom: PhantomData };
|
||||
};
|
||||
|
||||
let name_len = name.len();
|
||||
|
|
@ -244,3 +244,38 @@ where
|
|||
loc
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_with() -> Result<()> {
|
||||
let cases = [
|
||||
// Relative paths
|
||||
("tmp/test.zip/foo/bar", 3, 2, "test.zip/foo/bar", "foo/bar"),
|
||||
("tmp/test.zip/foo/bar/", 3, 2, "test.zip/foo/bar", "foo/bar"),
|
||||
// Absolute paths
|
||||
("/tmp/test.zip/foo/bar", 3, 2, "test.zip/foo/bar", "foo/bar"),
|
||||
("/tmp/test.zip/foo/bar/", 3, 2, "test.zip/foo/bar", "foo/bar"),
|
||||
// Relative path with parent components
|
||||
("tmp/test.zip/foo/bar/../..", 5, 4, "test.zip/foo/bar/../..", "foo/bar/../.."),
|
||||
("tmp/test.zip/foo/bar/../../", 5, 4, "test.zip/foo/bar/../..", "foo/bar/../.."),
|
||||
// Absolute path with parent components
|
||||
("/tmp/test.zip/foo/bar/../..", 5, 4, "test.zip/foo/bar/../..", "foo/bar/../.."),
|
||||
("/tmp/test.zip/foo/bar/../../", 5, 4, "test.zip/foo/bar/../..", "foo/bar/../.."),
|
||||
];
|
||||
|
||||
for (path, uri, urn, expect_uri, expect_urn) in cases {
|
||||
let loc = Loc::with(std::path::Path::new(path), uri, urn)?;
|
||||
assert_eq!(loc.uri().to_str().unwrap(), expect_uri);
|
||||
assert_eq!(loc.urn().to_str().unwrap(), expect_urn);
|
||||
|
||||
let loc = Loc::with(typed_path::UnixPath::new(path), uri, urn)?;
|
||||
assert_eq!(loc.uri().to_str().unwrap(), expect_uri);
|
||||
assert_eq!(loc.urn().to_str().unwrap(), expect_urn);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ impl From<PathBufDyn> for PathCow<'_> {
|
|||
fn from(value: PathBufDyn) -> Self { Self::Owned(value) }
|
||||
}
|
||||
|
||||
impl<'a> From<&'a PathCow<'_>> for PathCow<'a> {
|
||||
fn from(value: &'a PathCow<'_>) -> Self { Self::Borrowed(value.as_path()) }
|
||||
}
|
||||
|
||||
impl From<PathCow<'_>> for PathBufDyn {
|
||||
fn from(value: PathCow<'_>) -> Self { value.into_owned() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,10 @@ impl AsStrand for PathCow<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl AsStrand for &PathCow<'_> {
|
||||
fn as_strand(&self) -> Strand<'_> { (**self).as_strand() }
|
||||
}
|
||||
|
||||
impl AsStrand for Strand<'_> {
|
||||
fn as_strand(&self) -> Strand<'_> { *self }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ impl<'a> Strand<'a> {
|
|||
|
||||
let (skip, rest) = bytes.split_at(skip_len);
|
||||
let mut out = Vec::new();
|
||||
out.try_reserve_exact(bytes.len()).unwrap_or_else(|_| panic!());
|
||||
out.reserve_exact(bytes.len());
|
||||
out.extend(skip);
|
||||
|
||||
for &b in rest {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ impl Transliterator for &[u8] {
|
|||
// but instead of `+ 16` uses `| 15` to stay in the smallest allocation bucket
|
||||
// for short strings
|
||||
let mut out = String::new();
|
||||
out.try_reserve_exact(self.len() | 15).unwrap_or_else(|_| panic!());
|
||||
out.reserve_exact(self.len() | 15);
|
||||
out.push_str(unsafe { str::from_utf8_unchecked(ascii) });
|
||||
|
||||
for c in String::from_utf8_lossy(rest).chars() {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue