mirror of
https://github.com/wez/wezterm.git
synced 2024-11-22 22:42:48 +03:00
migrate some more code to the newer spawn mechanism
This commit is contained in:
parent
75eb16bec4
commit
9ec4694d89
95
Cargo.lock
generated
95
Cargo.lock
generated
@ -131,6 +131,31 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
|
||||
|
||||
[[package]]
|
||||
name = "async-std"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf6039b315300e057d198b9d3ab92ee029e31c759b7f1afae538145e6f18a3e"
|
||||
dependencies = [
|
||||
"async-task",
|
||||
"crossbeam-channel 0.4.0",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils 0.7.0",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-timer",
|
||||
"kv-log-macro",
|
||||
"log",
|
||||
"memchr",
|
||||
"mio",
|
||||
"mio-uds",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "1.2.1"
|
||||
@ -500,6 +525,15 @@ dependencies = [
|
||||
"crossbeam-utils 0.6.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c"
|
||||
dependencies = [
|
||||
"crossbeam-utils 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.7.2"
|
||||
@ -977,6 +1011,24 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866"
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e676577d229e70952ab25f3945795ba5b16d63ca794ca9d2c860e5595d20b5ff"
|
||||
|
||||
[[package]]
|
||||
name = "futures-timer"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.14"
|
||||
@ -1073,7 +1125,7 @@ checksum = "08d331ebcdbca4acbefe5da8c3299b2e246f198a8294cc5163354e743398b89d"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"byteorder",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-channel 0.3.9",
|
||||
"flate2",
|
||||
"nom 4.2.3",
|
||||
"num-traits 0.2.11",
|
||||
@ -1232,6 +1284,15 @@ version = "3.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc"
|
||||
|
||||
[[package]]
|
||||
name = "kv-log-macro"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c54d9f465d530a752e6ebdc217e081a7a614b48cb200f6f0aee21ba6bc9aabb"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@ -1504,6 +1565,17 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio-uds"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125"
|
||||
dependencies = [
|
||||
"iovec",
|
||||
"libc",
|
||||
"mio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miow"
|
||||
version = "0.2.1"
|
||||
@ -1755,6 +1827,12 @@ dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5941ec2d5ee5916c709580d71553b81a633df245bcc73c04dcbd62152ceefc4"
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "1.3.2"
|
||||
@ -1919,6 +1997,18 @@ dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8822eb8bb72452f038ebf6048efa02c3fe22bf83f76519c9583e47fc194a422"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.17"
|
||||
@ -2020,6 +2110,7 @@ name = "promise"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-std",
|
||||
"async-task",
|
||||
"lazy_static",
|
||||
"thiserror",
|
||||
@ -3153,7 +3244,7 @@ dependencies = [
|
||||
"core-foundation 0.7.0",
|
||||
"core-graphics 0.19.0",
|
||||
"core-text 15.0.0",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-channel 0.3.9",
|
||||
"daemonize",
|
||||
"dirs 1.0.5",
|
||||
"downcast-rs",
|
||||
|
@ -6,6 +6,7 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
async-task = "1.2"
|
||||
async-std = "1.4"
|
||||
anyhow = "1.0"
|
||||
thiserror = "1.0"
|
||||
lazy_static = "1.3"
|
||||
|
@ -148,3 +148,6 @@ where
|
||||
task.schedule();
|
||||
handle
|
||||
}
|
||||
|
||||
/// Block the current thread until the passed future completes.
|
||||
pub use async_std::task::block_on;
|
||||
|
@ -5,10 +5,10 @@ use super::utilsprites::RenderMetrics;
|
||||
use crate::config::{configuration, ConfigHandle};
|
||||
use crate::font::units::*;
|
||||
use crate::font::FontConfiguration;
|
||||
use crate::frontend::front_end;
|
||||
use crate::frontend::gui::scrollbar::*;
|
||||
use crate::frontend::gui::selection::*;
|
||||
use crate::frontend::gui::tabbar::{TabBarItem, TabBarState};
|
||||
use crate::frontend::{executor, front_end};
|
||||
use crate::keyassignment::{KeyAssignment, KeyMap, SpawnTabDomain};
|
||||
use crate::mux::renderable::{Renderable, RenderableDimensions, StableCursorPosition};
|
||||
use crate::mux::tab::{Tab, TabId};
|
||||
@ -178,12 +178,11 @@ impl<'a> term::TerminalHost for Host<'a> {
|
||||
// of our window loop; on Windows it can cause a panic due to
|
||||
// triggering our WndProc recursively.
|
||||
let link = link.clone();
|
||||
promise::Future::with_executor(executor(), move || {
|
||||
promise::spawn::spawn(async move {
|
||||
log::error!("clicking {}", link.uri());
|
||||
if let Err(err) = open::that(link.uri()) {
|
||||
log::error!("failed to open {}: {:?}", link.uri(), err);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1161,13 +1160,10 @@ impl TermWindow {
|
||||
let future = self.window.as_ref().unwrap().get_clipboard();
|
||||
promise::spawn::spawn(async move {
|
||||
if let Ok(clip) = future.await {
|
||||
promise::Future::with_executor(executor(), move || {
|
||||
let mux = Mux::get().unwrap();
|
||||
if let Some(tab) = mux.get_tab(tab_id) {
|
||||
tab.trickle_paste(clip)?;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
let mux = Mux::get().unwrap();
|
||||
if let Some(tab) = mux.get_tab(tab_id) {
|
||||
tab.trickle_paste(clip).ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1202,7 +1198,7 @@ impl TermWindow {
|
||||
}
|
||||
|
||||
pub fn spawn_new_window(&mut self) {
|
||||
promise::Future::with_executor(executor(), move || {
|
||||
async fn new_window() -> anyhow::Result<()> {
|
||||
let mux = Mux::get().unwrap();
|
||||
let fonts = Rc::new(FontConfiguration::new());
|
||||
let window_id = mux.new_empty_window();
|
||||
@ -1212,6 +1208,9 @@ impl TermWindow {
|
||||
let front_end = front_end().expect("to be called on gui thread");
|
||||
front_end.spawn_new_window(&fonts, &tab, window_id)?;
|
||||
Ok(())
|
||||
}
|
||||
promise::spawn::spawn(async move {
|
||||
new_window().await.ok();
|
||||
});
|
||||
}
|
||||
|
||||
@ -2536,12 +2535,11 @@ impl TermWindow {
|
||||
// of our window loop; on Windows it can cause a panic due to
|
||||
// triggering our WndProc recursively.
|
||||
let link = self.current_highlight.as_ref().unwrap().clone();
|
||||
promise::Future::with_executor(executor(), move || {
|
||||
promise::spawn::spawn(async move {
|
||||
log::error!("clicking {}", link.uri());
|
||||
if let Err(err) = open::that(link.uri()) {
|
||||
log::error!("failed to open {}: {:?}", link.uri(), err);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
} else {
|
||||
self.window.as_ref().unwrap().set_clipboard(text);
|
||||
@ -2656,13 +2654,10 @@ impl TermWindow {
|
||||
let future = self.window.as_ref().unwrap().get_clipboard();
|
||||
promise::spawn::spawn(async move {
|
||||
if let Ok(clip) = future.await {
|
||||
promise::Future::with_executor(executor(), move || {
|
||||
let mux = Mux::get().unwrap();
|
||||
if let Some(tab) = mux.get_tab(tab_id) {
|
||||
tab.trickle_paste(clip)?;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
let mux = Mux::get().unwrap();
|
||||
if let Some(tab) = mux.get_tab(tab_id) {
|
||||
tab.trickle_paste(clip).ok();
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
|
@ -44,14 +44,6 @@ pub fn executor() -> Box<dyn Executor> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn low_pri_executor() -> Box<dyn Executor> {
|
||||
let locked = LOW_PRI_EXECUTOR.lock().unwrap();
|
||||
match locked.as_ref() {
|
||||
Some(exec) => exec.clone_executor(),
|
||||
None => panic!("executor machinery not yet configured"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn front_end() -> Option<Rc<dyn FrontEnd>> {
|
||||
let mut res = None;
|
||||
FRONT_END.with(|f| {
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::frontend::{executor, low_pri_executor};
|
||||
use crate::mux::tab::{Tab, TabId};
|
||||
use crate::mux::window::{Window, WindowId};
|
||||
use crate::ratelim::RateLimiter;
|
||||
@ -7,7 +6,6 @@ use anyhow::{anyhow, Error};
|
||||
use domain::{Domain, DomainId};
|
||||
use log::{debug, error};
|
||||
use portable_pty::ExitStatus;
|
||||
use promise::Future;
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::collections::HashMap;
|
||||
use std::io::Read;
|
||||
@ -68,7 +66,7 @@ fn read_from_tab_pty(tab_id: TabId, mut reader: Box<dyn std::io::Read>) {
|
||||
let len = len as usize;
|
||||
let data = buf[pos..pos + len].to_vec();
|
||||
pos += len;
|
||||
Future::with_executor(low_pri_executor(), move || {
|
||||
promise::spawn::spawn_into_main_thread_with_low_priority(async move {
|
||||
let mux = Mux::get().unwrap();
|
||||
if let Some(tab) = mux.get_tab(tab_id) {
|
||||
tab.advance_bytes(
|
||||
@ -79,7 +77,6 @@ fn read_from_tab_pty(tab_id: TabId, mut reader: Box<dyn std::io::Read>) {
|
||||
);
|
||||
mux.notify(MuxNotification::TabOutput(tab_id));
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
Err(delay) => {
|
||||
@ -91,10 +88,9 @@ fn read_from_tab_pty(tab_id: TabId, mut reader: Box<dyn std::io::Read>) {
|
||||
}
|
||||
}
|
||||
}
|
||||
Future::with_executor(executor(), move || {
|
||||
promise::spawn::spawn_into_main_thread(async move {
|
||||
let mux = Mux::get().unwrap();
|
||||
mux.remove_tab(tab_id);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
use crate::frontend::executor;
|
||||
use crate::mux::domain::DomainId;
|
||||
use crate::mux::renderable::Renderable;
|
||||
use crate::mux::Mux;
|
||||
@ -27,7 +26,7 @@ struct Paste {
|
||||
|
||||
fn schedule_next_paste(paste: &Arc<Mutex<Paste>>) {
|
||||
let paste = Arc::clone(paste);
|
||||
promise::Future::with_executor(executor(), move || {
|
||||
promise::spawn::spawn(async move {
|
||||
let mut locked = paste.lock().unwrap();
|
||||
let mux = Mux::get().unwrap();
|
||||
let tab = mux.get_tab(locked.tab_id).unwrap();
|
||||
@ -42,8 +41,6 @@ fn schedule_next_paste(paste: &Arc<Mutex<Paste>>) {
|
||||
locked.offset += chunk;
|
||||
schedule_next_paste(&paste);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
|
48
src/ssh.rs
48
src/ssh.rs
@ -56,7 +56,7 @@ fn password_prompt(
|
||||
(output, unicode_column_width(placeholder) * cursor_position)
|
||||
}
|
||||
}
|
||||
match termwiztermtab::run(60, 10, move |mut term| {
|
||||
match promise::spawn::block_on(termwiztermtab::run(60, 10, move |mut term| {
|
||||
term.render(&[
|
||||
// Change::Attribute(AttributeChange::Intensity(Intensity::Bold)),
|
||||
Change::Title(title.to_string()),
|
||||
@ -73,9 +73,7 @@ fn password_prompt(
|
||||
} else {
|
||||
bail!("prompt cancelled");
|
||||
}
|
||||
})
|
||||
.wait()
|
||||
{
|
||||
})) {
|
||||
Ok(p) => Some(p),
|
||||
Err(p) => {
|
||||
log::error!("failed to prompt for pw: {}", p);
|
||||
@ -95,7 +93,7 @@ fn input_prompt(
|
||||
"SSH Authentication for {} @ {}\r\n{}\r\n{}\r\n",
|
||||
username, remote_address, instructions, prompt
|
||||
);
|
||||
match termwiztermtab::run(60, 10, move |mut term| {
|
||||
match promise::spawn::block_on(termwiztermtab::run(60, 10, move |mut term| {
|
||||
term.render(&[
|
||||
Change::Title(title.to_string()),
|
||||
Change::Text(text.to_string()),
|
||||
@ -110,9 +108,7 @@ fn input_prompt(
|
||||
} else {
|
||||
bail!("prompt cancelled");
|
||||
}
|
||||
})
|
||||
.wait()
|
||||
{
|
||||
})) {
|
||||
Ok(p) => Some(p),
|
||||
Err(p) => {
|
||||
log::error!("failed to prompt for pw: {}", p);
|
||||
@ -229,27 +225,27 @@ pub fn ssh_connect(remote_address: &str, username: &str) -> anyhow::Result<ssh2:
|
||||
remote_address, key_type, fingerprint
|
||||
);
|
||||
|
||||
let allow = termwiztermtab::run(80, 10, move |mut term| {
|
||||
let title = "🔐 wezterm: SSH authentication".to_string();
|
||||
term.render(&[Change::Title(title), Change::Text(message.to_string())])?;
|
||||
let allow =
|
||||
promise::spawn::block_on(termwiztermtab::run(80, 10, move |mut term| {
|
||||
let title = "🔐 wezterm: SSH authentication".to_string();
|
||||
term.render(&[Change::Title(title), Change::Text(message.to_string())])?;
|
||||
|
||||
let mut editor = LineEditor::new(term);
|
||||
editor.set_prompt("Enter [Y/n]> ");
|
||||
let mut editor = LineEditor::new(term);
|
||||
editor.set_prompt("Enter [Y/n]> ");
|
||||
|
||||
let mut host = NopLineEditorHost::default();
|
||||
loop {
|
||||
let line = match editor.read_line(&mut host) {
|
||||
Ok(Some(line)) => line,
|
||||
Ok(None) | Err(_) => return Ok(false),
|
||||
};
|
||||
match line.as_ref() {
|
||||
"y" | "Y" | "yes" | "YES" => return Ok(true),
|
||||
"n" | "N" | "no" | "NO" => return Ok(false),
|
||||
_ => continue,
|
||||
let mut host = NopLineEditorHost::default();
|
||||
loop {
|
||||
let line = match editor.read_line(&mut host) {
|
||||
Ok(Some(line)) => line,
|
||||
Ok(None) | Err(_) => return Ok(false),
|
||||
};
|
||||
match line.as_ref() {
|
||||
"y" | "Y" | "yes" | "YES" => return Ok(true),
|
||||
"n" | "N" | "no" | "NO" => return Ok(false),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.wait()?;
|
||||
}))?;
|
||||
|
||||
if !allow {
|
||||
bail!("user declined to trust host");
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
use crate::config::configuration;
|
||||
use crate::font::FontConfiguration;
|
||||
use crate::frontend::{executor, front_end};
|
||||
use crate::frontend::{front_end};
|
||||
use crate::mux::domain::{alloc_domain_id, Domain, DomainId, DomainState};
|
||||
use crate::mux::renderable::{Renderable, RenderableDimensions, StableCursorPosition};
|
||||
use crate::mux::tab::{alloc_tab_id, Tab, TabId};
|
||||
@ -15,7 +15,6 @@ use anyhow::{bail, Error};
|
||||
use crossbeam_channel::{unbounded as channel, Receiver, Sender};
|
||||
use filedescriptor::Pipe;
|
||||
use portable_pty::*;
|
||||
use promise::{Future, Promise};
|
||||
use rangeset::RangeSet;
|
||||
use std::cell::RefCell;
|
||||
use std::cell::RefMut;
|
||||
@ -375,11 +374,14 @@ impl termwiz::terminal::Terminal for TermWizTerminal {
|
||||
/// from the terminal window.
|
||||
/// When it completes its loop it will fulfil a promise and yield
|
||||
/// the return value from the function.
|
||||
pub fn run<T: Send + 'static, F: Send + 'static + Fn(TermWizTerminal) -> anyhow::Result<T>>(
|
||||
pub async fn run<
|
||||
T: Send + 'static,
|
||||
F: Send + 'static + Fn(TermWizTerminal) -> anyhow::Result<T>,
|
||||
>(
|
||||
width: usize,
|
||||
height: usize,
|
||||
f: F,
|
||||
) -> Future<T> {
|
||||
) -> anyhow::Result<T> {
|
||||
let (render_tx, render_rx) = channel();
|
||||
let (input_tx, input_rx) = channel();
|
||||
|
||||
@ -394,10 +396,12 @@ pub fn run<T: Send + 'static, F: Send + 'static + Fn(TermWizTerminal) -> anyhow:
|
||||
},
|
||||
};
|
||||
|
||||
let mut promise = Promise::new();
|
||||
let future = promise.get_future().expect("just made the promise");
|
||||
|
||||
Future::with_executor(executor(), move || {
|
||||
async fn register_tab(
|
||||
input_tx: Sender<InputEvent>,
|
||||
render_rx: Receiver<Vec<Change>>,
|
||||
width: usize,
|
||||
height: usize,
|
||||
) -> anyhow::Result<()> {
|
||||
let mux = Mux::get().unwrap();
|
||||
|
||||
// TODO: make a singleton
|
||||
@ -426,20 +430,22 @@ pub fn run<T: Send + 'static, F: Send + 'static + Fn(TermWizTerminal) -> anyhow:
|
||||
gui.spawn_new_window(&fontconfig, &tab, window_id)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
promise::spawn::spawn_into_main_thread(async move {
|
||||
register_tab(input_tx, render_rx, width, height).await.ok();
|
||||
});
|
||||
|
||||
std::thread::spawn(move || {
|
||||
promise.result(f(tw_term));
|
||||
});
|
||||
|
||||
future
|
||||
promise::spawn::spawn_into_new_thread(move || f(tw_term))
|
||||
.await
|
||||
.unwrap_or_else(|| bail!("task panicked or was cancelled"))
|
||||
}
|
||||
|
||||
pub fn message_box_ok(message: &str) {
|
||||
let title = "wezterm";
|
||||
let message = message.to_string();
|
||||
|
||||
run(60, 10, move |mut term| {
|
||||
promise::spawn::block_on(run(60, 10, move |mut term| {
|
||||
term.render(&[
|
||||
Change::Title(title.to_string()),
|
||||
Change::Text(message.to_string()),
|
||||
@ -452,7 +458,6 @@ pub fn message_box_ok(message: &str) {
|
||||
let mut host = NopLineEditorHost::default();
|
||||
editor.read_line(&mut host).ok();
|
||||
Ok(())
|
||||
})
|
||||
.wait()
|
||||
}))
|
||||
.ok();
|
||||
}
|
||||
|
@ -488,7 +488,7 @@ impl Display for ITermFileData {
|
||||
fn fmt(&self, f: &mut Formatter) -> Result<(), FmtError> {
|
||||
write!(f, "File")?;
|
||||
let mut sep = "=";
|
||||
let emit_sep = |sep, f: &mut Formatter| {
|
||||
let emit_sep = |sep, f: &mut Formatter| -> Result<&str, FmtError> {
|
||||
write!(f, "{}", sep)?;
|
||||
Ok(";")
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user