red: multi-character keybinds
This commit is contained in:
parent
d030e0d6f1
commit
6c8ae720d0
@ -78,7 +78,7 @@ impl View {
|
||||
|
||||
self.cursor_column = col;
|
||||
|
||||
if line.display_width(config.tab_width) + 1 <= self.width {
|
||||
if line.display_width(config.tab_width) < self.width {
|
||||
self.column_offset = 0;
|
||||
return;
|
||||
}
|
||||
@ -240,6 +240,10 @@ impl Buffer {
|
||||
self.lines.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.lines.is_empty()
|
||||
}
|
||||
|
||||
pub fn cursor_row(&self) -> usize {
|
||||
self.view.cursor_row
|
||||
}
|
||||
@ -277,6 +281,30 @@ impl Buffer {
|
||||
self.set_position(config, len, self.view.cursor_row);
|
||||
}
|
||||
|
||||
pub fn to_first_line(&mut self, config: &Config) {
|
||||
let len = self
|
||||
.lines
|
||||
.get(self.view.cursor_row)
|
||||
.map(Line::len)
|
||||
.unwrap_or(0);
|
||||
|
||||
self.set_position(config, len, 0);
|
||||
}
|
||||
|
||||
pub fn to_last_line(&mut self, config: &Config) {
|
||||
if self.lines.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let len = self
|
||||
.lines
|
||||
.get(self.view.cursor_row)
|
||||
.map(Line::len)
|
||||
.unwrap_or(0);
|
||||
|
||||
self.set_position(config, len, self.lines.len() - 1);
|
||||
}
|
||||
|
||||
pub fn set_column(&mut self, config: &Config, x: usize) {
|
||||
self.set_position(config, x, self.view.cursor_row);
|
||||
}
|
||||
@ -324,6 +352,10 @@ impl Buffer {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.view.width
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.view.height
|
||||
}
|
||||
@ -481,8 +513,18 @@ impl Buffer {
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
pub fn kill_line(&mut self, config: &Config) {
|
||||
if self.lines.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.lines.remove(self.view.cursor_row);
|
||||
self.move_cursor(config, 0, 1);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
pub fn number_width(&mut self) -> usize {
|
||||
if self.lines.len() == 0 {
|
||||
if self.lines.is_empty() {
|
||||
1
|
||||
} else {
|
||||
self.lines.len().ilog10() as usize + 1
|
||||
|
@ -13,12 +13,15 @@ pub enum Action {
|
||||
NewlineBefore,
|
||||
NewlineAfter,
|
||||
BreakLine,
|
||||
KillLine,
|
||||
|
||||
// Movement
|
||||
MoveFirstLine,
|
||||
MoveLastLine,
|
||||
MoveCharPrev,
|
||||
MoveCharNext,
|
||||
MoveLinePrev,
|
||||
MoveLineNext,
|
||||
MoveLineBack(usize),
|
||||
MoveLineForward(usize),
|
||||
MoveLineStart,
|
||||
MoveLineEnd,
|
||||
}
|
||||
@ -119,13 +122,16 @@ pub fn perform(buffer: &mut Buffer, config: &Config, action: Action) -> Result<(
|
||||
Action::NewlineBefore => buffer.newline_before(),
|
||||
Action::NewlineAfter => buffer.newline_after(false),
|
||||
Action::BreakLine => buffer.newline_after(true),
|
||||
Action::KillLine => buffer.kill_line(config),
|
||||
// Movement
|
||||
Action::MoveCharPrev => buffer.move_cursor(config, -1, 0),
|
||||
Action::MoveCharNext => buffer.move_cursor(config, 1, 0),
|
||||
Action::MoveLinePrev => buffer.move_cursor(config, 0, -1),
|
||||
Action::MoveLineNext => buffer.move_cursor(config, 0, 1),
|
||||
Action::MoveLineBack(count) => buffer.move_cursor(config, 0, -(count as isize)),
|
||||
Action::MoveLineForward(count) => buffer.move_cursor(config, 0, count as isize),
|
||||
Action::MoveLineStart => buffer.set_column(config, 0),
|
||||
Action::MoveLineEnd => buffer.to_line_end(config),
|
||||
Action::MoveFirstLine => buffer.to_first_line(config),
|
||||
Action::MoveLastLine => buffer.to_last_line(config),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,54 +1,66 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{buffer::Mode, command::Action};
|
||||
|
||||
use crate::{
|
||||
buffer::Mode,
|
||||
command::Action,
|
||||
keymap::{bind, KeyMap, PrefixNode},
|
||||
};
|
||||
|
||||
pub struct Config {
|
||||
// TODO must be a power of 2, lol
|
||||
pub tab_width: usize,
|
||||
pub number: bool,
|
||||
|
||||
pub nmap: HashMap<char, Vec<Action>>,
|
||||
pub imap: HashMap<char, Vec<Action>>,
|
||||
}
|
||||
|
||||
fn bind<I: IntoIterator<Item = Action>>(key: char, items: I) -> (char, Vec<Action>) {
|
||||
(key, items.into_iter().map(Into::into).collect())
|
||||
pub nmap: KeyMap,
|
||||
pub imap: KeyMap,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
use Action::*;
|
||||
|
||||
let nmap = KeyMap::from_iter([
|
||||
bind('i', [InsertBefore]),
|
||||
bind('a', [InsertAfter]),
|
||||
bind('h', [MoveCharPrev]),
|
||||
bind('l', [MoveCharNext]),
|
||||
bind('j', [MoveLineForward(1)]),
|
||||
bind("J", [MoveLineForward(25)]),
|
||||
bind('k', [MoveLineBack(1)]),
|
||||
bind("K", [MoveLineBack(25)]),
|
||||
bind("gg", [MoveFirstLine]),
|
||||
bind("G", [MoveLastLine]),
|
||||
bind('I', [MoveLineStart, InsertBefore]),
|
||||
bind('A', [MoveLineEnd, InsertAfter]),
|
||||
bind('o', [NewlineAfter, MoveLineForward(1), InsertBefore]),
|
||||
bind('O', [NewlineBefore, MoveLineBack(1), InsertBefore]),
|
||||
bind("dd", [KillLine]),
|
||||
]);
|
||||
|
||||
let imap = KeyMap::from_iter([
|
||||
bind('\x7F', [EraseBackward]),
|
||||
bind(
|
||||
'\n',
|
||||
[BreakLine, MoveLineForward(1), MoveLineStart, InsertBefore],
|
||||
),
|
||||
bind(
|
||||
'\x0D',
|
||||
[BreakLine, MoveLineForward(1), MoveLineStart, InsertBefore],
|
||||
),
|
||||
]);
|
||||
|
||||
Self {
|
||||
tab_width: 4,
|
||||
number: true,
|
||||
nmap: HashMap::from_iter([
|
||||
bind('i', [InsertBefore]),
|
||||
bind('a', [InsertAfter]),
|
||||
bind('h', [MoveCharPrev]),
|
||||
bind('l', [MoveCharNext]),
|
||||
bind('j', [MoveLineNext]),
|
||||
bind('k', [MoveLinePrev]),
|
||||
bind('I', [MoveLineStart, InsertBefore]),
|
||||
bind('A', [MoveLineEnd, InsertAfter]),
|
||||
bind('o', [NewlineAfter, MoveLineNext, InsertBefore]),
|
||||
bind('O', [NewlineBefore, MoveLinePrev, InsertBefore]),
|
||||
]),
|
||||
imap: HashMap::from_iter([
|
||||
bind('\x7F', [EraseBackward]),
|
||||
bind('\n', [BreakLine, MoveLineNext, MoveLineStart, InsertBefore]),
|
||||
bind('\x0D', [BreakLine, MoveLineNext, MoveLineStart, InsertBefore]),
|
||||
])
|
||||
nmap,
|
||||
imap,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn key(&self, mode: Mode, key: char) -> Option<&[Action]> {
|
||||
pub fn key(&self, mode: Mode, key: &str) -> Option<&PrefixNode<String, Vec<Action>>> {
|
||||
match mode {
|
||||
Mode::Normal => self.nmap.get(&key),
|
||||
Mode::Insert => self.imap.get(&key)
|
||||
}.map(AsRef::as_ref)
|
||||
Mode::Normal => self.nmap.get(key),
|
||||
Mode::Insert => self.imap.get(key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
100
red/src/keymap/map.rs
Normal file
100
red/src/keymap/map.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::{HashMap, HashSet},
|
||||
fmt,
|
||||
hash::Hash,
|
||||
};
|
||||
|
||||
pub trait Prefix: Sized + Eq + Hash + Clone {
|
||||
fn prefix(&self) -> Option<Self>;
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum PrefixNode<K: Prefix, V> {
|
||||
Prefix(HashSet<K>),
|
||||
Leaf(V),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PrefixMap<K: Prefix, V> {
|
||||
map: HashMap<K, PrefixNode<K, V>>,
|
||||
}
|
||||
|
||||
impl<K: Prefix, V> PrefixNode<K, V> {
|
||||
pub fn empty_prefix() -> Self {
|
||||
Self::Prefix(HashSet::new())
|
||||
}
|
||||
|
||||
pub fn add_suffix(&mut self, suffix: K) {
|
||||
if let Self::Prefix(set) = self {
|
||||
set.insert(suffix);
|
||||
} else {
|
||||
*self = Self::Prefix(HashSet::from_iter([suffix]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Prefix + fmt::Debug, V: fmt::Debug> fmt::Debug for PrefixNode<K, V> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Leaf(leaf) => f.debug_struct("Leaf").field("value", leaf).finish(),
|
||||
Self::Prefix(suffixes) => f.debug_struct("Prefix").field("suffixes", suffixes).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Prefix, V> PrefixMap<K, V> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, key: K, value: V) {
|
||||
if let Some(prefix) = key.prefix() {
|
||||
self.map
|
||||
.entry(prefix)
|
||||
.or_insert_with(PrefixNode::empty_prefix)
|
||||
.add_suffix(key.clone());
|
||||
}
|
||||
|
||||
// TODO remove all suffixes of `key`, if those exist
|
||||
self.map.insert(key, PrefixNode::Leaf(value));
|
||||
}
|
||||
|
||||
pub fn get<N>(&self, key: &N) -> Option<&PrefixNode<K, V>>
|
||||
where
|
||||
K: Borrow<N>,
|
||||
N: Eq + Hash + ?Sized,
|
||||
{
|
||||
self.map.get(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Prefix, V> FromIterator<(K, V)> for PrefixMap<K, V> {
|
||||
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
|
||||
let mut this = Self::new();
|
||||
for (k, v) in iter {
|
||||
this.insert(k, v);
|
||||
}
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Prefix + fmt::Debug, V: fmt::Debug> fmt::Debug for PrefixMap<K, V> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut dm = f.debug_map();
|
||||
|
||||
for (k, v) in self.map.iter().filter_map(|(k, v)| {
|
||||
if let PrefixNode::Leaf(leaf) = v {
|
||||
Some((k, leaf))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
dm.entry(k, v);
|
||||
}
|
||||
|
||||
dm.finish()
|
||||
}
|
||||
}
|
85
red/src/keymap/mod.rs
Normal file
85
red/src/keymap/mod.rs
Normal file
@ -0,0 +1,85 @@
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
hash::Hash,
|
||||
};
|
||||
|
||||
use crate::command::Action;
|
||||
|
||||
use self::map::{PrefixMap, Prefix};
|
||||
pub use self::map::PrefixNode;
|
||||
|
||||
mod map;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct KeyMap {
|
||||
map: PrefixMap<String, Vec<Action>>,
|
||||
}
|
||||
|
||||
impl KeyMap {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: PrefixMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get<N>(&self, key: &N) -> Option<&PrefixNode<String, Vec<Action>>>
|
||||
where
|
||||
String: Borrow<N>,
|
||||
N: Eq + Hash + ?Sized,
|
||||
{
|
||||
self.map.get(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<(String, Vec<Action>)> for KeyMap {
|
||||
fn from_iter<T: IntoIterator<Item = (String, Vec<Action>)>>(iter: T) -> Self {
|
||||
Self {
|
||||
map: PrefixMap::from_iter(iter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Prefix for String {
|
||||
fn prefix(&self) -> Option<Self> {
|
||||
if self.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut res = self.clone();
|
||||
res.remove(res.len() - 1);
|
||||
Some(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind<I: Into<String>, V: IntoIterator<Item = Action>>(
|
||||
key: I,
|
||||
actions: V,
|
||||
) -> (String, Vec<Action>) {
|
||||
(key.into(), actions.into_iter().collect())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{command::Action, keymap::PrefixNode};
|
||||
|
||||
use super::{bind, KeyMap};
|
||||
|
||||
#[test]
|
||||
fn from_iter() {
|
||||
let map = KeyMap::from_iter([
|
||||
bind("aa", [Action::InsertBefore, Action::MoveLineEnd]),
|
||||
]);
|
||||
|
||||
assert_eq!(
|
||||
map.get("a"),
|
||||
Some(&PrefixNode::Prefix(HashSet::from_iter(["aa".to_owned()])))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
map.get("aa"),
|
||||
Some(&PrefixNode::Leaf([Action::InsertBefore, Action::MoveLineEnd].to_vec()))
|
||||
);
|
||||
}
|
||||
}
|
151
red/src/main.rs
151
red/src/main.rs
@ -6,12 +6,14 @@ use std::{env, fmt::Write, path::Path};
|
||||
use buffer::{Buffer, Mode, SetMode};
|
||||
use config::Config;
|
||||
use error::Error;
|
||||
use keymap::PrefixNode;
|
||||
use term::{Clear, Color, Term};
|
||||
|
||||
pub mod buffer;
|
||||
pub mod command;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod keymap;
|
||||
pub mod line;
|
||||
pub mod term;
|
||||
|
||||
@ -27,52 +29,13 @@ pub struct State {
|
||||
command: String,
|
||||
message: Option<String>,
|
||||
status: Option<String>,
|
||||
key: String,
|
||||
top_mode: TopMode,
|
||||
config: Config,
|
||||
running: bool,
|
||||
number_width: usize,
|
||||
}
|
||||
|
||||
fn display_modeline(term: &mut Term, top_mode: TopMode, buf: &Buffer) -> Result<(), Error> {
|
||||
term.set_cursor_position(buf.height(), 0)?;
|
||||
|
||||
let bg = match (top_mode, buf.mode()) {
|
||||
(TopMode::Normal, Mode::Normal) => Color::Yellow,
|
||||
(TopMode::Normal, Mode::Insert) => Color::Cyan,
|
||||
(TopMode::Command, _) => Color::Green,
|
||||
};
|
||||
|
||||
term.set_background(bg)?;
|
||||
term.set_foreground(Color::Black)?;
|
||||
|
||||
match top_mode {
|
||||
TopMode::Normal => {
|
||||
write!(term, " {} ", buf.mode().as_str()).map_err(Error::TerminalFmtError)?;
|
||||
|
||||
if buf.is_modified() {
|
||||
term.set_background(Color::Magenta)?;
|
||||
term.set_foreground(Color::Default)?;
|
||||
} else {
|
||||
term.set_foreground(Color::Green)?;
|
||||
term.set_background(Color::Default)?;
|
||||
}
|
||||
}
|
||||
TopMode::Command => {
|
||||
write!(term, " COMMAND ").map_err(Error::TerminalFmtError)?;
|
||||
|
||||
term.set_foreground(Color::Green)?;
|
||||
term.set_background(Color::Default)?;
|
||||
}
|
||||
}
|
||||
|
||||
let name = buf.name().map(String::as_str).unwrap_or("<unnamed>");
|
||||
write!(term, " {}", name).map_err(Error::TerminalFmtError)?;
|
||||
term.clear(Clear::LineToEnd)?;
|
||||
term.reset_style()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub fn open<P: AsRef<Path>>(path: Option<P>) -> Result<Self, Error> {
|
||||
let config = Config::default();
|
||||
@ -96,6 +59,7 @@ impl State {
|
||||
message: None,
|
||||
status: None,
|
||||
command: String::new(),
|
||||
key: String::new(),
|
||||
running: true,
|
||||
buffer,
|
||||
term,
|
||||
@ -150,6 +114,56 @@ impl State {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_modeline(&mut self) -> Result<(), Error> {
|
||||
self.term.set_cursor_position(self.buffer.height(), 0)?;
|
||||
|
||||
let bg = match (self.top_mode, self.buffer.mode()) {
|
||||
(TopMode::Normal, Mode::Normal) => Color::Yellow,
|
||||
(TopMode::Normal, Mode::Insert) => Color::Cyan,
|
||||
(TopMode::Command, _) => Color::Green,
|
||||
};
|
||||
|
||||
self.term.set_background(bg)?;
|
||||
self.term.set_foreground(Color::Black)?;
|
||||
|
||||
match self.top_mode {
|
||||
TopMode::Normal => {
|
||||
write!(self.term, " {} ", self.buffer.mode().as_str())
|
||||
.map_err(Error::TerminalFmtError)?;
|
||||
|
||||
if self.buffer.is_modified() {
|
||||
self.term.set_background(Color::Magenta)?;
|
||||
self.term.set_foreground(Color::Default)?;
|
||||
} else {
|
||||
self.term.set_foreground(Color::Green)?;
|
||||
self.term.set_background(Color::Default)?;
|
||||
}
|
||||
}
|
||||
TopMode::Command => {
|
||||
write!(self.term, " COMMAND ").map_err(Error::TerminalFmtError)?;
|
||||
|
||||
self.term.set_foreground(Color::Green)?;
|
||||
self.term.set_background(Color::Default)?;
|
||||
}
|
||||
}
|
||||
|
||||
let name = self
|
||||
.buffer
|
||||
.name()
|
||||
.map(String::as_str)
|
||||
.unwrap_or("<unnamed>");
|
||||
write!(self.term, " {}", name).map_err(Error::TerminalFmtError)?;
|
||||
self.term.clear(Clear::LineToEnd)?;
|
||||
self.term
|
||||
.set_cursor_position(self.buffer.height(), self.buffer.width() - 10)?;
|
||||
self.term.set_foreground(Color::White)?;
|
||||
write!(self.term, "{}", self.key).map_err(Error::TerminalFmtError)?;
|
||||
|
||||
self.term.reset_style()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display(&mut self) -> Result<(), Error> {
|
||||
if self.buffer.is_dirty() {
|
||||
self.term.clear(Clear::All)?;
|
||||
@ -178,7 +192,8 @@ impl State {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
display_modeline(&mut self.term, self.top_mode, &self.buffer)?;
|
||||
self.display_modeline()?;
|
||||
|
||||
match self.top_mode {
|
||||
TopMode::Normal => {
|
||||
self.buffer
|
||||
@ -222,32 +237,47 @@ impl State {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_mode_key(&mut self, mode: Mode, key: char) -> Result<(), Error> {
|
||||
let buffer = &mut self.buffer;
|
||||
|
||||
self.key.push(key);
|
||||
|
||||
match self.config.key(mode, &self.key) {
|
||||
Some(PrefixNode::Leaf(actions)) => {
|
||||
self.key.clear();
|
||||
|
||||
for &action in actions {
|
||||
command::perform(buffer, &self.config, action)?;
|
||||
}
|
||||
}
|
||||
Some(PrefixNode::Prefix(_)) => {}
|
||||
None => {
|
||||
self.key.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if self.buffer().mode() != Mode::Normal {
|
||||
self.status = None;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_normal_key(&mut self, key: char) -> Result<(), Error> {
|
||||
match key {
|
||||
'\x1B' => {
|
||||
self.key.clear();
|
||||
self.buffer.set_mode(&self.config, SetMode::Normal);
|
||||
Ok(())
|
||||
}
|
||||
':' => {
|
||||
self.key.clear();
|
||||
self.command.clear();
|
||||
self.status = None;
|
||||
self.top_mode = TopMode::Command;
|
||||
Ok(())
|
||||
}
|
||||
_ => {
|
||||
let buffer = &mut self.buffer;
|
||||
if let Some(actions) = self.config.key(Mode::Normal, key) {
|
||||
for &action in actions {
|
||||
command::perform(buffer, &self.config, action)?;
|
||||
}
|
||||
}
|
||||
|
||||
if self.buffer().mode() != Mode::Normal {
|
||||
self.status = None;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => self.handle_mode_key(Mode::Normal, key),
|
||||
}
|
||||
}
|
||||
|
||||
@ -261,16 +291,7 @@ impl State {
|
||||
self.buffer.insert(&self.config, key);
|
||||
Ok(())
|
||||
}
|
||||
_ => {
|
||||
let buffer = &mut self.buffer;
|
||||
if let Some(actions) = self.config.key(Mode::Insert, key) {
|
||||
for &action in actions {
|
||||
command::perform(buffer, &self.config, action)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
_ => self.handle_mode_key(Mode::Insert, key),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,7 +172,7 @@ impl Term {
|
||||
|
||||
impl fmt::Write for Term {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
self.stdout.write_all(s.as_bytes()).map_err(|_| fmt::Error::default())
|
||||
self.stdout.write_all(s.as_bytes()).map_err(|_| fmt::Error)
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user