diff options
author | Tim Oram <dev@mitmaro.ca> | 2019-07-19 14:40:34 -0230 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-07-19 14:40:34 -0230 |
commit | a38f1b39027c160b597de80395c5f68cc166dde5 (patch) | |
tree | b747370d9daf4d30eab80224f7c12e64a187399f | |
parent | afd268b923b3fde1b749fed0f908f1aade26a1c4 (diff) | |
parent | 775603ad2c142ef582de712d1e542953cf9a0cb8 (diff) |
Merge pull request #152 from MitMaro/tim/horizontal-scroll
Add horizontal scrolling
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | src/config.rs | 4 | ||||
-rw-r--r-- | src/git_interactive.rs | 17 | ||||
-rw-r--r-- | src/help/help.rs | 65 | ||||
-rw-r--r-- | src/help/utils.rs | 12 | ||||
-rw-r--r-- | src/input/input_handler.rs | 4 | ||||
-rw-r--r-- | src/list/list.rs | 95 | ||||
-rw-r--r-- | src/scroll/scroll_position.rs | 55 | ||||
-rw-r--r-- | src/show_commit/show_commit.rs | 149 | ||||
-rw-r--r-- | src/view/line_segment.rs | 17 | ||||
-rw-r--r-- | src/view/view.rs | 22 | ||||
-rw-r--r-- | src/view/view_line.rs | 17 | ||||
-rw-r--r-- | test/git-rebase-todo-all-actions.in | 16 |
13 files changed, 389 insertions, 85 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index ea070e9..ab9ae40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Visual mode - change action and reorder with multiple selections - Configuration option for vertical spacing character - Configurable key bindings +- Horizontal scrolling ## [1.0.0] - 2019-04-10 diff --git a/src/config.rs b/src/config.rs index a1c73af..20918a7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -33,6 +33,8 @@ pub struct Config { pub input_force_rebase: String, pub input_help: String, pub input_move_down: String, + pub input_move_left: String, + pub input_move_right: String, pub input_move_selection_down: String, pub input_move_selection_up: String, pub input_move_up: String, @@ -149,6 +151,8 @@ impl Config { input_force_rebase: get_input(&git_config, "interactive-rebase-tool.inputForceRebase", "W")?, input_help: get_input(&git_config, "interactive-rebase-tool.inputHelp", "?")?, input_move_down: get_input(&git_config, "interactive-rebase-tool.inputMoveDown", "Down")?, + input_move_left: get_input(&git_config, "interactive-rebase-tool.inputMoveLeft", "Left")?, + input_move_right: get_input(&git_config, "interactive-rebase-tool.inputMoveRight", "Right")?, input_move_selection_down: get_input(&git_config, "interactive-rebase-tool.inputMoveSelectionDown", "j")?, input_move_selection_up: get_input(&git_config, "interactive-rebase-tool.inputMoveSelectionUp", "k")?, input_move_up: get_input(&git_config, "interactive-rebase-tool.inputMoveUp", "Up")?, diff --git a/src/git_interactive.rs b/src/git_interactive.rs index 3a86501..8ef42c7 100644 --- a/src/git_interactive.rs +++ b/src/git_interactive.rs @@ -230,23 +230,6 @@ impl GitInteractive { &self.selected_commit_stats } - pub fn get_commit_stats_length(&self) -> usize { - match &self.selected_commit_stats { - Some(s) => { - let mut len = s.get_file_stats_length(); - - match s.get_body() { - Some(b) => { - len += b.lines().count(); - }, - None => {}, - } - len + 3 // author + date + commit hash - }, - None => 0, - } - } - pub fn get_selected_line_hash(&self) -> &String { self.lines[self.selected_line_index - 1].get_hash() } diff --git a/src/help/help.rs b/src/help/help.rs index d0bf2a6..227f52d 100644 --- a/src/help/help.rs +++ b/src/help/help.rs @@ -1,5 +1,9 @@ use crate::git_interactive::GitInteractive; -use crate::help::utils::{get_list_normal_mode_help_lines, get_list_visual_mode_help_lines}; +use crate::help::utils::{ + get_list_normal_mode_help_lines, + get_list_visual_mode_help_lines, + get_max_help_description_length, +}; use crate::input::{Input, InputHandler}; use crate::process::{HandleInputResult, HandleInputResultBuilder, ProcessModule, State}; use crate::scroll::ScrollPosition; @@ -9,9 +13,11 @@ use crate::Config; pub struct Help<'h> { normal_mode_help_lines: [(String, &'h str); 21], + normal_mode_max_help_line_length: usize, return_state: State, scroll_position: ScrollPosition, visual_mode_help_lines: [(String, &'h str); 13], + visual_mode_max_help_line_length: usize, } impl<'h> ProcessModule for Help<'h> { @@ -32,17 +38,24 @@ impl<'h> ProcessModule for Help<'h> { view: &View, ) -> HandleInputResult { - let (_, window_height) = view.get_view_size(); + let (view_width, view_height) = view.get_view_size(); let input = input_handler.get_input(); let mut result = HandleInputResultBuilder::new(input); match input { + Input::MoveCursorLeft => { + self.scroll_position + .scroll_left(view_width, self.get_max_help_line_length()) + }, + Input::MoveCursorRight => { + self.scroll_position + .scroll_right(view_width, self.get_max_help_line_length()) + }, Input::MoveCursorDown => { self.scroll_position - .scroll_down(window_height, self.get_help_lines().len()); + .scroll_down(view_height, self.get_help_lines().len()); }, Input::MoveCursorUp => { - self.scroll_position - .scroll_up(window_height, self.get_help_lines().len()); + self.scroll_position.scroll_up(view_height, self.get_help_lines().len()); }, Input::Resize => { self.scroll_position.reset(); @@ -60,10 +73,13 @@ impl<'h> ProcessModule for Help<'h> { let mut view_lines: Vec<ViewLine> = vec![]; for line in self.get_help_lines() { - view_lines.push(ViewLine::new(vec![ - LineSegment::new_with_color(format!(" {:4} ", line.0).as_str(), WindowColor::IndicatorColor), - LineSegment::new(line.1), - ])); + view_lines.push(ViewLine::new_with_pinned_segments( + vec![ + LineSegment::new_with_color(format!(" {:4} ", line.0).as_str(), WindowColor::IndicatorColor), + LineSegment::new(line.1), + ], + 1, + )); } view.draw_title(false); @@ -76,7 +92,12 @@ impl<'h> ProcessModule for Help<'h> { view.draw_str(padding.as_str()); } - view.draw_view_lines(view_lines, self.scroll_position.get_position(), view_height - 3); + view.draw_view_lines( + view_lines, + self.scroll_position.get_top_position(), + self.scroll_position.get_left_position(), + view_height - 3, + ); view.set_color(WindowColor::IndicatorColor); view.draw_str("Any key to close"); @@ -85,11 +106,17 @@ impl<'h> ProcessModule for Help<'h> { impl<'h> Help<'h> { pub fn new(config: &'h Config) -> Self { + let normal_mode_help_lines = get_list_normal_mode_help_lines(config); + let normal_mode_max_help_line_length = get_max_help_description_length(&normal_mode_help_lines); + let visual_mode_help_lines = get_list_visual_mode_help_lines(config); + let visual_mode_max_help_line_length = get_max_help_description_length(&visual_mode_help_lines); Self { - normal_mode_help_lines: get_list_normal_mode_help_lines(config), + normal_mode_help_lines, + normal_mode_max_help_line_length, return_state: State::List(false), scroll_position: ScrollPosition::new(3, 6, 3), - visual_mode_help_lines: get_list_visual_mode_help_lines(config), + visual_mode_help_lines, + visual_mode_max_help_line_length, } } @@ -106,4 +133,18 @@ impl<'h> Help<'h> { &[] } } + + pub fn get_max_help_line_length(&self) -> usize { + if let State::List(visual_mode) = self.return_state { + if visual_mode { + self.visual_mode_max_help_line_length + 6 + } + else { + self.normal_mode_max_help_line_length + 6 + } + } + else { + 4 + } + } } diff --git a/src/help/utils.rs b/src/help/utils.rs index 7a8a97d..00cef87 100644 --- a/src/help/utils.rs +++ b/src/help/utils.rs @@ -1,4 +1,5 @@ use crate::Config; +use unicode_segmentation::UnicodeSegmentation; pub fn get_list_normal_mode_help_lines(config: &Config) -> [(String, &str); 21] { [ @@ -88,3 +89,14 @@ pub fn get_list_visual_mode_help_lines(config: &Config) -> [(String, &str); 13] ), ] } + +pub fn get_max_help_description_length(lines: &[(String, &str)]) -> usize { + let mut max_length = 0; + for (_, desc) in lines { + let len = UnicodeSegmentation::graphemes(*desc, true).count(); + if len > max_length { + max_length = len; + } + } + max_length +} diff --git a/src/input/input_handler.rs b/src/input/input_handler.rs index 3de6023..e12b9db 100644 --- a/src/input/input_handler.rs +++ b/src/input/input_handler.rs @@ -30,6 +30,8 @@ impl<'i> InputHandler<'i> { let input = match c { PancursesInput::Character(c) => c.to_string(), + PancursesInput::KeyLeft => String::from("Left"), + PancursesInput::KeyRight => String::from("Right"), PancursesInput::KeyDown => String::from("Down"), PancursesInput::KeyUp => String::from("Up"), PancursesInput::KeyPPage => String::from("PageUp"), @@ -52,6 +54,8 @@ impl<'i> InputHandler<'i> { i if i == self.config.input_force_abort.as_str() => Input::ForceAbort, i if i == self.config.input_force_rebase.as_str() => Input::ForceRebase, i if i == self.config.input_move_down.as_str() => Input::MoveCursorDown, + i if i == self.config.input_move_left.as_str() => Input::MoveCursorLeft, + i if i == self.config.input_move_right.as_str() => Input::MoveCursorRight, i if i == self.config.input_move_selection_down.as_str() => Input::SwapSelectedDown, i if i == self.config.input_move_selection_up.as_str() => Input::SwapSelectedUp, i if i == self.config.input_move_up.as_str() => Input::MoveCursorUp, diff --git a/src/list/list.rs b/src/list/list.rs index 60c3eaa..3c3a391 100644 --- a/src/list/list.rs +++ b/src/list/list.rs @@ -16,6 +16,7 @@ use crate::scroll::ScrollPosition; use crate::view::{LineSegment, View, ViewLine}; use crate::window::WindowColor; use std::cmp; +use unicode_segmentation::UnicodeSegmentation; #[derive(Debug, PartialEq)] enum ListState { @@ -33,6 +34,37 @@ pub struct List<'l> { visual_footer_full: String, } +fn get_maximum_line_length(is_full_width: bool, lines: &[Line]) -> usize { + let mut length = 0; + if is_full_width { + for line in lines { + let line_length = match *line.get_action() { + Action::Exec => UnicodeSegmentation::graphemes(line.get_command().as_str(), true).count(), + Action::Break => 0, + _ => 9 + UnicodeSegmentation::graphemes(line.get_comment().as_str(), true).count(), + } + 10; + + if line_length > length { + length = line_length; + } + } + } + else { + for line in lines { + let line_length = match *line.get_action() { + Action::Exec => UnicodeSegmentation::graphemes(line.get_command().as_str(), true).count(), + Action::Break => 0, + _ => 4 + UnicodeSegmentation::graphemes(line.get_comment().as_str(), true).count(), + } + 3; + + if line_length > length { + length = line_length; + } + } + } + length +} + impl<'l> ProcessModule for List<'l> { fn process(&mut self, git_interactive: &mut GitInteractive, view: &View) -> ProcessResult { let (_, view_height) = view.get_view_size(); @@ -40,6 +72,7 @@ impl<'l> ProcessModule for List<'l> { let selected_index = *git_interactive.get_selected_line_index() - 1; self.scroll_position .ensure_cursor_visible(selected_index, view_height, lines.len()); + ProcessResult::new() } @@ -47,12 +80,12 @@ impl<'l> ProcessModule for List<'l> { &mut self, input_handler: &InputHandler, git_interactive: &mut GitInteractive, - _view: &View, + view: &View, ) -> HandleInputResult { match self.state { - ListState::Normal => self.handle_normal_mode_input(input_handler, git_interactive), - ListState::Visual => self.handle_visual_mode_input(input_handler, git_interactive), + ListState::Normal => self.handle_normal_mode_input(input_handler, git_interactive, view), + ListState::Visual => self.handle_visual_mode_input(input_handler, git_interactive, view), } } @@ -68,19 +101,27 @@ impl<'l> ProcessModule for List<'l> { let selected_index = *git_interactive.get_selected_line_index() - 1; for (index, line) in git_interactive.get_lines().iter().enumerate() { - view_lines.push(ViewLine::new(self.get_todo_line_segments( - line, - selected_index == index, - is_visual_mode - && ((visual_index <= selected_index && index >= visual_index && index <= selected_index) - || (visual_index > selected_index && index >= selected_index && index <= visual_index)), - view_width, - ))); + view_lines.push(ViewLine::new_with_pinned_segments( + self.get_todo_line_segments( + line, + selected_index == index, + is_visual_mode + && ((visual_index <= selected_index && index >= visual_index && index <= selected_index) + || (visual_index > selected_index && index >= selected_index && index <= visual_index)), + view_width, + ), + if *line.get_action() == Action::Exec { 2 } else { 3 }, + )); } view.draw_title(true); - view.draw_view_lines(view_lines, self.scroll_position.get_position(), view_height - 2); + view.draw_view_lines( + view_lines, + self.scroll_position.get_top_position(), + self.scroll_position.get_left_position(), + view_height - 2, + ); view.set_color(WindowColor::Foreground); view.set_style(true, false, false); @@ -132,10 +173,12 @@ impl<'l> List<'l> { &mut self, input_handler: &InputHandler, git_interactive: &mut GitInteractive, + view: &View, ) -> HandleInputResult { let input = input_handler.get_input(); let mut result = HandleInputResultBuilder::new(input); + let (view_width, _) = view.get_view_size(); match input { Input::Help => { result = result.help(State::List(false)); @@ -172,6 +215,18 @@ impl<'l> List<'l> { }, Input::SwapSelectedDown => git_interactive.swap_selected_down(), Input::SwapSelectedUp => git_interactive.swap_selected_up(), + Input::MoveCursorLeft => { + self.scroll_position.scroll_left( + view_width, + get_maximum_line_length(view_width >= MINIMUM_FULL_WINDOW_WIDTH, git_interactive.get_lines()), + ) + }, + Input::MoveCursorRight => { + self.scroll_position.scroll_right( + view_width, + get_maximum_line_length(view_width >= MINIMUM_FULL_WINDOW_WIDTH, git_interactive.get_lines()), + ) + }, Input::MoveCursorDown => git_interactive.move_cursor_down(1), Input::MoveCursorUp => git_interactive.move_cursor_up(1), Input::MoveCursorPageDown => git_interactive.move_cursor_down(5), @@ -191,14 +246,28 @@ impl<'l> List<'l> { &mut self, input_handler: &InputHandler, git_interactive: &mut GitInteractive, + view: &View, ) -> HandleInputResult { let input = input_handler.get_input(); let mut result = HandleInputResultBuilder::new(input); + let (view_width, _) = view.get_view_size(); match input { Input::Help => { result = result.help(State::List(true)); }, + Input::MoveCursorLeft => { + self.scroll_position.scroll_left( + view_width, + get_maximum_line_length(view_width >= MINIMUM_FULL_WINDOW_WIDTH, git_interactive.get_lines()), + ) + }, + Input::MoveCursorRight => { + self.scroll_position.scroll_right( + view_width, + get_maximum_line_length(view_width >= MINIMUM_FULL_WINDOW_WIDTH, git_interactive.get_lines()), + ) + }, Input::MoveCursorDown => { git_interactive.move_cursor_down(1); }, @@ -259,7 +328,7 @@ impl<'l> List<'l> { line.get_command().clone() } else if *action == Action::Break { - String::from(" ") + String::from("") } else { let max_index = cmp::min(line.get_hash().len(), 8); diff --git a/src/scroll/scroll_position.rs b/src/scroll/scroll_position.rs index 8de43c7..e75272a 100644 --- a/src/scroll/scroll_position.rs +++ b/src/scroll/scroll_position.rs @@ -2,23 +2,26 @@ use std::cell::RefCell; pub struct ScrollPosition { big_scroll: usize, + left_value: RefCell<usize>, padding: usize, small_scroll: usize, - value: RefCell<usize>, + top_value: RefCell<usize>, } impl ScrollPosition { pub fn new(padding: usize, big_scroll: usize, small_scroll: usize) -> Self { Self { big_scroll, + left_value: RefCell::new(0 as usize), padding, small_scroll, - value: RefCell::new(0 as usize), + top_value: RefCell::new(0 as usize), } } pub fn reset(&self) { - self.value.replace(0); + self.left_value.replace(0); + self.top_value.replace(0); } pub fn scroll_up(&self, window_height: usize, lines_length: usize) { @@ -29,13 +32,39 @@ impl ScrollPosition { self.update_top(false, window_height, lines_length); } + pub fn scroll_left(&self, view_width: usize, max_line_width: usize) { + let current_value = *self.left_value.borrow(); + if current_value != 0 { + self.set_horizontal_scroll(current_value - 1, view_width, max_line_width); + } + } + + pub fn scroll_right(&self, view_width: usize, max_line_width: usize) { + let current_value = *self.left_value.borrow(); + self.set_horizontal_scroll(current_value + 1, view_width, max_line_width); + } + + fn set_horizontal_scroll(&self, new_value: usize, view_width: usize, max_line_width: usize) { + if (new_value + view_width) > max_line_width { + if view_width > max_line_width { + self.left_value.replace(0); + } + else { + self.left_value.replace(max_line_width - view_width); + } + } + else { + self.left_value.replace(new_value); + } + } + pub fn ensure_cursor_visible(&self, cursor: usize, window_height: usize, lines_length: usize) { let view_height = window_height as usize - self.padding; - let current_value = *self.value.borrow(); + let current_value = *self.top_value.borrow(); // TODO I think this can be simplified - self.value.replace(match cursor { + self.top_value.replace(match cursor { // show all if list is view height is long enough _ if lines_length <= view_height => 0, // last item selected, set top to show bottom of lines @@ -52,8 +81,12 @@ impl ScrollPosition { }); } - pub fn get_position(&self) -> usize { - *self.value.borrow() + pub fn get_top_position(&self) -> usize { + *self.top_value.borrow() + } + + pub fn get_left_position(&self) -> usize { + *self.left_value.borrow() } fn update_top(&self, scroll_up: bool, window_height: usize, lines_length: usize) { @@ -70,21 +103,21 @@ impl ScrollPosition { _ => 1, }; - let current_value = *self.value.borrow(); + let current_value = *self.top_value.borrow(); if scroll_up { if current_value < amount { self.reset(); } else { - self.value.replace(current_value - amount); + self.top_value.replace(current_value - amount); } } else if current_value + amount + view_height > lines_length { - self.value.replace(lines_length - view_height); + self.top_value.replace(lines_length - view_height); } else { - self.value.replace(current_value + amount); + self.top_value.replace(current_value + amount); } } } diff --git a/src/show_commit/show_commit.rs b/src/show_commit/show_commit.rs index f61a0d4..9fdd2c9 100644 --- a/src/show_commit/show_commit.rs +++ b/src/show_commit/show_commit.rs @@ -1,3 +1,4 @@ +use crate::commit::Commit; use crate::constants::MINIMUM_FULL_WINDOW_WIDTH; use crate::git_interactive::GitInteractive; use crate::input::{Input, InputHandler}; @@ -14,6 +15,7 @@ use crate::show_commit::util::get_stat_item_segments; use crate::view::{LineSegment, View, ViewLine}; use crate::window::WindowColor; use std::cmp; +use unicode_segmentation::UnicodeSegmentation; pub struct ShowCommit { scroll_position: ScrollPosition, @@ -39,22 +41,46 @@ impl ProcessModule for ShowCommit { view: &View, ) -> HandleInputResult { - let (_, window_height) = view.get_view_size(); + let (view_width, view_height) = view.get_view_size(); let input = input_handler.get_input(); let mut result = HandleInputResultBuilder::new(input); match input { + Input::MoveCursorLeft => { + self.scroll_position.scroll_left( + view_width, + self.get_max_line_length( + git_interactive.get_commit_stats(), + view_height >= MINIMUM_FULL_WINDOW_WIDTH, + ), + ) + }, + Input::MoveCursorRight => { + self.scroll_position.scroll_right( + view_width, + self.get_max_line_length( + git_interactive.get_commit_stats(), + view_height >= MINIMUM_FULL_WINDOW_WIDTH, + ), + ) + }, Input::MoveCursorDown => { - self.scroll_position - .scroll_down(window_height, git_interactive.get_commit_stats_length()) + self.scroll_position.scroll_down( + view_height, + self.get_commit_stats_length(git_interactive.get_commit_stats()), + ) }, Input::MoveCursorUp => { - self.scroll_position - .scroll_up(window_height, git_interactive.get_commit_stats_length()) + self.scroll_position.scroll_up( + view_height, + self.get_commit_stats_length(git_interactive.get_commit_stats()), + ) }, Input::Resize => { - self.scroll_position - .scroll_up(window_height as usize, git_interactive.get_commit_stats_length()); + self.scroll_position.scroll_up( + view_height as usize, + self.get_commit_stats_length(git_interactive.get_commit_stats()), + ); }, _ => { result = result.state(State::List(false)); @@ -160,7 +186,12 @@ impl ProcessModule for ShowCommit { None => {}, } - view.draw_view_lines(lines, self.scroll_position.get_position(), view_height); + view.draw_view_lines( + lines, + self.scroll_position.get_top_position(), + self.scroll_position.get_left_position(), + view_height, + ); view.set_color(WindowColor::IndicatorColor); view.draw_str("Any key to close"); @@ -173,4 +204,106 @@ impl ShowCommit { scroll_position: ScrollPosition::new(3, 6, 3), } } + + fn get_commit_stats_length(&self, commit: &Option<Commit>) -> usize { + match commit { + Some(c) => { + let mut len = c.get_file_stats_length(); + + match c.get_body() { + Some(b) => { + len += b.lines().count(); + }, + None => {}, + } + len + 3 // author + date + commit hash + }, + None => 0, + } + } + + fn get_max_line_length(&self, commit: &Option<Commit>, is_full_width: bool) -> usize { + match commit { + Some(c) => { + let full_hash = c.get_hash(); + let author = c.get_author(); + let committer = c.get_committer(); + let body = c.get_body(); + let file_stats = c.get_file_stats(); + + let mut max_line_length = if is_full_width { + full_hash.len() + 8 // 8 = "Commit: " + } + else { + cmp::min(full_hash.len(), 8) + }; + + max_line_length = cmp::max( + if is_full_width { + 35 // "Date: Sun Jul 8 00:34:60 2001+09:30" + } + else { + 29 // "Sun Jul 8 00:34:60 2001+09:30" + }, + max_line_length, + ); + + if let Some(a) = author.to_string() { + max_line_length = cmp::max( + if is_full_width { + UnicodeSegmentation::graphemes(a.as_str(), true).count() + 8 // 8 = "Author: " + } + else { + UnicodeSegmentation::graphemes(a.as_str(), true).count() + 3 // 3 = "A: " + }, + max_line_length, + ); + } + + if let Some(c) = committer.to_string() { + max_line_length = cmp::max( + if is_full_width { + UnicodeSegmentation::graphemes(c.as_str(), true).count() + 11 // 11 = "Committer: " + } + else { + UnicodeSegmentation::graphemes(c.as_str(), true).count() + 3 // 3 = "C: " + }, + max_line_length, + ); + }; + + if let Some(b) = body { + for line in b.lines() { + let line_length = UnicodeSegmentation::graphemes(line, true).count(); + if line_length > max_line_length { + max_line_length = line_length; + } + } + } + + if let Some(stats) = file_stats { + let additional_line_length = if is_full_width { + 13 // stat name + arrow + } + else { + 3 // stat name + arrow + }; + + for stat in stats { + let stat_line_length = + UnicodeSegmentation::graphemes(stat.get_to_name().as_str(), true).count() + + UnicodeSegmentation::graphemes(stat.get_from_name().as_str(), true).count() + + additional_line_length; + + if stat_line_length > max_line_length { + max_line_length = stat_line_length; + } + } + } + + max_line_length + }, + None => 0, + } + } } diff --git a/src/view/line_segment.rs b/src/view/line_segment.rs index b0e8a72..fcf3d92 100644 --- a/src/view/line_segment.rs +++ b/src/view/line_segment.rs @@ -40,20 +40,25 @@ impl LineSegment { } } - pub fn draw(&self, max_width: usize, window: &Window) -> usize { + pub fn draw(&self, left: usize, max_width: usize, window: &Window) -> (usize, usize) { window.color(self.color); window.set_style(self.dim, self.underline, self.reverse); let segment_length = UnicodeSegmentation::graphemes(self.text.as_str(), true).count(); - if segment_length >= max_width { + if segment_length <= left { + (0, segment_length) + } + else if segment_length - left >= max_width { let graphemes = UnicodeSegmentation::graphemes(self.text.as_str(), true); - let partial_line = graphemes.take(max_width).collect::<String>(); + let partial_line = graphemes.skip(left).take(max_width).collect::<String>(); window.draw_str(partial_line.as_str()); - max_width + (max_width, segment_length) } else { - window.draw_str(self.text.as_str()); - segment_length + let graphemes = UnicodeSegmentation::graphemes(self.text.as_str(), true); + let partial_line = graphemes.skip(left).collect::<String>(); + window.draw_str(partial_line.as_str()); + (segment_length - left, segment_length) } } } diff --git a/src/view/view.rs b/src/view/view.rs index 48a377d..321bb60 100644 --- a/src/view/view.rs +++ b/src/view/view.rs @@ -63,7 +63,7 @@ impl<'v> View<'v> { self.window.refresh(); } - pub fn draw_view_lines(&self, lines: Vec<ViewLine>, top: usize, height: usize) { + pub fn draw_view_lines(&self, lines: Vec<ViewLine>, top: usize, left: usize, height: usize) { let number_of_lines = lines.len(); let scroll_indicator_index = get_scroll_position(top, number_of_lines, height); @@ -71,7 +71,7 @@ impl<'v> View<'v> { let mut index: usize = 0; for line in lines.iter().skip(top).take(height) { - self.draw_view_line(line, show_scroll_bar); + self.draw_view_line(line, left, show_scroll_bar); if show_scroll_bar { self.window.color(WindowColor::Foreground); self.window.set_style(scroll_indicator_index != index, false, true); @@ -85,16 +85,28 @@ impl<'v> View<'v> { } } - pub fn draw_view_line(&self, line: &ViewLine, scrollbar: bool) { + pub fn draw_view_line(&self, line: &ViewLine, left: usize, scrollbar: bool) { let (window_width, _) = self.window.get_window_size(); let window_width = if scrollbar { window_width - 1 } else { window_width } as usize; let mut start = 0; - for segment in line.get_segments() { - start += segment.draw(window_width - start, &self.window); + let mut left_start = 0; + for (i, segment) in line.get_segments().iter().enumerate() { + // set left on first non-pinned segment + if i == line.get_number_of_pinned_segment() { + left_start = left; + } + let (amount_drawn, segment_size) = segment.draw(left_start, window_width - start, &self.window); + start += amount_drawn; if start >= window_width { break; } + if amount_drawn > 0 { + left_start = 0; + } + else { + left_start -= segment_size; + } } if start < window_width { diff --git a/src/view/view_line.rs b/src/view/view_line.rs index 818b025..f3e7d50 100644 --- a/src/view/view_line.rs +++ b/src/view/view_line.rs @@ -2,14 +2,29 @@ use crate::view::LineSegment; pub struct ViewLine { segments: Vec<LineSegment>, + pinned_segments: usize, } impl ViewLine { pub fn new(segments: Vec<LineSegment>) -> Self { - Self { segments } + Self { + segments, + pinned_segments: 0, + } + } + + pub fn new_with_pinned_segments(segments: Vec<LineSegment>, pinned_segments: usize) -> Self { + Self { + segments, + pinned_segments, + } } pub fn get_segments(&self) -> &Vec<LineSegment> { &self.segments } + + pub fn get_number_of_pinned_segment(&self) -> usize { + self.pinned_segments + } } diff --git a/test/git-rebase-todo-all-actions.in b/test/git-rebase-todo-all-actions.in index 4caa924..6fd972c 100644 --- a/test/git-rebase-todo-all-actions.in +++ b/test/git-rebase-todo-all-actions.in @@ -1,18 +1,10 @@ # A Commment -pick 166a8bc53895a340625c781bb5dbc2f9794a3a23 Added tests -reword bbbbbbbbbbbbbbb Added tests -edit ccccccccccccccc Added tests -exec git commit --amend -m "Foo bar" +pick 166a8bc53895a340625c781bb5dbc2f9794a3a23 abcdefghijklmnop +reword bbbbbbbbbbbbbbb Added tests, this line is longer than it probably needs to be +edit ccccccccccccccc Lorem ipsum st decourm +exec git commit --amend -m "Foo bar, with some more text to make this line really lone" break -squash ddddddddddddddd Added tests -fixup eeeeeeeeeeeeeee Added tests -drop fffffffffffffff Added tests -p 111111111111111 Added tests -r 222222222222222 Added tests -e 333333333333333 Added tests x git commit --amend -m "Foo bar" b -s 444444444444444 Added tests -f 555555555555555 Added tests d 666666666666666 Added tests # A Commment |