@@ -24,12 +24,13 @@ import type {SliceRegistry} from '../registry/SliceRegistry';
2424import type {
2525 CharIterator ,
2626 CharPredicate ,
27- Position ,
27+ EditorPosition ,
2828 TextRangeUnit ,
2929 ViewStyle ,
3030 ViewRange ,
3131 ViewSlice ,
3232 EditorUi ,
33+ EditorSelection ,
3334} from './types' ;
3435
3536/**
@@ -106,11 +107,18 @@ export class Editor<T = string> implements Printable {
106107 public cursors0 ( ) : UndefIterator < Cursor < T > > {
107108 const iterator = this . txt . localSlices . iterator0 ( ) ;
108109 return ( ) => {
109- const slice = iterator ( ) ;
110- return slice instanceof Cursor ? slice : void 0 ;
110+ while ( true ) {
111+ const slice = iterator ( ) ;
112+ if ( slice instanceof Cursor ) return slice ;
113+ if ( ! slice ) return ;
114+ }
111115 } ;
112116 }
113117
118+ public mainCursor ( ) : Cursor < T > | undefined {
119+ return this . cursors0 ( ) ( ) ;
120+ }
121+
114122 public cursors ( ) {
115123 return new UndefEndIter ( this . cursors0 ( ) ) ;
116124 }
@@ -169,7 +177,7 @@ export class Editor<T = string> implements Printable {
169177 * the contents is removed and the cursor is set at the start of the range
170178 * as caret.
171179 */
172- public collapseCursor ( cursor : Cursor < T > ) : void {
180+ public collapseCursor ( cursor : Range < T > ) : void {
173181 this . delRange ( cursor ) ;
174182 cursor . collapseToStart ( ) ;
175183 }
@@ -196,37 +204,42 @@ export class Editor<T = string> implements Printable {
196204 * Insert inline text at current cursor position. If cursor selects a range,
197205 * the range is removed and the text is inserted at the start of the range.
198206 */
199- public insert0 ( cursor : Cursor < T > , text : string ) : ITimespanStruct | undefined {
207+ public insert0 ( range : Range < T > , text : string ) : ITimespanStruct | undefined {
200208 if ( ! text ) return ;
201- if ( ! cursor . isCollapsed ( ) ) this . delRange ( cursor ) ;
202- const after = cursor . start . clone ( ) ;
209+ if ( ! range . isCollapsed ( ) ) this . delRange ( range ) ;
210+ const after = range . start . clone ( ) ;
203211 after . refAfter ( ) ;
204212 const txt = this . txt ;
205213 const textId = txt . ins ( after . id , text ) ;
206214 const span = new Timespan ( textId . sid , textId . time , text . length ) ;
207215 const shift = text . length - 1 ;
208216 const point = txt . point ( shift ? tick ( textId , shift ) : textId , Anchor . After ) ;
209- cursor . set ( point , point , CursorAnchor . Start ) ;
217+ if ( range instanceof Cursor ) range . set ( point , point , CursorAnchor . Start ) ;
218+ else range . set ( point ) ;
210219 return span ;
211220 }
212221
213222 /**
214223 * Inserts text at the cursor positions and collapses cursors, if necessary.
215- * The applies any pending inline formatting to the inserted text.
224+ * Then applies any pending inline formatting to the inserted text.
216225 */
217- public insert ( text : string ) : ITimespanStruct [ ] {
226+ public insert ( text : string , ranges ?: IterableIterator < Range < T > > | Range < T > [ ] ) : ITimespanStruct [ ] {
218227 const spans : ITimespanStruct [ ] = [ ] ;
219- if ( ! this . hasCursor ( ) ) this . addCursor ( ) ;
220- for ( let cursor : Cursor < T > | undefined , i = this . cursors0 ( ) ; ( cursor = i ( ) ) ; ) {
221- const span = this . insert0 ( cursor , text ) ;
228+ if ( ! ranges ) {
229+ if ( ! this . hasCursor ( ) ) this . addCursor ( ) ;
230+ ranges = this . cursors ( ) ;
231+ }
232+ if ( ! ranges ) return [ ] ;
233+ for ( const range of ranges ) {
234+ const span = this . insert0 ( range , text ) ;
222235 if ( span ) spans . push ( span ) ;
223236 const pending = this . pending . value ;
224237 if ( pending . size ) {
225238 this . pending . next ( new Map ( ) ) ;
226- const start = cursor . start . clone ( ) ;
239+ const start = range . start . clone ( ) ;
227240 start . step ( - text . length ) ;
228- const range = this . txt . range ( start , cursor . end . clone ( ) ) ;
229- for ( const [ type , data ] of pending ) this . toggleRangeExclFmt ( range , type , data ) ;
241+ const toggleRange = this . txt . range ( start , range . end . clone ( ) ) ;
242+ for ( const [ type , data ] of pending ) this . toggleRangeExclFmt ( toggleRange , type , data ) ;
230243 }
231244 }
232245 return spans ;
@@ -494,6 +507,11 @@ export class Editor<T = string> implements Printable {
494507 return point ;
495508 }
496509 case 'line' : {
510+ if ( steps > 0 ) for ( let i = 0 ; i < steps ; i ++ ) point = this . eol ( point ) ;
511+ else for ( let i = 0 ; i < - steps ; i ++ ) point = this . bol ( point ) ;
512+ return point ;
513+ }
514+ case 'vline' : {
497515 if ( steps > 0 ) for ( let i = 0 ; i < steps ; i ++ ) point = ui ?. eol ?.( point , 1 ) ?? this . eol ( point ) ;
498516 else for ( let i = 0 ; i < - steps ; i ++ ) point = ui ?. eol ?.( point , - 1 ) ?? this . bol ( point ) ;
499517 return point ;
@@ -605,18 +623,21 @@ export class Editor<T = string> implements Printable {
605623 } ) ;
606624 }
607625
608- public selectAt ( at : Position < T > , unit : TextRangeUnit | '' , ui ?: EditorUi < T > ) : void {
609- this . cursor . set ( this . point ( at ) ) ;
626+ public selectAt ( at : EditorPosition < T > , unit : TextRangeUnit | '' , ui ?: EditorUi < T > ) : void {
627+ this . cursor . set ( this . pos2point ( at ) ) ;
610628 if ( unit ) this . select ( unit , ui ) ;
611629 }
612630
613631 // --------------------------------------------------------------- formatting
614632
615- public eraseFormatting ( store : EditorSlices < T > = this . saved ) : void {
633+ public eraseFormatting (
634+ store : EditorSlices < T > = this . saved ,
635+ selection : Range < T > [ ] | IterableIterator < Range < T > > = this . cursors ( ) ,
636+ ) : void {
616637 const overlay = this . txt . overlay ;
617- for ( let i = this . cursors0 ( ) , cursor = i ( ) ; cursor ; cursor = i ( ) ) {
638+ for ( const range of selection ) {
618639 overlay . refresh ( ) ;
619- const contained = overlay . findContained ( cursor ) ;
640+ const contained = overlay . findContained ( range ) ;
620641 for ( const slice of contained ) {
621642 if ( slice instanceof PersistedSlice ) {
622643 switch ( slice . behavior ) {
@@ -628,7 +649,7 @@ export class Editor<T = string> implements Printable {
628649 }
629650 }
630651 overlay . refresh ( ) ;
631- const overlapping = overlay . findOverlapping ( cursor ) ;
652+ const overlapping = overlay . findOverlapping ( range ) ;
632653 for ( const slice of overlapping ) {
633654 switch ( slice . behavior ) {
634655 case SliceBehavior . One :
@@ -640,11 +661,14 @@ export class Editor<T = string> implements Printable {
640661 }
641662 }
642663
643- public clearFormatting ( store : EditorSlices < T > = this . saved ) : void {
664+ public clearFormatting (
665+ store : EditorSlices < T > = this . saved ,
666+ selection : Range < T > [ ] | IterableIterator < Range < T > > = this . cursors ( ) ,
667+ ) : void {
644668 const overlay = this . txt . overlay ;
645669 overlay . refresh ( ) ;
646- for ( let i = this . cursors0 ( ) , cursor = i ( ) ; cursor ; cursor = i ( ) ) {
647- const overlapping = overlay . findOverlapping ( cursor ) ;
670+ for ( const range of selection ) {
671+ const overlapping = overlay . findOverlapping ( range ) ;
648672 for ( const slice of overlapping ) store . del ( slice . id ) ;
649673 }
650674 }
@@ -691,18 +715,19 @@ export class Editor<T = string> implements Printable {
691715 type : CommonSliceType | string | number ,
692716 data ?: unknown ,
693717 store : EditorSlices < T > = this . saved ,
718+ selection : Range < T > [ ] | IterableIterator < Range < T > > = this . cursors ( ) ,
694719 ) : void {
695720 // TODO: handle mutually exclusive slices (<sub>, <sub>)
696721 this . txt . overlay . refresh ( ) ;
697- CURSORS : for ( let i = this . cursors0 ( ) , cursor = i ( ) ; cursor ; cursor = i ( ) ) {
698- if ( cursor . isCollapsed ( ) ) {
722+ SELECTION : for ( const range of selection ) {
723+ if ( range . isCollapsed ( ) ) {
699724 const pending = this . pending . value ;
700725 if ( pending . has ( type ) ) pending . delete ( type ) ;
701726 else pending . set ( type , data ) ;
702727 this . pending . next ( pending ) ;
703- continue CURSORS ;
728+ continue SELECTION ;
704729 }
705- this . toggleRangeExclFmt ( cursor , type , data , store ) ;
730+ this . toggleRangeExclFmt ( range , type , data , store ) ;
706731 }
707732 }
708733
@@ -834,22 +859,27 @@ export class Editor<T = string> implements Printable {
834859 return true ;
835860 }
836861
837- public split ( type ?: SliceType , data ?: unknown , slices : EditorSlices < T > = this . saved ) : void {
862+ public split (
863+ type ?: SliceType ,
864+ data ?: unknown ,
865+ selection : Range < T > [ ] | IterableIterator < Range < T > > = this . cursors ( ) ,
866+ slices : EditorSlices < T > = this . saved ,
867+ ) : void {
838868 if ( type === void 0 ) {
839- for ( let i = this . cursors0 ( ) , cursor = i ( ) ; cursor ; cursor = i ( ) ) {
840- this . collapseCursor ( cursor ) ;
841- const didInsertMarker = this . splitAt ( cursor . start , slices ) ;
842- if ( didInsertMarker ) cursor . move ( 1 ) ;
869+ for ( const range of selection ) {
870+ this . collapseCursor ( range ) ;
871+ const didInsertMarker = this . splitAt ( range . start , slices ) ;
872+ if ( didInsertMarker && range instanceof Cursor ) range . move ( 1 ) ;
843873 }
844874 } else {
845- for ( let i = this . cursors0 ( ) , cursor = i ( ) ; cursor ; cursor = i ( ) ) {
846- this . collapseCursor ( cursor ) ;
875+ for ( const range of selection ) {
876+ this . collapseCursor ( range ) ;
847877 if ( type === void 0 ) {
848878 // TODO: detect current block type
849879 type = CommonSliceType . p ;
850880 }
851881 slices . insMarker ( type , data ) ;
852- cursor . move ( 1 ) ;
882+ if ( range instanceof Cursor ) range . move ( 1 ) ;
853883 }
854884 }
855885 }
@@ -901,11 +931,11 @@ export class Editor<T = string> implements Printable {
901931 public tglMarker (
902932 type : SliceType ,
903933 data ?: unknown ,
934+ selection : Range < T > [ ] | IterableIterator < Range < T > > = this . cursors ( ) ,
904935 slices : EditorSlices < T > = this . saved ,
905936 def : SliceTypeStep = SliceTypeCon . p ,
906937 ) : void {
907- for ( let i = this . cursors0 ( ) , cursor = i ( ) ; cursor ; cursor = i ( ) )
908- this . tglMarkerAt ( cursor . start , type , data , slices , def ) ;
938+ for ( const range of selection ) this . tglMarkerAt ( range . start , type , data , slices , def ) ;
909939 }
910940
911941 /**
@@ -916,15 +946,19 @@ export class Editor<T = string> implements Printable {
916946 * @param slices The slices set to use, if new marker is inserted at the start
917947 * of the document.
918948 */
919- public updMarker ( type : SliceType , data ?: unknown , slices : EditorSlices < T > = this . saved ) : void {
920- for ( let i = this . cursors0 ( ) , cursor = i ( ) ; cursor ; cursor = i ( ) )
921- this . updMarkerAt ( cursor . start , type , data , slices ) ;
949+ public updMarker (
950+ type : SliceType ,
951+ data ?: unknown ,
952+ selection : Range < T > [ ] | IterableIterator < Range < T > > = this . cursors ( ) ,
953+ slices : EditorSlices < T > = this . saved ,
954+ ) : void {
955+ for ( const range of selection ) this . updMarkerAt ( range . start , type , data , slices ) ;
922956 }
923957
924- public delMarker ( ) : void {
958+ public delMarker ( selection : Range < T > [ ] | IterableIterator < Range < T > > = this . cursors ( ) ) : void {
925959 const markerPoints = new Set < MarkerOverlayPoint < T > > ( ) ;
926- for ( let i = this . cursors0 ( ) , cursor = i ( ) ; cursor ; cursor = i ( ) ) {
927- const markerPoint = this . txt . overlay . getOrNextLowerMarker ( cursor . start ) ;
960+ for ( const range of selection ) {
961+ const markerPoint = this . txt . overlay . getOrNextLowerMarker ( range . start ) ;
928962 if ( markerPoint ) markerPoints . add ( markerPoint ) ;
929963 }
930964 for ( const markerPoint of markerPoints ) {
@@ -1102,8 +1136,24 @@ export class Editor<T = string> implements Printable {
11021136
11031137 // ------------------------------------------------------------------ various
11041138
1105- public point ( at : Position < T > ) : Point < T > {
1106- return typeof at === 'number' ? this . txt . pointAt ( at ) : Array . isArray ( at ) ? this . txt . pointAt ( at [ 0 ] , at [ 1 ] ) : at ;
1139+ public pos2point ( at : EditorPosition < T > ) : Point < T > {
1140+ const txt = this . txt ;
1141+ return typeof at === 'number' ? txt . pointAt ( at ) : Array . isArray ( at ) ? txt . pointAt ( at [ 0 ] , at [ 1 ] ) : at ;
1142+ }
1143+
1144+ public sel2range ( at : EditorSelection < T > ) : [ range : Range < T > , anchor : CursorAnchor ] {
1145+ if ( ! Array . isArray ( at ) ) return [ at , CursorAnchor . End ] ;
1146+ const [ pos1 , pos2 ] = at ;
1147+ const p1 = this . pos2point ( pos1 ) ;
1148+ const txt = this . txt ;
1149+ if ( pos2 === undefined ) {
1150+ p1 . refAfter ( ) ;
1151+ return [ txt . range ( p1 ) , CursorAnchor . End ] ;
1152+ }
1153+ const p2 = this . pos2point ( pos2 ) ;
1154+ const range = txt . rangeFromPoints ( p1 , p2 ) ;
1155+ const anchor : CursorAnchor = range . start === p1 ? CursorAnchor . Start : CursorAnchor . End ;
1156+ return [ range , anchor ] ;
11071157 }
11081158
11091159 public end ( ) : Point < T > {
0 commit comments