1- import { Line , RangeSetBuilder , StateEffect , StateField , Text , Transaction } from "@codemirror/state" ;
1+ import { EditorState , Line , RangeSetBuilder , StateEffect , StateField , Transaction } from "@codemirror/state" ;
22import {
33 ViewUpdate ,
44 PluginValue ,
@@ -8,8 +8,10 @@ import {
88 Decoration ,
99 WidgetType ,
1010} from "@codemirror/view" ;
11+ import { syntaxTree } from "@codemirror/language" ;
1112import type BetterWordCount from "src/main" ;
1213import { getWordCount } from "src/utils/StatUtils" ;
14+ import { MATCH_COMMENT , MATCH_HTML_COMMENT } from "src/constants" ;
1315
1416export const pluginField = StateField . define < BetterWordCount > ( {
1517 create ( ) {
@@ -111,6 +113,7 @@ class SectionWidget extends WidgetType {
111113 }
112114}
113115
116+ const mdCommentRe = / % % / g;
114117class SectionWordCountEditorPlugin implements PluginValue {
115118 decorations : DecorationSet ;
116119 lineCounts : any [ ] = [ ] ;
@@ -122,20 +125,40 @@ class SectionWordCountEditorPlugin implements PluginValue {
122125 return ;
123126 }
124127
125- this . calculateLineCounts ( view . state . doc ) ;
128+ this . calculateLineCounts ( view . state , plugin ) ;
126129 this . decorations = this . mkDeco ( view ) ;
127130 }
128131
129- calculateLineCounts ( doc : Text ) {
130- for ( let index = 0 ; index < doc . lines ; index ++ ) {
131- const line = doc . line ( index + 1 ) ;
132- this . lineCounts . push ( getWordCount ( line . text ) ) ;
132+ calculateLineCounts ( state : EditorState , plugin : BetterWordCount ) {
133+ const stripComments = plugin . settings . countComments ;
134+ let docStr = state . doc . toString ( ) ;
135+
136+ if ( stripComments ) {
137+ // Strip out comments, but preserve new lines for accurate positioning data
138+ const preserveNl = ( match : string , offset : number , str : string ) => {
139+ let output = '' ;
140+ for ( let i = offset , len = offset + match . length ; i < len ; i ++ ) {
141+ if ( / [ \r \n ] / . test ( str [ i ] ) ) {
142+ output += str [ i ] ;
143+ }
144+ }
145+ return output ;
146+ }
147+
148+ docStr = docStr . replace ( MATCH_COMMENT , preserveNl ) . replace ( MATCH_HTML_COMMENT , preserveNl ) ;
149+ }
150+
151+ const lines = docStr . split ( state . facet ( EditorState . lineSeparator ) || / \r \n ? | \n / )
152+
153+ for ( let i = 0 , len = lines . length ; i < len ; i ++ ) {
154+ let line = lines [ i ] ;
155+ this . lineCounts . push ( getWordCount ( line ) ) ;
133156 }
134157 }
135158
136159 update ( update : ViewUpdate ) {
137160 const plugin = update . view . state . field ( pluginField ) ;
138- const { displaySectionCounts } = plugin . settings ;
161+ const { displaySectionCounts, countComments : stripComments } = plugin . settings ;
139162 let didSettingsChange = false ;
140163
141164 if ( this . lineCounts . length && ! displaySectionCounts ) {
@@ -144,37 +167,70 @@ class SectionWordCountEditorPlugin implements PluginValue {
144167 return ;
145168 } else if ( ! this . lineCounts . length && displaySectionCounts ) {
146169 didSettingsChange = true ;
147- this . calculateLineCounts ( update . startState . doc ) ;
170+ this . calculateLineCounts ( update . startState , plugin ) ;
148171 }
149172
150173 if ( update . docChanged ) {
151174 const startDoc = update . startState . doc ;
175+
152176 let tempDoc = startDoc ;
177+ let editStartLine = Infinity ;
178+ let editEndLine = - Infinity ;
153179
154180 update . changes . iterChanges ( ( fromA , toA , fromB , toB , text ) => {
155181 const from = fromB ;
156182 const to = fromB + ( toA - fromA ) ;
157183 const nextTo = from + text . length ;
158-
184+
159185 const fromLine = tempDoc . lineAt ( from ) ;
160186 const toLine = tempDoc . lineAt ( to ) ;
161187
162188 tempDoc = tempDoc . replace ( fromB , fromB + ( toA - fromA ) , text ) ;
163189
164- const fromLineNext = tempDoc . lineAt ( from ) ;
165- const toLineNext = tempDoc . lineAt ( nextTo ) ;
166-
190+ const nextFromLine = tempDoc . lineAt ( from ) ;
191+ const nextToLine = tempDoc . lineAt ( nextTo ) ;
167192 const lines : any [ ] = [ ] ;
168193
169- for ( let i = fromLineNext . number ; i <= toLineNext . number ; i ++ ) {
194+ for ( let i = nextFromLine . number ; i <= nextToLine . number ; i ++ ) {
170195 lines . push ( getWordCount ( tempDoc . line ( i ) . text ) ) ;
171196 }
172197
173198 const spliceStart = fromLine . number - 1 ;
174199 const spliceLen = toLine . number - fromLine . number + 1 ;
175200
201+ editStartLine = Math . min ( editStartLine , spliceStart ) ;
202+ editEndLine = Math . max ( editEndLine , spliceStart + ( nextToLine . number - nextFromLine . number + 1 ) ) ;
203+
176204 this . lineCounts . splice ( spliceStart , spliceLen , ...lines ) ;
177205 } ) ;
206+
207+ // Filter out any counts associated with comments in the lines that were edited
208+ if ( stripComments ) {
209+ const tree = syntaxTree ( update . state ) ;
210+ for ( let i = editStartLine ; i < editEndLine ; i ++ ) {
211+ const line = update . state . doc . line ( i + 1 ) ;
212+ let newLine = '' ;
213+ let pos = 0 ;
214+ let foundComment = false ;
215+
216+ tree . iterate ( {
217+ enter ( node ) {
218+ if ( node . name && / c o m m e n t / . test ( node . name ) ) {
219+ foundComment = true ;
220+ newLine += line . text . substring ( pos , node . from - line . from ) ;
221+ pos = node . to - line . from ;
222+ }
223+ } ,
224+ from : line . from ,
225+ to : line . to ,
226+ } ) ;
227+
228+ if ( foundComment ) {
229+ newLine += line . text . substring ( pos ) ;
230+ this . lineCounts [ i ] = getWordCount ( newLine ) ;
231+ }
232+ }
233+ }
178234 }
179235
180236 if ( update . docChanged || update . viewportChanged || didSettingsChange ) {
0 commit comments