@@ -662,30 +662,74 @@ impl Document {
662662 self . cancel_selection ( ) ;
663663 }
664664
665+ /// Find the word boundaries
666+ pub fn word_boundaries ( & mut self , line : & str ) -> Vec < ( usize , usize ) > {
667+ let re = r"(\s{2,}|[A-Za-z0-9_]+|\.)" ;
668+ let mut searcher = Searcher :: new ( re) ;
669+ let starts: Vec < Match > = searcher. lfinds ( line) ;
670+ let mut ends: Vec < Match > = starts. clone ( ) ;
671+ ends. iter_mut ( )
672+ . for_each ( |m| m. loc . x += m. text . chars ( ) . count ( ) ) ;
673+ let starts: Vec < usize > = starts. iter ( ) . map ( |m| m. loc . x ) . collect ( ) ;
674+ let ends: Vec < usize > = ends. iter ( ) . map ( |m| m. loc . x ) . collect ( ) ;
675+ starts. into_iter ( ) . zip ( ends) . collect ( )
676+ }
677+
678+ /// Find the current state of the cursor in relation to words
679+ pub fn cursor_word_state ( & mut self , words : & [ ( usize , usize ) ] , x : usize ) -> WordState {
680+ let in_word = words
681+ . iter ( )
682+ . position ( |( start, end) | * start <= x && x <= * end) ;
683+ if let Some ( idx) = in_word {
684+ let ( word_start, word_end) = words[ idx] ;
685+ if x == word_end {
686+ WordState :: AtEnd ( idx)
687+ } else if x == word_start {
688+ WordState :: AtStart ( idx)
689+ } else {
690+ WordState :: InCenter ( idx)
691+ }
692+ } else {
693+ WordState :: Out
694+ }
695+ }
696+
665697 /// Moves to the previous word in the document
666698 pub fn move_prev_word ( & mut self ) -> Status {
667699 let Loc { x, y } = self . char_loc ( ) ;
700+ // Handle case where we're at the beginning of the line
668701 if x == 0 && y != 0 {
669702 return Status :: StartOfLine ;
670703 }
671- let re = format ! ( "(\t | {{{}}}|^|\\ W|$| )" , self . tab_width) ;
672- let mut searcher = Searcher :: new ( & re) ;
673- let line = self
674- . line ( y)
675- . unwrap_or_default ( )
676- . chars ( )
677- . take ( x)
678- . collect :: < String > ( ) ;
679- let mut matches = searcher. rfinds ( & line) ;
680- if let Some ( mtch) = matches. first ( ) {
681- if mtch. loc . x == x {
682- matches. remove ( 0 ) ;
704+ // Find where all the words are
705+ let line = self . line ( y) . unwrap_or_default ( ) ;
706+ let words = self . word_boundaries ( & line) ;
707+ let state = self . cursor_word_state ( & words, x) ;
708+ // Work out where to move to
709+ let new_x = match state {
710+ // Go to start of line if at beginning
711+ WordState :: AtEnd ( 0 ) | WordState :: InCenter ( 0 ) | WordState :: AtStart ( 0 ) => 0 ,
712+ // Cursor is at the middle / end of a word, move to previous end
713+ WordState :: AtEnd ( idx) | WordState :: InCenter ( idx) => words[ idx. saturating_sub ( 1 ) ] . 1 ,
714+ WordState :: AtStart ( idx) => words[ idx. saturating_sub ( 1 ) ] . 0 ,
715+ WordState :: Out => {
716+ // Cursor is not touching any words, find previous end
717+ let mut shift_back = x;
718+ while let WordState :: Out = self . cursor_word_state ( & words, shift_back) {
719+ shift_back = shift_back. saturating_sub ( 1 ) ;
720+ if shift_back == 0 {
721+ break ;
722+ }
723+ }
724+ match self . cursor_word_state ( & words, shift_back) {
725+ WordState :: AtEnd ( idx) => words[ idx] . 1 ,
726+ _ => 0 ,
727+ }
683728 }
684- }
685- if let Some ( mtch) = matches. first_mut ( ) {
686- mtch. loc . y = self . loc ( ) . y ;
687- self . move_to ( & mtch. loc ) ;
688- }
729+ } ;
730+ // Perform the move
731+ self . move_to_x ( new_x) ;
732+ // Clean up
689733 self . old_cursor = self . loc ( ) . x ;
690734 Status :: None
691735 }
@@ -694,18 +738,97 @@ impl Document {
694738 pub fn move_next_word ( & mut self ) -> Status {
695739 let Loc { x, y } = self . char_loc ( ) ;
696740 let line = self . line ( y) . unwrap_or_default ( ) ;
741+ // Handle case where we're at the end of the line
697742 if x == line. chars ( ) . count ( ) && y != self . len_lines ( ) {
698743 return Status :: EndOfLine ;
699744 }
700- let re = format ! ( "(\t | {{{}}}|\\ W|$|^ +| )" , self . tab_width) ;
701- if let Some ( mut mtch) = self . next_match ( & re, 0 ) {
702- mtch. loc . x += mtch. text . chars ( ) . count ( ) ;
703- self . move_to ( & mtch. loc ) ;
704- }
745+ // Find and move to the next word
746+ let line = self . line ( y) . unwrap_or_default ( ) ;
747+ let words = self . word_boundaries ( & line) ;
748+ let state = self . cursor_word_state ( & words, x) ;
749+ // Work out where to move to
750+ let new_x = match state {
751+ // Cursor is at the middle / end of a word, move to next end
752+ WordState :: AtEnd ( idx) | WordState :: InCenter ( idx) => {
753+ if let Some ( word) = words. get ( idx + 1 ) {
754+ word. 1
755+ } else {
756+ // No next word exists, just go to end of line
757+ line. chars ( ) . count ( )
758+ }
759+ }
760+ WordState :: AtStart ( idx) => {
761+ // Cursor is at the start of a word, move to next start
762+ if let Some ( word) = words. get ( idx + 1 ) {
763+ word. 0
764+ } else {
765+ // No next word exists, just go to end of line
766+ line. chars ( ) . count ( )
767+ }
768+ }
769+ WordState :: Out => {
770+ // Cursor is not touching any words, find next start
771+ let mut shift_forward = x;
772+ while let WordState :: Out = self . cursor_word_state ( & words, shift_forward) {
773+ shift_forward += 1 ;
774+ if shift_forward >= line. chars ( ) . count ( ) {
775+ break ;
776+ }
777+ }
778+ match self . cursor_word_state ( & words, shift_forward) {
779+ WordState :: AtStart ( idx) => words[ idx] . 0 ,
780+ _ => line. chars ( ) . count ( ) ,
781+ }
782+ }
783+ } ;
784+ // Perform the move
785+ self . move_to_x ( new_x) ;
786+ // Clean up
705787 self . old_cursor = self . loc ( ) . x ;
706788 Status :: None
707789 }
708790
791+ /// Function to delete a word at a certain location
792+ /// # Errors
793+ /// Errors if out of range
794+ pub fn delete_word ( & mut self ) -> Result < ( ) > {
795+ let Loc { x, y } = self . char_loc ( ) ;
796+ let line = self . line ( y) . unwrap_or_default ( ) ;
797+ let words = self . word_boundaries ( & line) ;
798+ let state = self . cursor_word_state ( & words, x) ;
799+ let delete_upto = match state {
800+ WordState :: InCenter ( idx) | WordState :: AtEnd ( idx) => {
801+ // Delete back to start of this word
802+ words[ idx] . 0
803+ }
804+ WordState :: AtStart ( 0 ) => 0 ,
805+ WordState :: AtStart ( idx) => {
806+ // Delete back to start of the previous word
807+ words[ idx. saturating_sub ( 1 ) ] . 0
808+ }
809+ WordState :: Out => {
810+ // Delete back to the end of the previous word
811+ let mut shift_back = x;
812+ while let WordState :: Out = self . cursor_word_state ( & words, shift_back) {
813+ shift_back = shift_back. saturating_sub ( 1 ) ;
814+ if shift_back == 0 {
815+ break ;
816+ }
817+ }
818+ let char = line. chars ( ) . nth ( shift_back) ;
819+ let state = self . cursor_word_state ( & words, shift_back) ;
820+ match ( char, state) {
821+ // Shift to start of previous word if there is a space
822+ ( Some ( ' ' ) , WordState :: AtEnd ( idx) ) => words[ idx] . 0 ,
823+ // Shift to end of previous word if there is not a space
824+ ( _, WordState :: AtEnd ( idx) ) => words[ idx] . 1 ,
825+ _ => 0 ,
826+ }
827+ }
828+ } ;
829+ self . delete ( delete_upto..=x, y)
830+ }
831+
709832 /// Function to search the document to find the next occurance of a regex
710833 pub fn next_match ( & mut self , regex : & str , inc : usize ) -> Option < Match > {
711834 // Prepare
@@ -1204,3 +1327,11 @@ pub struct Cursor {
12041327 pub loc : Loc ,
12051328 pub selection_end : Loc ,
12061329}
1330+
1331+ /// State of a word
1332+ pub enum WordState {
1333+ AtStart ( usize ) ,
1334+ AtEnd ( usize ) ,
1335+ InCenter ( usize ) ,
1336+ Out ,
1337+ }
0 commit comments