@@ -8,197 +8,210 @@ import {
88} from "@codemirror/view" ;
99import { RangeSetBuilder , Text } from "@codemirror/state" ;
1010import { detectDirection } from './globals' ;
11+ import RtlPlugin from './main' ;
12+ import { editorInfoField , MarkdownView } from 'obsidian' ;
1113
1214type Region = { from : number ; to : number ; } ;
1315type DecorationRegion = Region & { dec : Decoration } ;
1416
15- class AutoDirectionPlugin implements PluginValue {
16- decorations : DecorationSet ;
17- // A cache mechanism for regions, so we don't need to calculate the decoration for a line if it doesn't
18- // change.
19- decorationRegions : DecorationRegion [ ] = [ ] ;
20- active = false ;
21-
22- rtlDec = Decoration . line ( {
23- attributes : { dir : 'rtl' } ,
24- } ) ;
25- ltrDec = Decoration . line ( {
26- attributes : { dir : 'ltr' } ,
27- } ) ;
28- emptyDirDec = Decoration . line ( {
29- attributes : { dir : "" } ,
30- } ) ;
31- autoDec = Decoration . line ( {
32- attributes : { dir : 'auto' } ,
33- } ) ;
34-
35- constructor ( _view : EditorView ) {
36- this . decorations = this . buildDecorations ( ) ;
37- }
38-
39- update ( vu : ViewUpdate ) {
40- if ( vu . viewportChanged || vu . docChanged ) {
41- const regions : Region [ ] = [ ] ;
42- if ( vu . docChanged ) {
43- // Trying to calculate the regions that have been changed and also modifying
44- // (shift) other regions regarding to that change.
45- // So for example if we have `First line\nSecond line\Test` any insertion or
46- // deletion on the second line will result in a shift on the next lines (here third
47- // line) and will add the second line region to regions array so we recalculate the direction.
48- vu . changes . iterChanges ( ( fromA , toA , fromB , toB ) => {
49- const shift = ( toB - fromB ) - ( toA - fromA ) ;
50- this . shiftDecorationRegions ( shift < 0 ? toB : toA , shift ) ;
51-
52- regions . push ( ...this . getLineRegions ( vu . state . doc , fromB , toB ) ) ;
53- } ) ;
54- }
17+ export interface AutoDirectionPlugin extends PluginValue {
18+ setActive ( active : boolean , view : EditorView ) : void ;
19+ }
5520
56- this . updateEx ( vu . view , regions ) ;
57- }
58- }
59-
60- destroy ( ) { }
61-
62- setActive ( active : boolean , view : EditorView ) {
63- const forceUpdate = this . active !== active ;
64- this . active = active ;
65- this . decorations = this . buildDecorations ( ) ;
66-
67- if ( forceUpdate ) {
68- this . updateEx ( view ) ;
69- }
70- view . dispatch ( ) ;
71- }
72-
73- // Calculate the line decoration (rtl|ltr|auto|none) for each line that has an intersection with
74- // given regions. Note that a single region could include or intersect with multiple lines.
75- updateEx ( view : EditorView , regions : Region [ ] = [ ] ) {
76- // If regions is empty recalculate the decoration for all lines in the viewport
77- if ( regions . length === 0 ) {
78- const { from, to} = view . viewport ;
79- regions = this . getLineRegions ( view . state . doc , from , to ) ;
80- }
81-
82- for ( const { from, to } of regions ) {
83- for ( let pos = from ; pos <= to ; ) {
84- const line = view . state . doc . lineAt ( pos ) ;
85-
86- let dec = this . emptyDirDec ;
87- if ( this . active ) {
88- const s = view . state . doc . sliceString ( line . from , line . to ) ;
89- const d = this . detectDecoration ( s ) ;
90- // If we couldn't find a proper decoration, use the line before the decoration
91- dec = d ? d : this . lineBeforeDecoration ( line . from ) ;
21+ export function getAutoDirectionPlugin ( rtlPlugin : RtlPlugin ) {
22+ return ViewPlugin . fromClass (
23+ class implements AutoDirectionPlugin {
24+ rtlPlugin : RtlPlugin ;
25+ view : EditorView ;
26+ decorations : DecorationSet ;
27+ // A cache mechanism for regions, so we don't need to calculate the decoration for a line if it doesn't
28+ // change.
29+ decorationRegions : DecorationRegion [ ] = [ ] ;
30+ active = false ;
31+
32+ rtlDec = Decoration . line ( {
33+ attributes : { dir : 'rtl' } ,
34+ } ) ;
35+ ltrDec = Decoration . line ( {
36+ attributes : { dir : 'ltr' } ,
37+ } ) ;
38+ emptyDirDec = Decoration . line ( {
39+ attributes : { dir : "" } ,
40+ } ) ;
41+ autoDec = Decoration . line ( {
42+ attributes : { dir : 'auto' } ,
43+ } ) ;
44+
45+ constructor ( view : EditorView ) {
46+ this . decorations = this . buildDecorations ( ) ;
47+ this . rtlPlugin = rtlPlugin ;
48+ this . view = view ;
49+ const editorInfo = this . view . state . field ( editorInfoField ) ;
50+ if ( editorInfo instanceof MarkdownView ) {
51+ this . rtlPlugin . adjustDirectionToView ( editorInfo , this ) ;
9252 }
53+ this . rtlPlugin . handleIframeEditor ( this . view . dom , this . view , editorInfo . file , this ) ;
54+ }
9355
94- this . addDecorationRegion ( { from : line . from , to : line . to , dec} ) ;
95- // Advance to the next line
96- pos = line . to + 1 ;
56+ update ( vu : ViewUpdate ) {
57+ if ( vu . viewportChanged || vu . docChanged ) {
58+ const regions : Region [ ] = [ ] ;
59+ if ( vu . docChanged ) {
60+ // Trying to calculate the regions that have been changed and also modifying
61+ // (shift) other regions according to that change.
62+ // So for example if we have `First line\nSecond line\Test` any insertion or
63+ // deletion on the second line will result in a shift on the next lines (here third
64+ // line) and will add the second line region to regions array so we recalculate the direction.
65+ vu . changes . iterChanges ( ( fromA , toA , fromB , toB ) => {
66+ const shift = ( toB - fromB ) - ( toA - fromA ) ;
67+ this . shiftDecorationRegions ( shift < 0 ? toB : toA , shift ) ;
68+
69+ regions . push ( ...this . getLineRegions ( vu . state . doc , fromB , toB ) ) ;
70+ } ) ;
71+ }
72+
73+ this . updateEx ( vu . view , regions ) ;
74+ }
9775 }
98- }
99-
100- this . decorations = this . buildDecorations ( ) ;
101- }
102-
103- buildDecorations ( ) : DecorationSet {
104- const builder = new RangeSetBuilder < Decoration > ( ) ;
105- for ( const dr of this . decorationRegions ) {
106- builder . add ( dr . from , dr . from , dr . dec ) ;
107- }
108-
109- return builder . finish ( ) ;
110- }
111-
112- // Adding a decoration region while keeping the decoration regions in order based on their
113- // start ('from' property). This will either replace a decoration region or add one in the middle
114- // or append to end of the decoration regions.
115- addDecorationRegion ( dr : DecorationRegion ) {
116- for ( let i = 0 ; i < this . decorationRegions . length ; i ++ ) {
117- if ( this . decorationRegions [ i ] . from < dr . from ) {
118- continue ;
76+
77+ destroy ( ) { }
78+
79+ setActive ( active : boolean , view : EditorView ) {
80+ const forceUpdate = this . active !== active ;
81+ this . active = active ;
82+ this . decorations = this . buildDecorations ( ) ;
83+
84+ if ( forceUpdate ) {
85+ this . updateEx ( view ) ;
86+ }
11987 }
12088
121- if ( this . decorationRegions [ i ] . from === dr . from ) {
122- this . decorationRegions [ i ] = dr ;
123- } else if ( this . decorationRegions [ i ] . from > dr . from ) {
124- this . decorationRegions . splice ( i , 0 , dr ) ;
89+ // Calculate the line decoration (rtl|ltr|auto|none) for each line that has an intersection with
90+ // given regions. Note that a single region could include or intersect with multiple lines.
91+ updateEx ( view : EditorView , regions : Region [ ] = [ ] ) {
92+ // If regions is empty recalculate the decoration for all lines in the viewport
93+ if ( regions . length === 0 ) {
94+ const { from, to} = view . viewport ;
95+ regions = this . getLineRegions ( view . state . doc , from , to ) ;
96+ }
97+
98+ for ( const { from, to } of regions ) {
99+ for ( let pos = from ; pos <= to ; ) {
100+ const line = view . state . doc . lineAt ( pos ) ;
101+
102+ let dec = this . emptyDirDec ;
103+ if ( this . active ) {
104+ const s = view . state . doc . sliceString ( line . from , line . to ) ;
105+ const d = this . detectDecoration ( s ) ;
106+ // If we couldn't find a proper decoration, use the line before the decoration
107+ dec = d ? d : this . lineBeforeDecoration ( line . from ) ;
108+ }
109+
110+ this . addDecorationRegion ( { from : line . from , to : line . to , dec} ) ;
111+ // Advance to the next line
112+ pos = line . to + 1 ;
113+ }
114+ }
115+
116+ this . decorations = this . buildDecorations ( ) ;
125117 }
126118
127- return ;
128- }
119+ buildDecorations ( ) : DecorationSet {
120+ const builder = new RangeSetBuilder < Decoration > ( ) ;
121+ for ( const dr of this . decorationRegions ) {
122+ builder . add ( dr . from , dr . from , dr . dec ) ;
123+ }
129124
130- this . decorationRegions . push ( dr ) ;
131- }
125+ return builder . finish ( ) ;
126+ }
132127
133- // Shifting every decorationRegions region which their start is after the 'from' variable based
134- // on the given amount.
135- shiftDecorationRegions ( from : number , amount : number ) {
136- if ( amount === 0 ) {
137- return ;
138- }
128+ // Adding a decoration region while keeping the decoration regions in order based on their
129+ // start ('from' property). This will either replace a decoration region or add one in the middle
130+ // or append to end of the decoration regions.
131+ addDecorationRegion ( dr : DecorationRegion ) {
132+ for ( let i = 0 ; i < this . decorationRegions . length ; i ++ ) {
133+ if ( this . decorationRegions [ i ] . from < dr . from ) {
134+ continue ;
135+ }
136+
137+ if ( this . decorationRegions [ i ] . from === dr . from ) {
138+ this . decorationRegions [ i ] = dr ;
139+ } else if ( this . decorationRegions [ i ] . from > dr . from ) {
140+ this . decorationRegions . splice ( i , 0 , dr ) ;
141+ }
142+
143+ return ;
144+ }
139145
140- for ( let i = 0 ; i < this . decorationRegions . length ; i ++ ) {
141- if ( this . decorationRegions [ i ] . from < from ) {
142- continue ;
146+ this . decorationRegions . push ( dr ) ;
143147 }
144148
145- this . decorationRegions [ i ] . from += amount ;
146- this . decorationRegions [ i ] . to += amount ;
149+ // Shifting every decorationRegions region which their start is after the 'from' variable based
150+ // on the given amount.
151+ shiftDecorationRegions ( from : number , amount : number ) {
152+ if ( amount === 0 ) {
153+ return ;
154+ }
147155
148- // The shift amount could be negative (on deletion). If after shifting with a negative
149- // amount the region gets below the 'from' variable we will remove the decoration region
150- // as the decoration will get calculated again.
151- if ( this . decorationRegions [ i ] . from <= from ) {
152- this . decorationRegions . splice ( i , 1 ) ;
153- i -- ;
156+ for ( let i = 0 ; i < this . decorationRegions . length ; i ++ ) {
157+ if ( this . decorationRegions [ i ] . from < from ) {
158+ continue ;
159+ }
160+
161+ this . decorationRegions [ i ] . from += amount ;
162+ this . decorationRegions [ i ] . to += amount ;
163+
164+ // The shift amount could be negative (on deletion). If after shifting with a negative
165+ // amount the region gets below the 'from' variable we will remove the decoration region
166+ // as the decoration will get calculated again.
167+ if ( this . decorationRegions [ i ] . from <= from ) {
168+ this . decorationRegions . splice ( i , 1 ) ;
169+ i -- ;
170+ }
171+ }
154172 }
155- }
156- }
157-
158- detectDecoration ( s : string ) : Decoration | null {
159- // Replacing so we don't get the 'x' character which is used to show a checked checkbox in
160- // markdown as a LTR direction indicator.
161- const direction = detectDirection ( s . replace ( '- [x]' , '' ) ) ;
162- switch ( direction ) {
163- case 'rtl' :
164- return this . rtlDec ;
165- case 'ltr' :
166- return this . ltrDec ;
167- }
168-
169- return null ;
170- }
171-
172- lineBeforeDecoration ( from : number , def = this . ltrDec ) : Decoration {
173- const l = this . decorationRegions . length ;
174- // If 'from' is out of decoration regions scope use the last one.
175- if ( l !== 0 && from > this . decorationRegions [ l - 1 ] . from ) {
176- return this . decorationRegions [ l - 1 ] . dec ;
177- }
178-
179- for ( let i = 0 ; i < l ; i ++ ) {
180- if ( i !== 0 && this . decorationRegions [ i ] . from >= from ) {
181- return this . decorationRegions [ i - 1 ] . dec ;
173+
174+ detectDecoration ( s : string ) : Decoration | null {
175+ // Replacing so we don't get the 'x' character which is used to show a checked checkbox in
176+ // markdown as a LTR direction indicator.
177+ const direction = detectDirection ( s . replace ( '- [x]' , '' ) ) ;
178+ switch ( direction ) {
179+ case 'rtl' :
180+ return this . rtlDec ;
181+ case 'ltr' :
182+ return this . ltrDec ;
183+ }
184+
185+ return null ;
182186 }
183- }
184187
185- return def ;
186- }
188+ lineBeforeDecoration ( from : number , def = this . ltrDec ) : Decoration {
189+ const l = this . decorationRegions . length ;
190+ // If 'from' is out of decoration regions scope use the last one.
191+ if ( l !== 0 && from > this . decorationRegions [ l - 1 ] . from ) {
192+ return this . decorationRegions [ l - 1 ] . dec ;
193+ }
194+
195+ for ( let i = 0 ; i < l ; i ++ ) {
196+ if ( i !== 0 && this . decorationRegions [ i ] . from >= from ) {
197+ return this . decorationRegions [ i - 1 ] . dec ;
198+ }
199+ }
187200
188- // Get all lines region between 'from' and 'to'
189- getLineRegions ( doc : Text , from : number , to : number ) : Region [ ] {
190- const regions : Region [ ] = [ ] ;
191- for ( let i = from ; i <= to ; i ++ ) {
192- const l = doc . lineAt ( i ) ;
193- i = l . to ;
201+ return def ;
202+ }
194203
195- regions . push ( { from : l . from , to : l . to } ) ;
196- }
204+ // Get all lines region between 'from' and 'to'
205+ getLineRegions ( doc : Text , from : number , to : number ) : Region [ ] {
206+ const regions : Region [ ] = [ ] ;
207+ for ( let i = from ; i <= to ; i ++ ) {
208+ const l = doc . lineAt ( i ) ;
209+ i = l . to ;
197210
198- return regions ;
199- }
200- }
211+ regions . push ( { from : l . from , to : l . to } ) ;
212+ }
201213
202- export const autoDirectionPlugin = ViewPlugin . fromClass ( AutoDirectionPlugin , {
203- decorations : ( v ) => v . decorations ,
204- } ) ;
214+ return regions ;
215+ }
216+ } , { decorations : ( v ) => v . decorations } ) ;
217+ }
0 commit comments