@@ -8,118 +8,151 @@ import { ListItem } from '../Task/ListItem';
88import { TaskLocation } from '../Task/TaskLocation' ;
99import { Cache } from './Cache' ;
1010
11- export function parseFileContent (
12- filePath : string ,
13- fileContent : string ,
14- listItems : ListItemCache [ ] | undefined ,
15- logger : Logger ,
16- fileCache : CachedMetadata ,
17- errorReporter : ( e : any , filePath : string , listItem : ListItemCache , line : string ) => void ,
18- ) {
19- const tasks : Task [ ] = [ ] ;
20- if ( listItems === undefined ) {
21- // When called via Cache, this function would never be called or files without list items.
22- // It is useful for tests to be act gracefully on sample Markdown files with no list items, however.
23- return tasks ;
11+ export class FileParser {
12+ private readonly filePath : string ;
13+ private readonly fileContent : string ;
14+ private readonly listItems : ListItemCache [ ] ;
15+ private readonly logger : Logger ;
16+ private readonly fileCache : CachedMetadata ;
17+ private readonly errorReporter : ( e : any , filePath : string , listItem : ListItemCache , line : string ) => void ;
18+
19+ private readonly fileLines : string [ ] ;
20+ private readonly line2ListItem : Map < number , ListItem > = new Map ( ) ;
21+ private readonly tasks : Task [ ] = [ ] ;
22+ private readonly dateFromFileName : Lazy < moment . Moment | null > ;
23+
24+ constructor (
25+ filePath : string ,
26+ fileContent : string ,
27+ listItems : ListItemCache [ ] ,
28+ logger : Logger ,
29+ fileCache : CachedMetadata ,
30+ errorReporter : ( e : any , filePath : string , listItem : ListItemCache , line : string ) => void ,
31+ ) {
32+ this . filePath = filePath ;
33+ this . fileContent = fileContent ;
34+ this . listItems = listItems ;
35+ this . logger = logger ;
36+ this . fileCache = fileCache ;
37+ this . errorReporter = errorReporter ;
38+ this . fileLines = this . fileContent . split ( '\n' ) ;
39+
40+ // Lazily store date extracted from filename to avoid parsing more than needed
41+ this . dateFromFileName = new Lazy ( ( ) => DateFallback . fromPath ( this . filePath ) ) ;
2442 }
2543
26- const tasksFile = new TasksFile ( filePath , fileCache ) ;
27- const fileLines = fileContent . split ( '\n' ) ;
28- const linesInFile = fileLines . length ;
29-
30- // Lazily store date extracted from filename to avoid parsing more than needed
31- // this.logger.debug(`getTasksFromFileContent() reading ${file.path}`);
32- const dateFromFileName = new Lazy ( ( ) => DateFallback . fromPath ( filePath ) ) ;
33-
34- // We want to store section information with every task so
35- // that we can use that when we post process the markdown
36- // rendered lists.
37- let currentSection : SectionCache | null = null ;
38- let sectionIndex = 0 ;
39- const line2ListItem : Map < number , ListItem > = new Map ( ) ;
40- for ( const listItem of listItems ) {
41- const lineNumber = listItem . position . start . line ;
42- if ( lineNumber >= linesInFile ) {
43- /*
44- Obsidian CachedMetadata has told us that there is a task on lineNumber, but there are
45- not that many lines in the file.
46-
47- This was the underlying cause of all the 'Stuck on "Loading Tasks..."' messages,
48- as it resulted in the line 'undefined' being parsed.
49-
50- Somehow the file had been shortened whilst Obsidian was closed, meaning that
51- when Obsidian started up, it got the new file content, but still had the old cached
52- data about locations of list items in the file.
53- */
54- logger . debug (
55- `${ filePath } Obsidian gave us a line number ${ lineNumber } past the end of the file. ${ linesInFile } .` ,
56- ) ;
57- return tasks ;
58- }
59- if ( currentSection === null || currentSection . position . end . line < lineNumber ) {
60- // We went past the current section (or this is the first task).
61- // Find the section that is relevant for this task and the following of the same section.
62- currentSection = Cache . getSection ( lineNumber , fileCache . sections ) ;
63- sectionIndex = 0 ;
44+ /**
45+ * **Warning**: This is designed to only be called **once per {@link FileParser} instance**.
46+ */
47+ public parseFileContent ( ) {
48+ if ( this . listItems === undefined ) {
49+ // When called via Cache, this function would never be called or files without list items.
50+ // It is useful for tests to be act gracefully on sample Markdown files with no list items, however.
51+ return this . tasks ;
6452 }
6553
66- if ( currentSection === null ) {
67- // Cannot process a task without a section.
68- continue ;
69- }
54+ const tasksFile = new TasksFile ( this . filePath , this . fileCache ) ;
55+ const linesInFile = this . fileLines . length ;
56+
57+ // this.logger.debug(`FileParser.parseFileContent() reading ${this.filePath}`);
7058
71- const line = fileLines [ lineNumber ] ;
72- if ( line === undefined ) {
73- logger . debug ( `${ filePath } : line ${ lineNumber } - ignoring 'undefined' line.` ) ;
74- continue ;
59+ // We want to store section information with every task so
60+ // that we can use that when we post process the markdown
61+ // rendered lists.
62+ let currentSection : SectionCache | null = null ;
63+ let sectionIndex = 0 ;
64+ for ( const listItem of this . listItems ) {
65+ const lineNumber = listItem . position . start . line ;
66+ if ( lineNumber >= linesInFile ) {
67+ /*
68+ Obsidian CachedMetadata has told us that there is a task on lineNumber, but there are
69+ not that many lines in the file.
70+
71+ This was the underlying cause of all the 'Stuck on "Loading Tasks..."' messages,
72+ as it resulted in the line 'undefined' being parsed.
73+
74+ Somehow the file had been shortened whilst Obsidian was closed, meaning that
75+ when Obsidian started up, it got the new file content, but still had the old cached
76+ data about locations of list items in the file.
77+ */
78+ this . logger . debug (
79+ `${ this . filePath } Obsidian gave us a line number ${ lineNumber } past the end of the file. ${ linesInFile } .` ,
80+ ) ;
81+ return this . tasks ;
82+ }
83+ if ( currentSection === null || currentSection . position . end . line < lineNumber ) {
84+ // We went past the current section (or this is the first task).
85+ // Find the section that is relevant for this task and the following of the same section.
86+ currentSection = Cache . getSection ( lineNumber , this . fileCache . sections ) ;
87+ sectionIndex = 0 ;
88+ }
89+
90+ if ( currentSection === null ) {
91+ // Cannot process a task without a section.
92+ continue ;
93+ }
94+
95+ const line = this . fileLines [ lineNumber ] ;
96+ if ( line === undefined ) {
97+ this . logger . debug ( `${ this . filePath } : line ${ lineNumber } - ignoring 'undefined' line.` ) ;
98+ continue ;
99+ }
100+
101+ const taskLocation = new TaskLocation (
102+ tasksFile ,
103+ lineNumber ,
104+ currentSection . position . start . line ,
105+ sectionIndex ,
106+ Cache . getPrecedingHeader ( lineNumber , this . fileCache . headings ) ,
107+ ) ;
108+ sectionIndex = this . parseLine ( listItem , line , taskLocation , lineNumber , sectionIndex ) ;
75109 }
76110
77- const taskLocation = new TaskLocation (
78- tasksFile ,
79- lineNumber ,
80- currentSection . position . start . line ,
81- sectionIndex ,
82- Cache . getPrecedingHeader ( lineNumber , fileCache . headings ) ,
83- ) ;
111+ return this . tasks ;
112+ }
113+
114+ private parseLine (
115+ listItem : ListItemCache ,
116+ line : string ,
117+ taskLocation : TaskLocation ,
118+ lineNumber : number ,
119+ sectionIndex : number ,
120+ ) {
84121 if ( listItem . task !== undefined ) {
85122 let task ;
86123 try {
87124 task = Task . fromLine ( {
88125 line,
89126 taskLocation : taskLocation ,
90- fallbackDate : dateFromFileName . value ,
127+ fallbackDate : this . dateFromFileName . value ,
91128 } ) ;
92129
93130 if ( task !== null ) {
94131 // listItem.parent could be negative if the parent is not found (in other words, it is a root task).
95132 // That is not a problem, as we never put a negative number in line2ListItem map, so parent will be null.
96- const parentListItem : ListItem | null = line2ListItem . get ( listItem . parent ) ?? null ;
133+ const parentListItem : ListItem | null = this . line2ListItem . get ( listItem . parent ) ?? null ;
97134 if ( parentListItem !== null ) {
98135 task = new Task ( {
99136 ...task ,
100137 parent : parentListItem ,
101138 } ) ;
102139 }
103140
104- line2ListItem . set ( lineNumber , task ) ;
141+ this . line2ListItem . set ( lineNumber , task ) ;
105142 }
106143 } catch ( e ) {
107- errorReporter ( e , filePath , listItem , line ) ;
108- continue ;
144+ this . errorReporter ( e , this . filePath , listItem , line ) ;
145+ return sectionIndex ;
109146 }
110147
111148 if ( task !== null ) {
112149 sectionIndex ++ ;
113- tasks . push ( task ) ;
150+ this . tasks . push ( task ) ;
114151 }
115152 } else {
116- const lineNumber = listItem . position . start . line ;
117-
118- const parentListItem : ListItem | null = line2ListItem . get ( listItem . parent ) ?? null ;
119-
120- line2ListItem . set ( lineNumber , new ListItem ( fileLines [ lineNumber ] , parentListItem ) ) ;
153+ const parentListItem : ListItem | null = this . line2ListItem . get ( listItem . parent ) ?? null ;
154+ this . line2ListItem . set ( lineNumber , new ListItem ( this . fileLines [ lineNumber ] , parentListItem ) ) ;
121155 }
156+ return sectionIndex ;
122157 }
123-
124- return tasks ;
125158}
0 commit comments