UNPKG

vscode-todo-plus

Version:

Manage todo lists with ease. Powerful, easy to use and customizable.

233 lines (138 loc) 5.74 kB
/* IMPORT */ import * as _ from 'lodash'; import * as querystring from 'querystring'; import * as vscode from 'vscode'; import Config from '../../../config'; import EmbeddedView from '../../../views/embedded'; import Folder from '../../folder'; /* ABSTRACT */ class Abstract { include = undefined; exclude = undefined; rootPaths = undefined; filesData = undefined; // { [filePath]: todo[] | undefined } watcher: vscode.FileSystemWatcher = undefined; async get ( rootPaths = Folder.getAllRootPaths (), groupByRoot = true, groupByType = true, groupByFile = true, filter: string | false = false ) { rootPaths = _.castArray ( rootPaths ); const config = Config.get (); this.include = config.embedded.include; this.exclude = config.embedded.exclude; if ( !this.filesData || !_.isEqual ( this.rootPaths, rootPaths ) ) { this.rootPaths = rootPaths; this.unwatchPaths (); await this.initFilesData ( rootPaths ); this.watchPaths (); } else { await this.updateFilesData (); } return this.getTodos ( groupByRoot, groupByType, groupByFile, filter ); } async watchPaths () { /* HELPERS */ const pathNormalizer = filePath => filePath.replace ( /\\/g, '/' ); /* HANDLERS */ const refresh = _.debounce ( () => EmbeddedView.refresh (), 250 ); const add = event => { if ( !this.filesData ) return; const filePath = pathNormalizer ( event.fsPath ); if ( this.filesData.hasOwnProperty ( filePath ) ) return; if ( !this.isIncluded ( filePath ) ) return; this.filesData[filePath] = undefined; refresh (); }; const change = event => { if ( !this.filesData ) return; const filePath = pathNormalizer ( event.fsPath ); if ( !this.isIncluded ( filePath ) ) return; this.filesData[filePath] = undefined; refresh (); }; const unlink = event => { if ( !this.filesData ) return; const filePath = pathNormalizer ( event.fsPath ); delete this.filesData[filePath]; refresh (); }; /* WATCHING */ this.include.forEach ( glob => { this.watcher = vscode.workspace.createFileSystemWatcher ( glob ); this.watcher.onDidCreate ( add ); this.watcher.onDidChange ( change ); this.watcher.onDidDelete ( unlink ); }); } unwatchPaths () { if ( !this.watcher ) return; this.watcher.dispose (); } getIncluded ( filePaths ) { const micromatch = require ( 'micromatch' ); // Lazy import for performance return micromatch ( filePaths, this.include, { ignore: this.exclude, dot: true } ); } isIncluded ( filePath ) { return !!this.getIncluded ([ filePath ]).length; } async initFilesData ( rootPaths ) { this.filesData = {}; } async updateFilesData () {} getTodos ( groupByRoot, groupByType, groupByFile, filter ) { if ( _.isEmpty ( this.filesData ) ) return; const todos = {}, // { [ROOT] { [TYPE] => { [FILEPATH] => [DATA] } } } filterRe = filter ? new RegExp ( _.escapeRegExp ( filter ), 'i' ) : false, filePaths = Object.keys ( this.filesData ); filePaths.forEach ( filePath => { const data = this.filesData[filePath]; if ( !data || !data.length ) return; const filePathGroup = groupByFile ? filePath : ''; data.forEach ( datum => { if ( filterRe && !filterRe.test ( datum.line ) ) return; const rootGroup = groupByRoot ? datum.root : ''; if ( !todos[rootGroup] ) todos[rootGroup] = {}; const typeGroup = groupByType ? datum.type : ''; if ( !todos[rootGroup][typeGroup] ) todos[rootGroup][typeGroup] = {}; if ( !todos[rootGroup][typeGroup][filePathGroup] ) todos[rootGroup][typeGroup][filePathGroup] = []; todos[rootGroup][typeGroup][filePathGroup].push ( datum ); }); }); const roots = Object.keys ( todos ); return roots.length > 1 ? todos : { '': todos[roots[0]] }; } renderTodos ( todos ) { if ( _.isEmpty ( todos ) ) return ''; const sepRe = new RegExp ( querystring.escape ( '/' ), 'g' ), config = Config.get (), { indentation, embedded: { file: { wholeLine } }, symbols: { box } } = config, lines = []; /* LINES */ const roots = Object.keys ( todos ).sort (); roots.forEach ( root => { if ( root ) { lines.push ( `\n${root}:` ); } const types = Object.keys ( todos[root] ).sort (); types.forEach ( type => { if ( type ) { lines.push ( `${root ? indentation : '\n'}${type}:` ); } const filePaths = Object.keys ( todos[root][type] ).sort (); filePaths.forEach ( filePath => { if ( filePath ) { const normalizedFilePath = `/${_.trimStart ( filePath, '/' )}`, encodedFilePath = querystring.escape ( normalizedFilePath ).replace ( sepRe, '/' ); lines.push ( `${root ? indentation : ''}${type ? indentation : ''}@file://${encodedFilePath}` ); } const data = todos[root][type][filePath]; data.forEach ( datum => { const normalizedFilePath = `/${_.trimStart ( datum.filePath, '/' )}`, encodedFilePath = querystring.escape ( normalizedFilePath ).replace ( sepRe, '/' ); lines.push ( `${root ? indentation : ''}${type ? indentation : ''}${filePath ? indentation : ''}${box} ${_.trimStart ( wholeLine ? datum.line : datum.message )} @file://${encodedFilePath}#${datum.lineNr + 1}` ); }); }); }); }); return lines.length ? `${lines.join ( '\n' )}\n` : ''; } } /* EXPORT */ export default Abstract;