M Cargo.toml +2 -2
@@ 1,7 1,7 @@
[workspace]
members = [
- "cursive",
- "cursive/plugins/*"
+ "cursive",
+ "cursive/plugins/*"
]
[package]
M cursive/default.nix +15 -15
@@ 1,23 1,23 @@
{ pkgs ? import <nixpkgs> {}, ... }:
pkgs.rustPlatform.buildRustPackage rec {
- name = "ted";
+ name = "ted";
- src = ./..;
- cargoSha256 = "0pyq5r1fqmkcacgczywn3ppg9d6liqva0xb4imq9j0acdabm0v5d";
+ src = ./..;
+ cargoSha256 = "0pyq5r1fqmkcacgczywn3ppg9d6liqva0xb4imq9j0acdabm0v5d";
- buildInputs = (import ./shell.nix { inherit pkgs; }).propagatedBuildInputs;
+ buildInputs = (import ./shell.nix { inherit pkgs; }).propagatedBuildInputs;
- buildPhase = ''
- runHook preBuild
- cargo build --release --frozen --all
- runHook postBuild
- '';
+ buildPhase = ''
+ runHook preBuild
+ cargo build --release --frozen --all
+ runHook postBuild
+ '';
- meta = with pkgs.stdenv.lib; {
- homepage = https://nest.pijul.com/dermetfan/ted;
- license = licenses.asl20;
- maintainers = [ "dermetfan <serverkorken@gmail.com>" ];
- platforms = platforms.unix;
- };
+ meta = with pkgs.stdenv.lib; {
+ homepage = https://nest.pijul.com/dermetfan/ted;
+ license = licenses.asl20;
+ maintainers = [ "dermetfan <serverkorken@gmail.com>" ];
+ platforms = platforms.unix;
+ };
}
M cursive/plugins/syntect/src/highlighting.rs +228 -228
@@ 3,303 3,303 @@ extern crate enumset;
extern crate frappe;
use ted_tui::{
- cursive::{
- theme::{
- Theme,
- Style,
- Color,
- Palette,
- PaletteColor,
- ColorStyle,
- Effect
- },
- utils::{
- markup::StyledIndexedSpan,
- span::IndexedCow
- }
- },
- highlight
+ cursive::{
+ theme::{
+ Theme,
+ Style,
+ Color,
+ Palette,
+ PaletteColor,
+ ColorStyle,
+ Effect
+ },
+ utils::{
+ markup::StyledIndexedSpan,
+ span::IndexedCow
+ }
+ },
+ highlight
};
use self::{
- syntect::{
- parsing::{
- SyntaxSet,
- SyntaxDefinition,
- ParseState,
- ScopeStack
- },
- highlighting::{
- ThemeSet,
- Theme as SyntectTheme,
- Style as SyntectStyle,
- Color as SyntectColor,
- FontStyle as SyntectFontStyle,
- Highlighter as SyntectHighlighter,
- HighlightState as SyntectHighlightState,
- HighlightIterator
- }
- },
- enumset::EnumSet,
- frappe::Signal
+ syntect::{
+ parsing::{
+ SyntaxSet,
+ SyntaxDefinition,
+ ParseState,
+ ScopeStack
+ },
+ highlighting::{
+ ThemeSet,
+ Theme as SyntectTheme,
+ Style as SyntectStyle,
+ Color as SyntectColor,
+ FontStyle as SyntectFontStyle,
+ Highlighter as SyntectHighlighter,
+ HighlightState as SyntectHighlightState,
+ HighlightIterator
+ }
+ },
+ enumset::EnumSet,
+ frappe::Signal
};
pub mod plugin {
- use super::{
- highlight::Highlighting,
- Theme
- };
+ use super::{
+ highlight::Highlighting,
+ Theme
+ };
- plugin!(highlighting: super::main);
+ plugin!(highlighting: super::main);
}
#[allow(dead_code)]
pub fn main(text: &str, theme: Option<Theme>) -> (Box<dyn highlight::Highlighting>, Theme) {
- let syntaxes = SyntaxSet::load_defaults_newlines();
- let themes = ThemeSet::load_defaults();
+ let syntaxes = SyntaxSet::load_defaults_newlines();
+ let themes = ThemeSet::load_defaults();
- let syntax = text.lines().nth(0)
- .and_then(|line| syntaxes.find_syntax_by_first_line(line))
- .unwrap_or_else(|| syntaxes.find_syntax_plain_text())
- .clone();
+ let syntax = text.lines().nth(0)
+ .and_then(|line| syntaxes.find_syntax_by_first_line(line))
+ .unwrap_or_else(|| syntaxes.find_syntax_plain_text())
+ .clone();
- let syntect_theme = themes.themes["base16-eighties.dark"].clone();
- let theme = self::theme(&syntect_theme, theme);
- let palette = theme.palette.clone();
+ let syntect_theme = themes.themes["base16-eighties.dark"].clone();
+ let theme = self::theme(&syntect_theme, theme);
+ let palette = theme.palette.clone();
- (
- Box::new(Highlighting::new(
- syntaxes, themes,
- syntax, syntect_theme,
- palette
- )),
- theme
- )
+ (
+ Box::new(Highlighting::new(
+ syntaxes, themes,
+ syntax, syntect_theme,
+ palette
+ )),
+ theme
+ )
}
pub struct Highlighting {
- _syntaxes: SyntaxSet,
- _themes: ThemeSet,
+ _syntaxes: SyntaxSet,
+ _themes: ThemeSet,
- syntax: SyntaxDefinition,
- theme: Signal<SyntectTheme>,
- palette: Palette
+ syntax: SyntaxDefinition,
+ theme: Signal<SyntectTheme>,
+ palette: Palette
}
impl Highlighting {
- pub fn new(syntaxes: SyntaxSet, themes: ThemeSet, syntax: SyntaxDefinition, theme: SyntectTheme, palette: Palette) -> Self {
- Self {
- _syntaxes: syntaxes,
- _themes: themes,
- syntax, palette,
- theme: Signal::constant(theme)
- }
- }
+ pub fn new(syntaxes: SyntaxSet, themes: ThemeSet, syntax: SyntaxDefinition, theme: SyntectTheme, palette: Palette) -> Self {
+ Self {
+ _syntaxes: syntaxes,
+ _themes: themes,
+ syntax, palette,
+ theme: Signal::constant(theme)
+ }
+ }
}
impl highlight::Highlighting for Highlighting {
- fn start(&self) -> Box<dyn highlight::Highlight> {
- Box::new(Highlight::new(&self.syntax, self.theme.clone()))
- }
+ fn start(&self) -> Box<dyn highlight::Highlight> {
+ Box::new(Highlight::new(&self.syntax, self.theme.clone()))
+ }
- fn cursor(&self, style: &mut Style) {
- let palette = &self.palette;
- self.theme.sample_with(|theme|
- if let Some(color) = theme.settings.caret {
- style.color = Some(mod_color_back(&style.color, &color, palette))
- } else {
- ().cursor(style)
- }
- );
- }
+ fn cursor(&self, style: &mut Style) {
+ let palette = &self.palette;
+ self.theme.sample_with(|theme|
+ if let Some(color) = theme.settings.caret {
+ style.color = Some(mod_color_back(&style.color, &color, palette))
+ } else {
+ ().cursor(style)
+ }
+ );
+ }
- fn cursor_line(&self, style: &mut Style) {
- let palette = &self.palette;
- self.theme.sample_with(|theme|
- if let Some(color) = theme.settings.line_highlight {
- style.color = Some(mod_color_back(&style.color, &color, palette))
- } else {
- ().cursor_line(style)
- }
- );
- }
+ fn cursor_line(&self, style: &mut Style) {
+ let palette = &self.palette;
+ self.theme.sample_with(|theme|
+ if let Some(color) = theme.settings.line_highlight {
+ style.color = Some(mod_color_back(&style.color, &color, palette))
+ } else {
+ ().cursor_line(style)
+ }
+ );
+ }
}
#[derive(Clone)]
pub struct Highlight {
- state: (ParseState, SyntectHighlightState),
- theme: Signal<SyntectTheme>
+ state: (ParseState, SyntectHighlightState),
+ theme: Signal<SyntectTheme>
}
impl Highlight {
- pub fn new(syntax: &SyntaxDefinition, theme: Signal<SyntectTheme>) -> Self {
- Self {
- state: theme.sample_with(|theme| (
- ParseState::new(syntax),
- SyntectHighlightState::new(
- &SyntectHighlighter::new(&theme), // XXX cache this for highlight() and get rid of `theme`
- ScopeStack::new()
- )
- )),
- theme
- }
- }
+ pub fn new(syntax: &SyntaxDefinition, theme: Signal<SyntectTheme>) -> Self {
+ Self {
+ state: theme.sample_with(|theme| (
+ ParseState::new(syntax),
+ SyntectHighlightState::new(
+ &SyntectHighlighter::new(&theme), // XXX cache this for highlight() and get rid of `theme`
+ ScopeStack::new()
+ )
+ )),
+ theme
+ }
+ }
}
impl highlight::Highlight for Highlight {
- fn highlight(&mut self, line: &str) -> Vec<StyledIndexedSpan> {
- let mut line_char_idx = 0;
- let state = &mut self.state;
- self.theme.sample_with(|theme| HighlightIterator::new(
- &mut state.1,
- state.0.parse_line(line).as_slice(),
- line,
- &SyntectHighlighter::new(&theme)
- )
- .map(|(style, span)| StyledIndexedSpan {
- content: IndexedCow::Borrowed {
- start: line_char_idx,
- end: {
- line_char_idx += span.len();
- line_char_idx
- }
- },
- attr: self::style(style)
- })
- .collect()
- )
- }
+ fn highlight(&mut self, line: &str) -> Vec<StyledIndexedSpan> {
+ let mut line_char_idx = 0;
+ let state = &mut self.state;
+ self.theme.sample_with(|theme| HighlightIterator::new(
+ &mut state.1,
+ state.0.parse_line(line).as_slice(),
+ line,
+ &SyntectHighlighter::new(&theme)
+ )
+ .map(|(style, span)| StyledIndexedSpan {
+ content: IndexedCow::Borrowed {
+ start: line_char_idx,
+ end: {
+ line_char_idx += span.len();
+ line_char_idx
+ }
+ },
+ attr: self::style(style)
+ })
+ .collect()
+ )
+ }
- fn parse(&mut self, line: &str) {
- let state = &mut self.state;
- self.theme.sample_with(|theme|
- HighlightIterator::new(
- &mut state.1,
- state.0.parse_line(line).as_slice(),
- line,
- &SyntectHighlighter::new(&theme)
- ).count()
- );
- }
+ fn parse(&mut self, line: &str) {
+ let state = &mut self.state;
+ self.theme.sample_with(|theme|
+ HighlightIterator::new(
+ &mut state.1,
+ state.0.parse_line(line).as_slice(),
+ line,
+ &SyntectHighlighter::new(&theme)
+ ).count()
+ );
+ }
- fn clone_dyn(&self) -> Box<dyn highlight::Highlight> {
- Box::new(self.clone())
- }
+ fn clone_dyn(&self) -> Box<dyn highlight::Highlight> {
+ Box::new(self.clone())
+ }
}
fn mod_color_back(target: &Option<ColorStyle>, back: &SyntectColor, palette: &Palette) -> ColorStyle {
- target
- .map(|x| ColorStyle::new(x.front,
- color_fake_alpha(*back,
- syntect_color(
- &x.back.resolve(palette)
- ).unwrap()
- )
- ))
- .unwrap_or_else(|| ColorStyle::new(PaletteColor::Primary, color(*back)))
+ target
+ .map(|x| ColorStyle::new(x.front,
+ color_fake_alpha(*back,
+ syntect_color(
+ &x.back.resolve(palette)
+ ).unwrap()
+ )
+ ))
+ .unwrap_or_else(|| ColorStyle::new(PaletteColor::Primary, color(*back)))
}
pub fn theme(syntect_theme: &SyntectTheme, proto: Option<Theme>) -> Theme {
- let mut theme = proto.unwrap_or_default();
+ let mut theme = proto.unwrap_or_default();
- // inherit Background
- if let Some(shadow) = syntect_theme.settings.shadow {
- theme.palette[PaletteColor::Shadow] = color(shadow);
- }
- if let Some(background) = syntect_theme.settings.background {
- theme.palette[PaletteColor::View] = color(background);
- }
- if let Some(foreground) = syntect_theme.settings.foreground {
- theme.palette[PaletteColor::Primary] = color(foreground);
- }
- if let Some(accent) = syntect_theme.settings.accent {
- theme.palette[PaletteColor::Secondary] = color(accent);
- }
- // inherit Tertiary
- // inherit TitlePrimary
- // inherit TitleSecondary
- if let Some(selection) = syntect_theme.settings.selection {
- theme.palette[PaletteColor::Highlight] = color(selection);
- }
- if let Some(inactive_selection) = syntect_theme.settings.inactive_selection {
- theme.palette[PaletteColor::HighlightInactive] = color(inactive_selection);
- }
+ // inherit Background
+ if let Some(shadow) = syntect_theme.settings.shadow {
+ theme.palette[PaletteColor::Shadow] = color(shadow);
+ }
+ if let Some(background) = syntect_theme.settings.background {
+ theme.palette[PaletteColor::View] = color(background);
+ }
+ if let Some(foreground) = syntect_theme.settings.foreground {
+ theme.palette[PaletteColor::Primary] = color(foreground);
+ }
+ if let Some(accent) = syntect_theme.settings.accent {
+ theme.palette[PaletteColor::Secondary] = color(accent);
+ }
+ // inherit Tertiary
+ // inherit TitlePrimary
+ // inherit TitleSecondary
+ if let Some(selection) = syntect_theme.settings.selection {
+ theme.palette[PaletteColor::Highlight] = color(selection);
+ }
+ if let Some(inactive_selection) = syntect_theme.settings.inactive_selection {
+ theme.palette[PaletteColor::HighlightInactive] = color(inactive_selection);
+ }
- theme
+ theme
}
pub fn style(style: SyntectStyle) -> Style {
- Style {
- color: Some(ColorStyle::new(
- color(style.foreground),
- color(style.background)
- )),
- effects: effect(style.font_style)
- }
+ Style {
+ color: Some(ColorStyle::new(
+ color(style.foreground),
+ color(style.background)
+ )),
+ effects: effect(style.font_style)
+ }
}
#[allow(dead_code)]
pub fn style_fake_alpha(style: SyntectStyle, background: SyntectColor) -> Style {
- Style {
- color: Some(ColorStyle::new(
- color_fake_alpha(style.foreground, background),
- color_fake_alpha(style.background, background)
- )),
- effects: effect(style.font_style)
- }
+ Style {
+ color: Some(ColorStyle::new(
+ color_fake_alpha(style.foreground, background),
+ color_fake_alpha(style.background, background)
+ )),
+ effects: effect(style.font_style)
+ }
}
pub fn color(color: SyntectColor) -> Color {
- Color::Rgb(color.r, color.g, color.b)
+ Color::Rgb(color.r, color.g, color.b)
}
pub fn color_fake_alpha(color: SyntectColor, background: SyntectColor) -> Color {
- lerp(
- (background.r, background.g, background.b),
- (color.r, color.g, color.b),
- color.a // XXX background.a is ignored
- )
+ lerp(
+ (background.r, background.g, background.b),
+ (color.r, color.g, color.b),
+ color.a // XXX background.a is ignored
+ )
}
pub fn effect(font_style: SyntectFontStyle) -> EnumSet<Effect> {
- let mut effects = EnumSet::new();
+ let mut effects = EnumSet::new();
- if font_style.contains(SyntectFontStyle::BOLD) {
- effects.insert(Effect::Bold);
- }
- if font_style.contains(SyntectFontStyle::UNDERLINE) {
- effects.insert(Effect::Underline);
- }
- if font_style.contains(SyntectFontStyle::ITALIC) {
- effects.insert(Effect::Italic);
- }
+ if font_style.contains(SyntectFontStyle::BOLD) {
+ effects.insert(Effect::Bold);
+ }
+ if font_style.contains(SyntectFontStyle::UNDERLINE) {
+ effects.insert(Effect::Underline);
+ }
+ if font_style.contains(SyntectFontStyle::ITALIC) {
+ effects.insert(Effect::Italic);
+ }
- effects
+ effects
}
/// Can only convert `Color::Rgb`.
pub fn syntect_color(color: &Color) -> Result<SyntectColor, ()> {
- match color {
- &Color::Rgb(r, g, b) => Ok(SyntectColor {
- r: r, g: g, b: b, a: u8::max_value()
- }),
- _ => Err(())
- }
+ match color {
+ &Color::Rgb(r, g, b) => Ok(SyntectColor {
+ r: r, g: g, b: b, a: u8::max_value()
+ }),
+ _ => Err(())
+ }
}
pub fn lerp(from: (u8, u8, u8), to: (u8, u8, u8), alpha: u8) -> Color {
- fn lerp1(from: u8, to: u8, alpha: u8) -> u8 {
- let diff = from.max(to) - from.min(to);
- let diff = (diff as f32 * (alpha as f32 / u8::max_value() as f32)) as u8;
- if from < to {
- from.saturating_add(diff)
- } else {
- to.saturating_sub(diff)
- }
- }
+ fn lerp1(from: u8, to: u8, alpha: u8) -> u8 {
+ let diff = from.max(to) - from.min(to);
+ let diff = (diff as f32 * (alpha as f32 / u8::max_value() as f32)) as u8;
+ if from < to {
+ from.saturating_add(diff)
+ } else {
+ to.saturating_sub(diff)
+ }
+ }
- Color::Rgb(
- lerp1(from.0, to.0, alpha),
- lerp1(from.1, to.1, alpha),
- lerp1(from.2, to.2, alpha)
- )
+ Color::Rgb(
+ lerp1(from.0, to.0, alpha),
+ lerp1(from.1, to.1, alpha),
+ lerp1(from.2, to.2, alpha)
+ )
}
M cursive/shell.nix +7 -7
@@ 1,13 1,13 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.stdenv.mkDerivation {
- name = "ted-tui-env";
+ name = "ted-tui-env";
- propagatedBuildInputs = with pkgs; [
- ncurses # cursive
- cmake # syntect
- ];
+ propagatedBuildInputs = with pkgs; [
+ ncurses # cursive
+ cmake # syntect
+ ];
- RUST_BACKTRACE = 1;
- RUST_LOG = "ted";
+ RUST_BACKTRACE = 1;
+ RUST_LOG = "ted";
}
M cursive/src/bindings/editor.rs +98 -98
@@ 1,116 1,116 @@
//! Key bindings for an `EditorView`.
use {
- frappe::Signal,
- either::Either,
- views::editor::{
- Cmd,
- HistoryCmd,
- Properties
- },
- cursive::event::{
- Event,
- Key
- },
- over::{
- OverError,
- arr::Arr
- },
- ted::{
- command::CursorCmd,
- trie::SequenceTrie
- }
+ frappe::Signal,
+ either::Either,
+ views::editor::{
+ Cmd,
+ HistoryCmd,
+ Properties
+ },
+ cursive::event::{
+ Event,
+ Key
+ },
+ over::{
+ OverError,
+ arr::Arr
+ },
+ ted::{
+ command::CursorCmd,
+ trie::SequenceTrie
+ }
};
pub fn parse_over(bindings: &Arr, props: &Properties) -> Result<SequenceTrie<Event, Cmd>, Either<OverError, ()>> {
- use either::Either::*;
+ use either::Either::*;
- let page_up = props.view_height.map(|h| -(*h as isize));
- let page_down = props.view_height.map(|h| *h as isize );
+ let page_up = props.view_height.map(|h| -(*h as isize));
+ let page_down = props.view_height.map(|h| *h as isize );
- let mut trie = SequenceTrie::new();
+ let mut trie = SequenceTrie::new();
- for binding in bindings.iter() {
- let binding = binding.get_tup().map_err(Left)?;
+ for binding in bindings.iter() {
+ let binding = binding.get_tup().map_err(Left)?;
- let events = binding.get(0).map_err(Left)?.get_arr().map_err(Left)?;
- let cmd = binding.get(1).map_err(Left)?.get_str().map_err(Left)?;
+ let events = binding.get(0).map_err(Left)?.get_arr().map_err(Left)?;
+ let cmd = binding.get(1).map_err(Left)?.get_str().map_err(Left)?;
- let events = {
- let mut vec = Vec::new();
- for event in events.iter() {
- let event = event.get_str().map_err(Left)?;
- let event = super::parse_event(&event).map_err(Right)?;
- vec.push(event);
- }
- vec
- };
+ let events = {
+ let mut vec = Vec::new();
+ for event in events.iter() {
+ let event = event.get_str().map_err(Left)?;
+ let event = super::parse_event(&event).map_err(Right)?;
+ vec.push(event);
+ }
+ vec
+ };
- trie.insert_owned(events, {
- match cmd.as_str() {
- "Enter" => Cmd::Cur(CursorCmd::Enter),
- "Backspace" => Cmd::Cur(CursorCmd::Backspace),
- "Delete" => Cmd::Cur(CursorCmd::Delete),
- "Indent" => Cmd::Cur(CursorCmd::Indent(props.indent.clone())),
- "Dedent" => Cmd::Cur(CursorCmd::Dedent(props.indent.clone())),
- "Left" => Cmd::Cur(CursorCmd::Left (false)),
- "Left-select" => Cmd::Cur(CursorCmd::Left (true )),
- "Right" => Cmd::Cur(CursorCmd::Right(false)),
- "Right-select" => Cmd::Cur(CursorCmd::Right(true )),
- "Up" => Cmd::Cur(CursorCmd::Vertical(false, Signal::constant(-1))),
- "Up-select" => Cmd::Cur(CursorCmd::Vertical(true, Signal::constant(-1))),
- "Down" => Cmd::Cur(CursorCmd::Vertical(false, Signal::constant( 1))),
- "Down-select" => Cmd::Cur(CursorCmd::Vertical(true, Signal::constant( 1))),
- "PageUp" => Cmd::Cur(CursorCmd::Vertical(false, page_up.clone())),
- "PageUp-select" => Cmd::Cur(CursorCmd::Vertical(true, page_up.clone())),
- "PageDown" => Cmd::Cur(CursorCmd::Vertical(false, page_down.clone())),
- "PageDown-select" => Cmd::Cur(CursorCmd::Vertical(true, page_down.clone())),
- "LineHome" => Cmd::Cur(CursorCmd::LineHome(false, false)),
- "LineHome-select" => Cmd::Cur(CursorCmd::LineHome(true, false)),
- "LineEnd" => Cmd::Cur(CursorCmd::LineEnd (false)),
- "LineEnd-select" => Cmd::Cur(CursorCmd::LineEnd (true )),
- "WordLeft" => Cmd::Cur(CursorCmd::WordLeft (false)),
- "WordLeft-select" => Cmd::Cur(CursorCmd::WordLeft (true )),
- "WordRight" => Cmd::Cur(CursorCmd::WordRight(false)),
- "WordRight-select" => Cmd::Cur(CursorCmd::WordRight(true )),
- "SelectWord" => Cmd::Cur(CursorCmd::SelectWord),
- "SpawnMultiCursor" => Cmd::Cur(CursorCmd::SpawnMultiCursor),
- "KillMultiCursor" => Cmd::Cur(CursorCmd::KillMultiCursor),
- "SkipMultiCursor" => Cmd::Cur(CursorCmd::SkipMultiCursor),
- "Undo" => Cmd::Hist(HistoryCmd::Undo),
- "Redo" => Cmd::Hist(HistoryCmd::Redo),
- _ => return Err(Right(()))
- }
- });
- }
+ trie.insert_owned(events, {
+ match cmd.as_str() {
+ "Enter" => Cmd::Cur(CursorCmd::Enter),
+ "Backspace" => Cmd::Cur(CursorCmd::Backspace),
+ "Delete" => Cmd::Cur(CursorCmd::Delete),
+ "Indent" => Cmd::Cur(CursorCmd::Indent(props.indent.clone())),
+ "Dedent" => Cmd::Cur(CursorCmd::Dedent(props.indent.clone())),
+ "Left" => Cmd::Cur(CursorCmd::Left (false)),
+ "Left-select" => Cmd::Cur(CursorCmd::Left (true )),
+ "Right" => Cmd::Cur(CursorCmd::Right(false)),
+ "Right-select" => Cmd::Cur(CursorCmd::Right(true )),
+ "Up" => Cmd::Cur(CursorCmd::Vertical(false, Signal::constant(-1))),
+ "Up-select" => Cmd::Cur(CursorCmd::Vertical(true, Signal::constant(-1))),
+ "Down" => Cmd::Cur(CursorCmd::Vertical(false, Signal::constant( 1))),
+ "Down-select" => Cmd::Cur(CursorCmd::Vertical(true, Signal::constant( 1))),
+ "PageUp" => Cmd::Cur(CursorCmd::Vertical(false, page_up.clone())),
+ "PageUp-select" => Cmd::Cur(CursorCmd::Vertical(true, page_up.clone())),
+ "PageDown" => Cmd::Cur(CursorCmd::Vertical(false, page_down.clone())),
+ "PageDown-select" => Cmd::Cur(CursorCmd::Vertical(true, page_down.clone())),
+ "LineHome" => Cmd::Cur(CursorCmd::LineHome(false, false)),
+ "LineHome-select" => Cmd::Cur(CursorCmd::LineHome(true, false)),
+ "LineEnd" => Cmd::Cur(CursorCmd::LineEnd (false)),
+ "LineEnd-select" => Cmd::Cur(CursorCmd::LineEnd (true )),
+ "WordLeft" => Cmd::Cur(CursorCmd::WordLeft (false)),
+ "WordLeft-select" => Cmd::Cur(CursorCmd::WordLeft (true )),
+ "WordRight" => Cmd::Cur(CursorCmd::WordRight(false)),
+ "WordRight-select" => Cmd::Cur(CursorCmd::WordRight(true )),
+ "SelectWord" => Cmd::Cur(CursorCmd::SelectWord),
+ "SpawnMultiCursor" => Cmd::Cur(CursorCmd::SpawnMultiCursor),
+ "KillMultiCursor" => Cmd::Cur(CursorCmd::KillMultiCursor),
+ "SkipMultiCursor" => Cmd::Cur(CursorCmd::SkipMultiCursor),
+ "Undo" => Cmd::Hist(HistoryCmd::Undo),
+ "Redo" => Cmd::Hist(HistoryCmd::Redo),
+ _ => return Err(Right(()))
+ }
+ });
+ }
- Ok(trie)
+ Ok(trie)
}
pub fn defaults(props: &Properties) -> SequenceTrie<Event, Cmd> {
- let page_up = props.view_height.map(|h| -(*h as isize));
- let page_down = props.view_height.map(|h| *h as isize );
+ let page_up = props.view_height.map(|h| -(*h as isize));
+ let page_down = props.view_height.map(|h| *h as isize );
- let mut bindings = SequenceTrie::new();
- bindings.insert(&[Event::Key (Key::Enter)], Cmd::Cur(CursorCmd::Enter));
- bindings.insert(&[Event::Key (Key::Backspace)], Cmd::Cur(CursorCmd::Backspace));
- bindings.insert(&[Event::Key (Key::Del)], Cmd::Cur(CursorCmd::Delete));
- bindings.insert(&[Event::Key (Key::Tab)], Cmd::Cur(CursorCmd::Indent (props.indent.clone()))); bindings.insert(&[Event::Shift (Key::Tab)], Cmd::Cur(CursorCmd::Dedent (props.indent.clone())));
- bindings.insert(&[Event::Key (Key::Left)], Cmd::Cur(CursorCmd::Left (false))); bindings.insert(&[Event::Shift (Key::Left)], Cmd::Cur(CursorCmd::Left (true)));
- bindings.insert(&[Event::Key (Key::Right)], Cmd::Cur(CursorCmd::Right (false))); bindings.insert(&[Event::Shift (Key::Right)], Cmd::Cur(CursorCmd::Right (true)));
- bindings.insert(&[Event::Key (Key::Up)], Cmd::Cur(CursorCmd::Vertical (false, Signal::constant(-1)))); bindings.insert(&[Event::Shift (Key::Up)], Cmd::Cur(CursorCmd::Vertical (true, Signal::constant(-1))));
- bindings.insert(&[Event::Key (Key::Down)], Cmd::Cur(CursorCmd::Vertical (false, Signal::constant( 1)))); bindings.insert(&[Event::Shift (Key::Down)], Cmd::Cur(CursorCmd::Vertical (true, Signal::constant( 1))));
- bindings.insert(&[Event::Key (Key::PageUp)], Cmd::Cur(CursorCmd::Vertical (false, page_up .clone()))); bindings.insert(&[Event::Shift (Key::PageUp)], Cmd::Cur(CursorCmd::Vertical (true, page_up)));
- bindings.insert(&[Event::Key (Key::PageDown)], Cmd::Cur(CursorCmd::Vertical (false, page_down.clone()))); bindings.insert(&[Event::Shift (Key::PageDown)], Cmd::Cur(CursorCmd::Vertical (true, page_down)));
- bindings.insert(&[Event::Key (Key::Home)], Cmd::Cur(CursorCmd::LineHome (false, false))); bindings.insert(&[Event::Shift (Key::Home)], Cmd::Cur(CursorCmd::LineHome (true, false)));
- bindings.insert(&[Event::Key (Key::End)], Cmd::Cur(CursorCmd::LineEnd (false))); bindings.insert(&[Event::Shift (Key::End)], Cmd::Cur(CursorCmd::LineEnd (true)));
- bindings.insert(&[Event::Ctrl(Key::Left)], Cmd::Cur(CursorCmd::WordLeft (false))); bindings.insert(&[Event::CtrlShift(Key::Left)], Cmd::Cur(CursorCmd::WordLeft (true)));
- bindings.insert(&[Event::Ctrl(Key::Right)], Cmd::Cur(CursorCmd::WordRight(false))); bindings.insert(&[Event::CtrlShift(Key::Right)], Cmd::Cur(CursorCmd::WordRight(true)));
- bindings.insert(&[Event::CtrlChar('w')], Cmd::Cur(CursorCmd::SelectWord));
- bindings.insert(&[Event::CtrlChar('n')], Cmd::Cur(CursorCmd::SpawnMultiCursor));
- bindings.insert(&[Event::CtrlChar('b')], Cmd::Cur(CursorCmd::KillMultiCursor));
- bindings.insert(&[Event::CtrlChar('p')], Cmd::Cur(CursorCmd::SkipMultiCursor));
- bindings.insert(&[Event::CtrlChar('g')], Cmd::Hist(HistoryCmd::Undo));
- bindings.insert(&[Event::CtrlChar('y')], Cmd::Hist(HistoryCmd::Redo));
- bindings
+ let mut bindings = SequenceTrie::new();
+ bindings.insert(&[Event::Key (Key::Enter)], Cmd::Cur(CursorCmd::Enter));
+ bindings.insert(&[Event::Key (Key::Backspace)], Cmd::Cur(CursorCmd::Backspace));
+ bindings.insert(&[Event::Key (Key::Del)], Cmd::Cur(CursorCmd::Delete));
+ bindings.insert(&[Event::Key (Key::Tab)], Cmd::Cur(CursorCmd::Indent (props.indent.clone()))); bindings.insert(&[Event::Shift (Key::Tab)], Cmd::Cur(CursorCmd::Dedent (props.indent.clone())));
+ bindings.insert(&[Event::Key (Key::Left)], Cmd::Cur(CursorCmd::Left (false))); bindings.insert(&[Event::Shift (Key::Left)], Cmd::Cur(CursorCmd::Left (true)));
+ bindings.insert(&[Event::Key (Key::Right)], Cmd::Cur(CursorCmd::Right (false))); bindings.insert(&[Event::Shift (Key::Right)], Cmd::Cur(CursorCmd::Right (true)));
+ bindings.insert(&[Event::Key (Key::Up)], Cmd::Cur(CursorCmd::Vertical (false, Signal::constant(-1)))); bindings.insert(&[Event::Shift (Key::Up)], Cmd::Cur(CursorCmd::Vertical (true, Signal::constant(-1))));
+ bindings.insert(&[Event::Key (Key::Down)], Cmd::Cur(CursorCmd::Vertical (false, Signal::constant( 1)))); bindings.insert(&[Event::Shift (Key::Down)], Cmd::Cur(CursorCmd::Vertical (true, Signal::constant( 1))));
+ bindings.insert(&[Event::Key (Key::PageUp)], Cmd::Cur(CursorCmd::Vertical (false, page_up .clone()))); bindings.insert(&[Event::Shift (Key::PageUp)], Cmd::Cur(CursorCmd::Vertical (true, page_up)));
+ bindings.insert(&[Event::Key (Key::PageDown)], Cmd::Cur(CursorCmd::Vertical (false, page_down.clone()))); bindings.insert(&[Event::Shift (Key::PageDown)], Cmd::Cur(CursorCmd::Vertical (true, page_down)));
+ bindings.insert(&[Event::Key (Key::Home)], Cmd::Cur(CursorCmd::LineHome (false, false))); bindings.insert(&[Event::Shift (Key::Home)], Cmd::Cur(CursorCmd::LineHome (true, false)));
+ bindings.insert(&[Event::Key (Key::End)], Cmd::Cur(CursorCmd::LineEnd (false))); bindings.insert(&[Event::Shift (Key::End)], Cmd::Cur(CursorCmd::LineEnd (true)));
+ bindings.insert(&[Event::Ctrl(Key::Left)], Cmd::Cur(CursorCmd::WordLeft (false))); bindings.insert(&[Event::CtrlShift(Key::Left)], Cmd::Cur(CursorCmd::WordLeft (true)));
+ bindings.insert(&[Event::Ctrl(Key::Right)], Cmd::Cur(CursorCmd::WordRight(false))); bindings.insert(&[Event::CtrlShift(Key::Right)], Cmd::Cur(CursorCmd::WordRight(true)));
+ bindings.insert(&[Event::CtrlChar('w')], Cmd::Cur(CursorCmd::SelectWord));
+ bindings.insert(&[Event::CtrlChar('n')], Cmd::Cur(CursorCmd::SpawnMultiCursor));
+ bindings.insert(&[Event::CtrlChar('b')], Cmd::Cur(CursorCmd::KillMultiCursor));
+ bindings.insert(&[Event::CtrlChar('p')], Cmd::Cur(CursorCmd::SkipMultiCursor));
+ bindings.insert(&[Event::CtrlChar('g')], Cmd::Hist(HistoryCmd::Undo));
+ bindings.insert(&[Event::CtrlChar('y')], Cmd::Hist(HistoryCmd::Redo));
+ bindings
}
M cursive/src/bindings/mod.rs +123 -123
@@ 4,139 4,139 @@ use std::borrow::Cow;
use cursive::event::{Event, Key};
fn parse_event(str: &str) -> Result<Event, ()> {
- use cursive::event::Event::*;
+ use cursive::event::Event::*;
- let (variant, inner) = {
- let mut split = str.split_at(
- str.find('-').ok_or(())?
- );
- // cut off the leading hyphen
- split.1 = &split.1[1..];
- split
- };
+ let (variant, inner) = {
+ let mut split = str.split_at(
+ str.find('-').ok_or(())?
+ );
+ // cut off the leading hyphen
+ split.1 = &split.1[1..];
+ split
+ };
- match variant {
- "Char" |
- "CtrlChar" |
- "AltChar" => {
- let ch: char = inner.parse().map_err(|_| ())?;
- Ok(match variant {
- "Char" => Char (ch),
- "CtrlChar" => CtrlChar(ch),
- "AltChar" => AltChar (ch),
- _ => unreachable!()
- })
- },
- "Key" |
- "Shift" |
- "Alt" |
- "AltShift" |
- "Ctrl" |
- "CtrlShift" |
- "CtrlAlt" => {
- let key = parse_key(inner).map_err(|_| ())?;
- Ok(match variant {
- "Key" => Key (key),
- "Shift" => Shift (key),
- "Alt" => Alt (key),
- "AltShift" => AltShift (key),
- "Ctrl" => Ctrl (key),
- "CtrlShift" => CtrlShift(key),
- "CtrlAlt" => CtrlAlt (key),
- _ => unreachable!()
- })
- },
- _ => Err(())
- }
+ match variant {
+ "Char" |
+ "CtrlChar" |
+ "AltChar" => {
+ let ch: char = inner.parse().map_err(|_| ())?;
+ Ok(match variant {
+ "Char" => Char (ch),
+ "CtrlChar" => CtrlChar(ch),
+ "AltChar" => AltChar (ch),
+ _ => unreachable!()
+ })
+ },
+ "Key" |
+ "Shift" |
+ "Alt" |
+ "AltShift" |
+ "Ctrl" |
+ "CtrlShift" |
+ "CtrlAlt" => {
+ let key = parse_key(inner).map_err(|_| ())?;
+ Ok(match variant {
+ "Key" => Key (key),
+ "Shift" => Shift (key),
+ "Alt" => Alt (key),
+ "AltShift" => AltShift (key),
+ "Ctrl" => Ctrl (key),
+ "CtrlShift" => CtrlShift(key),
+ "CtrlAlt" => CtrlAlt (key),
+ _ => unreachable!()
+ })
+ },
+ _ => Err(())
+ }
}
pub fn stringify_event(event: &Event) -> Cow<str> {
- match *event {
- Event::WindowResize => Cow::from("WindowResize"),
- Event::Refresh => Cow::from("Refresh"),
- Event::Exit => Cow::from("Exit"),
- Event::Char (c) => Cow::from(format!("Char-{}", c)),
- Event::CtrlChar (c) => Cow::from(format!("CtrlChar-{}", c)),
- Event::AltChar (c) => Cow::from(format!("AltChar-{}", c)),
- Event::Key (ref key) => Cow::from(format!("Key-{}", stringify_key(key))),
- Event::Shift (ref key) => Cow::from(format!("Shift-{}", stringify_key(key))),
- Event::Alt (ref key) => Cow::from(format!("Alt-{}", stringify_key(key))),
- Event::AltShift (ref key) => Cow::from(format!("AltShift-{}", stringify_key(key))),
- Event::Ctrl (ref key) => Cow::from(format!("Ctrl-{}", stringify_key(key))),
- Event::CtrlShift(ref key) => Cow::from(format!("CtrlShift-{}", stringify_key(key))),
- Event::CtrlAlt (ref key) => Cow::from(format!("CtrlAlt-{}", stringify_key(key))),
- Event::Unknown(ref bytes) => Cow::from(format!("{:?}", bytes)),
- Event::Mouse { .. } => unimplemented!()
- }
+ match *event {
+ Event::WindowResize => Cow::from("WindowResize"),
+ Event::Refresh => Cow::from("Refresh"),
+ Event::Exit => Cow::from("Exit"),
+ Event::Char (c) => Cow::from(format!("Char-{}", c)),
+ Event::CtrlChar (c) => Cow::from(format!("CtrlChar-{}", c)),
+ Event::AltChar (c) => Cow::from(format!("AltChar-{}", c)),
+ Event::Key (ref key) => Cow::from(format!("Key-{}", stringify_key(key))),
+ Event::Shift (ref key) => Cow::from(format!("Shift-{}", stringify_key(key))),
+ Event::Alt (ref key) => Cow::from(format!("Alt-{}", stringify_key(key))),
+ Event::AltShift (ref key) => Cow::from(format!("AltShift-{}", stringify_key(key))),
+ Event::Ctrl (ref key) => Cow::from(format!("Ctrl-{}", stringify_key(key))),
+ Event::CtrlShift(ref key) => Cow::from(format!("CtrlShift-{}", stringify_key(key))),
+ Event::CtrlAlt (ref key) => Cow::from(format!("CtrlAlt-{}", stringify_key(key))),
+ Event::Unknown(ref bytes) => Cow::from(format!("{:?}", bytes)),
+ Event::Mouse { .. } => unimplemented!()
+ }
}
fn parse_key(str: &str) -> Result<Key, ()> {
- use cursive::event::Key::*;
+ use cursive::event::Key::*;
- Ok(match str {
- "Enter" => Enter,
- "Tab" => Tab,
- "Backspace" => Backspace,
- "Esc" => Esc,
- "Left" => Left,
- "Right" => Right,
- "Up" => Up,
- "Down" => Down,
- "Ins" => Ins,
- "Del" => Del,
- "Home" => Home,
- "End" => End,
- "PageUp" => PageUp,
- "PageDown" => PageDown,
- "PauseBreak" => PauseBreak,
- "NumpadCenter" => NumpadCenter,
- "F0" => F0,
- "F1" => F1,
- "F2" => F2,
- "F3" => F3,
- "F4" => F4,
- "F5" => F5,
- "F6" => F6,
- "F7" => F7,
- "F8" => F8,
- "F9" => F9,
- "F10" => F10,
- "F11" => F11,
- "F12" => F12,
- _ => return Err(())
- })
+ Ok(match str {
+ "Enter" => Enter,
+ "Tab" => Tab,
+ "Backspace" => Backspace,
+ "Esc" => Esc,
+ "Left" => Left,
+ "Right" => Right,
+ "Up" => Up,
+ "Down" => Down,
+ "Ins" => Ins,
+ "Del" => Del,
+ "Home" => Home,
+ "End" => End,
+ "PageUp" => PageUp,
+ "PageDown" => PageDown,
+ "PauseBreak" => PauseBreak,
+ "NumpadCenter" => NumpadCenter,
+ "F0" => F0,
+ "F1" => F1,
+ "F2" => F2,
+ "F3" => F3,
+ "F4" => F4,
+ "F5" => F5,
+ "F6" => F6,
+ "F7" => F7,
+ "F8" => F8,
+ "F9" => F9,
+ "F10" => F10,
+ "F11" => F11,
+ "F12" => F12,
+ _ => return Err(())
+ })
}
pub fn stringify_key(key: &Key) -> &'static str {
- match *key {
- Key::Enter => "Enter",
- Key::Tab => "Tab",
- Key::Backspace => "Backspace",
- Key::Esc => "Esc",
- Key::Left => "Left",
- Key::Right => "Right",
- Key::Up => "Up",
- Key::Down => "Down",
- Key::Ins => "Ins",
- Key::Del => "Del",
- Key::Home => "Home",
- Key::End => "End",
- Key::PageUp => "PageUp",
- Key::PageDown => "PageDown",
- Key::PauseBreak => "PauseBreak",
- Key::NumpadCenter => "NumpadCenter",
- Key::F0 => "F0",
- Key::F1 => "F1",
- Key::F2 => "F2",
- Key::F3 => "F3",
- Key::F4 => "F4",
- Key::F5 => "F5",
- Key::F6 => "F6",
- Key::F7 => "F7",
- Key::F8 => "F8",
- Key::F9 => "F9",
- Key::F10 => "F10",
- Key::F11 => "F11",
- Key::F12 => "F12"
- }
+ match *key {
+ Key::Enter => "Enter",
+ Key::Tab => "Tab",
+ Key::Backspace => "Backspace",
+ Key::Esc => "Esc",
+ Key::Left => "Left",
+ Key::Right => "Right",
+ Key::Up => "Up",
+ Key::Down => "Down",
+ Key::Ins => "Ins",
+ Key::Del => "Del",
+ Key::Home => "Home",
+ Key::End => "End",
+ Key::PageUp => "PageUp",
+ Key::PageDown => "PageDown",
+ Key::PauseBreak => "PauseBreak",
+ Key::NumpadCenter => "NumpadCenter",
+ Key::F0 => "F0",
+ Key::F1 => "F1",
+ Key::F2 => "F2",
+ Key::F3 => "F3",
+ Key::F4 => "F4",
+ Key::F5 => "F5",
+ Key::F6 => "F6",
+ Key::F7 => "F7",
+ Key::F8 => "F8",
+ Key::F9 => "F9",
+ Key::F10 => "F10",
+ Key::F11 => "F11",
+ Key::F12 => "F12"
+ }
}
M cursive/src/highlight.rs +26 -26
@@ 1,47 1,47 @@
use cursive::{
- utils::markup::StyledIndexedSpan,
- theme::{
- Style,
- Effect
- }
+ utils::markup::StyledIndexedSpan,
+ theme::{
+ Style,
+ Effect
+ }
};
pub trait Highlighting {
- fn start(&self) -> Box<dyn Highlight>;
+ fn start(&self) -> Box<dyn Highlight>;
- fn cursor(&self, style: &mut Style) {
- style.effects.insert(Effect::Reverse);
- }
+ fn cursor(&self, style: &mut Style) {
+ style.effects.insert(Effect::Reverse);
+ }
- fn cursor_line(&self, _: &mut Style) {}
+ fn cursor_line(&self, _: &mut Style) {}
}
/// **Stateful**: highlights lines based on its state.
pub trait Highlight {
- /// Highlight the next line.
- fn highlight(&mut self, line: &str) -> Vec<StyledIndexedSpan>;
+ /// Highlight the next line.
+ fn highlight(&mut self, line: &str) -> Vec<StyledIndexedSpan>;
- /// Only parses the next line to advance internal state.
- /// This is equivalent to `highlight()` without output.
- fn parse(&mut self, line: &str);
+ /// Only parses the next line to advance internal state.
+ /// This is equivalent to `highlight()` without output.
+ fn parse(&mut self, line: &str);
- fn clone_dyn(&self) -> Box<dyn Highlight>;
+ fn clone_dyn(&self) -> Box<dyn Highlight>;
}
impl Highlighting for () {
- fn start(&self) -> Box<dyn Highlight> {
- Box::new(())
- }
+ fn start(&self) -> Box<dyn Highlight> {
+ Box::new(())
+ }
}
impl Highlight for () {
- fn highlight(&mut self, line: &str) -> Vec<StyledIndexedSpan> {
- vec![StyledIndexedSpan::simple(line, Style::none())]
- }
+ fn highlight(&mut self, line: &str) -> Vec<StyledIndexedSpan> {
+ vec![StyledIndexedSpan::simple(line, Style::none())]
+ }
- fn parse(&mut self, _: &str) {}
+ fn parse(&mut self, _: &str) {}
- fn clone_dyn(&self) -> Box<dyn Highlight> {
- Box::new(())
- }
+ fn clone_dyn(&self) -> Box<dyn Highlight> {
+ Box::new(())
+ }
}
M cursive/src/keys.rs +53 -53
@@ 1,82 1,82 @@
use cursive::event::{
- Event,
- Key
+ Event,
+ Key
};
/// Cursive does not parse escape sequences, so for example,
/// `AltChar(':')` is never produced. Instead we receive a
/// sequence of `Key(Esc)` followed by `Char(':')`.
pub fn parse_esc_seq() -> impl FnMut(&Event) -> Option<Event> {
- let mut esc = false;
+ let mut esc = false;
- move |event| {
- let result = if esc {
- match event {
- &Event::Char(char) => Some(Event::AltChar(char)),
- &Event::Key(Key::Esc) => Some(Event::Key(Key::Esc)),
- &Event::Key(key) => Some(Event::Alt(key)),
- &Event::Shift(key) => Some(Event::AltShift(key)),
- _ => None
- }
- } else {
- None
- };
+ move |event| {
+ let result = if esc {
+ match event {
+ &Event::Char(char) => Some(Event::AltChar(char)),
+ &Event::Key(Key::Esc) => Some(Event::Key(Key::Esc)),
+ &Event::Key(key) => Some(Event::Alt(key)),
+ &Event::Shift(key) => Some(Event::AltShift(key)),
+ _ => None
+ }
+ } else {
+ None
+ };
- esc = !esc && *event == Event::Key(Key::Esc);
+ esc = !esc && *event == Event::Key(Key::Esc);
- result
- }
+ result
+ }
}
pub fn translate() -> impl FnMut(Event) -> Option<Event> {
- let mut esc = parse_esc_seq();
+ let mut esc = parse_esc_seq();
- move |event| match esc(&event) {
- Some(event) => Some(event),
- // ignore if this is the first esc (escape esc itself)
- None if event == Event::Key(Key::Esc) => None,
- None => Some(match event {
- Event::Unknown(ref bytes) if *bytes == [0; 4] => Event::CtrlChar(' '),
- _ => event
- })
- }
+ move |event| match esc(&event) {
+ Some(event) => Some(event),
+ // ignore if this is the first esc (escape esc itself)
+ None if event == Event::Key(Key::Esc) => None,
+ None => Some(match event {
+ Event::Unknown(ref bytes) if *bytes == [0; 4] => Event::CtrlChar(' '),
+ _ => event
+ })
+ }
}
#[cfg(test)]
mod tests {
- use cursive::event::{Event, Key};
+ use cursive::event::{Event, Key};
- #[test]
- fn parse_esc_seq() {
- let mut esc = super::parse_esc_seq();
+ #[test]
+ fn parse_esc_seq() {
+ let mut esc = super::parse_esc_seq();
- assert_eq!(esc(&Event::Key(Key::Esc)), None);
- assert_eq!(esc(&Event::Char(':')), Some(Event::AltChar(':')));
+ assert_eq!(esc(&Event::Key(Key::Esc)), None);
+ assert_eq!(esc(&Event::Char(':')), Some(Event::AltChar(':')));
- assert_eq!(esc(&Event::Key(Key::Esc)), None);
- assert_eq!(esc(&Event::Key(Key::Backspace)), Some(Event::Alt(Key::Backspace)));
+ assert_eq!(esc(&Event::Key(Key::Esc)), None);
+ assert_eq!(esc(&Event::Key(Key::Backspace)), Some(Event::Alt(Key::Backspace)));
- assert_eq!(esc(&Event::Key(Key::Esc)), None);
- assert_eq!(esc(&Event::Shift(Key::Backspace)), Some(Event::AltShift(Key::Backspace)));
+ assert_eq!(esc(&Event::Key(Key::Esc)), None);
+ assert_eq!(esc(&Event::Shift(Key::Backspace)), Some(Event::AltShift(Key::Backspace)));
- assert_eq!(esc(&Event::Key(Key::Esc)), None);
- assert_eq!(esc(&Event::Key(Key::Esc)), Some(Event::Key(Key::Esc)));
- assert_eq!(esc(&Event::Char('a')), None);
+ assert_eq!(esc(&Event::Key(Key::Esc)), None);
+ assert_eq!(esc(&Event::Key(Key::Esc)), Some(Event::Key(Key::Esc)));
+ assert_eq!(esc(&Event::Char('a')), None);
- assert_eq!(esc(&Event::Char('a')), None);
- assert_eq!(esc(&Event::Char('a')), None);
- }
+ assert_eq!(esc(&Event::Char('a')), None);
+ assert_eq!(esc(&Event::Char('a')), None);
+ }
- #[test]
- fn translate() {
- let mut trans = super::translate();
+ #[test]
+ fn translate() {
+ let mut trans = super::translate();
- assert_eq!(trans(Event::Key(Key::Esc)), None);
- assert_eq!(trans(Event::Key(Key::Esc)), Some(Event::Key(Key::Esc)));
+ assert_eq!(trans(Event::Key(Key::Esc)), None);
+ assert_eq!(trans(Event::Key(Key::Esc)), Some(Event::Key(Key::Esc)));
- assert_eq!(trans(Event::Key(Key::Esc)), None);
- assert_eq!(trans(Event::Char('a')), Some(Event::AltChar('a')));
+ assert_eq!(trans(Event::Key(Key::Esc)), None);
+ assert_eq!(trans(Event::Char('a')), Some(Event::AltChar('a')));
- assert_eq!(trans(Event::Unknown(vec![0; 4])), Some(Event::CtrlChar(' ')));
- }
+ assert_eq!(trans(Event::Unknown(vec![0; 4])), Some(Event::CtrlChar(' ')));
+ }
}
M cursive/src/lib.rs +4 -4
@@ 14,11 14,11 @@ pub mod plugin;
/// TODO remove and use the `range_contains` feature once stable
pub(crate) trait RangeContains<Idx: PartialOrd<Idx>> {
- fn contains(&self, item: Idx) -> bool;
+ fn contains(&self, item: Idx) -> bool;
}
impl<Idx: PartialOrd<Idx>> RangeContains<Idx> for std::ops::Range<Idx> {
- fn contains(&self, item: Idx) -> bool {
- self.start <= item && item < self.end
- }
+ fn contains(&self, item: Idx) -> bool {
+ self.start <= item && item < self.end
+ }
}
M cursive/src/main.rs +121 -121
@@ 6,149 6,149 @@ extern crate env_logger;
#[macro_use] extern crate frappe;
use ted_tui::{
- bindings,
- cursive::{
- self,
- Cursive,
- event::Event,
- views::{
- Canvas,
- LinearLayout
- },
- theme::{
- Style,
- ColorStyle
- }
- },
- views::{
- self,
- EditorView,
- LineNumbers
- },
- ted::{
- buffer::Buffer,
- trie::SequenceTrie
- },
- plugin::Registry
+ bindings,
+ cursive::{
+ self,
+ Cursive,
+ event::Event,
+ views::{
+ Canvas,
+ LinearLayout
+ },
+ theme::{
+ Style,
+ ColorStyle
+ }
+ },
+ views::{
+ self,
+ EditorView,
+ LineNumbers
+ },
+ ted::{
+ buffer::Buffer,
+ trie::SequenceTrie
+ },
+ plugin::Registry
};
const THEME_DEFAULT: &str = concat!(
- "shadow = false", '\n',
- "borders = \"none\"", '\n',
- "[colors]", '\n',
- "background = \"default\"", '\n',
- "view = \"default\"", '\n',
- "primary = \"default\""
+ "shadow = false", '\n',
+ "borders = \"none\"", '\n',
+ "[colors]", '\n',
+ "background = \"default\"", '\n',
+ "view = \"default\"", '\n',
+ "primary = \"default\""
);
fn main() {
- env_logger::init();
+ env_logger::init();
- // load user config
- let config = dirs::config_dir()
- .and_then(|mut path| {
- path.push("ted");
- path.push("tui.over");
- let path = path.to_str()?;
+ // load user config
+ let config = dirs::config_dir()
+ .and_then(|mut path| {
+ path.push("ted");
+ path.push("tui.over");
+ let path = path.to_str()?;
- info!("Loading config from {}", path);
- over::Obj::from_file(path).ok()
- })
- .unwrap_or_else(|| obj!{});
- debug!("Configured: {}", config);
+ info!("Loading config from {}", path);
+ over::Obj::from_file(path).ok()
+ })
+ .unwrap_or_else(|| obj!{});
+ debug!("Configured: {}", config);
- let mut plugins = Registry::new();
- if let Ok(paths) = config.get_arr("plugins") {
- for path in paths.iter() {
- if let over::value::Value::Str(ref path) = path {
- plugins.load(path)
- .expect(&format!("failed to load plugin: {}", path));
- }
- }
- }
+ let mut plugins = Registry::new();
+ if let Ok(paths) = config.get_arr("plugins") {
+ for path in paths.iter() {
+ if let over::value::Value::Str(ref path) = path {
+ plugins.load(path)
+ .expect(&format!("failed to load plugin: {}", path));
+ }
+ }
+ }
- let mut siv = Cursive::default();
- siv.load_toml(THEME_DEFAULT).expect("failed to load theme");
- siv.add_global_callback(Event::CtrlChar('q'), |siv| siv.quit());
+ let mut siv = Cursive::default();
+ siv.load_toml(THEME_DEFAULT).expect("failed to load theme");
+ siv.add_global_callback(Event::CtrlChar('q'), |siv| siv.quit());
- let text = "";
+ let text = "";
- let mut keymap = SequenceTrie::new();
- let editor = {
- let mut editor = EditorView::new()
- .with_buffer(Buffer::from_str(text))
- .with_indent(|sink| if let Ok(indent) = config.get_str("indent") { sink.send(indent); })
- .with_bindings(|trie, props| {
- // set defaults
- for (path, cmd) in bindings::editor::defaults(props).iter() {
- trie.insert(path, (*cmd).clone());
- }
+ let mut keymap = SequenceTrie::new();
+ let editor = {
+ let mut editor = EditorView::new()
+ .with_buffer(Buffer::from_str(text))
+ .with_indent(|sink| if let Ok(indent) = config.get_str("indent") { sink.send(indent); })
+ .with_bindings(|trie, props| {
+ // set defaults
+ for (path, cmd) in bindings::editor::defaults(props).iter() {
+ trie.insert(path, (*cmd).clone());
+ }
- // overwrite with custom bindings
- if let Ok(bindings) = config.get_arr("bindings") {
- for (path, cmd) in bindings::editor::parse_over(&bindings, props)
- .expect("invalid key bindings").iter()
- {
- trie.insert(path.clone(), (*cmd).clone());
- }
- }
+ // overwrite with custom bindings
+ if let Ok(bindings) = config.get_arr("bindings") {
+ for (path, cmd) in bindings::editor::parse_over(&bindings, props)
+ .expect("invalid key bindings").iter()
+ {
+ trie.insert(path.clone(), (*cmd).clone());
+ }
+ }
- // make a copy for the keymap visualization
- for (path, cmd) in trie.iter() {
- keymap.insert(path, (*cmd).clone());
- }
- });
+ // make a copy for the keymap visualization
+ for (path, cmd) in trie.iter() {
+ keymap.insert(path, (*cmd).clone());
+ }
+ });
- if let Some((highlighting, theme)) = plugins.highlighting(text, Some(siv.current_theme().clone())) {
- editor = editor.with_highlighting(highlighting);
- siv.set_theme(theme);
- }
- editor.build()
- };
+ if let Some((highlighting, theme)) = plugins.highlighting(text, Some(siv.current_theme().clone())) {
+ editor = editor.with_highlighting(highlighting);
+ siv.set_theme(theme);
+ }
+ editor.build()
+ };
- siv.add_fullscreen_layer(views::translate_keys(
- views::visualize_keymap(
- layout_default(
- editor,
- ColorStyle::tertiary()
- ),
- keymap
- )
- ));
+ siv.add_fullscreen_layer(views::translate_keys(
+ views::visualize_keymap(
+ layout_default(
+ editor,
+ ColorStyle::tertiary()
+ ),
+ keymap
+ )
+ ));
- siv.run();
+ siv.run();
}
fn layout_default<S>(editor: EditorView, gutter_style: S) -> Canvas<LinearLayout>
where S: 'static + Into<Style> {
- let line_numbers = LineNumbers::new(
- {
- let props = editor.props();
- signal_lift!(
- props.start_line .clone(),
- props.view_height.clone(),
- props.len_lines .clone()
- => |start_line, view_height, len_lines|
- *start_line + 1..
- (*start_line + *view_height).min(*len_lines) + 1
- )
- },
- gutter_style
- );
+ let line_numbers = LineNumbers::new(
+ {
+ let props = editor.props();
+ signal_lift!(
+ props.start_line .clone(),
+ props.view_height.clone(),
+ props.len_lines .clone()
+ => |start_line, view_height, len_lines|
+ *start_line + 1..
+ (*start_line + *view_height).min(*len_lines) + 1
+ )
+ },
+ gutter_style
+ );
- Canvas::wrap(
- LinearLayout::horizontal()
- .child(line_numbers)
- .child(editor)
- ).with_layout(|layout, size| {
- use cursive::traits::View;
+ Canvas::wrap(
+ LinearLayout::horizontal()
+ .child(line_numbers)
+ .child(editor)
+ ).with_layout(|layout, size| {
+ use cursive::traits::View;
- /* We need to make sure the editor
- * is layed out first as the line
- * numbers adapt to it. */
- layout.get_child_mut(1).unwrap()
- .layout(size);
+ /* We need to make sure the editor
+ * is layed out first as the line
+ * numbers adapt to it. */
+ layout.get_child_mut(1).unwrap()
+ .layout(size);
- layout.layout(size);
- })
+ layout.layout(size);
+ })
}
M cursive/src/plugin.rs +3 -3
@@ 1,8 1,8 @@
use super::{
- highlight::Highlighting,
- cursive::theme::Theme
+ highlight::Highlighting,
+ cursive::theme::Theme
};
plugin_registry! {
- fn highlighting(text: &str, theme: Option<Theme>) -> (Box<dyn Highlighting>, Theme);
+ fn highlighting(text: &str, theme: Option<Theme>) -> (Box<dyn Highlighting>, Theme);
}
M cursive/src/views/editor.rs +773 -773
@@ 1,204 1,204 @@
use super::unicode_width::UnicodeWidthStr;
use {
- frappe::{
- Sink,
- Stream,
- Signal
- },
- std::{
- fmt,
- ops::Range,
- cmp::{
- Ord,
- Ordering
- },
- time::{
- Instant,
- Duration
- },
- collections::{
- BTreeMap,
- BTreeSet,
- BinaryHeap
- }
- },
- cursive::{
- Printer,
- view::{
- View,
- ScrollBase
- },
- event::{
- Event,
- EventResult,
- MouseButton,
- MouseEvent
- },
- theme::{
- Style,
- ColorStyle
- },
- vec::Vec2,
- direction::Direction,
- utils::markup::StyledIndexedSpan
- },
- ted::{
- buffer::{
- self,
- Buffer
- },
- cursor::{
- self,
- Cursor
- },
- history::{
- self,
- History
- },
- cache::{
- RangeCache,
- CharCache
- },
- trie::{
- SequenceTrie,
- TrieState
- },
- command::CursorCmd
- },
- highlight::{
- Highlighting,
- Highlight
- }
+ frappe::{
+ Sink,
+ Stream,
+ Signal
+ },
+ std::{
+ fmt,
+ ops::Range,
+ cmp::{
+ Ord,
+ Ordering
+ },
+ time::{
+ Instant,
+ Duration
+ },
+ collections::{
+ BTreeMap,
+ BTreeSet,
+ BinaryHeap
+ }
+ },
+ cursive::{
+ Printer,
+ view::{
+ View,
+ ScrollBase
+ },
+ event::{
+ Event,
+ EventResult,
+ MouseButton,
+ MouseEvent
+ },
+ theme::{
+ Style,
+ ColorStyle
+ },
+ vec::Vec2,
+ direction::Direction,
+ utils::markup::StyledIndexedSpan
+ },
+ ted::{
+ buffer::{
+ self,
+ Buffer
+ },
+ cursor::{
+ self,
+ Cursor
+ },
+ history::{
+ self,
+ History
+ },
+ cache::{
+ RangeCache,
+ CharCache
+ },
+ trie::{
+ SequenceTrie,
+ TrieState
+ },
+ command::CursorCmd
+ },
+ highlight::{
+ Highlighting,
+ Highlight
+ }
};
/// An open buffer with a cursor and history.
pub struct EditorView {
- buf: Buffer,
- cur: Cursor,
- keymap: KeyMapper,
- highlighting: Box<dyn Highlighting>,
- props: Properties,
- hist: History<Transaction>,
+ buf: Buffer,
+ cur: Cursor,
+ keymap: KeyMapper,
+ highlighting: Box<dyn Highlighting>,
+ props: Properties,
+ hist: History<Transaction>,
- // view stuff
- scrollbase: ScrollBase,
- focus_scroll: bool,
- scroll_x: usize,
- size: Vec2,
- view_width: usize,
- click: (Instant, u8),
+ // view stuff
+ scrollbase: ScrollBase,
+ focus_scroll: bool,
+ scroll_x: usize,
+ size: Vec2,
+ view_width: usize,
+ click: (Instant, u8),
- // rendering
- lines: BTreeMap<usize, String>,
- spans: BTreeMap<usize, Vec<StyledIndexedSpan>>,
- highlight_heap: BinaryHeap<LineHighlightState>,
+ // rendering
+ lines: BTreeMap<usize, String>,
+ spans: BTreeMap<usize, Vec<StyledIndexedSpan>>,
+ highlight_heap: BinaryHeap<LineHighlightState>,
- lines_to_highlight: BTreeSet<usize>,
- /// Draw this instead of `spans` if a cursor is on the line.
- spans_highlight: BTreeMap<usize, Vec<StyledIndexedSpan>>,
- /// To draw the empty space after the line.
- line_highlight: Style,
- /// To borrow a `str` of only spaces for drawing behind the line.
- space: CharCache,
+ lines_to_highlight: BTreeSet<usize>,
+ /// Draw this instead of `spans` if a cursor is on the line.
+ spans_highlight: BTreeMap<usize, Vec<StyledIndexedSpan>>,
+ /// To draw the empty space after the line.
+ line_highlight: Style,
+ /// To borrow a `str` of only spaces for drawing behind the line.
+ space: CharCache,
- /// (line_idx, x_range)
- selections: Vec<(usize, Range<usize>)>,
- /// (line_idx, x, style)
- cursors: Vec<(usize, usize, Style)>
+ /// (line_idx, x_range)
+ selections: Vec<(usize, Range<usize>)>,
+ /// (line_idx, x, style)
+ cursors: Vec<(usize, usize, Style)>
}
type KeyMapper = TrieState<Event, Cmd>;
struct LineHighlightState {
- line_idx: usize,
- highlight: Box<dyn Highlight>
+ line_idx: usize,
+ highlight: Box<dyn Highlight>
}
impl PartialEq for LineHighlightState {
- fn eq(&self, other: &Self) -> bool {
- self.line_idx == other.line_idx
- }
+ fn eq(&self, other: &Self) -> bool {
+ self.line_idx == other.line_idx
+ }
}
impl Eq for LineHighlightState {}
impl PartialOrd for LineHighlightState {
- fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- self.line_idx.partial_cmp(&other.line_idx)
- }
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+ self.line_idx.partial_cmp(&other.line_idx)
+ }
}
impl Ord for LineHighlightState {
- fn cmp(&self, other: &Self) -> Ordering {
- self.line_idx.cmp(&other.line_idx)
- }
+ fn cmp(&self, other: &Self) -> Ordering {
+ self.line_idx.cmp(&other.line_idx)
+ }
}
#[derive(Clone)]
pub enum Cmd {
- Cur(CursorCmd),
- Hist(HistoryCmd)
+ Cur(CursorCmd),
+ Hist(HistoryCmd)
}
#[derive(Clone)]
pub enum HistoryCmd {
- Undo, Redo
+ Undo, Redo
}
impl fmt::Display for Cmd {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match *self {
- Cmd::Cur (ref cmd) => cmd.fmt(f),
- Cmd::Hist(ref cmd) => cmd.fmt(f)
- }
- }
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Cmd::Cur (ref cmd) => cmd.fmt(f),
+ Cmd::Hist(ref cmd) => cmd.fmt(f)
+ }
+ }
}
impl fmt::Display for HistoryCmd {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "{}", match *self {
- HistoryCmd::Undo => "Undo",
- HistoryCmd::Redo => "Redo"
- })
- }
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", match *self {
+ HistoryCmd::Undo => "Undo",
+ HistoryCmd::Redo => "Redo"
+ })
+ }
}
#[derive(Clone)]
pub struct Properties {
- view_height_sink: Sink<usize>,
- start_line_sink: Sink<usize>,
- len_lines_sink: Sink<usize>,
- /// You can set the indent manually with this.
- pub indent_sink: Sink<String>,
+ view_height_sink: Sink<usize>,
+ start_line_sink: Sink<usize>,
+ len_lines_sink: Sink<usize>,
+ /// You can set the indent manually with this.
+ pub indent_sink: Sink<String>,
- /// For page up/down on the keymap.
- pub view_height: Signal<usize>,
- /// To update line numbers.
- pub start_line: Signal<usize>,
- /// To update line numbers.
- pub len_lines: Signal<usize>,
- pub indent: Signal<String>
+ /// For page up/down on the keymap.
+ pub view_height: Signal<usize>,
+ /// To update line numbers.
+ pub start_line: Signal<usize>,
+ /// To update line numbers.
+ pub len_lines: Signal<usize>,
+ pub indent: Signal<String>
}
impl Properties {
- fn new(indent: String) -> Self {
- let view_height_sink = Sink::new();
- let start_line_sink = Sink::new();
- let len_lines_sink = Sink::new();
- let indent_sink = Sink::new();
+ fn new(indent: String) -> Self {
+ let view_height_sink = Sink::new();
+ let start_line_sink = Sink::new();
+ let len_lines_sink = Sink::new();
+ let indent_sink = Sink::new();
- Self {
- view_height: view_height_sink.stream().hold(0),
- start_line: start_line_sink .stream().hold(0),
- len_lines: len_lines_sink .stream().hold(0),
- indent: indent_sink .stream().hold(indent),
+ Self {
+ view_height: view_height_sink.stream().hold(0),
+ start_line: start_line_sink .stream().hold(0),
+ len_lines: len_lines_sink .stream().hold(0),
+ indent: indent_sink .stream().hold(indent),
- view_height_sink: view_height_sink,
- start_line_sink: start_line_sink,
- len_lines_sink: len_lines_sink,
- indent_sink: indent_sink
- }
- }
+ view_height_sink: view_height_sink,
+ start_line_sink: start_line_sink,
+ len_lines_sink: len_lines_sink,
+ indent_sink: indent_sink
+ }
+ }
}
/// A transaction consist of one or more buffer events ("edits")
@@ 208,727 208,727 @@ struct Transaction(Box<[buffer::FiringEv
/// Builder for an `EditorView`.
pub struct Builder {
- props: Properties,
- bindings: SequenceTrie<Event, Cmd>,
+ props: Properties,
+ bindings: SequenceTrie<Event, Cmd>,
- buf: Option<Buffer>,
- highlighting: Box<dyn Highlighting>
+ buf: Option<Buffer>,
+ highlighting: Box<dyn Highlighting>
}
impl Builder {
- fn new() -> Self {
- Self {
- props: Properties::new(" ".to_owned()),
- bindings: SequenceTrie::new(),
+ fn new() -> Self {
+ Self {
+ props: Properties::new(" ".to_owned()),
+ bindings: SequenceTrie::new(),
- buf: None,
- highlighting: Box::new(())
- }
- }
+ buf: None,
+ highlighting: Box::new(())
+ }
+ }
- pub fn with_highlighting(mut self, highlighting: Box<dyn Highlighting>) -> Self {
- self.highlighting = highlighting;
- self
- }
+ pub fn with_highlighting(mut self, highlighting: Box<dyn Highlighting>) -> Self {
+ self.highlighting = highlighting;
+ self
+ }
- pub fn with_buffer(mut self, buf: Buffer) -> Self {
- self.buf = Some(buf);
- self
- }
+ pub fn with_buffer(mut self, buf: Buffer) -> Self {
+ self.buf = Some(buf);
+ self
+ }
- pub fn with_bindings<F>(mut self, f: F) -> Self
- where F: FnOnce(&mut SequenceTrie<Event, Cmd>, &Properties) {
- f(&mut self.bindings, &self.props);
- self
- }
+ pub fn with_bindings<F>(mut self, f: F) -> Self
+ where F: FnOnce(&mut SequenceTrie<Event, Cmd>, &Properties) {
+ f(&mut self.bindings, &self.props);
+ self
+ }
- pub fn with_indent<F>(self, f: F) -> Self
- where F: FnOnce(&Sink<String>) {
- f(&self.props.indent_sink);
- self
- }
+ pub fn with_indent<F>(self, f: F) -> Self
+ where F: FnOnce(&Sink<String>) {
+ f(&self.props.indent_sink);
+ self
+ }
- pub fn build(self) -> EditorView {
- let buf = self.buf.unwrap_or_else(|| Buffer::from_str(""));
+ pub fn build(self) -> EditorView {
+ let buf = self.buf.unwrap_or_else(|| Buffer::from_str(""));
- EditorView {
- cur: Cursor::new(&buf, &[(0, None)]),
- buf,
- keymap: KeyMapper::new(self.bindings),
- props: self.props,
- hist: History::new(),
+ EditorView {
+ cur: Cursor::new(&buf, &[(0, None)]),
+ buf,
+ keymap: KeyMapper::new(self.bindings),
+ props: self.props,
+ hist: History::new(),
- scrollbase: ScrollBase::new(),
- focus_scroll: true,
- scroll_x: 0,
- size: Vec2::zero(),
- view_width: 0,
- click: (Instant::now(), 0),
+ scrollbase: ScrollBase::new(),
+ focus_scroll: true,
+ scroll_x: 0,
+ size: Vec2::zero(),
+ view_width: 0,
+ click: (Instant::now(), 0),
- lines: BTreeMap::new(),
- spans: BTreeMap::new(),
- highlight_heap: BinaryHeap::new(),
- lines_to_highlight: BTreeSet::new(),
- line_highlight: {
- let mut style = ColorStyle::primary().into();
- self.highlighting.cursor_line(&mut style);
- style
- },
- spans_highlight: BTreeMap::new(),
- space: CharCache::new(' '),
- selections: Vec::new(),
- cursors: Vec::new(),
+ lines: BTreeMap::new(),
+ spans: BTreeMap::new(),
+ highlight_heap: BinaryHeap::new(),
+ lines_to_highlight: BTreeSet::new(),
+ line_highlight: {
+ let mut style = ColorStyle::primary().into();
+ self.highlighting.cursor_line(&mut style);
+ style
+ },
+ spans_highlight: BTreeMap::new(),
+ space: CharCache::new(' '),
+ selections: Vec::new(),
+ cursors: Vec::new(),
- highlighting: self.highlighting
- }
- }
+ highlighting: self.highlighting
+ }
+ }
}
impl EditorView {
- pub fn new() -> Builder {
- Builder::new()
- }
+ pub fn new() -> Builder {
+ Builder::new()
+ }
- pub fn props(&self) -> &Properties {
- &self.props
- }
+ pub fn props(&self) -> &Properties {
+ &self.props
+ }
- fn char_idx_at(&self, position: Vec2) -> usize {
- let text = self.buf.text();
- let last_line_idx = text.len_lines() - 1;
- let line_idx = (position.y + self.scrollbase.start_line)
- .min(last_line_idx);
- let line = text.line(line_idx);
- let line_len = line.len_chars();
- text.line_to_char(line_idx) + position.x.min(
- if line_len == 0 { 0 } else {
- line_len -
- if line_idx < last_line_idx { 1 } else { 0 }
- }
- )
- }
+ fn char_idx_at(&self, position: Vec2) -> usize {
+ let text = self.buf.text();
+ let last_line_idx = text.len_lines() - 1;
+ let line_idx = (position.y + self.scrollbase.start_line)
+ .min(last_line_idx);
+ let line = text.line(line_idx);
+ let line_len = line.len_chars();
+ text.line_to_char(line_idx) + position.x.min(
+ if line_len == 0 { 0 } else {
+ line_len -
+ if line_idx < last_line_idx { 1 } else { 0 }
+ }
+ )
+ }
}
impl From<Buffer> for EditorView {
- fn from(buf: Buffer) -> Self {
- Self::new()
- .with_buffer(buf)
- .build()
- }
+ fn from(buf: Buffer) -> Self {
+ Self::new()
+ .with_buffer(buf)
+ .build()
+ }
}
impl View for EditorView {
- fn take_focus(&mut self, _: Direction) -> bool {
- true
- }
+ fn take_focus(&mut self, _: Direction) -> bool {
+ true
+ }
- fn required_size(&mut self, constraint: Vec2) -> Vec2 {
- constraint
- }
+ fn required_size(&mut self, constraint: Vec2) -> Vec2 {
+ constraint
+ }
- fn layout(&mut self, size: Vec2) {
- self.size = size;
+ fn layout(&mut self, size: Vec2) {
+ self.size = size;
- /* Assume no wrapping so that each line has a height of 1
- * and the content height is simply the number of lines … */
- self.scrollbase.set_heights(size.y, self.buf.text().len_lines());
+ /* Assume no wrapping so that each line has a height of 1
+ * and the content height is simply the number of lines … */
+ self.scrollbase.set_heights(size.y, self.buf.text().len_lines());
- // Cache view width.
- let old_view_width = self.view_width;
- self.view_width = if !self.scrollbase.scrollable() { size.x } else {
- self.scrollbase.scrollbar_x(size.x)
- .saturating_sub(self.scrollbase.right_padding)
- };
+ // Cache view width.
+ let old_view_width = self.view_width;
+ self.view_width = if !self.scrollbase.scrollable() { size.x } else {
+ self.scrollbase.scrollbar_x(size.x)
+ .saturating_sub(self.scrollbase.right_padding)
+ };
- // Focus scroll now to render the correct window.
- if self.focus_scroll {
- self.focus_scroll = false;
+ // Focus scroll now to render the correct window.
+ if self.focus_scroll {
+ self.focus_scroll = false;
- // vertical
- for (char_idx, sel_start) in self.cur.positions() {
- /* We expect char indices to be in order.
- * This will show all cursors that fit,
- * excluding earlier ones if necessary. */
- if let Some(sel_start) = sel_start {
- self.scrollbase.scroll_to(self.buf.text().char_to_line(sel_start));
- }
- self.scrollbase.scroll_to(self.buf.text().char_to_line(char_idx));
- }
- }
- if self.focus_scroll || self.view_width < old_view_width {
- // horizontal
- self.scroll_x = self.cur.positions()
- .map(|(char_idx, _)| {
- let line_idx = self.buf.text().char_to_line(char_idx)
- .min(self.buf.text().len_lines().saturating_sub(1));
- char_idx - self.buf.text().line_to_char(line_idx)
- })
- .max()
- .map(|max_x| {
- /* Cursor char index is exclusive so one less is available
- * if we want the cursor to stay on screen. */
- let available_width = self.view_width.saturating_sub(1);
- if max_x > self.scroll_x + available_width {
- max_x - available_width
- } else if max_x < self.scroll_x {
- max_x
- } else {
- self.scroll_x
- }
- })
- .unwrap_or(0);
- }
+ // vertical
+ for (char_idx, sel_start) in self.cur.positions() {
+ /* We expect char indices to be in order.
+ * This will show all cursors that fit,
+ * excluding earlier ones if necessary. */
+ if let Some(sel_start) = sel_start {
+ self.scrollbase.scroll_to(self.buf.text().char_to_line(sel_start));
+ }
+ self.scrollbase.scroll_to(self.buf.text().char_to_line(char_idx));
+ }
+ }
+ if self.focus_scroll || self.view_width < old_view_width {
+ // horizontal
+ self.scroll_x = self.cur.positions()
+ .map(|(char_idx, _)| {
+ let line_idx = self.buf.text().char_to_line(char_idx)
+ .min(self.buf.text().len_lines().saturating_sub(1));
+ char_idx - self.buf.text().line_to_char(line_idx)
+ })
+ .max()
+ .map(|max_x| {
+ /* Cursor char index is exclusive so one less is available
+ * if we want the cursor to stay on screen. */
+ let available_width = self.view_width.saturating_sub(1);
+ if max_x > self.scroll_x + available_width {
+ max_x - available_width
+ } else if max_x < self.scroll_x {
+ max_x
+ } else {
+ self.scroll_x
+ }
+ })
+ .unwrap_or(0);
+ }
- // Compute the indices of lines we need to cache.
- let line_idc: Range<usize> =
- self.scrollbase.start_line..
- (self.scrollbase.start_line + self.scrollbase.view_height)
- .min(self.buf.text().len_lines());
+ // Compute the indices of lines we need to cache.
+ let line_idc: Range<usize> =
+ self.scrollbase.start_line..
+ (self.scrollbase.start_line + self.scrollbase.view_height)
+ .min(self.buf.text().len_lines());
- // Automatically allocate and cache the line strings.
- fn get_line<'a, 'b>(line_idx: usize, buf: &'b Buffer, lines: &'a mut BTreeMap<usize, String>) -> &'a str {
- if let None = lines.get(&line_idx) {
- lines.insert(line_idx,
- // allocate owned string
- buf.text().line(line_idx).to_string()
- );
- }
- lines.get(&line_idx).unwrap().as_str()
- }
+ // Automatically allocate and cache the line strings.
+ fn get_line<'a, 'b>(line_idx: usize, buf: &'b Buffer, lines: &'a mut BTreeMap<usize, String>) -> &'a str {
+ if let None = lines.get(&line_idx) {
+ lines.insert(line_idx,
+ // allocate owned string
+ buf.text().line(line_idx).to_string()
+ );
+ }
+ lines.get(&line_idx).unwrap().as_str()
+ }
- { // Cache the line styles.
- let mut current_highlight = None;
- for line_idx in line_idc.clone() {
- if let None = self.spans.get(&line_idx) {
- let mut highlight = current_highlight.take()
- .unwrap_or_else(|| {
- let (start_line_idx, mut highlight) = self.highlight_heap.peek()
- .map(|state| (state.line_idx + 1, state.highlight.clone_dyn()))
- .unwrap_or_else(|| (0, self.highlighting.start()));
+ { // Cache the line styles.
+ let mut current_highlight = None;
+ for line_idx in line_idc.clone() {
+ if let None = self.spans.get(&line_idx) {
+ let mut highlight = current_highlight.take()
+ .unwrap_or_else(|| {
+ let (start_line_idx, mut highlight) = self.highlight_heap.peek()
+ .map(|state| (state.line_idx + 1, state.highlight.clone_dyn()))
+ .unwrap_or_else(|| (0, self.highlighting.start()));
- for line_idx in start_line_idx..line_idx {
- highlight.parse(get_line(line_idx, &self.buf, &mut self.lines));
- }
+ for line_idx in start_line_idx..line_idx {
+ highlight.parse(get_line(line_idx, &self.buf, &mut self.lines));
+ }
- highlight
- });
+ highlight
+ });
- self.spans.insert(line_idx,
- // highlight syntax
- highlight.highlight(get_line(line_idx, &self.buf, &mut self.lines))
- );
+ self.spans.insert(line_idx,
+ // highlight syntax
+ highlight.highlight(get_line(line_idx, &self.buf, &mut self.lines))
+ );
- current_highlight = Some(highlight);
- }
- }
+ current_highlight = Some(highlight);
+ }
+ }
- if let Some(highlight) = current_highlight {
- self.highlight_heap.push(LineHighlightState {
- line_idx: line_idc.end - 1,
- highlight
- });
- }
- }
+ if let Some(highlight) = current_highlight {
+ self.highlight_heap.push(LineHighlightState {
+ line_idx: line_idc.end - 1,
+ highlight
+ });
+ }
+ }
- { // Cache the cursor line highlight.
- use super::super::RangeContains;
+ { // Cache the cursor line highlight.
+ use super::super::RangeContains;
- self.lines_to_highlight.clear();
+ self.lines_to_highlight.clear();
- let len_lines = self.buf.text().len_lines();
+ let len_lines = self.buf.text().len_lines();
- let mut selecting = false;
- for (char_idx, sel) in self.cur.positions() {
- let line_idx = self.buf.text().char_to_line(char_idx)
- .min(len_lines.saturating_sub(1));
+ let mut selecting = false;
+ for (char_idx, sel) in self.cur.positions() {
+ let line_idx = self.buf.text().char_to_line(char_idx)
+ .min(len_lines.saturating_sub(1));
- // figure out lines to highlight
- if RangeContains::contains(&line_idc, line_idx) {
- self.lines_to_highlight.insert(line_idx);
- }
+ // figure out lines to highlight
+ if RangeContains::contains(&line_idc, line_idx) {
+ self.lines_to_highlight.insert(line_idx);
+ }
- selecting |= sel.is_some();
- }
+ selecting |= sel.is_some();
+ }
- if !selecting {
- /* Grow the space cache if necessary.
- * We simply allocate enough for an empty line. */
- self.space.grow(self.view_width);
+ if !selecting {
+ /* Grow the space cache if necessary.
+ * We simply allocate enough for an empty line. */
+ self.space.grow(self.view_width);
- /* Shrink the space string to save memory.
- * Tolerate jitter so we don't have to grow again on every layout. */
- self.space.truncate_shrink_min(self.view_width, 15); // XXX what is sensible?
+ /* Shrink the space string to save memory.
+ * Tolerate jitter so we don't have to grow again on every layout. */
+ self.space.truncate_shrink_min(self.view_width, 15); // XXX what is sensible?
- // highlight lines
- for line_idx in self.lines_to_highlight.iter() {
- if let None = self.spans_highlight.get(line_idx) {
- let spans = self.spans.get(line_idx)
- .map(|spans| spans.iter()
- .cloned()
- .map(|mut span| {
- // highlight span
- self.highlighting.cursor_line(&mut span.attr);
- span
- })
- .collect())
- .unwrap_or_else(|| vec![]);
+ // highlight lines
+ for line_idx in self.lines_to_highlight.iter() {
+ if let None = self.spans_highlight.get(line_idx) {
+ let spans = self.spans.get(line_idx)
+ .map(|spans| spans.iter()
+ .cloned()
+ .map(|mut span| {
+ // highlight span
+ self.highlighting.cursor_line(&mut span.attr);
+ span
+ })
+ .collect())
+ .unwrap_or_else(|| vec![]);
- self.spans_highlight.insert(*line_idx, spans);
- }
- }
- } else {
- // Do not highlight cursor line when selecting.
- self.lines_to_highlight.clear()
- }
- }
+ self.spans_highlight.insert(*line_idx, spans);
+ }
+ }
+ } else {
+ // Do not highlight cursor line when selecting.
+ self.lines_to_highlight.clear()
+ }
+ }
- { // Cache selection.
- self.selections.clear();
+ { // Cache selection.
+ self.selections.clear();
- // Translate each selection to an
- // x range for each line it affects.
- for sel in self.cur.positions()
- .filter_map(|(end, start)| start.map(|start| cursor::select((start, end))))
- .filter(|s| s.start != s.end) // no need to render empty selections
- {
- let text = self.buf.text();
+ // Translate each selection to an
+ // x range for each line it affects.
+ for sel in self.cur.positions()
+ .filter_map(|(end, start)| start.map(|start| cursor::select((start, end))))
+ .filter(|s| s.start != s.end) // no need to render empty selections
+ {
+ let text = self.buf.text();
- let start_line = text.char_to_line(sel.start);
- let last_line = text.char_to_line(sel.end.saturating_sub(1));
+ let start_line = text.char_to_line(sel.start);
+ let last_line = text.char_to_line(sel.end.saturating_sub(1));
- for line_idx in start_line..=last_line {
- let line_start_char_idx = text.line_to_char(line_idx);
+ for line_idx in start_line..=last_line {
+ let line_start_char_idx = text.line_to_char(line_idx);
- let x_range =
- if line_idx != start_line {
- cursor::jump::line_start(text, line_start_char_idx)
- } else {
- sel.start
- }
- - line_start_char_idx
- ..
- if line_idx != last_line {
- cursor::jump::line_end(text, line_start_char_idx) + 1
- } else {
- sel.end
- }
- - line_start_char_idx;
+ let x_range =
+ if line_idx != start_line {
+ cursor::jump::line_start(text, line_start_char_idx)
+ } else {
+ sel.start
+ }
+ - line_start_char_idx
+ ..
+ if line_idx != last_line {
+ cursor::jump::line_end(text, line_start_char_idx) + 1
+ } else {
+ sel.end
+ }
+ - line_start_char_idx;
- self.selections.push((line_idx, x_range));
- }
- }
- }
+ self.selections.push((line_idx, x_range));
+ }
+ }
+ }
- { // Cache cursor.
- self.cursors.clear();
+ { // Cache cursor.
+ self.cursors.clear();
- if self.selections.is_empty() {
- for (char_idx, _) in self.cur.positions() {
- let line_idx = self.buf.text().char_to_line(char_idx)
- .min(self.buf.text().len_lines().saturating_sub(1));
+ if self.selections.is_empty() {
+ for (char_idx, _) in self.cur.positions() {
+ let line_idx = self.buf.text().char_to_line(char_idx)
+ .min(self.buf.text().len_lines().saturating_sub(1));
- // skip cursors off screen
- if line_idx < self.scrollbase.start_line ||
- line_idx > self.scrollbase.start_line + self.scrollbase.view_height
- { continue }
+ // skip cursors off screen
+ if line_idx < self.scrollbase.start_line ||
+ line_idx > self.scrollbase.start_line + self.scrollbase.view_height
+ { continue }
- let x = char_idx - self.buf.text().line_to_char(line_idx);
+ let x = char_idx - self.buf.text().line_to_char(line_idx);
- let mut style: Style = ColorStyle::primary().into(); // fallback
- // find style at span under cursor
- if let Some(spans) = self.spans.get(&line_idx) {
- let line = get_line(line_idx, &self.buf, &mut self.lines);
+ let mut style: Style = ColorStyle::primary().into(); // fallback
+ // find style at span under cursor
+ if let Some(spans) = self.spans.get(&line_idx) {
+ let line = get_line(line_idx, &self.buf, &mut self.lines);
- let mut x_acc = 0;
- for span in spans.iter() {
- x_acc += span.content.resolve(line).chars().count();
- if x_acc > x {
- style = span.attr.clone();
- break
- }
- }
- }
- // modify style by theme
- self.highlighting.cursor(&mut style);
+ let mut x_acc = 0;
+ for span in spans.iter() {
+ x_acc += span.content.resolve(line).chars().count();
+ if x_acc > x {
+ style = span.attr.clone();
+ break
+ }
+ }
+ }
+ // modify style by theme
+ self.highlighting.cursor(&mut style);
- self.cursors.push((line_idx, x, style));
- }
- }
- }
+ self.cursors.push((line_idx, x, style));
+ }
+ }
+ }
- // TODO wrap
+ // TODO wrap
- /* … then after wrapping is applied,
- * set the actual content height to
- * avoid rendering lines off screen. */
- // self.scrollbase.set_heights(self.size.y,
- // self.buf.text().len_lines() // TODO pending wrapping; sum line heights
- // );
+ /* … then after wrapping is applied,
+ * set the actual content height to
+ * avoid rendering lines off screen. */
+ // self.scrollbase.set_heights(self.size.y,
+ // self.buf.text().len_lines() // TODO pending wrapping; sum line heights
+ // );
- self.props.view_height_sink.send(self.scrollbase.view_height);
- self.props.start_line_sink .send(self.scrollbase.start_line);
- self.props.len_lines_sink .send(self.buf.text().len_lines());
- }
+ self.props.view_height_sink.send(self.scrollbase.view_height);
+ self.props.start_line_sink .send(self.scrollbase.start_line);
+ self.props.len_lines_sink .send(self.buf.text().len_lines());
+ }
- fn draw(&self, printer: &Printer) {
- // Print with x-scroll.
- let print = |printer: &Printer, pos: (usize, usize), text: &str| {
- if let Some(x) = pos.0.checked_sub(self.scroll_x) {
- printer.print((x, pos.1), &text[..
- text.char_indices()
- .nth(self.view_width.saturating_sub(x))
- .map(|(byte_idx, _)| byte_idx)
- .unwrap_or(text.len())
- ]);
- } else {
- /* We cannot draw starting before the left boundary,
- * so we'll have to clip the text and print at `(0, y)`. */
- let overhang = self.scroll_x - pos.0;
- printer.print((0, pos.1), &text[
- text.char_indices()
- .nth(overhang)
- .map(|(byte_idx,_)| byte_idx)
- .unwrap_or(text.len())
- ..
- text.char_indices()
- .nth(overhang + self.view_width)
- .map(|(byte_idx,_)| byte_idx)
- .unwrap_or(text.len())
- ]);
- }
- };
+ fn draw(&self, printer: &Printer) {
+ // Print with x-scroll.
+ let print = |printer: &Printer, pos: (usize, usize), text: &str| {
+ if let Some(x) = pos.0.checked_sub(self.scroll_x) {
+ printer.print((x, pos.1), &text[..
+ text.char_indices()
+ .nth(self.view_width.saturating_sub(x))
+ .map(|(byte_idx, _)| byte_idx)
+ .unwrap_or(text.len())
+ ]);
+ } else {
+ /* We cannot draw starting before the left boundary,
+ * so we'll have to clip the text and print at `(0, y)`. */
+ let overhang = self.scroll_x - pos.0;
+ printer.print((0, pos.1), &text[
+ text.char_indices()
+ .nth(overhang)
+ .map(|(byte_idx,_)| byte_idx)
+ .unwrap_or(text.len())
+ ..
+ text.char_indices()
+ .nth(overhang + self.view_width)
+ .map(|(byte_idx,_)| byte_idx)
+ .unwrap_or(text.len())
+ ]);
+ }
+ };
- /* Draw the styled text.
- * Since line highlighting always affects the whole line,
- * we can also do that here before rendering other stuff … */
- self.scrollbase.draw(printer, |printer, line_idx| {
- let line = self.lines.get(&line_idx).expect("line not allocated in draw()").as_str();
+ /* Draw the styled text.
+ * Since line highlighting always affects the whole line,
+ * we can also do that here before rendering other stuff … */
+ self.scrollbase.draw(printer, |printer, line_idx| {
+ let line = self.lines.get(&line_idx).expect("line not allocated in draw()").as_str();
- let highlight_line = self.lines_to_highlight.contains(&line_idx);
- let spans = if highlight_line {
- self.spans_highlight.get(&line_idx).expect("line not highlighted in draw()")
- } else {
- self.spans .get(&line_idx).expect("line not styled in draw()")
- };
+ let highlight_line = self.lines_to_highlight.contains(&line_idx);
+ let spans = if highlight_line {
+ self.spans_highlight.get(&line_idx).expect("line not highlighted in draw()")
+ } else {
+ self.spans .get(&line_idx).expect("line not styled in draw()")
+ };
- let mut x = 0;
- for span in spans.iter() {
- printer.with_style(span.attr, |printer| {
- let text = span.content.resolve(line);
+ let mut x = 0;
+ for span in spans.iter() {
+ printer.with_style(span.attr, |printer| {
+ let text = span.content.resolve(line);
- /* Trailing newlines must be skipped
- * since printing them doesn't allow
- * the view background to be drawn. */
- let text = if text.ends_with("\n") {
- &text[..text.len() - 1]
- } else {
- &text[..]
- };
+ /* Trailing newlines must be skipped
+ * since printing them doesn't allow
+ * the view background to be drawn. */
+ let text = if text.ends_with("\n") {
+ &text[..text.len() - 1]
+ } else {
+ &text[..]
+ };
- print(printer, (x, 0), text);
- x += text.width();
- });
- }
+ print(printer, (x, 0), text);
+ x += text.width();
+ });
+ }
- // Highlight the rest of the line with spaces.
- if highlight_line && x.saturating_sub(self.scroll_x) < self.view_width {
- let empty_x = self.view_width + self.scroll_x - x;
+ // Highlight the rest of the line with spaces.
+ if highlight_line && x.saturating_sub(self.scroll_x) < self.view_width {
+ let empty_x = self.view_width + self.scroll_x - x;
- printer.with_style(self.line_highlight, |printer|
- print(printer, (x, 0), &self.space.get(empty_x).unwrap())
- );
- };
- });
+ printer.with_style(self.line_highlight, |printer|
+ print(printer, (x, 0), &self.space.get(empty_x).unwrap())
+ );
+ };
+ });
- /* … that has to be drawn over specific spans of the text.
- * But now we have to take scroll offset into account. */
- // `pos`: (x, line_idx)
- let print_span = |printer: &Printer, pos: (usize, usize), text: &str| {
- let (x, line_idx) = pos;
+ /* … that has to be drawn over specific spans of the text.
+ * But now we have to take scroll offset into account. */
+ // `pos`: (x, line_idx)
+ let print_span = |printer: &Printer, pos: (usize, usize), text: &str| {
+ let (x, line_idx) = pos;
- // overflow here means the line is out of view
- if let Some(y) = line_idx.checked_sub(self.scrollbase.start_line) {
- print(printer, (x, y), text);
- }
- };
+ // overflow here means the line is out of view
+ if let Some(y) = line_idx.checked_sub(self.scrollbase.start_line) {
+ print(printer, (x, y), text);
+ }
+ };
- /* More specifically, we'll want to change the style
- * but not the text. We cannot draw a style directly
- * so we'll need to print the same text over. */
- // `span`: (x_range, line_idx)
- let print_span_text = |printer: &Printer, span: (&Range<usize>, usize)| {
- let (x_range, line_idx) = span;
- let line = self.lines.get(&line_idx)
- .map(|s| s.as_str())
- .unwrap_or(" ");
+ /* More specifically, we'll want to change the style
+ * but not the text. We cannot draw a style directly
+ * so we'll need to print the same text over. */
+ // `span`: (x_range, line_idx)
+ let print_span_text = |printer: &Printer, span: (&Range<usize>, usize)| {
+ let (x_range, line_idx) = span;
+ let line = self.lines.get(&line_idx)
+ .map(|s| s.as_str())
+ .unwrap_or(" ");
- let mut print_break = false;
- print_span(printer, (x_range.start, line_idx), {
- let text = &line[
- line.char_indices().nth(x_range.start)
- .map(|x| x.0)
- .unwrap_or(line.len())
- ..
- line.char_indices().nth(x_range.end)
- .map(|x| x.0)
- .unwrap_or(line.len())
- ];
- if text.ends_with("\n") {
- print_break = true;
- &text[..text.len() - 1]
- } else if text.is_empty() {
- print_break = true;
- text
- } else {
- text
- }
- });
+ let mut print_break = false;
+ print_span(printer, (x_range.start, line_idx), {
+ let text = &line[
+ line.char_indices().nth(x_range.start)
+ .map(|x| x.0)
+ .unwrap_or(line.len())
+ ..
+ line.char_indices().nth(x_range.end)
+ .map(|x| x.0)
+ .unwrap_or(line.len())
+ ];
+ if text.ends_with("\n") {
+ print_break = true;
+ &text[..text.len() - 1]
+ } else if text.is_empty() {
+ print_break = true;
+ text
+ } else {
+ text
+ }
+ });
- if print_break {
- print_span(printer, (x_range.end - 1, line_idx), " ")
- }
- };
+ if print_break {
+ print_span(printer, (x_range.end - 1, line_idx), " ")
+ }
+ };
- // Draw selection.
- printer.with_selection(true, |printer|
- for &(line_idx, ref x_range) in self.selections.iter() {
- print_span_text(printer, (x_range, line_idx))
- }
- );
+ // Draw selection.
+ printer.with_selection(true, |printer|
+ for &(line_idx, ref x_range) in self.selections.iter() {
+ print_span_text(printer, (x_range, line_idx))
+ }
+ );
- // Draw the cursor.
- for &(line_idx, x, style) in self.cursors.iter() {
- printer.with_style(style, |printer|
- print_span_text(printer, (&(x..x + 1), line_idx))
- );
- }
- }
+ // Draw the cursor.
+ for &(line_idx, x, style) in self.cursors.iter() {
+ printer.with_style(style, |printer|
+ print_span_text(printer, (&(x..x + 1), line_idx))
+ );
+ }
+ }
- fn on_event(&mut self, event: Event) -> EventResult {
- use self::cursor::{CursorEvent, BufferEvent};
+ fn on_event(&mut self, event: Event) -> EventResult {
+ use self::cursor::{CursorEvent, BufferEvent};
- self.focus_scroll = true;
+ self.focus_scroll = true;
- let buf_events = self.buf.events().collect::<Vec<_>>();
- let mut tx_buf = self.buf.events().collect::<Vec<_>>();
- let cur_pos = self.cur.positions().collect::<Vec<_>>().into_boxed_slice();
+ let buf_events = self.buf.events().collect::<Vec<_>>();
+ let mut tx_buf = self.buf.events().collect::<Vec<_>>();
+ let cur_pos = self.cur.positions().collect::<Vec<_>>().into_boxed_slice();
- // XXX any event will reset the keymapper
- let result = match self.keymap.next(&event) {
- Some(&Cmd::Cur(ref cmd)) => Some({
- use self::cursor::Event;
+ // XXX any event will reset the keymapper
+ let result = match self.keymap.next(&event) {
+ Some(&Cmd::Cur(ref cmd)) => Some({
+ use self::cursor::Event;
- if let Some(event) = cmd.run(&self.cur, &self.buf) {
- match event {
- Event::Cur(event) => self.cur.handle_cur(& self.buf, event),
- Event::Buf(event) => self.cur.handle_buf(&mut self.buf, event)
- }
+ if let Some(event) = cmd.run(&self.cur, &self.buf) {
+ match event {
+ Event::Cur(event) => self.cur.handle_cur(& self.buf, event),
+ Event::Buf(event) => self.cur.handle_buf(&mut self.buf, event)
+ }
- EventResult::Consumed(None)
- } else {
- EventResult::Ignored
- }
- }),
- Some(&Cmd::Hist(ref cmd)) => Some({
- tx_buf = Stream::never().collect();
+ EventResult::Consumed(None)
+ } else {
+ EventResult::Ignored
+ }
+ }),
+ Some(&Cmd::Hist(ref cmd)) => Some({
+ tx_buf = Stream::never().collect();
- match *cmd {
- HistoryCmd::Undo => match self.hist.undo() {
- Ok(ref tx) => {
- for event in tx.0.iter() {
- self.buf.handle((**event).clone());
- }
- self.cur.handle_cur(&self.buf, CursorEvent::Jump(tx.1.to_vec()));
- EventResult::Consumed(None)
- }
- Err(_) => EventResult::Ignored
- }
- HistoryCmd::Redo => match self.hist.redo() {
- Ok(ref tx) => {
- self.cur.handle_cur(&self.buf, CursorEvent::Jump(tx.1.to_vec()));
- for event in tx.0.iter() {
- self.buf.handle((**event).clone());
- }
- EventResult::Consumed(None)
- }
- Err(_) => EventResult::Ignored
- }
- }
- }),
- None => None // break match block here to satisfy the borrow checker
- };
- let result = if result.is_none() && self.keymap.path().is_empty() {
- match event {
- Event::Char(char) => {
- self.cur.handle_buf(&mut self.buf, BufferEvent::Insert(char.to_string()));
- EventResult::Consumed(None)
- }
- Event::Mouse { event: MouseEvent::WheelUp, .. } => {
- self.focus_scroll = false;
+ match *cmd {
+ HistoryCmd::Undo => match self.hist.undo() {
+ Ok(ref tx) => {
+ for event in tx.0.iter() {
+ self.buf.handle((**event).clone());
+ }
+ self.cur.handle_cur(&self.buf, CursorEvent::Jump(tx.1.to_vec()));
+ EventResult::Consumed(None)
+ }
+ Err(_) => EventResult::Ignored
+ }
+ HistoryCmd::Redo => match self.hist.redo() {
+ Ok(ref tx) => {
+ self.cur.handle_cur(&self.buf, CursorEvent::Jump(tx.1.to_vec()));
+ for event in tx.0.iter() {
+ self.buf.handle((**event).clone());
+ }
+ EventResult::Consumed(None)
+ }
+ Err(_) => EventResult::Ignored
+ }
+ }
+ }),
+ None => None // break match block here to satisfy the borrow checker
+ };
+ let result = if result.is_none() && self.keymap.path().is_empty() {
+ match event {
+ Event::Char(char) => {
+ self.cur.handle_buf(&mut self.buf, BufferEvent::Insert(char.to_string()));
+ EventResult::Consumed(None)
+ }
+ Event::Mouse { event: MouseEvent::WheelUp, .. } => {
+ self.focus_scroll = false;
- if self.scrollbase.can_scroll_up() {
- self.scrollbase.scroll_up(5);
- EventResult::Consumed(None)
- } else {
- EventResult::Ignored
- }
- }
- Event::Mouse { event: MouseEvent::WheelDown, .. } => {
- self.focus_scroll = false;
+ if self.scrollbase.can_scroll_up() {
+ self.scrollbase.scroll_up(5);
+ EventResult::Consumed(None)
+ } else {
+ EventResult::Ignored
+ }
+ }
+ Event::Mouse { event: MouseEvent::WheelDown, .. } => {
+ self.focus_scroll = false;
- if self.scrollbase.can_scroll_down() {
- self.scrollbase.scroll_down(5);
- EventResult::Consumed(None)
- } else {
- EventResult::Ignored
- }
- }
- Event::Mouse { event: MouseEvent::Press(MouseButton::Left), position, offset }
- if self.scrollbase.scrollable() && position.checked_sub(offset)
- .map(|position| {
- let right_padding = self.scrollbase.right_padding;
- self.scrollbase.start_drag(position, self.size.x) ||
- self.scrollbase.start_drag(
- position,
- self.size.x.saturating_sub(right_padding)
- )
- })
- .unwrap_or(false)
- => {
- self.focus_scroll = false;
- EventResult::Consumed(None)
- }
- Event::Mouse { event: MouseEvent::Hold(MouseButton::Left), position, offset }
- if self.scrollbase.is_dragging() => {
- self.scrollbase.drag(
- position.saturating_sub(offset)
- );
- self.focus_scroll = false;
- EventResult::Consumed(None)
- }
- Event::Mouse { event: MouseEvent::Release(MouseButton::Left), .. }
- if self.scrollbase.is_dragging() => {
- self.scrollbase.release_grab();
- self.focus_scroll = false;
- EventResult::Consumed(None)
- }
- Event::Mouse { event: MouseEvent::Press(_), position, offset }
- if position.fits_in_rect(offset, self.size) => {
- if let Some(mut position) = position.checked_sub(offset) {
- position.x += self.scroll_x;
- let char_idx = self.char_idx_at(position);
+ if self.scrollbase.can_scroll_down() {
+ self.scrollbase.scroll_down(5);
+ EventResult::Consumed(None)
+ } else {
+ EventResult::Ignored
+ }
+ }
+ Event::Mouse { event: MouseEvent::Press(MouseButton::Left), position, offset }
+ if self.scrollbase.scrollable() && position.checked_sub(offset)
+ .map(|position| {
+ let right_padding = self.scrollbase.right_padding;
+ self.scrollbase.start_drag(position, self.size.x) ||
+ self.scrollbase.start_drag(
+ position,
+ self.size.x.saturating_sub(right_padding)
+ )
+ })
+ .unwrap_or(false)
+ => {
+ self.focus_scroll = false;
+ EventResult::Consumed(None)
+ }
+ Event::Mouse { event: MouseEvent::Hold(MouseButton::Left), position, offset }
+ if self.scrollbase.is_dragging() => {
+ self.scrollbase.drag(
+ position.saturating_sub(offset)
+ );
+ self.focus_scroll = false;
+ EventResult::Consumed(None)
+ }
+ Event::Mouse { event: MouseEvent::Release(MouseButton::Left), .. }
+ if self.scrollbase.is_dragging() => {
+ self.scrollbase.release_grab();
+ self.focus_scroll = false;
+ EventResult::Consumed(None)
+ }
+ Event::Mouse { event: MouseEvent::Press(_), position, offset }
+ if position.fits_in_rect(offset, self.size) => {
+ if let Some(mut position) = position.checked_sub(offset) {
+ position.x += self.scroll_x;
+ let char_idx = self.char_idx_at(position);
- if self.click.0.elapsed() <= Duration::from_millis(250) {
- self.click.1 += 1;
- } else {
- self.click.1 = 0;
- }
- self.click.0 = Instant::now();
+ if self.click.0.elapsed() <= Duration::from_millis(250) {
+ self.click.1 += 1;
+ } else {
+ self.click.1 = 0;
+ }
+ self.click.0 = Instant::now();
- self.cur.handle_cur(&self.buf, CursorEvent::Jump(vec![(char_idx, None)]));
- match self.click.1 {
- 0 => { // single click sets cursor
- /* This is handled above as the cursor should
- * be set on subsequent clicks as well. */
- }
- 1 => { // double click selects word
- let positions = cursor::jump::select_words(&self.cur, self.buf.text());
- self.cur.handle_cur(&self.buf, CursorEvent::Jump(positions));
- }
- 2 => { // triple click selects line
- let positions = cursor::jump::select_lines(&self.cur, self.buf.text());
- self.cur.handle_cur(&self.buf, CursorEvent::Jump(positions));
- }
- _ => self.click.1 = 0
- }
- EventResult::Consumed(None)
- } else {
- EventResult::Ignored
- }
- }
- Event::Mouse { event: MouseEvent::Hold(_), position, offset }
- if position.fits_in_rect(offset, self.size) => {
- if let Some(mut position) = position.checked_sub(offset) {
- if let Some((char_idx, sel_start)) = self.cur.positions().next() {
- position.x += self.scroll_x;
+ self.cur.handle_cur(&self.buf, CursorEvent::Jump(vec![(char_idx, None)]));
+ match self.click.1 {
+ 0 => { // single click sets cursor
+ /* This is handled above as the cursor should
+ * be set on subsequent clicks as well. */
+ }
+ 1 => { // double click selects word
+ let positions = cursor::jump::select_words(&self.cur, self.buf.text());
+ self.cur.handle_cur(&self.buf, CursorEvent::Jump(positions));
+ }
+ 2 => { // triple click selects line
+ let positions = cursor::jump::select_lines(&self.cur, self.buf.text());
+ self.cur.handle_cur(&self.buf, CursorEvent::Jump(positions));
+ }
+ _ => self.click.1 = 0
+ }
+ EventResult::Consumed(None)
+ } else {
+ EventResult::Ignored
+ }
+ }
+ Event::Mouse { event: MouseEvent::Hold(_), position, offset }
+ if position.fits_in_rect(offset, self.size) => {
+ if let Some(mut position) = position.checked_sub(offset) {
+ if let Some((char_idx, sel_start)) = self.cur.positions().next() {
+ position.x += self.scroll_x;
- let pos_char_idx = self.char_idx_at(position);
- self.cur.handle_cur(&self.buf, CursorEvent::Jump(vec![(
- pos_char_idx,
- if let Some(sel_start) = sel_start {
- if pos_char_idx == sel_start {
- None
- } else {
- Some(sel_start)
- }
- } else {
- Some(char_idx)
- }
- )]));
- }
- EventResult::Consumed(None)
- } else {
- EventResult::Ignored
- }
- }
- _ => EventResult::Ignored
- }
- } else {
- EventResult::Ignored
- };
+ let pos_char_idx = self.char_idx_at(position);
+ self.cur.handle_cur(&self.buf, CursorEvent::Jump(vec![(
+ pos_char_idx,
+ if let Some(sel_start) = sel_start {
+ if pos_char_idx == sel_start {
+ None
+ } else {
+ Some(sel_start)
+ }
+ } else {
+ Some(char_idx)
+ }
+ )]));
+ }
+ EventResult::Consumed(None)
+ } else {
+ EventResult::Ignored
+ }
+ }
+ _ => EventResult::Ignored
+ }
+ } else {
+ EventResult::Ignored
+ };
- if buf_events.has_changed() {
- let buf_events = buf_events.sample().into_boxed_slice();
+ if buf_events.has_changed() {
+ let buf_events = buf_events.sample().into_boxed_slice();
- /* XXX only invalidate lines that
- * have really changed and
- * keep lines that only moved */
- let first_damage = buf_events.iter()
- .map(|event| {
- use self::buffer::Event;
+ /* XXX only invalidate lines that
+ * have really changed and
+ * keep lines that only moved */
+ let first_damage = buf_events.iter()
+ .map(|event| {
+ use self::buffer::Event;
- match **event {
- Event::Insert { ref char_idx, .. } => *char_idx,
- Event::Remove { ref char_idx_range } => char_idx_range.start
- }
- })
- .min()
- .map(|char_idx|
- self.buf.text().char_to_line(char_idx)
- .min(self.buf.text().len_lines().saturating_sub(1))
- )
- .unwrap();
- self.lines .invalidate_from(first_damage..);
- self.spans .invalidate_from(first_damage..);
- self.spans_highlight.invalidate_from(first_damage..);
- while self.highlight_heap.peek()
- .map(|x| x.line_idx >= first_damage)
- .unwrap_or(false)
- {
- self.highlight_heap.pop();
- }
- }
+ match **event {
+ Event::Insert { ref char_idx, .. } => *char_idx,
+ Event::Remove { ref char_idx_range } => char_idx_range.start
+ }
+ })
+ .min()
+ .map(|char_idx|
+ self.buf.text().char_to_line(char_idx)
+ .min(self.buf.text().len_lines().saturating_sub(1))
+ )
+ .unwrap();
+ self.lines .invalidate_from(first_damage..);
+ self.spans .invalidate_from(first_damage..);
+ self.spans_highlight.invalidate_from(first_damage..);
+ while self.highlight_heap.peek()
+ .map(|x| x.line_idx >= first_damage)
+ .unwrap_or(false)
+ {
+ self.highlight_heap.pop();
+ }
+ }
- if tx_buf.has_changed() {
- self.hist.record(Transaction(
- tx_buf.sample().into_boxed_slice(),
- cur_pos
- ));
- }
+ if tx_buf.has_changed() {
+ self.hist.record(Transaction(
+ tx_buf.sample().into_boxed_slice(),
+ cur_pos
+ ));
+ }
- result
- }
+ result
+ }
}
impl history::Undo for Transaction {
- type Undo = Transaction;
+ type Undo = Transaction;
- fn undo(&self) -> Self::Undo {
- let events_buf = self.0.iter()
- .map(|x| x.undo())
- .rev()
- .collect::<Vec<_>>()
- .into_boxed_slice();
- Transaction(events_buf, self.1.clone())
- }
+ fn undo(&self) -> Self::Undo {
+ let events_buf = self.0.iter()
+ .map(|x| x.undo())
+ .rev()
+ .collect::<Vec<_>>()
+ .into_boxed_slice();
+ Transaction(events_buf, self.1.clone())
+ }
}
M cursive/src/views/gutter.rs +71 -71
@@ 1,91 1,91 @@
use {
- std::ops::Range,
- frappe::Signal,
- cursive::{
- Printer,
- view::View,
- theme::Style,
- vec::Vec2
- },
- ted::{
- cache::CharCache,
- num::{
- NumDigits,
- Digits
- }
- }
+ std::ops::Range,
+ frappe::Signal,
+ cursive::{
+ Printer,
+ view::View,
+ theme::Style,
+ vec::Vec2
+ },
+ ted::{
+ cache::CharCache,
+ num::{
+ NumDigits,
+ Digits
+ }
+ }
};
pub struct LineNumbers {
- range: Signal<Range<usize>>,
- style: Style,
- pad_right: usize,
+ range: Signal<Range<usize>>,
+ style: Style,
+ pad_right: usize,
- space: CharCache,
- size: Vec2
+ space: CharCache,
+ size: Vec2
}
impl LineNumbers {
- pub fn new<S>(range: Signal<Range<usize>>, style: S) -> Self
- where S: Into<Style> {
- Self {
- style: style.into(),
- pad_right: 1,
- space: CharCache::new(' '),
- size: range.sample_with(|range| Vec2::new(
- Self::range_width(&*range).into(),
- range.len()
- )),
- range
- }
- }
+ pub fn new<S>(range: Signal<Range<usize>>, style: S) -> Self
+ where S: Into<Style> {
+ Self {
+ style: style.into(),
+ pad_right: 1,
+ space: CharCache::new(' '),
+ size: range.sample_with(|range| Vec2::new(
+ Self::range_width(&*range).into(),
+ range.len()
+ )),
+ range
+ }
+ }
- fn width(&self) -> usize {
- usize::from(self.nums_width()) + self.pad_right
- }
+ fn width(&self) -> usize {
+ usize::from(self.nums_width()) + self.pad_right
+ }
- fn nums_width(&self) -> u8 {
- self.range.sample_with(|range| Self::range_width(&*range))
- }
+ fn nums_width(&self) -> u8 {
+ self.range.sample_with(|range| Self::range_width(&*range))
+ }
- fn range_width(range: &Range<usize>) -> u8 {
- if range.len() > 0 {
- (range.end - 1).num_digits()
- } else { 0 }
- }
+ fn range_width(range: &Range<usize>) -> u8 {
+ if range.len() > 0 {
+ (range.end - 1).num_digits()
+ } else { 0 }
+ }
}
impl View for LineNumbers {
- fn required_size(&mut self, constraint: Vec2) -> Vec2 {
- Vec2::new(self.width(), constraint.y)
- }
+ fn required_size(&mut self, constraint: Vec2) -> Vec2 {
+ Vec2::new(self.width(), constraint.y)
+ }
- fn layout(&mut self, size: Vec2) {
- self.size = size;
+ fn layout(&mut self, size: Vec2) {
+ self.size = size;
- // allocate enough for line 1
- self.space.grow(self.size.x - self.pad_right - 1);
- self.space.grow(self.pad_right);
- self.space.truncate_shrink_min(self.size.x, 10);
- }
+ // allocate enough for line 1
+ self.space.grow(self.size.x - self.pad_right - 1);
+ self.space.grow(self.pad_right);
+ self.space.truncate_shrink_min(self.size.x, 10);
+ }
- fn draw(&self, printer: &Printer) {
- printer.with_style(self.style, |printer| {
- let nums_width: usize = self.nums_width().into();
+ fn draw(&self, printer: &Printer) {
+ printer.with_style(self.style, |printer| {
+ let nums_width: usize = self.nums_width().into();
- for (y, line) in self.range.sample().enumerate().take(self.size.y) {
- // print line number
- let mut x = nums_width;
- for digit in line.digits().str() {
- x -= 1;
- printer.print((x, y), digit);
- }
- // print left indent
- printer.print((0, y), self.space.get(x).expect("not enough space allocated"));
+ for (y, line) in self.range.sample().enumerate().take(self.size.y) {
+ // print line number
+ let mut x = nums_width;
+ for digit in line.digits().str() {
+ x -= 1;
+ printer.print((x, y), digit);
+ }
+ // print left indent
+ printer.print((0, y), self.space.get(x).expect("not enough space allocated"));
- // print right padding
- printer.print((nums_width, y), self.space.get(self.pad_right).expect("not enough space allocated"));
- }
- });
- }
+ // print right padding
+ printer.print((nums_width, y), self.space.get(self.pad_right).expect("not enough space allocated"));
+ }
+ });
+ }
}
M cursive/src/views/line.rs +61 -61
@@ 1,82 1,82 @@
use {
- ted::cache::CharCache,
- cursive::{
- Printer,
- view::View,
- vec::Vec2
- }
+ ted::cache::CharCache,
+ cursive::{
+ Printer,
+ view::View,
+ vec::Vec2
+ }
};
pub struct Line {
- tipe: Type,
+ tipe: Type,
- size: Vec2
+ size: Vec2
}
pub enum Type {
- /// Should be faster than `Printer::print_hline()`
- /// because the string is cached.
- Horizontal(CharCache),
+ /// Should be faster than `Printer::print_hline()`
+ /// because the string is cached.
+ Horizontal(CharCache),
- /// Much less performant because
- /// we can only draw by line, causing
- /// more draw calls to the backend.
- ///
- /// This simply forwards to `Printer::print_vline()`.
- #[allow(dead_code)]
- Vertical(String)
+ /// Much less performant because
+ /// we can only draw by line, causing
+ /// more draw calls to the backend.
+ ///
+ /// This simply forwards to `Printer::print_vline()`.
+ #[allow(dead_code)]
+ Vertical(String)
}
impl Line {
- /// ─
- pub fn horizontal() -> Self {
- Self {
- tipe: Type::Horizontal(CharCache::new('\u{2500}')),
- size: Vec2::zero()
- }
- }
+ /// ─
+ pub fn horizontal() -> Self {
+ Self {
+ tipe: Type::Horizontal(CharCache::new('\u{2500}')),
+ size: Vec2::zero()
+ }
+ }
- /// ━
- #[allow(dead_code)]
- pub fn horizontal_heavy() -> Self {
- Self {
- tipe: Type::Horizontal(CharCache::new('\u{2501}')),
- size: Vec2::zero()
- }
- }
+ /// ━
+ #[allow(dead_code)]
+ pub fn horizontal_heavy() -> Self {
+ Self {
+ tipe: Type::Horizontal(CharCache::new('\u{2501}')),
+ size: Vec2::zero()
+ }
+ }
- /// │
- #[allow(dead_code)]
- pub fn vertical() -> Self {
- Self {
- tipe: Type::Vertical("\u{2502}".to_string()),
- size: Vec2::zero()
- }
- }
+ /// │
+ #[allow(dead_code)]
+ pub fn vertical() -> Self {
+ Self {
+ tipe: Type::Vertical("\u{2502}".to_string()),
+ size: Vec2::zero()
+ }
+ }
- /// ┃
- #[allow(dead_code)]
- pub fn vertical_heavy() -> Self {
- Self {
- tipe: Type::Vertical("\u{2503}".to_string()),
- size: Vec2::zero()
- }
- }
+ /// ┃
+ #[allow(dead_code)]
+ pub fn vertical_heavy() -> Self {
+ Self {
+ tipe: Type::Vertical("\u{2503}".to_string()),
+ size: Vec2::zero()
+ }
+ }
}
impl View for Line {
- fn layout(&mut self, size: Vec2) {
- self.size = size;
+ fn layout(&mut self, size: Vec2) {
+ self.size = size;
- if let Type::Horizontal(ref mut cache) = self.tipe {
- cache.grow(size.x);
- }
- }
+ if let Type::Horizontal(ref mut cache) = self.tipe {
+ cache.grow(size.x);
+ }
+ }
- fn draw(&self, printer: &Printer) {
- match self.tipe {
- Type::Horizontal(ref cache) => printer.print ((0, self.size.y / 2), cache.get(self.size.x).unwrap()),
- Type::Vertical (ref char ) => printer.print_vline((self.size.x / 2, 0), self.size.y, &char)
- }
- }
+ fn draw(&self, printer: &Printer) {
+ match self.tipe {
+ Type::Horizontal(ref cache) => printer.print ((0, self.size.y / 2), cache.get(self.size.x).unwrap()),
+ Type::Vertical (ref char ) => printer.print_vline((self.size.x / 2, 0), self.size.y, &char)
+ }
+ }
}
M cursive/src/views/mod.rs +128 -128
@@ 10,152 10,152 @@ pub use self::gutter::LineNumbers;
pub use self::line::Line;
use {
- keys,
- bindings,
- std::fmt::Display,
- ted::trie::{
- TrieState,
- SequenceTrie
- },
- cursive::{
- view::View,
- views::{
- Canvas,
- StackView,
- LinearLayout,
- ListView,
- BoxView,
- Layer,
- TextView
- },
- event::Event,
- traits::{
- Finder,
- Identifiable,
- Boxable
- }
- }
+ keys,
+ bindings,
+ std::fmt::Display,
+ ted::trie::{
+ TrieState,
+ SequenceTrie
+ },
+ cursive::{
+ view::View,
+ views::{
+ Canvas,
+ StackView,
+ LinearLayout,
+ ListView,
+ BoxView,
+ Layer,
+ TextView
+ },
+ event::Event,
+ traits::{
+ Finder,
+ Identifiable,
+ Boxable
+ }
+ }
};
pub fn translate_keys<V: View>(view: V) -> Canvas<V> {
- let mut translate = keys::translate();
+ let mut translate = keys::translate();
- Canvas::wrap(view).with_on_event(move |view, event| {
- use cursive::event::EventResult;
+ Canvas::wrap(view).with_on_event(move |view, event| {
+ use cursive::event::EventResult;
- view.on_event(match translate(event) {
- Some(event) => event,
- None => return EventResult::Ignored
- })
- })
+ view.on_event(match translate(event) {
+ Some(event) => event,
+ None => return EventResult::Ignored
+ })
+ })
}
pub fn visualize_keymap<V, C>(view: V, bindings: SequenceTrie<Event, C>) -> Canvas<StackView> where
- V: View,
- C: 'static + Display
+ V: View,
+ C: 'static + Display
{
- const ID_KEYMAP: &str = "keymap";
- const ID_INNER : &str = "inner" ;
+ const ID_KEYMAP: &str = "keymap";
+ const ID_INNER : &str = "inner" ;
- let mut state = TrieState::new(bindings);
- let mut overlay: Option<BoxView<LinearLayout>> = Some(
- LinearLayout::vertical()
- .child(cursive::views::DummyView .full_height())
- .child(Line::horizontal() .full_width ())
- .child(Layer::new(ListView::new().with_id(ID_KEYMAP)).full_width ())
- .full_width()
- );
+ let mut state = TrieState::new(bindings);
+ let mut overlay: Option<BoxView<LinearLayout>> = Some(
+ LinearLayout::vertical()
+ .child(cursive::views::DummyView .full_height())
+ .child(Line::horizontal() .full_width ())
+ .child(Layer::new(ListView::new().with_id(ID_KEYMAP)).full_width ())
+ .full_width()
+ );
- Canvas::wrap(StackView::new()
- .fullscreen_layer(view
- .with_id(ID_INNER)
- .full_screen()
- )
- ).with_on_event(move |stack, event| {
- use std::mem::replace;
+ Canvas::wrap(StackView::new()
+ .fullscreen_layer(view
+ .with_id(ID_INNER)
+ .full_screen()
+ )
+ ).with_on_event(move |stack, event| {
+ use std::mem::replace;
- let show = {
- let mut update_keymap = |keymap: &mut ListView| {
- match event {
- Event::WindowResize |
- Event::Refresh => {}
- Event::Mouse { .. } => {
- state.reset();
- keymap.clear();
- }
- Event::Char (_) |
- Event::CtrlChar (_) |
- Event::AltChar (_) |
- Event::Key (_) |
- Event::Shift (_) |
- Event::Alt (_) |
- Event::AltShift (_) |
- Event::Ctrl (_) |
- Event::CtrlShift(_) |
- Event::CtrlAlt (_) => {
- keymap.clear();
+ let show = {
+ let mut update_keymap = |keymap: &mut ListView| {
+ match event {
+ Event::WindowResize |
+ Event::Refresh => {}
+ Event::Mouse { .. } => {
+ state.reset();
+ keymap.clear();
+ }
+ Event::Char (_) |
+ Event::CtrlChar (_) |
+ Event::AltChar (_) |
+ Event::Key (_) |
+ Event::Shift (_) |
+ Event::Alt (_) |
+ Event::AltShift (_) |
+ Event::Ctrl (_) |
+ Event::CtrlShift(_) |
+ Event::CtrlAlt (_) => {
+ keymap.clear();
- /* XXX could make this quicker
- * by caching all possible lists
- * in another SequenceTrie */
- state.next(&event);
- let node = state.node();
- if !node.is_leaf() && !state.path().is_empty() {
- // Build the list for the current node.
+ /* XXX could make this quicker
+ * by caching all possible lists
+ * in another SequenceTrie */
+ state.next(&event);
+ let node = state.node();
+ if !node.is_leaf() && !state.path().is_empty() {
+ // Build the list for the current node.
- // sort submenus first
- let mut children = node.children_with_keys();
- children.sort_unstable_by_key(|(_, cmd)| cmd.is_leaf());
+ // sort submenus first
+ let mut children = node.children_with_keys();
+ children.sort_unstable_by_key(|(_, cmd)| cmd.is_leaf());
- let mut add_delim = children.len() > 0 && !children[0].1.is_leaf();
- for (event, cmd) in children {
- if add_delim && cmd.is_leaf() {
- keymap.add_delimiter();
- add_delim = false;
- }
+ let mut add_delim = children.len() > 0 && !children[0].1.is_leaf();
+ for (event, cmd) in children {
+ if add_delim && cmd.is_leaf() {
+ keymap.add_delimiter();
+ add_delim = false;
+ }
- keymap.add_child(
- &bindings::stringify_event(event),
- TextView::new(if !cmd.is_leaf() { "…".to_owned() } else {
- format!("{}", cmd.value().expect("empty command"))
- })
- );
- }
- }
- }
- _ => {}
- }
+ keymap.add_child(
+ &bindings::stringify_event(event),
+ TextView::new(if !cmd.is_leaf() { "…".to_owned() } else {
+ format!("{}", cmd.value().expect("empty command"))
+ })
+ );
+ }
+ }
+ }
+ _ => {}
+ }
- keymap.len() > 0
- };
+ keymap.len() > 0
+ };
- if let Some(ref mut layer) = overlay {
- layer.find_id(ID_KEYMAP, &mut update_keymap).unwrap()
- } else {
- stack.find_id(ID_KEYMAP, &mut update_keymap).unwrap()
- }
- };
+ if let Some(ref mut layer) = overlay {
+ layer.find_id(ID_KEYMAP, &mut update_keymap).unwrap()
+ } else {
+ stack.find_id(ID_KEYMAP, &mut update_keymap).unwrap()
+ }
+ };
- // Show or hide the overlay.
- if show && overlay.is_some() {
- stack.add_transparent_layer(
- replace(&mut overlay, None).unwrap()
- );
- }
- if !show && overlay.is_none() {
- replace(&mut overlay, Some(
- *stack.pop_layer().unwrap()
- .as_boxed_any()
- .downcast().unwrap()
- ));
- }
+ // Show or hide the overlay.
+ if show && overlay.is_some() {
+ stack.add_transparent_layer(
+ replace(&mut overlay, None).unwrap()
+ );
+ }
+ if !show && overlay.is_none() {
+ replace(&mut overlay, Some(
+ *stack.pop_layer().unwrap()
+ .as_boxed_any()
+ .downcast().unwrap()
+ ));
+ }
- // Forward the intercepted event.
- if show {
- /* The overlay is blocking the event from the inner view
- * so we need to separately fire it again. */
- stack.find_id(ID_INNER, |view: &mut V| view.on_event(event.clone()));
- }
- stack.on_event(event)
- })
+ // Forward the intercepted event.
+ if show {
+ /* The overlay is blocking the event from the inner view
+ * so we need to separately fire it again. */
+ stack.find_id(ID_INNER, |view: &mut V| view.on_event(event.clone()));
+ }
+ stack.on_event(event)
+ })
}
M src/buffer.rs +206 -206
@@ 1,268 1,268 @@
use {
- ropey::Rope,
- frappe::{
- Sink,
- Stream,
- Signal
- },
- std::ops::{
- Deref,
- Range
- }
+ ropey::Rope,
+ frappe::{
+ Sink,
+ Stream,
+ Signal
+ },
+ std::ops::{
+ Deref,
+ Range
+ }
};
pub struct Buffer {
- text: Rope,
- event_sink: Sink<FiringEvent>
+ text: Rope,
+ event_sink: Sink<FiringEvent>
}
impl Buffer {
- pub fn from_str(text: &str) -> Self {
- Self {
- text: Rope::from_str(text),
- event_sink: Sink::new()
- }
- }
+ pub fn from_str(text: &str) -> Self {
+ Self {
+ text: Rope::from_str(text),
+ event_sink: Sink::new()
+ }
+ }
- pub fn text(&self) -> &Rope {
- &self.text
- }
+ pub fn text(&self) -> &Rope {
+ &self.text
+ }
- pub fn events(&self) -> Stream<FiringEvent> {
- self.event_sink.stream()
- }
+ pub fn events(&self) -> Stream<FiringEvent> {
+ self.event_sink.stream()
+ }
- pub fn handle(&mut self, event: Event) {
- let text = self.text.clone();
+ pub fn handle(&mut self, event: Event) {
+ let text = self.text.clone();
- match *&event {
- Event::Insert { char_idx, ref text } => self.text.insert(char_idx, text),
- Event::Remove { ref char_idx_range } => self.text.remove(char_idx_range.clone())
- }
+ match *&event {
+ Event::Insert { char_idx, ref text } => self.text.insert(char_idx, text),
+ Event::Remove { ref char_idx_range } => self.text.remove(char_idx_range.clone())
+ }
- self.event_sink.send(FiringEvent { event, text });
- }
+ self.event_sink.send(FiringEvent { event, text });
+ }
- pub fn signal_char_idx(&self, char_idx: usize) -> Signal<usize> {
- let current_sink = Sink::new();
- let current_signal = current_sink.stream().hold(char_idx);
+ pub fn signal_char_idx(&self, char_idx: usize) -> Signal<usize> {
+ let current_sink = Sink::new();
+ let current_signal = current_sink.stream().hold(char_idx);
- self.event_sink.stream()
- .map(move |event| {
- let value = event.update_char_idx(current_signal.sample());
- current_sink.send(value);
- value
- })
- .hold(char_idx)
- }
+ self.event_sink.stream()
+ .map(move |event| {
+ let value = event.update_char_idx(current_signal.sample());
+ current_sink.send(value);
+ value
+ })
+ .hold(char_idx)
+ }
- pub fn signal_line_idx(&self, line_idx: usize) -> Signal<usize> {
- let current_sink = Sink::new();
- let current_signal = current_sink.stream().hold(line_idx);
+ pub fn signal_line_idx(&self, line_idx: usize) -> Signal<usize> {
+ let current_sink = Sink::new();
+ let current_signal = current_sink.stream().hold(line_idx);
- self.event_sink.stream()
- .map(move |event| {
- let value = event.update_line_idx(current_signal.sample());
- current_sink.send(value);
- value
- })
- .hold(line_idx)
- }
+ self.event_sink.stream()
+ .map(move |event| {
+ let value = event.update_line_idx(current_signal.sample());
+ current_sink.send(value);
+ value
+ })
+ .hold(line_idx)
+ }
}
/// Event wrapper with text it applies to.
#[derive(Clone)]
pub struct FiringEvent {
- event: Event,
- text: Rope
+ event: Event,
+ text: Rope
}
impl FiringEvent {
- fn update_char_idx(&self, idx: usize) -> usize {
- match &**self {
- &Event::Insert { char_idx, ref text } if
- char_idx <= idx
- => idx + text.chars().count(),
+ fn update_char_idx(&self, idx: usize) -> usize {
+ match &**self {
+ &Event::Insert { char_idx, ref text } if
+ char_idx <= idx
+ => idx + text.chars().count(),
- &Event::Remove { ref char_idx_range } if
- char_idx_range.start < idx
- => if char_idx_range.end <= idx {
- idx - (char_idx_range.end - char_idx_range.start)
- } else {
- char_idx_range.start
- },
+ &Event::Remove { ref char_idx_range } if
+ char_idx_range.start < idx
+ => if char_idx_range.end <= idx {
+ idx - (char_idx_range.end - char_idx_range.start)
+ } else {
+ char_idx_range.start
+ },
- _ => idx
- }
- }
+ _ => idx
+ }
+ }
- fn update_line_idx(&self, line_idx: usize) -> usize {
- match &**self {
- &Event::Insert { char_idx, ref text } if
- char_idx <= self.text.line_to_char(line_idx)
- => line_idx + text.matches('\n').count(),
+ fn update_line_idx(&self, line_idx: usize) -> usize {
+ match &**self {
+ &Event::Insert { char_idx, ref text } if
+ char_idx <= self.text.line_to_char(line_idx)
+ => line_idx + text.matches('\n').count(),
- &Event::Remove { ref char_idx_range } if
- self.text.char_to_line(char_idx_range.start) < line_idx ||
- self.text.char_to_line(char_idx_range.end) < line_idx
- => line_idx - self.text.slice(char_idx_range.clone()).to_string().matches('\n').count(),
+ &Event::Remove { ref char_idx_range } if
+ self.text.char_to_line(char_idx_range.start) < line_idx ||
+ self.text.char_to_line(char_idx_range.end) < line_idx
+ => line_idx - self.text.slice(char_idx_range.clone()).to_string().matches('\n').count(),
- _ => line_idx
- }
- }
+ _ => line_idx
+ }
+ }
}
impl Deref for FiringEvent {
- type Target = Event;
+ type Target = Event;
- fn deref(&self) -> &Self::Target {
- &self.event
- }
+ fn deref(&self) -> &Self::Target {
+ &self.event
+ }
}
#[derive(Clone)]
pub enum Event {
- Insert {
- char_idx: usize,
- text: String
- },
- Remove {
- char_idx_range: Range<usize>
- }
+ Insert {
+ char_idx: usize,
+ text: String
+ },
+ Remove {
+ char_idx_range: Range<usize>
+ }
}
impl super::history::Undo for FiringEvent {
- type Undo = FiringEvent;
+ type Undo = FiringEvent;
- fn undo(&self) -> Self::Undo {
- FiringEvent {
- event: match **self {
- Event::Insert { char_idx, ref text } => Event::Remove {
- char_idx_range: char_idx..char_idx + text.len()
- },
- Event::Remove { ref char_idx_range } => Event::Insert {
- char_idx: char_idx_range.start,
- text: self.text.slice(char_idx_range.clone()).to_string()
- }
- },
- text: self.text.clone()
- }
- }
+ fn undo(&self) -> Self::Undo {
+ FiringEvent {
+ event: match **self {
+ Event::Insert { char_idx, ref text } => Event::Remove {
+ char_idx_range: char_idx..char_idx + text.len()
+ },
+ Event::Remove { ref char_idx_range } => Event::Insert {
+ char_idx: char_idx_range.start,
+ text: self.text.slice(char_idx_range.clone()).to_string()
+ }
+ },
+ text: self.text.clone()
+ }
+ }
}
#[cfg(test)]
mod tests {
- use super::*;
+ use super::*;
- #[test]
- fn from_str() {
- let text = "Hello, Ropey!";
- let buf = Buffer::from_str(text);
- assert_eq!(text, buf.text().to_string());
- }
+ #[test]
+ fn from_str() {
+ let text = "Hello, Ropey!";
+ let buf = Buffer::from_str(text);
+ assert_eq!(text, buf.text().to_string());
+ }
- #[test]
- fn signal_char_idx() {
- fn sample(char_idc: &(Signal<usize>, Signal<usize>, Signal<usize>)) -> (usize, usize, usize) {(
- char_idc.0.sample(),
- char_idc.1.sample(),
- char_idc.2.sample()
- )}
+ #[test]
+ fn signal_char_idx() {
+ fn sample(char_idc: &(Signal<usize>, Signal<usize>, Signal<usize>)) -> (usize, usize, usize) {(
+ char_idc.0.sample(),
+ char_idc.1.sample(),
+ char_idc.2.sample()
+ )}
- let mut buf = Buffer::from_str("Hello, Ropey!");
- let char_idc = (
- buf.signal_char_idx(5),
- buf.signal_char_idx(7),
- buf.signal_char_idx(12)
- );
- assert_eq!((5, 7, 12), sample(&char_idc));
+ let mut buf = Buffer::from_str("Hello, Ropey!");
+ let char_idc = (
+ buf.signal_char_idx(5),
+ buf.signal_char_idx(7),
+ buf.signal_char_idx(12)
+ );
+ assert_eq!((5, 7, 12), sample(&char_idc));
- buf.handle(Event::Insert {
- char_idx: 5,
- text: " there".to_owned()
- });
- assert_eq!("Hello there, Ropey!", buf.text().to_string());
- assert_eq!((11, 13, 18), sample(&char_idc));
+ buf.handle(Event::Insert {
+ char_idx: 5,
+ text: " there".to_owned()
+ });
+ assert_eq!("Hello there, Ropey!", buf.text().to_string());
+ assert_eq!((11, 13, 18), sample(&char_idc));
- buf.handle(Event::Insert {
- char_idx: 18,
- text: " my friend".to_owned()
- });
- assert_eq!("Hello there, Ropey my friend!", buf.text().to_string());
- assert_eq!((11, 13, 28), sample(&char_idc));
+ buf.handle(Event::Insert {
+ char_idx: 18,
+ text: " my friend".to_owned()
+ });
+ assert_eq!("Hello there, Ropey my friend!", buf.text().to_string());
+ assert_eq!((11, 13, 28), sample(&char_idc));
- buf.handle(Event::Remove {
- char_idx_range: 5..11
- });
- assert_eq!("Hello, Ropey my friend!", buf.text().to_string());
- assert_eq!((5, 7, 22), sample(&char_idc));
+ buf.handle(Event::Remove {
+ char_idx_range: 5..11
+ });
+ assert_eq!("Hello, Ropey my friend!", buf.text().to_string());
+ assert_eq!((5, 7, 22), sample(&char_idc));
- buf.handle(Event::Remove {
- char_idx_range: 12..22
- });
- assert_eq!("Hello, Ropey!", buf.text().to_string());
- assert_eq!((5, 7, 12), sample(&char_idc));
- }
+ buf.handle(Event::Remove {
+ char_idx_range: 12..22
+ });
+ assert_eq!("Hello, Ropey!", buf.text().to_string());
+ assert_eq!((5, 7, 12), sample(&char_idc));
+ }
- #[test]
- fn signal_line_idx() {
- fn sample(line_idc: &(Signal<usize>, Signal<usize>, Signal<usize>)) -> (usize, usize, usize) {(
- line_idc.0.sample(),
- line_idc.1.sample(),
- line_idc.2.sample()
- )}
+ #[test]
+ fn signal_line_idx() {
+ fn sample(line_idc: &(Signal<usize>, Signal<usize>, Signal<usize>)) -> (usize, usize, usize) {(
+ line_idc.0.sample(),
+ line_idc.1.sample(),
+ line_idc.2.sample()
+ )}
- let mut buf = Buffer::from_str("1\n2\n3");
- let line_idc = (
- buf.signal_line_idx(0),
- buf.signal_line_idx(1),
- buf.signal_line_idx(2)
- );
- assert_eq!((0, 1, 2), sample(&line_idc));
+ let mut buf = Buffer::from_str("1\n2\n3");
+ let line_idc = (
+ buf.signal_line_idx(0),
+ buf.signal_line_idx(1),
+ buf.signal_line_idx(2)
+ );
+ assert_eq!((0, 1, 2), sample(&line_idc));
- buf.handle(Event::Insert {
- char_idx: 5,
- text: "\n4".to_owned()
- });
- assert_eq!("1\n2\n3\n4", buf.text().to_string());
- assert_eq!((0, 1, 2), sample(&line_idc));
+ buf.handle(Event::Insert {
+ char_idx: 5,
+ text: "\n4".to_owned()
+ });
+ assert_eq!("1\n2\n3\n4", buf.text().to_string());
+ assert_eq!((0, 1, 2), sample(&line_idc));
- buf.handle(Event::Insert {
- char_idx: 3,
- text: "a".to_owned()
- });
- assert_eq!("1\n2a\n3\n4", buf.text().to_string());
- assert_eq!((0, 1, 2), sample(&line_idc));
+ buf.handle(Event::Insert {
+ char_idx: 3,
+ text: "a".to_owned()
+ });
+ assert_eq!("1\n2a\n3\n4", buf.text().to_string());
+ assert_eq!((0, 1, 2), sample(&line_idc));
- buf.handle(Event::Insert {
- char_idx: 5,
- text: "3!\n".to_owned()
- });
- assert_eq!("1\n2a\n3!\n3\n4", buf.text().to_string());
- assert_eq!((0, 1, 3), sample(&line_idc));
+ buf.handle(Event::Insert {
+ char_idx: 5,
+ text: "3!\n".to_owned()
+ });
+ assert_eq!("1\n2a\n3!\n3\n4", buf.text().to_string());
+ assert_eq!((0, 1, 3), sample(&line_idc));
- buf.handle(Event::Remove {
- char_idx_range: 9..11
- });
- assert_eq!("1\n2a\n3!\n3", buf.text().to_string());
- assert_eq!((0, 1, 3), sample(&line_idc));
+ buf.handle(Event::Remove {
+ char_idx_range: 9..11
+ });
+ assert_eq!("1\n2a\n3!\n3", buf.text().to_string());
+ assert_eq!((0, 1, 3), sample(&line_idc));
- buf.handle(Event::Remove {
- char_idx_range: 0..2
- });
- assert_eq!("2a\n3!\n3", buf.text().to_string());
- assert_eq!((0, 0, 2), sample(&line_idc));
+ buf.handle(Event::Remove {
+ char_idx_range: 0..2
+ });
+ assert_eq!("2a\n3!\n3", buf.text().to_string());
+ assert_eq!((0, 0, 2), sample(&line_idc));
- buf.handle(Event::Remove {
- char_idx_range: 3..6
- });
- assert_eq!("2a\n3", buf.text().to_string());
- assert_eq!((0, 0, 1), sample(&line_idc));
+ buf.handle(Event::Remove {
+ char_idx_range: 3..6
+ });
+ assert_eq!("2a\n3", buf.text().to_string());
+ assert_eq!((0, 0, 1), sample(&line_idc));
- buf.handle(Event::Remove {
- char_idx_range: 0..4
- });
- assert!(buf.text().to_string().is_empty());
- assert_eq!((0, 0, 0), sample(&line_idc));
- }
+ buf.handle(Event::Remove {
+ char_idx_range: 0..4
+ });
+ assert!(buf.text().to_string().is_empty());
+ assert_eq!((0, 0, 0), sample(&line_idc));
+ }
}
M src/cache.rs +62 -62
@@ 2,83 2,83 @@ use std::ops::RangeFrom;
use std::collections::BTreeMap;
pub trait RangeCache {
- /// Returns whether the index was cached.
- fn invalidate(&mut self, idx: usize) -> bool;
+ /// Returns whether the index was cached.
+ fn invalidate(&mut self, idx: usize) -> bool;
- /// Returns whether at least one index was cached.
- // TODO name invalidate_range() instead with RangeArgument once stable
- fn invalidate_from(&mut self, idc: RangeFrom<usize>) -> bool;
+ /// Returns whether at least one index was cached.
+ // TODO name invalidate_range() instead with RangeArgument once stable
+ fn invalidate_from(&mut self, idc: RangeFrom<usize>) -> bool;
}
impl<T> RangeCache for BTreeMap<usize, T> {
- fn invalidate(&mut self, idx: usize) -> bool {
- self.remove(&idx).is_some()
- }
+ fn invalidate(&mut self, idx: usize) -> bool {
+ self.remove(&idx).is_some()
+ }
- fn invalidate_from(&mut self, idc: RangeFrom<usize>) -> bool {
- let mut changed = false;
- for line_idx in idc.start..=*self.keys().max().unwrap_or(&idc.start) {
- changed |= self.invalidate(line_idx);
- }
- changed
- }
+ fn invalidate_from(&mut self, idc: RangeFrom<usize>) -> bool {
+ let mut changed = false;
+ for line_idx in idc.start..=*self.keys().max().unwrap_or(&idc.start) {
+ changed |= self.invalidate(line_idx);
+ }
+ changed
+ }
}
pub struct CharCache {
- /// We could simply use the first char
- /// of the `cache` string but we keep
- /// it here for performance.
- char: char,
+ /// We could simply use the first char
+ /// of the `cache` string but we keep
+ /// it here for performance.
+ char: char,
- /// We know that this contains only `self.char`,
- /// so code point indices are always on its multiples
- /// and we can access them directly.
- cache: String
+ /// We know that this contains only `self.char`,
+ /// so code point indices are always on its multiples
+ /// and we can access them directly.
+ cache: String
}
impl CharCache {
- pub fn new(char: char) -> Self {
- Self {
- char,
- cache: char.to_string()
- }
- }
+ pub fn new(char: char) -> Self {
+ Self {
+ char,
+ cache: char.to_string()
+ }
+ }
- pub fn len_chars(&self) -> usize {
- self.cache.len() * self.char.len_utf8()
- }
+ pub fn len_chars(&self) -> usize {
+ self.cache.len() * self.char.len_utf8()
+ }
- /// Returns a string slice of the given length,
- /// containing only the same char.
- ///
- /// # Errors
- /// When the cache is not long enough. Use `grow()` first.
- pub fn get(&self, len_chars: usize) -> Result<&str, ()> {
- let len_bytes = len_chars * self.char.len_utf8();
- if len_bytes > self.cache.len() { Err(()) } else {
- Ok(&self.cache[..len_bytes])
- }
- }
+ /// Returns a string slice of the given length,
+ /// containing only the same char.
+ ///
+ /// # Errors
+ /// When the cache is not long enough. Use `grow()` first.
+ pub fn get(&self, len_chars: usize) -> Result<&str, ()> {
+ let len_bytes = len_chars * self.char.len_utf8();
+ if len_bytes > self.cache.len() { Err(()) } else {
+ Ok(&self.cache[..len_bytes])
+ }
+ }
- /// Like `get()`, but this cannot fail
- /// as it will grow the cache as needed.
- pub fn grow(&mut self, len_chars: usize) -> &str {
- let char_len = self.char.len_utf8();
- for _ in self.cache.len() / char_len..len_chars {
- self.cache.push(self.char);
- }
- &self.cache[..len_chars * char_len]
- }
+ /// Like `get()`, but this cannot fail
+ /// as it will grow the cache as needed.
+ pub fn grow(&mut self, len_chars: usize) -> &str {
+ let char_len = self.char.len_utf8();
+ for _ in self.cache.len() / char_len..len_chars {
+ self.cache.push(self.char);
+ }
+ &self.cache[..len_chars * char_len]
+ }
- pub fn truncate_shrink(&mut self, len_chars: usize) {
- self.cache.truncate(len_chars * self.char.len_utf8());
- self.cache.shrink_to_fit();
- }
+ pub fn truncate_shrink(&mut self, len_chars: usize) {
+ self.cache.truncate(len_chars * self.char.len_utf8());
+ self.cache.shrink_to_fit();
+ }
- /// Calls `truncate_shrink` only if at least `min` chars would be truncated.
- pub fn truncate_shrink_min(&mut self, len_chars: usize, min: usize) {
- if self.len_chars().saturating_sub(len_chars) >= min {
- self.truncate_shrink(len_chars);
- }
- }
+ /// Calls `truncate_shrink` only if at least `min` chars would be truncated.
+ pub fn truncate_shrink_min(&mut self, len_chars: usize, min: usize) {
+ if self.len_chars().saturating_sub(len_chars) >= min {
+ self.truncate_shrink(len_chars);
+ }
+ }
}
M src/command.rs +129 -129
@@ 4,13 4,13 @@
//! - multiple events (ordered or not)
use {
- frappe::Signal,
- std::fmt,
- buffer::Buffer,
- cursor::{
- self,
- Cursor
- }
+ frappe::Signal,
+ std::fmt,
+ buffer::Buffer,
+ cursor::{
+ self,
+ Cursor
+ }
};
/// These produce a `cursor::Event`.
@@ 19,135 19,135 @@ use {
/// that indicates whether to select.
#[derive(Clone)]
pub enum CursorCmd {
- Enter,
- Backspace, Delete,
- Indent(Signal<String>), Dedent(Signal<String>),
+ Enter,
+ Backspace, Delete,
+ Indent(Signal<String>), Dedent(Signal<String>),
- // TODO name these better
- Left(bool), Right(bool),
- /// `(select, num_lines)`
- Vertical(bool, Signal<isize>),
- /// `(select, smart)`
- LineHome(bool, bool),
- LineEnd(bool),
- WordLeft(bool), WordRight(bool),
- SelectWord,
- SpawnMultiCursor, KillMultiCursor, SkipMultiCursor
+ // TODO name these better
+ Left(bool), Right(bool),
+ /// `(select, num_lines)`
+ Vertical(bool, Signal<isize>),
+ /// `(select, smart)`
+ LineHome(bool, bool),
+ LineEnd(bool),
+ WordLeft(bool), WordRight(bool),
+ SelectWord,
+ SpawnMultiCursor, KillMultiCursor, SkipMultiCursor
}
impl fmt::Display for CursorCmd {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- let fmt_string;
- write!(f, "{}", match *self {
- CursorCmd::Enter => "Enter",
- CursorCmd::Backspace => "Backspace",
- CursorCmd::Delete => "Delete",
- CursorCmd::Indent(_) => "Indent",
- CursorCmd::Dedent(_) => "Dedent",
- CursorCmd::SelectWord => "SelectWord",
- CursorCmd::SpawnMultiCursor => "SpawnMultiCursor",
- CursorCmd::KillMultiCursor => "KillMultiCursor",
- CursorCmd::SkipMultiCursor => "SkipMultiCursor",
- CursorCmd::Left (select) => { fmt_string = format!("Left{}", if select { "-select" } else { "" }); fmt_string.as_str() },
- CursorCmd::Right(select) => { fmt_string = format!("Right{}", if select { "-select" } else { "" }); fmt_string.as_str() },
- CursorCmd::LineHome (select, _smart) => { fmt_string = format!("LineHome{}", if select { "-select" } else { "" }); fmt_string.as_str() },
- CursorCmd::LineEnd (select) => { fmt_string = format!("LineEnd{}", if select { "-select" } else { "" }); fmt_string.as_str() },
- CursorCmd::WordLeft (select) => { fmt_string = format!("WordLeft{}", if select { "-select" } else { "" }); fmt_string.as_str() },
- CursorCmd::WordRight(select) => { fmt_string = format!("WordRight{}", if select { "-select" } else { "" }); fmt_string.as_str() },
- CursorCmd::Vertical(select, ref num_lines_sig) => { fmt_string = format!("{}{}",
- match num_lines_sig.sample() {
- -1 => "Up",
- 1 => "Down",
- n @ _ if n < -1 => "PageUp",
- n @ _ if n > 1 => "PageDown",
- _ => unimplemented!()
- },
- if select { "-select" } else { "" }
- ); fmt_string.as_str() }
- })
- }
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let fmt_string;
+ write!(f, "{}", match *self {
+ CursorCmd::Enter => "Enter",
+ CursorCmd::Backspace => "Backspace",
+ CursorCmd::Delete => "Delete",
+ CursorCmd::Indent(_) => "Indent",
+ CursorCmd::Dedent(_) => "Dedent",
+ CursorCmd::SelectWord => "SelectWord",
+ CursorCmd::SpawnMultiCursor => "SpawnMultiCursor",
+ CursorCmd::KillMultiCursor => "KillMultiCursor",
+ CursorCmd::SkipMultiCursor => "SkipMultiCursor",
+ CursorCmd::Left (select) => { fmt_string = format!("Left{}", if select { "-select" } else { "" }); fmt_string.as_str() },
+ CursorCmd::Right(select) => { fmt_string = format!("Right{}", if select { "-select" } else { "" }); fmt_string.as_str() },
+ CursorCmd::LineHome (select, _smart) => { fmt_string = format!("LineHome{}", if select { "-select" } else { "" }); fmt_string.as_str() },
+ CursorCmd::LineEnd (select) => { fmt_string = format!("LineEnd{}", if select { "-select" } else { "" }); fmt_string.as_str() },
+ CursorCmd::WordLeft (select) => { fmt_string = format!("WordLeft{}", if select { "-select" } else { "" }); fmt_string.as_str() },
+ CursorCmd::WordRight(select) => { fmt_string = format!("WordRight{}", if select { "-select" } else { "" }); fmt_string.as_str() },
+ CursorCmd::Vertical(select, ref num_lines_sig) => { fmt_string = format!("{}{}",
+ match num_lines_sig.sample() {
+ -1 => "Up",
+ 1 => "Down",
+ n @ _ if n < -1 => "PageUp",
+ n @ _ if n > 1 => "PageDown",
+ _ => unimplemented!()
+ },
+ if select { "-select" } else { "" }
+ ); fmt_string.as_str() }
+ })
+ }
}
impl CursorCmd {
- pub fn run(&self, cur: &Cursor, buf: &Buffer) -> Option<cursor::Event> {
- use cursor::{Event, CursorEvent, BufferEvent};
+ pub fn run(&self, cur: &Cursor, buf: &Buffer) -> Option<cursor::Event> {
+ use cursor::{Event, CursorEvent, BufferEvent};
- match self {
- // Event::Buf
- &CursorCmd::Enter => Some(Event::Buf(BufferEvent::Insert('\n'.to_string()))),
- &CursorCmd::Backspace => Some(Event::Buf(BufferEvent::Backspace)),
- &CursorCmd::Delete => Some(Event::Buf(BufferEvent::Delete)),
- &CursorCmd::Indent(ref indent) => Some(Event::Buf(BufferEvent::Indent(indent.clone()))),
- &CursorCmd::Dedent(ref indent) => Some(Event::Buf(BufferEvent::Dedent(indent.clone()))),
+ match self {
+ // Event::Buf
+ &CursorCmd::Enter => Some(Event::Buf(BufferEvent::Insert('\n'.to_string()))),
+ &CursorCmd::Backspace => Some(Event::Buf(BufferEvent::Backspace)),
+ &CursorCmd::Delete => Some(Event::Buf(BufferEvent::Delete)),
+ &CursorCmd::Indent(ref indent) => Some(Event::Buf(BufferEvent::Indent(indent.clone()))),
+ &CursorCmd::Dedent(ref indent) => Some(Event::Buf(BufferEvent::Dedent(indent.clone()))),
- // Event::Cur
- &CursorCmd::Left(select) => Some(Event::Cur(CursorEvent::Jump(
- cur.positions()
- .map(|(char_idx, sel_start)| (
- match sel_start {
- Some(sel_start) if !select => cursor::select((sel_start, char_idx)).start,
- _ => cursor::jump::left(char_idx)
- },
- if select { sel_start.or(Some(char_idx)) } else { None }
- ))
- .collect()
- ))),
- &CursorCmd::Right(select) => Some(Event::Cur(CursorEvent::Jump(
- cur.positions()
- .map(|(char_idx, sel_start)| (
- match sel_start {
- Some(sel_start) if !select => cursor::select((sel_start, char_idx)).end,
- _ => cursor::jump::right(buf.text(), char_idx)
- },
- if select { sel_start.or(Some(char_idx)) } else { None }
- ))
- .collect()
- ))),
- &CursorCmd::Vertical(select, ref num_lines) => Some(Event::Cur(CursorEvent::Jump(
- cur.positions()
- .map(|(char_idx, sel_start)| (
- cursor::jump::vertical(buf.text(), char_idx, num_lines.sample()),
- if select { sel_start.or(Some(char_idx)) } else { None }
- ))
- .collect()
- ))),
- // TODO smart home
- &CursorCmd::LineHome(select, _smart) => Some(Event::Cur(CursorEvent::Jump(
- cur.positions()
- .map(|(char_idx, sel_start)| (
- cursor::jump::line_start(buf.text(), char_idx),
- if select { sel_start.or(Some(char_idx)) } else { None }
- ))
- .collect()
- ))),
- &CursorCmd::LineEnd(select) => Some(Event::Cur(CursorEvent::Jump(
- cur.positions()
- .map(|(char_idx, sel_start)| (
- cursor::jump::line_end(buf.text(), char_idx),
- if select { sel_start.or(Some(char_idx)) } else { None }
- ))
- .collect()
- ))),
- &CursorCmd::WordLeft(select) => Some(Event::Cur(CursorEvent::Jump(
- cur.positions()
- .map(|(char_idx, sel_start)| (
- cursor::jump::word_left(buf.text(), char_idx),
- if select { sel_start.or(Some(char_idx)) } else { None }
- ))
- .collect()
- ))),
- &CursorCmd::WordRight(select) => Some(Event::Cur(CursorEvent::Jump(
- cur.positions()
- .map(|(char_idx, sel_start)| (
- cursor::jump::word_right(buf.text(), char_idx),
- if select { sel_start.or(Some(char_idx)) } else { None }
- ))
- .collect()
- ))),
- &CursorCmd::SelectWord => Some(Event::Cur(CursorEvent::Jump(cursor::jump::select_words(cur, buf.text())))),
- &CursorCmd::SpawnMultiCursor => cursor::jump::spawn(cur, buf.text()).map(CursorEvent::Jump).map(Event::Cur),
- &CursorCmd::KillMultiCursor => cursor::jump::die (cur ).map(CursorEvent::Jump).map(Event::Cur),
- &CursorCmd::SkipMultiCursor => cursor::jump::skip (cur, buf.text()).map(CursorEvent::Jump).map(Event::Cur)
- }
- }
+ // Event::Cur
+ &CursorCmd::Left(select) => Some(Event::Cur(CursorEvent::Jump(
+ cur.positions()
+ .map(|(char_idx, sel_start)| (
+ match sel_start {
+ Some(sel_start) if !select => cursor::select((sel_start, char_idx)).start,
+ _ => cursor::jump::left(char_idx)
+ },
+ if select { sel_start.or(Some(char_idx)) } else { None }
+ ))
+ .collect()
+ ))),
+ &CursorCmd::Right(select) => Some(Event::Cur(CursorEvent::Jump(
+ cur.positions()
+ .map(|(char_idx, sel_start)| (
+ match sel_start {
+ Some(sel_start) if !select => cursor::select((sel_start, char_idx)).end,
+ _ => cursor::jump::right(buf.text(), char_idx)
+ },
+ if select { sel_start.or(Some(char_idx)) } else { None }
+ ))
+ .collect()
+ ))),
+ &CursorCmd::Vertical(select, ref num_lines) => Some(Event::Cur(CursorEvent::Jump(
+ cur.positions()
+ .map(|(char_idx, sel_start)| (
+ cursor::jump::vertical(buf.text(), char_idx, num_lines.sample()),
+ if select { sel_start.or(Some(char_idx)) } else { None }
+ ))
+ .collect()
+ ))),
+ // TODO smart home
+ &CursorCmd::LineHome(select, _smart) => Some(Event::Cur(CursorEvent::Jump(
+ cur.positions()
+ .map(|(char_idx, sel_start)| (
+ cursor::jump::line_start(buf.text(), char_idx),
+ if select { sel_start.or(Some(char_idx)) } else { None }
+ ))
+ .collect()
+ ))),
+ &CursorCmd::LineEnd(select) => Some(Event::Cur(CursorEvent::Jump(
+ cur.positions()
+ .map(|(char_idx, sel_start)| (
+ cursor::jump::line_end(buf.text(), char_idx),
+ if select { sel_start.or(Some(char_idx)) } else { None }
+ ))
+ .collect()
+ ))),
+ &CursorCmd::WordLeft(select) => Some(Event::Cur(CursorEvent::Jump(
+ cur.positions()
+ .map(|(char_idx, sel_start)| (
+ cursor::jump::word_left(buf.text(), char_idx),
+ if select { sel_start.or(Some(char_idx)) } else { None }
+ ))
+ .collect()
+ ))),
+ &CursorCmd::WordRight(select) => Some(Event::Cur(CursorEvent::Jump(
+ cur.positions()
+ .map(|(char_idx, sel_start)| (
+ cursor::jump::word_right(buf.text(), char_idx),
+ if select { sel_start.or(Some(char_idx)) } else { None }
+ ))
+ .collect()
+ ))),
+ &CursorCmd::SelectWord => Some(Event::Cur(CursorEvent::Jump(cursor::jump::select_words(cur, buf.text())))),
+ &CursorCmd::SpawnMultiCursor => cursor::jump::spawn(cur, buf.text()).map(CursorEvent::Jump).map(Event::Cur),
+ &CursorCmd::KillMultiCursor => cursor::jump::die (cur ).map(CursorEvent::Jump).map(Event::Cur),
+ &CursorCmd::SkipMultiCursor => cursor::jump::skip (cur, buf.text()).map(CursorEvent::Jump).map(Event::Cur)
+ }
+ }
}
M src/cursor/jump.rs +369 -369
@@ 7,468 7,468 @@ use unicode_segmentation::UnicodeSegment
/// Go one char to the right.
pub fn right(text: &Rope, char_idx: usize) -> usize {
- (char_idx + 1).min(text.len_chars())
+ (char_idx + 1).min(text.len_chars())
}
/// Go one char to the left.
pub fn left(char_idx: usize) -> usize {
- char_idx.saturating_sub(1)
+ char_idx.saturating_sub(1)
}
/// Tries to keep the x position if possible.
pub fn vertical(text: &Rope, char_idx: usize, num_lines: isize) -> usize {
- // > If char_idx is one-past-the-end, then one-past-the-end line index is returned.
- // Circumvent this behavior by clipping to the last valid line index.
- let line_idx = text.char_to_line(char_idx)
- .min(text.len_lines().saturating_sub(1));
- if num_lines < 0 && line_idx > 0 || num_lines > 0 && line_idx < text.len_lines() - 1 {
- let x = char_idx - text.line_to_char(line_idx);
- let next_line_idx = (
- if num_lines > 0 {
- line_idx + num_lines as usize
- } else if let (value, false) = line_idx.overflowing_sub(num_lines.abs() as usize) {
- value
- } else { 0 }
- ).min(
- if text.len_lines() > 0 {
- text.len_lines() - 1
- } else { 0 }
- );
- let line = text.line(next_line_idx);
- let line_len = line.len_chars();
+ // > If char_idx is one-past-the-end, then one-past-the-end line index is returned.
+ // Circumvent this behavior by clipping to the last valid line index.
+ let line_idx = text.char_to_line(char_idx)
+ .min(text.len_lines().saturating_sub(1));
+ if num_lines < 0 && line_idx > 0 || num_lines > 0 && line_idx < text.len_lines() - 1 {
+ let x = char_idx - text.line_to_char(line_idx);
+ let next_line_idx = (
+ if num_lines > 0 {
+ line_idx + num_lines as usize
+ } else if let (value, false) = line_idx.overflowing_sub(num_lines.abs() as usize) {
+ value
+ } else { 0 }
+ ).min(
+ if text.len_lines() > 0 {
+ text.len_lines() - 1
+ } else { 0 }
+ );
+ let line = text.line(next_line_idx);
+ let line_len = line.len_chars();
- text.line_to_char(next_line_idx) +
- x.min(line_len.saturating_sub(
- if line_len > 0 && line.char(line_len - 1) == '\n' { 1 } else { 0 }
- ))
- } else {
- char_idx
- }
+ text.line_to_char(next_line_idx) +
+ x.min(line_len.saturating_sub(
+ if line_len > 0 && line.char(line_len - 1) == '\n' { 1 } else { 0 }
+ ))
+ } else {
+ char_idx
+ }
}
/// Go to the first char on the line.
pub fn line_start(text: &Rope, char_idx: usize) -> usize {
- let line_idx = text.char_to_line(char_idx)
- .min(text.len_lines().saturating_sub(1));
- text.line_to_char(line_idx)
+ let line_idx = text.char_to_line(char_idx)
+ .min(text.len_lines().saturating_sub(1));
+ text.line_to_char(line_idx)
}
/// Go to the last char on the line.
pub fn line_end(text: &Rope, char_idx: usize) -> usize {
- let line_idx = text.char_to_line(char_idx);
- let len_lines = text.len_lines();
+ let line_idx = text.char_to_line(char_idx);
+ let len_lines = text.len_lines();
- text.line_to_char(line_idx) +
- if line_idx < len_lines {
- let line = text.line(line_idx);
- let line_len = line.len_chars();
+ text.line_to_char(line_idx) +
+ if line_idx < len_lines {
+ let line = text.line(line_idx);
+ let line_len = line.len_chars();
- if line_len > 0 {
- line_len -
- if line_idx == len_lines - 1 && // last line
- text.char(text.len_chars() - 1) != '\n' // no trailing newline
- {
- 0 // End of line is end of text, so we go one-past-the-end.
- } else {
- 1 // Step onto the previous line break.
- }
- } else { 0 }
- } else { 0 }
+ if line_len > 0 {
+ line_len -
+ if line_idx == len_lines - 1 && // last line
+ text.char(text.len_chars() - 1) != '\n' // no trailing newline
+ {
+ 0 // End of line is end of text, so we go one-past-the-end.
+ } else {
+ 1 // Step onto the previous line break.
+ }
+ } else { 0 }
+ } else { 0 }
}
/// Go to the previous word boundary.
pub fn word_left(text: &Rope, char_idx: usize) -> usize {
- word(text, char_idx, -1)
+ word(text, char_idx, -1)
}
/// Go to the next word boundary.
pub fn word_right(text: &Rope, char_idx: usize) -> usize {
- word(text, char_idx, 1)
+ word(text, char_idx, 1)
}
fn word(text: &Rope, char_idx: usize, direction: i8) -> usize {
- if direction.is_negative() && char_idx == 0 ||
- direction.is_positive() && char_idx == text.len_chars()
- { return char_idx }
+ if direction.is_negative() && char_idx == 0 ||
+ direction.is_positive() && char_idx == text.len_chars()
+ { return char_idx }
- enum Next {
- Whitespace,
- Word
- }
+ enum Next {
+ Whitespace,
+ Word
+ }
- impl Next {
- fn matches(&self, char: char) -> bool {
- match self {
- &Next::Whitespace => char.is_whitespace(),
- &Next::Word => char.is_alphanumeric()
- }
- }
+ impl Next {
+ fn matches(&self, char: char) -> bool {
+ match self {
+ &Next::Whitespace => char.is_whitespace(),
+ &Next::Word => char.is_alphanumeric()
+ }
+ }
- fn next(self, char: char) -> Option<Self> {
- if self.matches(char) {
- Some(self)
- } else {
- match self {
- Next::Whitespace => Some(Next::Word),
- Next::Word => None
- }
- }
- }
- }
+ fn next(self, char: char) -> Option<Self> {
+ if self.matches(char) {
+ Some(self)
+ } else {
+ match self {
+ Next::Whitespace => Some(Next::Word),
+ Next::Word => None
+ }
+ }
+ }
+ }
- let len_chars = text.len_chars();
- let text = if direction.is_negative() {
- text.slice(..char_idx)
- } else if direction.is_positive() {
- text.slice(char_idx + 1..)
- } else {
- return char_idx
- };
- let mut next = Next::Whitespace;
- let mut current_idx = char_idx;
- /* We need to create both iterators for them to live long enough
- * even though we don't know whether we want to reverse yet.
- * XXX could overcome this by replacing the generalized for loop below with two specialized loops */
- let mut chars_enumerate = Chars::from_slice(text).enumerate();
- let mut chars_rev_enumerate = Chars::from_slice(text).rev().enumerate();
- let chars: &mut Iterator<Item=(usize, char)> = if direction.is_negative() {
- &mut chars_rev_enumerate
- } else {
- &mut chars_enumerate
- };
- let mut terminated = false;
- for (count, char) in chars {
- next = match next.next(char) {
- Some(next) => {
- current_idx = if direction.is_negative() {
- char_idx - count - 1
- } else {
- char_idx + count + 1
- };
- next
- },
- None => {
- if direction.is_positive() {
- current_idx = char_idx + count + 1;
- }
- terminated = true;
- break
- }
- };
- }
- /* Go to first or one-past-the-end char index
- * if we just stopped because there was no char left to handle. */
- if !terminated {
- if current_idx == len_chars - 1 {
- current_idx = len_chars;
- } else if current_idx == 1 {
- current_idx = 0
- }
- }
- current_idx
+ let len_chars = text.len_chars();
+ let text = if direction.is_negative() {
+ text.slice(..char_idx)
+ } else if direction.is_positive() {
+ text.slice(char_idx + 1..)
+ } else {
+ return char_idx
+ };
+ let mut next = Next::Whitespace;
+ let mut current_idx = char_idx;
+ /* We need to create both iterators for them to live long enough
+ * even though we don't know whether we want to reverse yet.
+ * XXX could overcome this by replacing the generalized for loop below with two specialized loops */
+ let mut chars_enumerate = Chars::from_slice(text).enumerate();
+ let mut chars_rev_enumerate = Chars::from_slice(text).rev().enumerate();
+ let chars: &mut Iterator<Item=(usize, char)> = if direction.is_negative() {
+ &mut chars_rev_enumerate
+ } else {
+ &mut chars_enumerate
+ };
+ let mut terminated = false;
+ for (count, char) in chars {
+ next = match next.next(char) {
+ Some(next) => {
+ current_idx = if direction.is_negative() {
+ char_idx - count - 1
+ } else {
+ char_idx + count + 1
+ };
+ next
+ },
+ None => {
+ if direction.is_positive() {
+ current_idx = char_idx + count + 1;
+ }
+ terminated = true;
+ break
+ }
+ };
+ }
+ /* Go to first or one-past-the-end char index
+ * if we just stopped because there was no char left to handle. */
+ if !terminated {
+ if current_idx == len_chars - 1 {
+ current_idx = len_chars;
+ } else if current_idx == 1 {
+ current_idx = 0
+ }
+ }
+ current_idx
}
/// Returns the new positions if a cursor was spawned.
pub fn spawn(cur: &Cursor, text: &Rope) -> Option<Vec<Position>> {
- if let Some((end, Some(start))) = cur.positions().rev().next() {
- let found = {
- let mut found = None;
+ if let Some((end, Some(start))) = cur.positions().rev().next() {
+ let found = {
+ let mut found = None;
- let sel = super::select((start, end));
- let sel_text = text.slice(sel.clone());
+ let sel = super::select((start, end));
+ let sel_text = text.slice(sel.clone());
- let mut sel_candidate_idx = 0; // expected char
- for (search_idx, char) in text.slice(sel.end..).chars().enumerate() {
- if sel_text.char(sel_candidate_idx) == char {
- // Found match, expect the next char.
- sel_candidate_idx += 1;
- } else {
- // We expected another char, reset the progress.
- sel_candidate_idx = 0;
- }
+ let mut sel_candidate_idx = 0; // expected char
+ for (search_idx, char) in text.slice(sel.end..).chars().enumerate() {
+ if sel_text.char(sel_candidate_idx) == char {
+ // Found match, expect the next char.
+ sel_candidate_idx += 1;
+ } else {
+ // We expected another char, reset the progress.
+ sel_candidate_idx = 0;
+ }
- if sel_candidate_idx == sel_text.len_chars() {
- // We found all chars of the selection.
- let found_start = sel.end + search_idx + 1 - sel_text.len_chars();
- let found_end = sel.end + search_idx + 1;
- found = Some(
- if end > start {(
- found_start,
- Some(found_end)
- )} else {(
- found_end,
- Some(found_start)
- )}
- );
- break
- }
- }
+ if sel_candidate_idx == sel_text.len_chars() {
+ // We found all chars of the selection.
+ let found_start = sel.end + search_idx + 1 - sel_text.len_chars();
+ let found_end = sel.end + search_idx + 1;
+ found = Some(
+ if end > start {(
+ found_start,
+ Some(found_end)
+ )} else {(
+ found_end,
+ Some(found_start)
+ )}
+ );
+ break
+ }
+ }
- found
- };
+ found
+ };
- found.map(|found| {
- let found_iter = [found];
- let found_iter = found_iter.iter().map(|idx| *idx);
- cur.positions().chain(found_iter).collect::<Vec<_>>()
- })
- } else {
- None
- }
+ found.map(|found| {
+ let found_iter = [found];
+ let found_iter = found_iter.iter().map(|idx| *idx);
+ cur.positions().chain(found_iter).collect::<Vec<_>>()
+ })
+ } else {
+ None
+ }
}
pub fn die(cur: &Cursor) -> Option<Vec<Position>> {
- if cur.positions().len() > 1 {
- let positions = {
- let mut positions = cur.positions();
- positions.next_back();
- positions.collect()
- };
- Some(positions)
- } else {
- None
- }
+ if cur.positions().len() > 1 {
+ let positions = {
+ let mut positions = cur.positions();
+ positions.next_back();
+ positions.collect()
+ };
+ Some(positions)
+ } else {
+ None
+ }
}
pub fn skip(cur: &Cursor, text: &Rope) -> Option<Vec<Position>> {
- spawn(cur, text).map(|mut positions| {
- let len = positions.len();
- positions.remove(len - 2);
- positions
- })
+ spawn(cur, text).map(|mut positions| {
+ let len = positions.len();
+ positions.remove(len - 2);
+ positions
+ })
}
pub fn select_words(cur: &Cursor, text: &Rope) -> Vec<Position> {
- // XXX allocate each line only once for all cursors on it or avoid allocation altogether
- cur.positions()
- .map(|pos| match pos {
- (char_idx, None) => select_word(text, char_idx),
- (_, Some(sel_start)) => select_word(text, sel_start)
- })
- .map(|sel| (sel.end, Some(sel.start)))
- .collect()
+ // XXX allocate each line only once for all cursors on it or avoid allocation altogether
+ cur.positions()
+ .map(|pos| match pos {
+ (char_idx, None) => select_word(text, char_idx),
+ (_, Some(sel_start)) => select_word(text, sel_start)
+ })
+ .map(|sel| (sel.end, Some(sel.start)))
+ .collect()
}
fn select_word(text: &Rope, char_idx: usize) -> Range<usize> {
- let line_idx = text.char_to_line(char_idx);
- if line_idx == text.len_lines() {
- return char_idx..char_idx
- }
+ let line_idx = text.char_to_line(char_idx);
+ if line_idx == text.len_lines() {
+ return char_idx..char_idx
+ }
- let line_char_idx = text.line_to_char(line_idx);
- let line = text.line(line_idx);
- let line_string = line.to_string();
- let x = char_idx - line_char_idx;
+ let line_char_idx = text.line_to_char(line_idx);
+ let line = text.line(line_idx);
+ let line_string = line.to_string();
+ let x = char_idx - line_char_idx;
- let mut sel = x..line.len_chars();
- for (idx, _) in line_string.split_word_bound_indices() {
- let idx = line_string[..idx].chars().count();
- if idx > x {
- sel.end = idx;
- break
- } else {
- sel.start = idx;
- }
- }
- sel.start += line_char_idx;
- sel.end += line_char_idx;
- sel
+ let mut sel = x..line.len_chars();
+ for (idx, _) in line_string.split_word_bound_indices() {
+ let idx = line_string[..idx].chars().count();
+ if idx > x {
+ sel.end = idx;
+ break
+ } else {
+ sel.start = idx;
+ }
+ }
+ sel.start += line_char_idx;
+ sel.end += line_char_idx;
+ sel
}
pub fn select_lines(cur: &Cursor, text: &Rope) -> Vec<Position> {
- cur.positions()
- .map(|(char_idx, _)| select_line(text, char_idx))
- .map(|sel| (sel.end, Some(sel.start)))
- .collect()
+ cur.positions()
+ .map(|(char_idx, _)| select_line(text, char_idx))
+ .map(|sel| (sel.end, Some(sel.start)))
+ .collect()
}
fn select_line(text: &Rope, char_idx: usize) -> Range<usize> {
- let line_idx = text.char_to_line(char_idx);
- if line_idx < text.len_lines() {
- text.line_to_char(line_idx)..text.line_to_char(line_idx + 1)
- } else {
- char_idx..char_idx
- }
+ let line_idx = text.char_to_line(char_idx);
+ if line_idx < text.len_lines() {
+ text.line_to_char(line_idx)..text.line_to_char(line_idx + 1)
+ } else {
+ char_idx..char_idx
+ }
}
struct Chars<'a> {
- text: RopeSlice<'a>,
- char_idx: usize,
- popped: usize
+ text: RopeSlice<'a>,
+ char_idx: usize,
+ popped: usize
}
impl<'a> Chars<'a> {
- #[allow(dead_code)]
- pub fn from_rope(text: &'a Rope) -> Self {
- Self {
- text: text.slice(..),
- char_idx: 0,
- popped: 0
- }
- }
+ #[allow(dead_code)]
+ pub fn from_rope(text: &'a Rope) -> Self {
+ Self {
+ text: text.slice(..),
+ char_idx: 0,
+ popped: 0
+ }
+ }
- pub fn from_slice(text: RopeSlice<'a>) -> Self {
- Self {
- text: text,
- char_idx: 0,
- popped: 0
- }
- }
+ pub fn from_slice(text: RopeSlice<'a>) -> Self {
+ Self {
+ text: text,
+ char_idx: 0,
+ popped: 0
+ }
+ }
}
impl<'a> Iterator for Chars<'a> {
- type Item = char;
+ type Item = char;
- fn next(&mut self) -> Option<Self::Item> {
- let idx = self.char_idx;
- if idx < self.text.len_chars() - self.popped {
- let char = self.text.char(idx);
- self.char_idx += 1;
- Some(char)
- } else {
- None
- }
- }
+ fn next(&mut self) -> Option<Self::Item> {
+ let idx = self.char_idx;
+ if idx < self.text.len_chars() - self.popped {
+ let char = self.text.char(idx);
+ self.char_idx += 1;
+ Some(char)
+ } else {
+ None
+ }
+ }
}
impl<'a> DoubleEndedIterator for Chars<'a> {
- fn next_back(&mut self) -> Option<Self::Item> {
- let len_chars = self.text.len_chars();
+ fn next_back(&mut self) -> Option<Self::Item> {
+ let len_chars = self.text.len_chars();
- if self.popped < len_chars {
- let idx = len_chars - 1 - self.popped;
- if idx > self.char_idx {
- let char = self.text.char(idx);
- self.popped += 1;
- Some(char)
- } else {
- None
- }
- } else {
- None
- }
- }
+ if self.popped < len_chars {
+ let idx = len_chars - 1 - self.popped;
+ if idx > self.char_idx {
+ let char = self.text.char(idx);
+ self.popped += 1;
+ Some(char)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ }
}
impl<'a> ExactSizeIterator for Chars<'a> {
- fn len(&self) -> usize {
- self.text.len_chars()
- }
+ fn len(&self) -> usize {
+ self.text.len_chars()
+ }
}
#[cfg(test)]
mod tests {
- use ropey::Rope;
+ use ropey::Rope;
- const TEXT: &str = concat!(
- "1st\n",
- "2nd line\n",
- "3rd line"
- );
+ const TEXT: &str = concat!(
+ "1st\n",
+ "2nd line\n",
+ "3rd line"
+ );
- #[test]
- fn right() {{
- let text = Rope::from_str(TEXT);
- let len_chars = text.len_chars();
- assert_eq!(1, super::right(&text, 0));
- assert_eq!(len_chars - 1, super::right(&text, len_chars - 2));
- assert_eq!(len_chars, super::right(&text, len_chars - 1));
- assert_eq!(len_chars, super::right(&text, len_chars));
- } {
- let text = Rope::from_str("");
- assert_eq!(0, super::right(&text, 0));
- }}
+ #[test]
+ fn right() {{
+ let text = Rope::from_str(TEXT);
+ let len_chars = text.len_chars();
+ assert_eq!(1, super::right(&text, 0));
+ assert_eq!(len_chars - 1, super::right(&text, len_chars - 2));
+ assert_eq!(len_chars, super::right(&text, len_chars - 1));
+ assert_eq!(len_chars, super::right(&text, len_chars));
+ } {
+ let text = Rope::from_str("");
+ assert_eq!(0, super::right(&text, 0));
+ }}
- #[test]
- fn left() {
- assert_eq!(0, super::left(0));
- assert_eq!(0, super::left(1));
- assert_eq!(1, super::left(2));
- }
+ #[test]
+ fn left() {
+ assert_eq!(0, super::left(0));
+ assert_eq!(0, super::left(1));
+ assert_eq!(1, super::left(2));
+ }
- #[test]
- fn down() {{
- let text = Rope::from_str(TEXT);
- assert_eq!(text.line_to_char(1), super::vertical(&text, text.line_to_char(0), 1));
- assert_eq!(text.line_to_char(2), super::vertical(&text, text.line_to_char(1), 1));
- assert_eq!(text.line_to_char(2), super::vertical(&text, text.line_to_char(2), 1));
- assert_eq!(text.line_to_char(2) + 3, super::vertical(&text, text.line_to_char(1) + 3, 1));
- assert_eq!(text.len_chars(), super::vertical(&text, text.line_to_char(2) - 1, 1));
- } {
- let text = Rope::from_str("");
- assert_eq!(0, super::vertical(&text, 0, 1));
- }}
+ #[test]
+ fn down() {{
+ let text = Rope::from_str(TEXT);
+ assert_eq!(text.line_to_char(1), super::vertical(&text, text.line_to_char(0), 1));
+ assert_eq!(text.line_to_char(2), super::vertical(&text, text.line_to_char(1), 1));
+ assert_eq!(text.line_to_char(2), super::vertical(&text, text.line_to_char(2), 1));
+ assert_eq!(text.line_to_char(2) + 3, super::vertical(&text, text.line_to_char(1) + 3, 1));
+ assert_eq!(text.len_chars(), super::vertical(&text, text.line_to_char(2) - 1, 1));
+ } {
+ let text = Rope::from_str("");
+ assert_eq!(0, super::vertical(&text, 0, 1));
+ }}
- #[test]
- fn up() {{
- let text = Rope::from_str(TEXT);
- assert_eq!(text.line_to_char(1), super::vertical(&text, text.line_to_char(2), -1));
- assert_eq!(text.line_to_char(0), super::vertical(&text, text.line_to_char(1), -1));
- assert_eq!(text.line_to_char(0), super::vertical(&text, text.line_to_char(0), -1));
- assert_eq!(text.line_to_char(0) + 3, super::vertical(&text, text.line_to_char(1) + 3, -1));
- assert_eq!(text.line_to_char(1) - 1, super::vertical(&text, text.line_to_char(2) - 2, -1));
- } {
- let text = Rope::from_str("");
- assert_eq!(0, super::vertical(&text, 0, -1));
- }}
+ #[test]
+ fn up() {{
+ let text = Rope::from_str(TEXT);
+ assert_eq!(text.line_to_char(1), super::vertical(&text, text.line_to_char(2), -1));
+ assert_eq!(text.line_to_char(0), super::vertical(&text, text.line_to_char(1), -1));
+ assert_eq!(text.line_to_char(0), super::vertical(&text, text.line_to_char(0), -1));
+ assert_eq!(text.line_to_char(0) + 3, super::vertical(&text, text.line_to_char(1) + 3, -1));
+ assert_eq!(text.line_to_char(1) - 1, super::vertical(&text, text.line_to_char(2) - 2, -1));
+ } {
+ let text = Rope::from_str("");
+ assert_eq!(0, super::vertical(&text, 0, -1));
+ }}
- #[test]
- fn line_start() {{
- let text = Rope::from_str(TEXT);
- assert_eq!(text.line_to_char(1), super::line_start(&text, 12));
- assert_eq!(text.line_to_char(1), super::line_start(&text, text.line_to_char(1)));
- } {
- let text = Rope::from_str("");
- assert_eq!(0, super::line_start(&text, 0));
- }}
+ #[test]
+ fn line_start() {{
+ let text = Rope::from_str(TEXT);
+ assert_eq!(text.line_to_char(1), super::line_start(&text, 12));
+ assert_eq!(text.line_to_char(1), super::line_start(&text, text.line_to_char(1)));
+ } {
+ let text = Rope::from_str("");
+ assert_eq!(0, super::line_start(&text, 0));
+ }}
- #[test]
- fn line_end() {{
- let text = Rope::from_str(TEXT);
- assert_eq!(text.line_to_char(2) - 1, super::line_end(&text, 12));
- assert_eq!(text.line_to_char(2) - 1, super::line_end(&text, text.line_to_char(2) - 1));
- } {
- let text = Rope::from_str("");
- assert_eq!(0, super::line_end(&text, 0));
- }}
+ #[test]
+ fn line_end() {{
+ let text = Rope::from_str(TEXT);
+ assert_eq!(text.line_to_char(2) - 1, super::line_end(&text, 12));
+ assert_eq!(text.line_to_char(2) - 1, super::line_end(&text, text.line_to_char(2) - 1));
+ } {
+ let text = Rope::from_str("");
+ assert_eq!(0, super::line_end(&text, 0));
+ }}
- #[test]
- fn word_left() {{
- let text = Rope::from_str(TEXT);
- let len_chars = text.len_chars();
- assert_eq!(len_chars - 4, super::word_left(&text, len_chars));
- assert_eq!(len_chars - 8, super::word_left(&text, len_chars - 4));
- } {
- let text = Rope::from_str("");
- assert_eq!(0, super::word_left(&text, 0));
- }}
+ #[test]
+ fn word_left() {{
+ let text = Rope::from_str(TEXT);
+ let len_chars = text.len_chars();
+ assert_eq!(len_chars - 4, super::word_left(&text, len_chars));
+ assert_eq!(len_chars - 8, super::word_left(&text, len_chars - 4));
+ } {
+ let text = Rope::from_str("");
+ assert_eq!(0, super::word_left(&text, 0));
+ }}
- #[test]
- fn word_right() {{
- let text = Rope::from_str(TEXT);
- let len_chars = text.len_chars();
- assert_eq!(len_chars - 5, super::word_right(&text, len_chars - 8));
- assert_eq!(len_chars, super::word_right(&text, len_chars - 5));
- } {
- let text = Rope::from_str("");
- assert_eq!(0, super::word_right(&text, 0));
- }}
+ #[test]
+ fn word_right() {{
+ let text = Rope::from_str(TEXT);
+ let len_chars = text.len_chars();
+ assert_eq!(len_chars - 5, super::word_right(&text, len_chars - 8));
+ assert_eq!(len_chars, super::word_right(&text, len_chars - 5));
+ } {
+ let text = Rope::from_str("");
+ assert_eq!(0, super::word_right(&text, 0));
+ }}
- #[test]
- fn select_word() {{
- let text = Rope::from_str("\none two three");
- assert_eq!(5..8, super::select_word(&text, 5));
- assert_eq!(5..8, super::select_word(&text, 6));
- assert_eq!(5..8, super::select_word(&text, 7));
- assert_ne!(5..8, super::select_word(&text, 8));
- assert_eq!(9..14, super::select_word(&text, 9));
- assert_eq!(9..14, super::select_word(&text, 10));
- assert_eq!(9..14, super::select_word(&text, 11));
- assert_eq!(9..14, super::select_word(&text, 12));
- assert_eq!(9..14, super::select_word(&text, 13));
- assert_ne!(9..14, super::select_word(&text, 14));
- } {
- let text = Rope::from_str("");
- assert_eq!(0..0, super::select_word(&text, 0));
- }}
+ #[test]
+ fn select_word() {{
+ let text = Rope::from_str("\none two three");
+ assert_eq!(5..8, super::select_word(&text, 5));
+ assert_eq!(5..8, super::select_word(&text, 6));
+ assert_eq!(5..8, super::select_word(&text, 7));
+ assert_ne!(5..8, super::select_word(&text, 8));
+ assert_eq!(9..14, super::select_word(&text, 9));
+ assert_eq!(9..14, super::select_word(&text, 10));
+ assert_eq!(9..14, super::select_word(&text, 11));
+ assert_eq!(9..14, super::select_word(&text, 12));
+ assert_eq!(9..14, super::select_word(&text, 13));
+ assert_ne!(9..14, super::select_word(&text, 14));
+ } {
+ let text = Rope::from_str("");
+ assert_eq!(0..0, super::select_word(&text, 0));
+ }}
}
M src/cursor/mod.rs +272 -272
@@ 1,21 1,21 @@
pub mod jump;
use {
- Ropey,
- frappe::Signal,
- ropey::Rope,
- std::ops::Range,
- buffer::{
- self,
- Buffer
- }
+ Ropey,
+ frappe::Signal,
+ ropey::Rope,
+ std::ops::Range,
+ buffer::{
+ self,
+ Buffer
+ }
};
/// Imagine a cursor as the primary actor through which to edit a buffer.
/// A cursor can have multiple positions, aka multi-cursor.
pub struct Cursor {
- positions: Vec<PositionSignal>
- // TODO Insert/overwrite mode?
+ positions: Vec<PositionSignal>
+ // TODO Insert/overwrite mode?
}
type PositionSignal = (Signal<usize>, Option<Signal<usize>>);
@@ 24,327 24,327 @@ type PositionSignal = (Signal<usize>, Op
pub type Position = (usize, Option<usize>);
impl Cursor {
- pub fn new<'a, I>(buf: &Buffer, positions: I) -> Self
- where I: IntoIterator<Item=&'a Position> {
- let mut cur = Self {
- positions: Vec::new()
- };
- cur.set_positions(buf, positions);
- cur
- }
+ pub fn new<'a, I>(buf: &Buffer, positions: I) -> Self
+ where I: IntoIterator<Item=&'a Position> {
+ let mut cur = Self {
+ positions: Vec::new()
+ };
+ cur.set_positions(buf, positions);
+ cur
+ }
- pub fn positions(&self) -> Positions {
- Positions::new(self.positions.as_slice())
- }
+ pub fn positions(&self) -> Positions {
+ Positions::new(self.positions.as_slice())
+ }
- /// Handles a mutation of the cursor, leaving the buffer unchanged.
- pub fn handle_cur(&mut self, buf: &Buffer, event: CursorEvent) {
- match event {
- CursorEvent::Jump(mut positions) => {
- positions.sort();
- positions.dedup();
+ /// Handles a mutation of the cursor, leaving the buffer unchanged.
+ pub fn handle_cur(&mut self, buf: &Buffer, event: CursorEvent) {
+ match event {
+ CursorEvent::Jump(mut positions) => {
+ positions.sort();
+ positions.dedup();
- self.set_positions(buf, positions.iter());
- }
- }
- }
+ self.set_positions(buf, positions.iter());
+ }
+ }
+ }
- /// Handles a mutation of the buffer.
- pub fn handle_buf(&mut self, buf: &mut Buffer, event: BufferEvent) {
- let pos_lines = |text: &Rope, pos: Position| -> Range<usize> {
- let (char_idx, sel_start) = pos;
+ /// Handles a mutation of the buffer.
+ pub fn handle_buf(&mut self, buf: &mut Buffer, event: BufferEvent) {
+ let pos_lines = |text: &Rope, pos: Position| -> Range<usize> {
+ let (char_idx, sel_start) = pos;
- if let Some(sel_start) = sel_start {
- let sel = self::select((sel_start, char_idx));
+ if let Some(sel_start) = sel_start {
+ let sel = self::select((sel_start, char_idx));
- text.char_to_line(sel.start)..
- (text.char_to_line(sel.end) + 1)
- .min(text.len_lines())
- } else {
- let line = text.char_to_line(char_idx)
- .min(text.len_lines().saturating_sub(1));
- line..line + 1
- }
- };
+ text.char_to_line(sel.start)..
+ (text.char_to_line(sel.end) + 1)
+ .min(text.len_lines())
+ } else {
+ let line = text.char_to_line(char_idx)
+ .min(text.len_lines().saturating_sub(1));
+ line..line + 1
+ }
+ };
- match event {
- BufferEvent::Insert(ref text) => {
- for &mut (ref char_idx_signal, ref mut sel_start) in &mut self.positions {
- if sel_start.is_some() { // delete and clear selection
- let start_idx = sel_start.as_ref().unwrap().sample();
- let end_idx = char_idx_signal.sample();
- *sel_start = None;
+ match event {
+ BufferEvent::Insert(ref text) => {
+ for &mut (ref char_idx_signal, ref mut sel_start) in &mut self.positions {
+ if sel_start.is_some() { // delete and clear selection
+ let start_idx = sel_start.as_ref().unwrap().sample();
+ let end_idx = char_idx_signal.sample();
+ *sel_start = None;
- buf.handle(buffer::Event::Remove {
- char_idx_range: select((start_idx, end_idx))
- });
- }
+ buf.handle(buffer::Event::Remove {
+ char_idx_range: select((start_idx, end_idx))
+ });
+ }
- buf.handle(buffer::Event::Insert {
- char_idx: char_idx_signal.sample(),
- text: text.clone()
- });
- }
- }
- BufferEvent::Backspace => {
- for &mut (ref char_idx_signal, ref mut sel_start) in &mut self.positions {
- let char_idx = char_idx_signal.sample();
- if sel_start.is_some() {
- let start_idx = sel_start.as_ref().unwrap().sample();
- *sel_start = None;
+ buf.handle(buffer::Event::Insert {
+ char_idx: char_idx_signal.sample(),
+ text: text.clone()
+ });
+ }
+ }
+ BufferEvent::Backspace => {
+ for &mut (ref char_idx_signal, ref mut sel_start) in &mut self.positions {
+ let char_idx = char_idx_signal.sample();
+ if sel_start.is_some() {
+ let start_idx = sel_start.as_ref().unwrap().sample();
+ *sel_start = None;
- buf.handle(buffer::Event::Remove {
- char_idx_range: select((start_idx, char_idx))
- });
- } else if let (char_idx, false) = char_idx.overflowing_sub(1) {
- buf.handle(buffer::Event::Remove {
- char_idx_range: char_idx..char_idx + 1
- });
- }
- }
- }
- BufferEvent::Delete => {
- for &mut (ref char_idx_signal, ref mut sel_start) in &mut self.positions {
- let char_idx = char_idx_signal.sample();
- if sel_start.is_some() {
- let start_idx = sel_start.as_ref().unwrap().sample();
- *sel_start = None;
+ buf.handle(buffer::Event::Remove {
+ char_idx_range: select((start_idx, char_idx))
+ });
+ } else if let (char_idx, false) = char_idx.overflowing_sub(1) {
+ buf.handle(buffer::Event::Remove {
+ char_idx_range: char_idx..char_idx + 1
+ });
+ }
+ }
+ }
+ BufferEvent::Delete => {
+ for &mut (ref char_idx_signal, ref mut sel_start) in &mut self.positions {
+ let char_idx = char_idx_signal.sample();
+ if sel_start.is_some() {
+ let start_idx = sel_start.as_ref().unwrap().sample();
+ *sel_start = None;
- buf.handle(buffer::Event::Remove {
- char_idx_range: select((start_idx, char_idx))
- });
- } else if char_idx < buf.text().len_chars() {
- buf.handle(buffer::Event::Remove {
- char_idx_range: char_idx..char_idx + 1
- });
- }
- }
- }
- BufferEvent::Indent(indent) => if buf.text().len_chars() == 0 {
- /* An empty Rope causes weird math afterwards
- * (see docs of `char_to_line()` and `line_to_char()`)
- * so we just handle this simple case here. */
- buf.handle(buffer::Event::Insert {
- char_idx: 0,
- text: indent.sample()
- })
- } else {
- for pos in self.positions() {
- for line_idx in pos_lines(buf.text(), pos) {
- let line_start_idx = buf.text().line_to_char(line_idx);
- buf.handle(buffer::Event::Insert {
- char_idx: line_start_idx,
- text: indent.sample()
- })
- }
- }
- },
- BufferEvent::Dedent(indent) => indent.sample_with(|indent| {
- if buf.text().len_chars() == 0 { return } // nothing to dedent
+ buf.handle(buffer::Event::Remove {
+ char_idx_range: select((start_idx, char_idx))
+ });
+ } else if char_idx < buf.text().len_chars() {
+ buf.handle(buffer::Event::Remove {
+ char_idx_range: char_idx..char_idx + 1
+ });
+ }
+ }
+ }
+ BufferEvent::Indent(indent) => if buf.text().len_chars() == 0 {
+ /* An empty Rope causes weird math afterwards
+ * (see docs of `char_to_line()` and `line_to_char()`)
+ * so we just handle this simple case here. */
+ buf.handle(buffer::Event::Insert {
+ char_idx: 0,
+ text: indent.sample()
+ })
+ } else {
+ for pos in self.positions() {
+ for line_idx in pos_lines(buf.text(), pos) {
+ let line_start_idx = buf.text().line_to_char(line_idx);
+ buf.handle(buffer::Event::Insert {
+ char_idx: line_start_idx,
+ text: indent.sample()
+ })
+ }
+ }
+ },
+ BufferEvent::Dedent(indent) => indent.sample_with(|indent| {
+ if buf.text().len_chars() == 0 { return } // nothing to dedent
- for pos in self.positions() {
- for line_idx in pos_lines(buf.text(), pos) {
- if {
- let line = buf.text().line(line_idx);
- line.starts_with(&indent)
- } {
- let line_start_idx = buf.text().line_to_char(line_idx);
- buf.handle(buffer::Event::Remove {
- char_idx_range:
- line_start_idx..
- line_start_idx + indent.chars().count()
- });
- }
- }
- }
- })
- }
- }
+ for pos in self.positions() {
+ for line_idx in pos_lines(buf.text(), pos) {
+ if {
+ let line = buf.text().line(line_idx);
+ line.starts_with(&indent)
+ } {
+ let line_start_idx = buf.text().line_to_char(line_idx);
+ buf.handle(buffer::Event::Remove {
+ char_idx_range:
+ line_start_idx..
+ line_start_idx + indent.chars().count()
+ });
+ }
+ }
+ }
+ })
+ }
+ }
- fn set_positions<'a, I>(&mut self, buf: &Buffer, positions: I)
- where I: IntoIterator<Item=&'a Position> {
- let positions = positions.into_iter()
- .map(|&(char_idx, sel_start)| (
- buf.signal_char_idx(char_idx),
- sel_start.map(|sel_start| buf.signal_char_idx(sel_start))
- ));
+ fn set_positions<'a, I>(&mut self, buf: &Buffer, positions: I)
+ where I: IntoIterator<Item=&'a Position> {
+ let positions = positions.into_iter()
+ .map(|&(char_idx, sel_start)| (
+ buf.signal_char_idx(char_idx),
+ sel_start.map(|sel_start| buf.signal_char_idx(sel_start))
+ ));
- self.positions.clear();
- self.positions.extend(positions);
- }
+ self.positions.clear();
+ self.positions.extend(positions);
+ }
}
pub fn select(range: (usize, usize)) -> Range<usize> {
- let (start_idx, end_idx) = range;
- if start_idx < end_idx {
- start_idx..end_idx
- } else {
- end_idx..start_idx
- }
+ let (start_idx, end_idx) = range;
+ if start_idx < end_idx {
+ start_idx..end_idx
+ } else {
+ end_idx..start_idx
+ }
}
/// Unused in this module but provided for completeness.
pub enum Event {
- Cur(CursorEvent),
- Buf(BufferEvent)
+ Cur(CursorEvent),
+ Buf(BufferEvent)
}
/// An event that mutates the cursor.
pub enum CursorEvent {
- /// Positions will be deduped for you.
- Jump(Vec<Position>)
+ /// Positions will be deduped for you.
+ Jump(Vec<Position>)
}
/// An event that mutates the buffer.
pub enum BufferEvent {
- Insert(String),
- Backspace,
- Delete,
- Indent(Signal<String>), Dedent(Signal<String>)
+ Insert(String),
+ Backspace,
+ Delete,
+ Indent(Signal<String>), Dedent(Signal<String>)
}
pub struct Positions<'a> {
- items: &'a [PositionSignal],
- idx: usize,
- idx_back: usize
+ items: &'a [PositionSignal],
+ idx: usize,
+ idx_back: usize
}
impl<'a> Positions<'a> {
- pub fn new(positions: &'a [PositionSignal]) -> Self {
- Positions {
- items: positions,
- idx: 0,
- idx_back: 0
- }
- }
+ pub fn new(positions: &'a [PositionSignal]) -> Self {
+ Positions {
+ items: positions,
+ idx: 0,
+ idx_back: 0
+ }
+ }
}
impl<'a> Iterator for Positions<'a> {
- type Item = Position;
+ type Item = Position;
- fn next(&mut self) -> Option<Self::Item> {
- if self.idx < self.items.len() - self.idx_back {
- let item = &self.items[self.idx];
- self.idx += 1;
- Some((
- item.0.sample(),
- match item.1 {
- Some(ref signal) => Some(signal.sample()),
- None => None
- }
- ))
- } else {
- None
- }
- }
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.idx < self.items.len() - self.idx_back {
+ let item = &self.items[self.idx];
+ self.idx += 1;
+ Some((
+ item.0.sample(),
+ match item.1 {
+ Some(ref signal) => Some(signal.sample()),
+ None => None
+ }
+ ))
+ } else {
+ None
+ }
+ }
}
impl<'a> DoubleEndedIterator for Positions<'a> {
- fn next_back(&mut self) -> Option<Self::Item> {
- let len = self.items.len();
+ fn next_back(&mut self) -> Option<Self::Item> {
+ let len = self.items.len();
- if self.idx_back < len - self.idx {
- let item = &self.items[len - 1 - self.idx_back];
- self.idx_back += 1;
- Some((
- item.0.sample(),
- match item.1 {
- Some(ref signal) => Some(signal.sample()),
- None => None
- }
- ))
- } else {
- None
- }
- }
+ if self.idx_back < len - self.idx {
+ let item = &self.items[len - 1 - self.idx_back];
+ self.idx_back += 1;
+ Some((
+ item.0.sample(),
+ match item.1 {
+ Some(ref signal) => Some(signal.sample()),
+ None => None
+ }
+ ))
+ } else {
+ None
+ }
+ }
}
impl<'a> ExactSizeIterator for Positions<'a> {
- fn len(&self) -> usize {
- self.items.len()
- }
+ fn len(&self) -> usize {
+ self.items.len()
+ }
}
#[cfg(test)]
mod tests {
- use super::*;
+ use super::*;
- const TEXT: &str = concat!(
- "This is line number 1\n",
- "Soon follows line number 2\n",
- "Which, in turn, is followed by line number 3\n",
- "In close call to line number 4\n",
- "Within proximity of line number 5\n",
- "Right beside line number 6\n"
- );
+ const TEXT: &str = concat!(
+ "This is line number 1\n",
+ "Soon follows line number 2\n",
+ "Which, in turn, is followed by line number 3\n",
+ "In close call to line number 4\n",
+ "Within proximity of line number 5\n",
+ "Right beside line number 6\n"
+ );
- #[test]
- fn new() {
- let mut buf = Buffer::from_str(TEXT);
- let positions: Vec<Position> = TEXT.lines().enumerate()
- .map(|(line_idx, _)| (buf.text().line_to_char(line_idx), None))
- .collect();
- let cur = Cursor::new(&mut buf, positions.iter());
+ #[test]
+ fn new() {
+ let mut buf = Buffer::from_str(TEXT);
+ let positions: Vec<Position> = TEXT.lines().enumerate()
+ .map(|(line_idx, _)| (buf.text().line_to_char(line_idx), None))
+ .collect();
+ let cur = Cursor::new(&mut buf, positions.iter());
- assert_eq!(positions, cur.positions().collect::<Vec<_>>());
- }
+ assert_eq!(positions, cur.positions().collect::<Vec<_>>());
+ }
- #[test]
- fn insert() {
- let mut buf = Buffer::from_str(TEXT);
- let mut cur = Cursor::new(&mut buf, &[(0, None)]);
+ #[test]
+ fn insert() {
+ let mut buf = Buffer::from_str(TEXT);
+ let mut cur = Cursor::new(&mut buf, &[(0, None)]);
- const CHARS: [char; 17] = [
- 'L', 'i', 'n', 'e', 's', ' ',
- 'b', 'e', 'g', 'i', 'n', ' ',
- 'a', 't', ' ', '0', '\n'
- ];
- for char in &CHARS {
- cur.handle_buf(&mut buf, BufferEvent::Insert(char.to_string()));
- }
- assert_eq!(CHARS.iter().collect::<String>(), buf.text().line(0).to_string());
+ const CHARS: [char; 17] = [
+ 'L', 'i', 'n', 'e', 's', ' ',
+ 'b', 'e', 'g', 'i', 'n', ' ',
+ 'a', 't', ' ', '0', '\n'
+ ];
+ for char in &CHARS {
+ cur.handle_buf(&mut buf, BufferEvent::Insert(char.to_string()));
+ }
+ assert_eq!(CHARS.iter().collect::<String>(), buf.text().line(0).to_string());
- const LINE_IDC: [usize; 2] = [0, 1];
- let positions = LINE_IDC.iter()
- .map(|line_idx| (buf.text().line_to_char(*line_idx), None))
- .collect::<Vec<_>>();
- cur.set_positions(&mut buf, &positions);
- const PREFIX: &str = "->";
- cur.handle_buf(&mut buf, BufferEvent::Insert(PREFIX.to_owned()));
- for line_idx in &LINE_IDC {
- assert!(buf.text().line(*line_idx).to_string().starts_with(PREFIX));
- }
- }
+ const LINE_IDC: [usize; 2] = [0, 1];
+ let positions = LINE_IDC.iter()
+ .map(|line_idx| (buf.text().line_to_char(*line_idx), None))
+ .collect::<Vec<_>>();
+ cur.set_positions(&mut buf, &positions);
+ const PREFIX: &str = "->";
+ cur.handle_buf(&mut buf, BufferEvent::Insert(PREFIX.to_owned()));
+ for line_idx in &LINE_IDC {
+ assert!(buf.text().line(*line_idx).to_string().starts_with(PREFIX));
+ }
+ }
- #[test]
- fn backspace() {
- let mut buf = Buffer::from_str(TEXT);
- let mut cur = Cursor::new(&mut buf, &[(0, None)]);
+ #[test]
+ fn backspace() {
+ let mut buf = Buffer::from_str(TEXT);
+ let mut cur = Cursor::new(&mut buf, &[(0, None)]);
- cur.handle_buf(&mut buf, BufferEvent::Backspace);
- assert_eq!("This is line number 1\n", buf.text().line(0).to_string());
+ cur.handle_buf(&mut buf, BufferEvent::Backspace);
+ assert_eq!("This is line number 1\n", buf.text().line(0).to_string());
- let positions = [(buf.text().len_chars(), None)];
- cur.set_positions(&mut buf, &positions);
- cur.handle_buf(&mut buf, BufferEvent::Backspace);
- let line_idx = buf.text().len_lines() - 1;
- assert_eq!("Right beside line number 6", buf.text().line(line_idx).to_string());
+ let positions = [(buf.text().len_chars(), None)];
+ cur.set_positions(&mut buf, &positions);
+ cur.handle_buf(&mut buf, BufferEvent::Backspace);
+ let line_idx = buf.text().len_lines() - 1;
+ assert_eq!("Right beside line number 6", buf.text().line(line_idx).to_string());
- for _ in 0..9 {
- cur.handle_buf(&mut buf, BufferEvent::Backspace);
- }
- assert_eq!("Right beside line", buf.text().line(line_idx).to_string());
+ for _ in 0..9 {
+ cur.handle_buf(&mut buf, BufferEvent::Backspace);
+ }
+ assert_eq!("Right beside line", buf.text().line(line_idx).to_string());
- const LINE_IDC: [usize; 2] = [2, 3];
- let positions = LINE_IDC.iter()
- // set cursor to end of line
- .map(|line_idx| (buf.text().line_to_char(*line_idx + 1) - 1, None))
- .collect::<Vec<_>>();
- cur.set_positions(&mut buf, &positions);
- cur.handle_buf(&mut buf, BufferEvent::Backspace);
- cur.handle_buf(&mut buf, BufferEvent::Backspace);
- for line_idx in &LINE_IDC {
- assert!(buf.text().line(*line_idx).to_string().ends_with("line number\n"));
- }
- }
+ const LINE_IDC: [usize; 2] = [2, 3];
+ let positions = LINE_IDC.iter()
+ // set cursor to end of line
+ .map(|line_idx| (buf.text().line_to_char(*line_idx + 1) - 1, None))
+ .collect::<Vec<_>>();
+ cur.set_positions(&mut buf, &positions);
+ cur.handle_buf(&mut buf, BufferEvent::Backspace);
+ cur.handle_buf(&mut buf, BufferEvent::Backspace);
+ for line_idx in &LINE_IDC {
+ assert!(buf.text().line(*line_idx).to_string().ends_with("line number\n"));
+ }
+ }
}
M src/history.rs +87 -87
@@ 1,121 1,121 @@
/// If an Undo's Undo is itself an Undo,
/// its undo() must produce the initial Undo. :P
pub trait Undo {
- type Undo;
+ type Undo;
- fn undo(&self) -> Self::Undo;
+ fn undo(&self) -> Self::Undo;
}
pub struct History<T: Undo> {
- events: Vec<T>,
- len: usize
+ events: Vec<T>,
+ len: usize
}
impl<T: Undo> History<T> {
- pub fn new() -> Self {
- Self {
- events: Vec::new(),
- len: 0
- }
- }
+ pub fn new() -> Self {
+ Self {
+ events: Vec::new(),
+ len: 0
+ }
+ }
- pub fn len(&self) -> usize {
- self.len
- }
+ pub fn len(&self) -> usize {
+ self.len
+ }
- pub fn undone(&self) -> usize {
- self.events.len().saturating_sub(self.len)
- }
+ pub fn undone(&self) -> usize {
+ self.events.len().saturating_sub(self.len)
+ }
- pub fn record(&mut self, event: T) {
- self.events.truncate(self.len);
- self.events.push(event);
- self.len = self.events.len();
- }
+ pub fn record(&mut self, event: T) {
+ self.events.truncate(self.len);
+ self.events.push(event);
+ self.len = self.events.len();
+ }
- pub fn undo(&mut self) -> Result<T::Undo, ()> {
- if self.len > 0 {
- self.len -= 1;
- let undo = self.events[self.len].undo();
- Ok(undo)
- } else {
- Err(())
- }
- }
+ pub fn undo(&mut self) -> Result<T::Undo, ()> {
+ if self.len > 0 {
+ self.len -= 1;
+ let undo = self.events[self.len].undo();
+ Ok(undo)
+ } else {
+ Err(())
+ }
+ }
}
impl<T: Undo + Clone> History<T> {
- pub fn redo(&mut self) -> Result<T, ()> {
- if self.len < self.events.len() {
- let redo = self.events[self.len].clone();
- self.len += 1;
- Ok(redo)
- } else {
- Err(())
- }
- }
+ pub fn redo(&mut self) -> Result<T, ()> {
+ if self.len < self.events.len() {
+ let redo = self.events[self.len].clone();
+ self.len += 1;
+ Ok(redo)
+ } else {
+ Err(())
+ }
+ }
}
#[cfg(test)]
mod tests {
- use super::*;
+ use super::*;
- /// Meaningless test event.
- #[derive(PartialEq, Clone)]
- enum Event {
- Forward,
- Backward
- }
+ /// Meaningless test event.
+ #[derive(PartialEq, Clone)]
+ enum Event {
+ Forward,
+ Backward
+ }
- impl Undo for Event {
- type Undo = Event;
+ impl Undo for Event {
+ type Undo = Event;
- fn undo(&self) -> Self::Undo {
- match *self {
- Event::Forward => Event::Backward,
- Event::Backward => Event::Forward
- }
- }
- }
+ fn undo(&self) -> Self::Undo {
+ match *self {
+ Event::Forward => Event::Backward,
+ Event::Backward => Event::Forward
+ }
+ }
+ }
- #[test]
- fn main() {
- let mut hist = History::new();
+ #[test]
+ fn main() {
+ let mut hist = History::new();
- assert!(hist.undo().is_err());
- assert!(hist.redo().is_err());
+ assert!(hist.undo().is_err());
+ assert!(hist.redo().is_err());
- assert_eq!(0, hist.len());
- assert_eq!(0, hist.undone());
+ assert_eq!(0, hist.len());
+ assert_eq!(0, hist.undone());
- hist.record(Event::Forward);
+ hist.record(Event::Forward);
- assert_eq!(1, hist.len());
- assert_eq!(0, hist.undone());
+ assert_eq!(1, hist.len());
+ assert_eq!(0, hist.undone());
- hist.record(Event::Backward);
+ hist.record(Event::Backward);
- assert_eq!(2, hist.len());
- assert_eq!(0, hist.undone());
+ assert_eq!(2, hist.len());
+ assert_eq!(0, hist.undone());
- assert!(match hist.undo() {
- Result::Ok(Event::Forward) => true,
- _ => false
- });
- assert!(match hist.undo() {
- Result::Ok(Event::Backward) => true,
- _ => false
- });
- assert!(hist.undo().is_err());
+ assert!(match hist.undo() {
+ Result::Ok(Event::Forward) => true,
+ _ => false
+ });
+ assert!(match hist.undo() {
+ Result::Ok(Event::Backward) => true,
+ _ => false
+ });
+ assert!(hist.undo().is_err());
- assert!(match hist.redo() {
- Result::Ok(Event::Forward) => true,
- _ => false
- });
- assert!(match hist.redo() {
- Result::Ok(Event::Backward) => true,
- _ => false
- });
- assert!(hist.redo().is_err());
- }
+ assert!(match hist.redo() {
+ Result::Ok(Event::Forward) => true,
+ _ => false
+ });
+ assert!(match hist.redo() {
+ Result::Ok(Event::Backward) => true,
+ _ => false
+ });
+ assert!(hist.redo().is_err());
+ }
}
M src/lib.rs +17 -17
@@ 12,29 12,29 @@ pub mod cache;
pub mod num;
pub(crate) trait Ropey {
- fn starts_with(&self, prefix: &str) -> bool;
+ fn starts_with(&self, prefix: &str) -> bool;
}
impl Ropey for ropey::Rope {
- fn starts_with(&self, prefix: &str) -> bool {
- Ropey::starts_with(&self.slice(..), prefix)
- }
+ fn starts_with(&self, prefix: &str) -> bool {
+ Ropey::starts_with(&self.slice(..), prefix)
+ }
}
impl<'a> Ropey for ropey::RopeSlice<'a> {
- fn starts_with(&self, prefix: &str) -> bool {
- let prefix_num_chars = prefix.chars().count();
+ fn starts_with(&self, prefix: &str) -> bool {
+ let prefix_num_chars = prefix.chars().count();
- if self.len_chars() < prefix_num_chars {
- return false
- }
+ if self.len_chars() < prefix_num_chars {
+ return false
+ }
- prefix.chars()
- .enumerate()
- .take_while(|&(i, char)| self.char(i) == char)
- .map(|(i, _)| i)
- .max()
- ==
- prefix_num_chars.checked_sub(1)
- }
+ prefix.chars()
+ .enumerate()
+ .take_while(|&(i, char)| self.char(i) == char)
+ .map(|(i, _)| i)
+ .max()
+ ==
+ prefix_num_chars.checked_sub(1)
+ }
}
M src/num.rs +133 -133
@@ 1,185 1,185 @@
macro_rules! ints {
- ($mac:ident) => {
- $mac!(u8);
- $mac!(i8);
- $mac!(u16);
- $mac!(i16);
- $mac!(u32);
- $mac!(i32);
- $mac!(u64);
- $mac!(i64);
- #[cfg(has_u128)]
- $mac!(u128);
- #[cfg(has_i128)]
- $mac!(i128);
- $mac!(usize);
- $mac!(isize);
- }
+ ($mac:ident) => {
+ $mac!(u8);
+ $mac!(i8);
+ $mac!(u16);
+ $mac!(i16);
+ $mac!(u32);
+ $mac!(i32);
+ $mac!(u64);
+ $mac!(i64);
+ #[cfg(has_u128)]
+ $mac!(u128);
+ #[cfg(has_i128)]
+ $mac!(i128);
+ $mac!(usize);
+ $mac!(isize);
+ }
}
pub trait NumDigits {
- type Output;
+ type Output;
- fn num_digits(&self) -> Self::Output;
+ fn num_digits(&self) -> Self::Output;
}
macro_rules! impl_numdigits {
- ($ty:ty) => {
- impl NumDigits for $ty {
- type Output = u8;
+ ($ty:ty) => {
+ impl NumDigits for $ty {
+ type Output = u8;
- fn num_digits(&self) -> Self::Output {
- let mut digits = 1;
- let mut num = *self;
- while num >= 10 {
- num /= 10;
- digits += 1;
- }
- digits
- }
- }
- }
+ fn num_digits(&self) -> Self::Output {
+ let mut digits = 1;
+ let mut num = *self;
+ while num >= 10 {
+ num /= 10;
+ digits += 1;
+ }
+ digits
+ }
+ }
+ }
}
ints!(impl_numdigits);
pub trait Digits: Sized {
- fn digits(&self) -> DigitIter<Self>;
+ fn digits(&self) -> DigitIter<Self>;
}
impl<T> Digits for T where T: Clone {
- fn digits(&self) -> DigitIter<T> {
- DigitIter(self.clone())
- }
+ fn digits(&self) -> DigitIter<T> {
+ DigitIter(self.clone())
+ }
}
pub struct DigitIter<T>(T);
impl<T> DigitIter<T> {
- pub fn str(self) -> DigitStrIter<T> {
- DigitStrIter(self)
- }
+ pub fn str(self) -> DigitStrIter<T> {
+ DigitStrIter(self)
+ }
}
macro_rules! impl_digititer {
- ($ty:ty) => {
- impl Iterator for DigitIter<$ty> {
- type Item = u8;
+ ($ty:ty) => {
+ impl Iterator for DigitIter<$ty> {
+ type Item = u8;
- fn next(&mut self) -> Option<Self::Item> {
- if self.0 == 0 { return None }
+ fn next(&mut self) -> Option<Self::Item> {
+ if self.0 == 0 { return None }
- let digit = self.0 % 10;
- self.0 /= 10;
+ let digit = self.0 % 10;
+ self.0 /= 10;
- Some(digit as u8)
- }
+ Some(digit as u8)
+ }
- fn size_hint(&self) -> (usize, Option<usize>) {
- let num_digits = self.0.num_digits().into();
- (num_digits, Some(num_digits))
- }
- }
- }
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ let num_digits = self.0.num_digits().into();
+ (num_digits, Some(num_digits))
+ }
+ }
+ }
}
ints!(impl_digititer);
impl<T> ExactSizeIterator for DigitIter<T> where
- T: NumDigits<Output=u8>,
- DigitIter<T>: Iterator<Item=u8>
+ T: NumDigits<Output=u8>,
+ DigitIter<T>: Iterator<Item=u8>
{
- fn len(&self) -> usize {
- self.0.num_digits().into()
- }
+ fn len(&self) -> usize {
+ self.0.num_digits().into()
+ }
}
pub struct DigitStrIter<T>(DigitIter<T>);
impl<T> Iterator for DigitStrIter<T> where DigitIter<T>: Iterator<Item=u8> {
- type Item = &'static str;
+ type Item = &'static str;
- fn next(&mut self) -> Option<Self::Item> {
- self.0.next().map(|digit| match &digit {
- 0 => "0",
- 1 => "1",
- 2 => "2",
- 3 => "3",
- 4 => "4",
- 5 => "5",
- 6 => "6",
- 7 => "7",
- 8 => "8",
- 9 => "9",
- _ => unreachable!()
- })
- }
+ fn next(&mut self) -> Option<Self::Item> {
+ self.0.next().map(|digit| match &digit {
+ 0 => "0",
+ 1 => "1",
+ 2 => "2",
+ 3 => "3",
+ 4 => "4",
+ 5 => "5",
+ 6 => "6",
+ 7 => "7",
+ 8 => "8",
+ 9 => "9",
+ _ => unreachable!()
+ })
+ }
- fn size_hint(&self) -> (usize, Option<usize>) {
- self.0.size_hint()
- }
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.0.size_hint()
+ }
}
impl<T> ExactSizeIterator for DigitStrIter<T> where
- T: NumDigits<Output=u8>,
- DigitIter<T>: ExactSizeIterator,
- DigitStrIter<T>: Iterator<Item=&'static str>
+ T: NumDigits<Output=u8>,
+ DigitIter<T>: ExactSizeIterator,
+ DigitStrIter<T>: Iterator<Item=&'static str>
{
- fn len(&self) -> usize {
- self.0.len()
- }
+ fn len(&self) -> usize {
+ self.0.len()
+ }
}
#[cfg(test)]
mod tests {
- use super::*;
+ use super::*;
- #[test]
- fn num_digits() {
- assert_eq!(0u8.num_digits(), 1);
- assert_eq!(1u8.num_digits(), 1);
- assert_eq!(12i8.num_digits(), 2);
- assert_eq!(123u16.num_digits(), 3);
- assert_eq!(1234i16.num_digits(), 4);
- assert_eq!(12345u32.num_digits(), 5);
- assert_eq!(123456i32.num_digits(), 6);
- assert_eq!(1234567u64.num_digits(), 7);
- assert_eq!(12345678i64.num_digits(), 8);
- #[cfg(has_u128)]
- assert_eq!(123456789u128.num_digits(), 9);
- #[cfg(has_i128)]
- assert_eq!(1234567890i128.num_digits(), 10);
- }
+ #[test]
+ fn num_digits() {
+ assert_eq!(0u8.num_digits(), 1);
+ assert_eq!(1u8.num_digits(), 1);
+ assert_eq!(12i8.num_digits(), 2);
+ assert_eq!(123u16.num_digits(), 3);
+ assert_eq!(1234i16.num_digits(), 4);
+ assert_eq!(12345u32.num_digits(), 5);
+ assert_eq!(123456i32.num_digits(), 6);
+ assert_eq!(1234567u64.num_digits(), 7);
+ assert_eq!(12345678i64.num_digits(), 8);
+ #[cfg(has_u128)]
+ assert_eq!(123456789u128.num_digits(), 9);
+ #[cfg(has_i128)]
+ assert_eq!(1234567890i128.num_digits(), 10);
+ }
- #[test]
- fn digits() {
- macro_rules! test {
- ($ty:ty) => {
- let num: $ty = 123;
- let mut iter = num.digits();
- assert_eq!(iter.len(), 3);
- assert_eq!(iter.size_hint(), (3, Some(3)));
- assert_eq!(iter.next(), Some(3));
- assert_eq!(iter.next(), Some(2));
- assert_eq!(iter.next(), Some(1));
- assert_eq!(iter.next(), None);
- }
- }
- ints!(test);
- }
+ #[test]
+ fn digits() {
+ macro_rules! test {
+ ($ty:ty) => {
+ let num: $ty = 123;
+ let mut iter = num.digits();
+ assert_eq!(iter.len(), 3);
+ assert_eq!(iter.size_hint(), (3, Some(3)));
+ assert_eq!(iter.next(), Some(3));
+ assert_eq!(iter.next(), Some(2));
+ assert_eq!(iter.next(), Some(1));
+ assert_eq!(iter.next(), None);
+ }
+ }
+ ints!(test);
+ }
- #[test]
- fn digits_str() {
- macro_rules! test {
- ($ty:ty) => {
- let num: $ty = 123;
- let mut iter = num.digits().str();
- assert_eq!(iter.len(), 3);
- assert_eq!(iter.size_hint(), (3, Some(3)));
- assert_eq!(iter.next(), Some("3"));
- assert_eq!(iter.next(), Some("2"));
- assert_eq!(iter.next(), Some("1"));
- assert_eq!(iter.next(), None);
- }
- }
- ints!(test);
- }
+ #[test]
+ fn digits_str() {
+ macro_rules! test {
+ ($ty:ty) => {
+ let num: $ty = 123;
+ let mut iter = num.digits().str();
+ assert_eq!(iter.len(), 3);
+ assert_eq!(iter.size_hint(), (3, Some(3)));
+ assert_eq!(iter.next(), Some("3"));
+ assert_eq!(iter.next(), Some("2"));
+ assert_eq!(iter.next(), Some("1"));
+ assert_eq!(iter.next(), None);
+ }
+ }
+ ints!(test);
+ }
}
M src/plugin.rs +61 -61
@@ 2,80 2,80 @@ pub extern crate libloading;
#[macro_export]
macro_rules! lib_name {
- ($name:expr) => {
- #[no_mangle]
- pub fn name() -> &'static str {
- $name
- }
- }
+ ($name:expr) => {
+ #[no_mangle]
+ pub fn name() -> &'static str {
+ $name
+ }
+ }
}
#[macro_export]
macro_rules! plugin_registry {
- ($( fn $name:ident($( $param_name:ident: $param_ty:ty ),*) -> $ty:ty; )*) => {
- pub struct Registry {
- libs: ::std::collections::HashMap<String, $crate::plugin::libloading::Library>,
+ ($( fn $name:ident($( $param_name:ident: $param_ty:ty ),*) -> $ty:ty; )*) => {
+ pub struct Registry {
+ libs: ::std::collections::HashMap<String, $crate::plugin::libloading::Library>,
- $( $name: Option<String> ),*
- }
+ $( $name: Option<String> ),*
+ }
- impl Registry {
- pub fn new() -> Self {
- Self {
- libs: Default::default(),
+ impl Registry {
+ pub fn new() -> Self {
+ Self {
+ libs: Default::default(),
- $( $name: None ),*
- }
- }
+ $( $name: None ),*
+ }
+ }
- pub fn load<'a>(&mut self, lib: &str) -> ::std::io::Result<()> {
- let lib = $crate::plugin::libloading::Library::new(lib)?;
+ pub fn load<'a>(&mut self, lib: &str) -> ::std::io::Result<()> {
+ let lib = $crate::plugin::libloading::Library::new(lib)?;
- let name = unsafe { lib.get::<fn() -> &'a str>(b"name")?() }.to_owned();
+ let name = unsafe { lib.get::<fn() -> &'a str>(b"name")?() }.to_owned();
- // probe for plugins
- $(
- if let Ok(_) = unsafe { lib.get::<fn($( $param_ty ),*) -> $ty>(stringify!($name).as_bytes()) } {
- self.$name = Some(name.clone());
- }
- )*
+ // probe for plugins
+ $(
+ if let Ok(_) = unsafe { lib.get::<fn($( $param_ty ),*) -> $ty>(stringify!($name).as_bytes()) } {
+ self.$name = Some(name.clone());
+ }
+ )*
- self.libs.insert(name, lib);
- Ok(())
- }
+ self.libs.insert(name, lib);
+ Ok(())
+ }
- pub fn unload(&mut self, lib: &str) {
- let set_none_if_equal = |plugin: &mut Option<String>|
- if plugin.is_some() && plugin.as_ref().unwrap() == lib {
- *plugin = None;
- }
- ;
- $( set_none_if_equal(&mut self.$name); )*
+ pub fn unload(&mut self, lib: &str) {
+ let set_none_if_equal = |plugin: &mut Option<String>|
+ if plugin.is_some() && plugin.as_ref().unwrap() == lib {
+ *plugin = None;
+ }
+ ;
+ $( set_none_if_equal(&mut self.$name); )*
- self.libs.remove(lib);
- }
+ self.libs.remove(lib);
+ }
- $(
- pub fn $name(&self, $( $param_name: $param_ty ),*) -> Option<$ty> {
- self.$name.as_ref().map(|name| unsafe {
- self.libs[name].get::<fn($( $param_ty ),*) -> $ty>(stringify!($name).as_bytes())
- .expect(concat!("symbol not found: ", stringify!($name)))
- ($( $param_name ),*)
- })
- }
- )*
- }
+ $(
+ pub fn $name(&self, $( $param_name: $param_ty ),*) -> Option<$ty> {
+ self.$name.as_ref().map(|name| unsafe {
+ self.libs[name].get::<fn($( $param_ty ),*) -> $ty>(stringify!($name).as_bytes())
+ .expect(concat!("symbol not found: ", stringify!($name)))
+ ($( $param_name ),*)
+ })
+ }
+ )*
+ }
- #[macro_export]
- macro_rules! plugin {
- $(
- ($name: $func:path) => {
- #[no_mangle]
- pub fn $name($( $param_name: $param_ty ),*) -> $ty {
- $func($( $param_name ),*)
- }
- }
- );*
- }
- }
+ #[macro_export]
+ macro_rules! plugin {
+ $(
+ ($name: $func:path) => {
+ #[no_mangle]
+ pub fn $name($( $param_name: $param_ty ),*) -> $ty {
+ $func($( $param_name ),*)
+ }
+ }
+ );*
+ }
+ }
}
M src/trie.rs +59 -59
@@ 4,81 4,81 @@ pub use self::sequence_trie::*;
pub struct TrieState<K, V>
where K: TrieKey + Clone {
- trie: SequenceTrie<K, V>,
- path: Vec<K>
+ trie: SequenceTrie<K, V>,
+ path: Vec<K>
}
impl<K, V> TrieState<K, V>
where K: TrieKey + Clone {
- pub fn new(trie: SequenceTrie<K, V>) -> Self {
- Self {
- trie,
- path: Vec::new()
- }
- }
+ pub fn new(trie: SequenceTrie<K, V>) -> Self {
+ Self {
+ trie,
+ path: Vec::new()
+ }
+ }
- pub fn reset(&mut self) {
- self.path.clear();
- }
+ pub fn reset(&mut self) {
+ self.path.clear();
+ }
- pub fn next(&mut self, event: &K) -> Option<&V> {
- if let Some(node) = {
- let key = [event];
- let key = self.path.iter().chain(key.iter().map(|x| *x));
- self.trie.get_node(key)
- } {
- if let Some(cmd) = node.value() {
- if node.is_leaf() {
- self.path.clear();
- } else {
- self.path.push(event.clone());
- }
- Some(cmd)
- } else {
- self.path.push(event.clone());
- None
- }
- } else {
- self.path.clear();
- None
- }
- }
+ pub fn next(&mut self, event: &K) -> Option<&V> {
+ if let Some(node) = {
+ let key = [event];
+ let key = self.path.iter().chain(key.iter().map(|x| *x));
+ self.trie.get_node(key)
+ } {
+ if let Some(cmd) = node.value() {
+ if node.is_leaf() {
+ self.path.clear();
+ } else {
+ self.path.push(event.clone());
+ }
+ Some(cmd)
+ } else {
+ self.path.push(event.clone());
+ None
+ }
+ } else {
+ self.path.clear();
+ None
+ }
+ }
- pub fn path(&self) -> &[K] {
- &self.path[..]
- }
+ pub fn path(&self) -> &[K] {
+ &self.path[..]
+ }
- pub fn node(&self) -> &SequenceTrie<K, V> {
- self.trie.get_node(&self.path).unwrap()
- }
+ pub fn node(&self) -> &SequenceTrie<K, V> {
+ self.trie.get_node(&self.path).unwrap()
+ }
}
impl<K, V> From<SequenceTrie<K, V>> for TrieState<K, V>
where K: TrieKey + Clone {
- fn from(trie: SequenceTrie<K, V>) -> Self {
- Self::new(trie)
- }
+ fn from(trie: SequenceTrie<K, V>) -> Self {
+ Self::new(trie)
+ }
}
#[cfg(test)]
mod tests {
- use super::*;
+ use super::*;
- #[test]
- fn smoke() {
- let mut state = TrieState::new({
- let mut bindings = SequenceTrie::<&'static str, &'static str>::new();
- bindings.insert(&["up"], &"up");
- bindings.insert(&["left", "right"], &"left->right");
- bindings.insert(&["left", "right", "down"], &"left->right->down");
- bindings
- });
+ #[test]
+ fn smoke() {
+ let mut state = TrieState::new({
+ let mut bindings = SequenceTrie::<&'static str, &'static str>::new();
+ bindings.insert(&["up"], &"up");
+ bindings.insert(&["left", "right"], &"left->right");
+ bindings.insert(&["left", "right", "down"], &"left->right->down");
+ bindings
+ });
- assert_eq!(state.next(&"down" ), None );
- assert_eq!(state.next(&"up" ), Some(&"up" ));
- assert_eq!(state.next(&"right"), None );
- assert_eq!(state.next(&"left" ), None );
- assert_eq!(state.next(&"right"), Some(&"left->right" ));
- assert_eq!(state.next(&"down" ), Some(&"left->right->down"));
- }
+ assert_eq!(state.next(&"down" ), None );
+ assert_eq!(state.next(&"up" ), Some(&"up" ));
+ assert_eq!(state.next(&"right"), None );
+ assert_eq!(state.next(&"left" ), None );
+ assert_eq!(state.next(&"right"), Some(&"left->right" ));
+ assert_eq!(state.next(&"down" ), Some(&"left->right->down"));
+ }
}