diff --git a/README.md b/README.md index 1b092bf9..95bd5ccb 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,16 @@ The resulting workflow can look like Grant's 🥳 - [contributing](https://github.com/bhoov/manim-notebook/blob/main/CONTRIBUTING.md) - [wiki](https://github.com/bhoov/manim-notebook/wiki) + +

+ +## Troubleshooting + +If you encounter an issue, search for some related keywords first in the [issues](https://github.com/bhoov/manim-notebook/issues). If you can't find anything, feel free to open a new issue. To analyze the problem, we need a **log file** from you: + +- Reload the VSCode window. This is important for us such that only important log messages are included in the log file and not unrelated ones. +- Open the command palette `Ctrl+Shift+P` (or `Cmd+Shift+P`). Use the command `Developer: Set Log Level...`, click on `Manim Notebook` and set the log level to `Trace`. +- Now reproduce the issue, e.g. by running a command that causes the problem. +- Open the command palette again and use the command `Manim Notebook: Open Log File`. +- Attach the log file to your GitHub issue. To do so, right-click on the opened log file header (the tab pane that shows the filename at the top of the editor) and select `Reveal In File Explorer` (or `Reveal in Finder`). Then drag and drop the file into the GitHub issue text field. +- Last, but not least, don't forget to set the log level back to `Info` to avoid performance issues. `Developer: Set Log Level...` -> `Manim Notebook` -> `Info`. diff --git a/package.json b/package.json index e25bdc45..2fc66046 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,11 @@ "command": "manim-notebook.clearScene", "title": "Remove all objects from scene", "category": "Manim Notebook" + }, + { + "command": "manim-notebook.openLogFile", + "title": "Open Log File", + "category": "Manim Notebook" } ], "keybindings": [ diff --git a/src/extension.ts b/src/extension.ts index 35bd6567..ed40993c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,6 +5,8 @@ import { ManimCell } from './manimCell'; import { ManimCellRanges } from './manimCellRanges'; import { previewCode } from './previewCode'; import { startScene, exitScene } from './startStopScene'; +import { loggerName } from './logger'; +import Logger from './logger'; export function activate(context: vscode.ExtensionContext) { @@ -40,14 +42,22 @@ export function activate(context: vscode.ExtensionContext) { } ); + const openLogFileCommand = vscode.commands.registerCommand( + 'manim-notebook.openLogFile', async () => { + openLogFile(context); + }); + context.subscriptions.push( previewManimCellCommand, previewSelectionCommand, startSceneCommand, exitSceneCommand, - clearSceneCommand + clearSceneCommand, + openLogFileCommand ); registerManimCellProviders(context); + + Logger.info("Manim Notebook activated"); } export function deactivate() { } @@ -167,3 +177,34 @@ function registerManimCellProviders(context: vscode.ExtensionContext) { manimCell.applyCellDecorations(window.activeTextEditor); } } + +/** + * Opens the Manim Notebook log file in a new editor. + * + * @param context The extension context. + */ +function openLogFile(context: vscode.ExtensionContext) { + const logFilePath = vscode.Uri.joinPath(context.logUri, `${loggerName}.log`); + vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: "Opening Manim Notebook log file...", + cancellable: false + }, async (progressIndicator, token) => { + await new Promise(async (resolve) => { + try { + const doc = await vscode.workspace.openTextDocument(logFilePath); + await vscode.window.showTextDocument(doc); + } catch { + vscode.window.showErrorMessage("Could not open Manim Notebook log file"); + } finally { + resolve(); + } + + // I've also tried to open the log file in the OS browser, + // but didn't get it to work via: + // commands.executeCommand("revealFileInOS", logFilePath); + // For a sample usage, see this: + // https://github.com/microsoft/vscode/blob/9de080f7cbcec77de4ef3e0d27fbf9fd335d3fba/extensions/typescript-language-features/src/typescriptServiceClient.ts#L580-L586 + }); + }); +} \ No newline at end of file diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 00000000..3c4fe72f --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,89 @@ +import { window } from 'vscode'; +import * as path from 'path'; + +export const loggerName = 'Manim Notebook'; +const logger = window.createOutputChannel(loggerName, { log: true }); + +export default class Logger { + + public static trace(message: string) { + logger.trace(`${Logger.getFormattedCallerInformation()} ${message}`); + } + + public static debug(message: string) { + logger.debug(`${Logger.getFormattedCallerInformation()} ${message}`); + } + + public static info(message: string) { + logger.info(`${Logger.getFormattedCallerInformation()} ${message}`); + } + + public static warn(message: string) { + logger.warn(`${Logger.getFormattedCallerInformation()} ${message}`); + } + + public static error(message: string) { + logger.error(`${Logger.getFormattedCallerInformation()} ${message}`); + } + + /** + * Returns formatted caller information in the form of + * "[filename] [methodname]". + * + * It works by creating a stack trace and extracting the file name from the + * third line of the stack trace, e.g. + * + * Error: + * at Logger.getCurrentFileName (manim-notebook/out/logger.js:32:19) + * at Logger.info (manim-notebook/out/logger.js:46:39) + * at activate (manim-notebook/out/extension.js:37:21) + * ... + * + * where "extension.js:37:21" is the file that called the logger method + * and "activate" is the respective method. + * + * Another example where the Logger is called in a Promise might be: + * + * Error: + * at Function.getFormattedCallerInformation (manim-notebook/src/logger.ts:46:23) + * at Function.info (manim-notebook/src/logger.ts:18:31) + * at manim-notebook/src/extension.ts:199:12 + * + * where "extension.ts:199:12" is the file that called the logger method + * and the method is unknown. + */ + private static getFormattedCallerInformation(): string { + const error = new Error(); + const stack = error.stack; + + const unknownString = "[unknown] [unknown]"; + + if (!stack) { + return unknownString; + } + + const stackLines = stack.split('\n'); + if (stackLines.length < 4) { + return unknownString; + } + + const callerLine = stackLines[3]; + if (!callerLine) { + return unknownString; + } + + const fileMatch = callerLine.match(/(?:[^\(\s])*?:\d+:\d+/); + let fileName = 'unknown'; + if (fileMatch && fileMatch[0]) { + fileName = path.basename(fileMatch[0]); + } + + const methodMatch = callerLine.match(/at (\w+) \(/); + let methodName = 'unknown'; + if (methodMatch && methodMatch[1]) { + methodName = methodMatch[1]; + } + + return `[${fileName}] [${methodName}]`; + } +}