@@ -11,13 +11,14 @@ import {UndefEndIter, type UndefIterator} from '../../../util/iterator';
1111import { PersistedSlice } from '../slice/PersistedSlice' ;
1212import { ValueSyncStore } from '../../../util/events/sync-store' ;
1313import { formatType } from '../slice/util' ;
14- import type { CommonSliceType } from '../slice' ;
14+ import { CommonSliceType , type SliceType } from '../slice' ;
1515import type { ChunkSlice } from '../util/ChunkSlice' ;
1616import type { Peritext } from '../Peritext' ;
1717import type { Point } from '../rga/Point' ;
1818import type { Range } from '../rga/Range' ;
1919import type { CharIterator , CharPredicate , Position , TextRangeUnit } from './types' ;
2020import type { Printable } from 'tree-dump' ;
21+ import { tick } from '../../../json-crdt-patch' ;
2122
2223/**
2324 * For inline boolean ("Overwrite") slices, both range endpoints should be
@@ -123,16 +124,46 @@ export class Editor<T = string> implements Printable {
123124 for ( let cursor : Cursor < T > | undefined , i = this . cursors0 ( ) ; ( cursor = i ( ) ) ; ) this . delCursor ( cursor ) ;
124125 }
125126
127+ /**
128+ * Ensures there is no range selection. If user has selected a range,
129+ * the contents is removed and the cursor is set at the start of the range
130+ * as caret.
131+ */
132+ public collapseCursor ( cursor : Cursor < T > ) : void {
133+ this . delRange ( cursor ) ;
134+ cursor . collapseToStart ( ) ;
135+ }
136+
137+ public collapseCursors ( ) : void {
138+ for ( let i = this . cursors0 ( ) , cursor = i ( ) ; cursor ; cursor = i ( ) ) this . collapseCursor ( cursor ) ;
139+ }
140+
126141 // ------------------------------------------------------------- text editing
127142
128143 /**
129144 * Insert inline text at current cursor position. If cursor selects a range,
130145 * the range is removed and the text is inserted at the start of the range.
131146 */
147+ public insert0 ( cursor : Cursor < T > , text : string ) : void {
148+ if ( ! text ) return ;
149+ if ( ! cursor . isCollapsed ( ) ) this . delRange ( cursor ) ;
150+ const after = cursor . start . clone ( ) ;
151+ after . refAfter ( ) ;
152+ const txt = this . txt ;
153+ const textId = txt . ins ( after . id , text ) ;
154+ const shift = text . length - 1 ;
155+ const point = txt . point ( shift ? tick ( textId , shift ) : textId , Anchor . After ) ;
156+ cursor . set ( point , point , CursorAnchor . Start ) ;
157+ }
158+
159+ /**
160+ * Inserts text at the cursor positions and collapses cursors, if necessary.
161+ * The applies any pending inline formatting to the inserted text.
162+ */
132163 public insert ( text : string ) : void {
133164 if ( ! this . hasCursor ( ) ) this . addCursor ( ) ;
134165 for ( let cursor : Cursor < T > | undefined , i = this . cursors0 ( ) ; ( cursor = i ( ) ) ; ) {
135- cursor . insert ( text ) ;
166+ this . insert0 ( cursor , text ) ;
136167 const pending = this . pending . value ;
137168 if ( pending . size ) {
138169 this . pending . next ( new Map ( ) ) ;
@@ -149,7 +180,16 @@ export class Editor<T = string> implements Printable {
149180 * select a range, deletes the whole range.
150181 */
151182 public del ( step : number = - 1 ) : void {
152- this . forCursor ( ( cursor ) => cursor . del ( step ) ) ;
183+ this . delete ( step , 'char' ) ;
184+ }
185+
186+ public delRange ( range : Range < T > ) : void {
187+ const txt = this . txt ;
188+ const overlay = txt . overlay ;
189+ const contained = overlay . findContained ( range ) ;
190+ for ( const slice of contained )
191+ if ( slice instanceof PersistedSlice && slice . behavior !== SliceBehavior . Cursor ) slice . del ( ) ;
192+ txt . delStr ( range ) ;
153193 }
154194
155195 /**
@@ -160,9 +200,10 @@ export class Editor<T = string> implements Printable {
160200 * @param unit A unit of deletion: "char", "word", "line".
161201 */
162202 public delete ( step : number , unit : 'char' | 'word' | 'line' ) : void {
163- this . forCursor ( ( cursor ) => {
203+ const txt = this . txt ;
204+ for ( let i = this . cursors0 ( ) , cursor = i ( ) ; cursor ; cursor = i ( ) ) {
164205 if ( ! cursor . isCollapsed ( ) ) {
165- cursor . collapse ( ) ;
206+ this . collapseCursor ( cursor ) ;
166207 return ;
167208 }
168209 let point1 = cursor . start . clone ( ) ;
@@ -173,12 +214,11 @@ export class Editor<T = string> implements Printable {
173214 point1 = this . skip ( point1 , - 1 , unit ) ;
174215 point2 = this . skip ( point2 , 1 , unit ) ;
175216 }
176- const txt = this . txt ;
177217 const range = txt . range ( point1 , point2 ) ;
178- txt . delStr ( range ) ;
218+ this . delRange ( range ) ;
179219 point1 . refAfter ( ) ;
180220 cursor . set ( point1 ) ;
181- } ) ;
221+ }
182222 }
183223
184224 // ----------------------------------------------------------------- movement
@@ -494,7 +534,7 @@ export class Editor<T = string> implements Printable {
494534 public select ( unit : TextRangeUnit ) : void {
495535 this . forCursor ( ( cursor ) => {
496536 const range = this . range ( cursor . start , unit ) ;
497- if ( range ) cursor . setRange ( range ) ;
537+ if ( range ) cursor . set ( range . start , range . end , CursorAnchor . Start ) ;
498538 else this . delCursors ;
499539 } ) ;
500540 }
@@ -506,14 +546,6 @@ export class Editor<T = string> implements Printable {
506546
507547 // --------------------------------------------------------------- formatting
508548
509- protected getSliceStore ( slice : PersistedSlice < T > ) : EditorSlices < T > | undefined {
510- const sid = slice . id . sid ;
511- if ( sid === this . saved . slices . set . doc . clock . sid ) return this . saved ;
512- if ( sid === this . extra . slices . set . doc . clock . sid ) return this . extra ;
513- if ( sid === this . local . slices . set . doc . clock . sid ) return this . local ;
514- return ;
515- }
516-
517549 protected toggleRangeExclFmt (
518550 range : Range < T > ,
519551 type : CommonSliceType | string | number ,
@@ -527,12 +559,7 @@ export class Editor<T = string> implements Printable {
527559 const needToRemoveFormatting = complete . has ( type ) ;
528560 makeRangeExtendable ( range ) ;
529561 const contained = overlay . findContained ( range ) ;
530- for ( const slice of contained ) {
531- if ( slice instanceof PersistedSlice && slice . type === type ) {
532- const deletionStore = this . getSliceStore ( slice ) ;
533- if ( deletionStore ) deletionStore . del ( slice . id ) ;
534- }
535- }
562+ for ( const slice of contained ) if ( slice instanceof PersistedSlice && slice . type === type ) slice . del ( ) ;
536563 if ( needToRemoveFormatting ) {
537564 overlay . refresh ( ) ;
538565 const [ complete2 , partial2 ] = overlay . stat ( range , 1e6 ) ;
@@ -584,10 +611,8 @@ export class Editor<T = string> implements Printable {
584611 switch ( slice . behavior ) {
585612 case SliceBehavior . One :
586613 case SliceBehavior . Many :
587- case SliceBehavior . Erase : {
588- const deletionStore = this . getSliceStore ( slice ) ;
589- if ( deletionStore ) deletionStore . del ( slice . id ) ;
590- }
614+ case SliceBehavior . Erase :
615+ slice . del ( ) ;
591616 }
592617 }
593618 }
@@ -613,6 +638,18 @@ export class Editor<T = string> implements Printable {
613638 }
614639 }
615640
641+ public split ( type ?: SliceType , data ?: unknown , slices : EditorSlices < T > = this . saved ) : void {
642+ for ( let i = this . cursors0 ( ) , cursor = i ( ) ; cursor ; cursor = i ( ) ) {
643+ this . collapseCursor ( cursor ) ;
644+ if ( type === void 0 ) {
645+ // TODO: detect current block type
646+ type = CommonSliceType . p ;
647+ }
648+ slices . insMarker ( type , data ) ;
649+ cursor . move ( 1 ) ;
650+ }
651+ }
652+
616653 // ------------------------------------------------------------------ various
617654
618655 public point ( at : Position < T > ) : Point < T > {
0 commit comments