import dayjs from 'dayjs'; import throttle from 'lodash/throttle'; import { languages, TextDocumentChangeEvent, TextEditor, window, workspace } from 'vscode'; import { getNextFewTasks } from './commands/getFewNextTasks'; import { doUpdateEditorDecorations } from './decorations'; import { resetAllRecurringTasks } from './documentActions'; import { $config, $state, Constants, counterStatusBar, Global, mainStatusBar, updateLastVisitGlobalState, updateState } from './extension'; import { updateAllTreeViews } from './treeViewProviders/treeViews'; import { VscodeContext } from './types'; import { getDocumentForDefaultFile } from './utils/extensionUtils'; import { sleep } from './utils/utils'; import { setContext } from './utils/vscodeUtils'; let changeActiveEditorEventInProgress = false; /** * Active text editor changes (tab). * * This event can be fired multiple times very quickly 5-20ms interval. */ export async function onChangeActiveTextEditor(editor: TextEditor | undefined): Promise<void> { if (changeActiveEditorEventInProgress) { await sleep(50); } if (changeActiveEditorEventInProgress) { await sleep(200); } changeActiveEditorEventInProgress = true; if ($state.theRightFileOpened) { deactivateEditorFeatures(); } if (editor && isTheRightFileName(editor)) { $state.activeDocument = editor.document; $state.activeDocumentTabSize = typeof editor.options.tabSize === 'number' ? editor.options.tabSize : $config.tabSize; await updateEverything(editor); activateEditorFeatures(editor); await setContext(VscodeContext.IsActive, true); const needReset = checkIfNeedResetRecurringTasks(editor.document.uri.toString()); if (needReset) { await resetAllRecurringTasks(editor.document, needReset.lastVisit); await updateEverything(); await updateLastVisitGlobalState(editor.document.uri.toString(), new Date()); } } else { $state.activeDocument = await getDocumentForDefaultFile(); $state.activeDocumentTabSize = $config.tabSize; $state.theRightFileOpened = false; await updateEverything(); await setContext(VscodeContext.IsActive, false); } changeActiveEditorEventInProgress = false; } /** * Only run reset all recurring tasks when needed (first open file in a day) */ export function checkIfNeedResetRecurringTasks(filePath: string): {lastVisit: Date} | undefined { const lastVisitForFile = $state.lastVisitByFile[filePath]; if (lastVisitForFile) { if (!dayjs().isSame(lastVisitForFile, 'day')) { // First time this file opened this day => reset return { lastVisit: lastVisitForFile, }; } else { // This file was already reset this day return undefined; } } else { // New file return { lastVisit: new Date(), }; } } /** * Called when active text document changes (typing in it, for instance) */ export function onChangeTextDocument(e: TextDocumentChangeEvent) { const activeTextEditor = window.activeTextEditor; if (activeTextEditor && $state.theRightFileOpened) { updateEverything(activeTextEditor); } } /** * Match Uri of editor against a glob specified by user. */ export function isTheRightFileName(editor: TextEditor): boolean { return languages.match({ pattern: $config.activatePattern, }, editor.document) !== 0; } /** * Activate document text change event listener. */ export function activateEditorFeatures(editor: TextEditor) { $state.theRightFileOpened = true; Global.changeTextDocumentDisposable = workspace.onDidChangeTextDocument(onChangeTextDocument); counterStatusBar.show(); } /** * Deactivate document text change event listener. */ export function deactivateEditorFeatures() { Global.changeTextDocumentDisposable?.dispose(); counterStatusBar.hide(); } /** * - Update state (parse the active/default file) * - Update editor decorations * - Update status bar item * - Update all tree views (including webview, excluding archived tasks) */ export const updateEverything = throttle(async (editor?: TextEditor) => { await updateState(); if (editor && isTheRightFileName(editor)) { doUpdateEditorDecorations(editor); counterStatusBar.update($state.tasks); } mainStatusBar.update(getNextFewTasks()); updateAllTreeViews(); }, Constants.ThrottleEverything);