@@ -24,12 +24,13 @@ import type {SliceRegistry} from '../registry/SliceRegistry';
24
24
import type {
25
25
CharIterator ,
26
26
CharPredicate ,
27
- Position ,
27
+ EditorPosition ,
28
28
TextRangeUnit ,
29
29
ViewStyle ,
30
30
ViewRange ,
31
31
ViewSlice ,
32
32
EditorUi ,
33
+ EditorSelection ,
33
34
} from './types' ;
34
35
35
36
/**
@@ -106,11 +107,18 @@ export class Editor<T = string> implements Printable {
106
107
public cursors0 ( ) : UndefIterator < Cursor < T > > {
107
108
const iterator = this . txt . localSlices . iterator0 ( ) ;
108
109
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
+ }
111
115
} ;
112
116
}
113
117
118
+ public mainCursor ( ) : Cursor < T > | undefined {
119
+ return this . cursors0 ( ) ( ) ;
120
+ }
121
+
114
122
public cursors ( ) {
115
123
return new UndefEndIter ( this . cursors0 ( ) ) ;
116
124
}
@@ -169,7 +177,7 @@ export class Editor<T = string> implements Printable {
169
177
* the contents is removed and the cursor is set at the start of the range
170
178
* as caret.
171
179
*/
172
- public collapseCursor ( cursor : Cursor < T > ) : void {
180
+ public collapseCursor ( cursor : Range < T > ) : void {
173
181
this . delRange ( cursor ) ;
174
182
cursor . collapseToStart ( ) ;
175
183
}
@@ -196,37 +204,42 @@ export class Editor<T = string> implements Printable {
196
204
* Insert inline text at current cursor position. If cursor selects a range,
197
205
* the range is removed and the text is inserted at the start of the range.
198
206
*/
199
- public insert0 ( cursor : Cursor < T > , text : string ) : ITimespanStruct | undefined {
207
+ public insert0 ( range : Range < T > , text : string ) : ITimespanStruct | undefined {
200
208
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 ( ) ;
203
211
after . refAfter ( ) ;
204
212
const txt = this . txt ;
205
213
const textId = txt . ins ( after . id , text ) ;
206
214
const span = new Timespan ( textId . sid , textId . time , text . length ) ;
207
215
const shift = text . length - 1 ;
208
216
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 ) ;
210
219
return span ;
211
220
}
212
221
213
222
/**
214
223
* 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.
216
225
*/
217
- public insert ( text : string ) : ITimespanStruct [ ] {
226
+ public insert ( text : string , ranges ?: IterableIterator < Range < T > > | Range < T > [ ] ) : ITimespanStruct [ ] {
218
227
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 ) ;
222
235
if ( span ) spans . push ( span ) ;
223
236
const pending = this . pending . value ;
224
237
if ( pending . size ) {
225
238
this . pending . next ( new Map ( ) ) ;
226
- const start = cursor . start . clone ( ) ;
239
+ const start = range . start . clone ( ) ;
227
240
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 ) ;
230
243
}
231
244
}
232
245
return spans ;
@@ -494,6 +507,11 @@ export class Editor<T = string> implements Printable {
494
507
return point ;
495
508
}
496
509
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' : {
497
515
if ( steps > 0 ) for ( let i = 0 ; i < steps ; i ++ ) point = ui ?. eol ?.( point , 1 ) ?? this . eol ( point ) ;
498
516
else for ( let i = 0 ; i < - steps ; i ++ ) point = ui ?. eol ?.( point , - 1 ) ?? this . bol ( point ) ;
499
517
return point ;
@@ -605,18 +623,21 @@ export class Editor<T = string> implements Printable {
605
623
} ) ;
606
624
}
607
625
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 ) ) ;
610
628
if ( unit ) this . select ( unit , ui ) ;
611
629
}
612
630
613
631
// --------------------------------------------------------------- formatting
614
632
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 {
616
637
const overlay = this . txt . overlay ;
617
- for ( let i = this . cursors0 ( ) , cursor = i ( ) ; cursor ; cursor = i ( ) ) {
638
+ for ( const range of selection ) {
618
639
overlay . refresh ( ) ;
619
- const contained = overlay . findContained ( cursor ) ;
640
+ const contained = overlay . findContained ( range ) ;
620
641
for ( const slice of contained ) {
621
642
if ( slice instanceof PersistedSlice ) {
622
643
switch ( slice . behavior ) {
@@ -628,7 +649,7 @@ export class Editor<T = string> implements Printable {
628
649
}
629
650
}
630
651
overlay . refresh ( ) ;
631
- const overlapping = overlay . findOverlapping ( cursor ) ;
652
+ const overlapping = overlay . findOverlapping ( range ) ;
632
653
for ( const slice of overlapping ) {
633
654
switch ( slice . behavior ) {
634
655
case SliceBehavior . One :
@@ -640,11 +661,14 @@ export class Editor<T = string> implements Printable {
640
661
}
641
662
}
642
663
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 {
644
668
const overlay = this . txt . overlay ;
645
669
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 ) ;
648
672
for ( const slice of overlapping ) store . del ( slice . id ) ;
649
673
}
650
674
}
@@ -691,18 +715,19 @@ export class Editor<T = string> implements Printable {
691
715
type : CommonSliceType | string | number ,
692
716
data ?: unknown ,
693
717
store : EditorSlices < T > = this . saved ,
718
+ selection : Range < T > [ ] | IterableIterator < Range < T > > = this . cursors ( ) ,
694
719
) : void {
695
720
// TODO: handle mutually exclusive slices (<sub>, <sub>)
696
721
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 ( ) ) {
699
724
const pending = this . pending . value ;
700
725
if ( pending . has ( type ) ) pending . delete ( type ) ;
701
726
else pending . set ( type , data ) ;
702
727
this . pending . next ( pending ) ;
703
- continue CURSORS ;
728
+ continue SELECTION ;
704
729
}
705
- this . toggleRangeExclFmt ( cursor , type , data , store ) ;
730
+ this . toggleRangeExclFmt ( range , type , data , store ) ;
706
731
}
707
732
}
708
733
@@ -834,22 +859,27 @@ export class Editor<T = string> implements Printable {
834
859
return true ;
835
860
}
836
861
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 {
838
868
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 ) ;
843
873
}
844
874
} 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 ) ;
847
877
if ( type === void 0 ) {
848
878
// TODO: detect current block type
849
879
type = CommonSliceType . p ;
850
880
}
851
881
slices . insMarker ( type , data ) ;
852
- cursor . move ( 1 ) ;
882
+ if ( range instanceof Cursor ) range . move ( 1 ) ;
853
883
}
854
884
}
855
885
}
@@ -901,11 +931,11 @@ export class Editor<T = string> implements Printable {
901
931
public tglMarker (
902
932
type : SliceType ,
903
933
data ?: unknown ,
934
+ selection : Range < T > [ ] | IterableIterator < Range < T > > = this . cursors ( ) ,
904
935
slices : EditorSlices < T > = this . saved ,
905
936
def : SliceTypeStep = SliceTypeCon . p ,
906
937
) : 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 ) ;
909
939
}
910
940
911
941
/**
@@ -916,15 +946,19 @@ export class Editor<T = string> implements Printable {
916
946
* @param slices The slices set to use, if new marker is inserted at the start
917
947
* of the document.
918
948
*/
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 ) ;
922
956
}
923
957
924
- public delMarker ( ) : void {
958
+ public delMarker ( selection : Range < T > [ ] | IterableIterator < Range < T > > = this . cursors ( ) ) : void {
925
959
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 ) ;
928
962
if ( markerPoint ) markerPoints . add ( markerPoint ) ;
929
963
}
930
964
for ( const markerPoint of markerPoints ) {
@@ -1102,8 +1136,24 @@ export class Editor<T = string> implements Printable {
1102
1136
1103
1137
// ------------------------------------------------------------------ various
1104
1138
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 ] ;
1107
1157
}
1108
1158
1109
1159
public end ( ) : Point < T > {
0 commit comments