"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.deactivate = exports.activate = void 0; const vscode = require("vscode"); const path = require("path"); const crypto = require("crypto"); const child_process_promise_1 = require("child-process-promise"); const fs = require("fs"); const lookpath_1 = require("lookpath"); const untildify = require("untildify"); const shlex_1 = require("shlex"); const AsyncLock = require("async-lock"); const allSettled = require("promise.allsettled"); const mypy_1 = require("./mypy"); const diagnostics = new Map(); const outputChannel = vscode.window.createOutputChannel('Mypy'); let _context; let lock = new AsyncLock(); let statusBarItem; let activeChecks = 0; let checkIndex = 1; let activated = false; let logFile; function activate(context) { return __awaiter(this, void 0, void 0, function* () { activated = true; _context = context; context.subscriptions.push(outputChannel); const extension = vscode.extensions.getExtension('matangover.mypy'); const currentVersion = extension === null || extension === void 0 ? void 0 : extension.packageJSON.version; context.globalState.update('extensionVersion', currentVersion); initDebugLog(context); output(`Mypy extension activated, version ${currentVersion}`); if ((extension === null || extension === void 0 ? void 0 : extension.extensionKind) === vscode.ExtensionKind.Workspace) { output('Running remotely'); } if (logFile) { output(`Saving debug log to: ${logFile}`); } statusBarItem = vscode.window.createStatusBarItem(); context.subscriptions.push(statusBarItem); statusBarItem.text = "$(gear~spin) mypy"; output('Registering listener for interpreter changed event'); const pythonExtensionAPI = yield getPythonExtensionAPI(undefined); if (pythonExtensionAPI !== undefined) { const handler = pythonExtensionAPI.environments.onDidChangeActiveEnvironmentPath(activeInterpreterChanged); context.subscriptions.push(handler); output('Listener registered'); } context.subscriptions.push(vscode.workspace.onDidChangeWorkspaceFolders(workspaceFoldersChanged), vscode.workspace.onDidSaveTextDocument(documentSaved), vscode.workspace.onDidDeleteFiles(filesDeleted), vscode.workspace.onDidRenameFiles(filesRenamed), vscode.workspace.onDidCreateFiles(filesCreated), vscode.workspace.onDidChangeConfiguration(configurationChanged), vscode.commands.registerCommand("mypy.recheckWorkspace", recheckWorkspace), vscode.commands.registerCommand("mypy.restartAndRecheckWorkspace", restartAndRecheckWorkspace)); // This is used to show the custom commands only when the extension is active. vscode.commands.executeCommand("setContext", "mypy.activated", true); // Do _not_ await this call on purpose, so that extension activation finishes quickly. This is // important because if VS Code is closed before the checks are done, deactivate will only be // called if activate has already finished. forEachFolder(vscode.workspace.workspaceFolders, folder => checkWorkspace(folder.uri)); output('Activation complete'); }); } exports.activate = activate; function initDebugLog(context) { const mypyConfig = vscode.workspace.getConfiguration('mypy'); const debug = mypyConfig.get('debugLogging', false); if (debug) { try { const storageDir = context.globalStorageUri.fsPath; fs.mkdirSync(storageDir, { recursive: true }); logFile = path.join(storageDir, "mypy_extension.log"); } catch (e) { output(`Failed to create extension storage directory: ${e}`); } } } function deactivate() { return __awaiter(this, void 0, void 0, function* () { activated = false; output(`Mypy extension deactivating, shutting down daemons...`); yield forEachFolder(vscode.workspace.workspaceFolders, folder => stopDaemon(folder.uri)); output(`Mypy daemons stopped, extension deactivated`); vscode.commands.executeCommand("setContext", "mypy.activated", false); }); } exports.deactivate = deactivate; function workspaceFoldersChanged(e) { return __awaiter(this, void 0, void 0, function* () { const format = (folders) => folders.map(f => f.name).join(", ") || "none"; output(`Workspace folders changed. Added: ${format(e.added)}. Removed: ${format(e.removed)}.`); yield forEachFolder(e.removed, (folder) => __awaiter(this, void 0, void 0, function* () { var _a; yield stopDaemon(folder.uri); (_a = diagnostics.get(folder.uri)) === null || _a === void 0 ? void 0 : _a.dispose(); diagnostics.delete(folder.uri); })); yield forEachFolder(e.added, folder => checkWorkspace(folder.uri)); }); } function forEachFolder(folders, func, ignoreErrors = true) { return __awaiter(this, void 0, void 0, function* () { if (folders === undefined) { return; } // Run the function for each callback, and catch errors if any. // Use allSettled instead of Promise.all to always await all Promises, even if one rejects. const promises = folders.map(func); const results = yield allSettled(promises); const rejections = []; for (const [index, result] of results.entries()) { if (result.status === "rejected") { const folder = folders[index]; const folderUri = folder instanceof vscode.Uri ? folder : folder.uri; rejections.push({ folder: folderUri.fsPath, error: result.reason }); } } if (rejections.length > 0) { if (ignoreErrors) { const errorString = rejections.map(r => `${r.folder}: ${errorToString(r.error)}`).join("\n"); output("forEachFolder ignored errors in the following folders:\n" + errorString); } else { throw rejections; } } }); } function errorToString(error) { if (error instanceof Error && error.stack) { return error.stack; } else { return String(error); } } function stopDaemon(folder, retry = true) { return __awaiter(this, void 0, void 0, function* () { output(`Stop daemon: ${folder.fsPath}`); const result = yield runDmypy(folder, 'stop'); if (result.success) { output(`Stopped daemon: ${folder.fsPath}`); } else { if (retry) { // Daemon stopping can fail with 'Status file not found' if the daemon has been started // very recently and hasn't written the status file yet. In that case, retry, otherwise // we might leave a zombie daemon running. This happened due to the following events: // 1. Open folder in VS Code, and then add another workspace folder // 2. VS Code fires onDidChangeWorkspaceFolders and onDidChangeConfiguration, which // causes us to queue two checks. (This is probably a bug in VS Code.) // 3. VS Code immediately restarts the Extension Host process, which causes our // extension to deactivate. // 4. We try to stop the daemon but it is not yet running. We then start the daemon // because of the queued check(s), which results in a zombie daemon. // This simple retry solves the issue. output(`Daemon stopping failed, retrying in 1 second: ${folder.fsPath}`); yield sleep(1000); yield stopDaemon(folder, false); } else { output(`Daemon stopping failed again, giving up: ${folder.fsPath}`); } } }); } function runDmypy(folder, dmypyCommand, mypyArgs = [], warnIfFailed = false, successfulExitCodes, addPythonExecutableArgument = false, currentCheck, retryIfDaemonStuck = true) { return __awaiter(this, void 0, void 0, function* () { let dmypyGlobalArgs = []; let dmypyCommandArgs = []; let statusFilePath = null; // Store the dmypy status file in the extension's workspace storage folder, instead of the // default location which is .dmypy.json in the cwd. if ((_context === null || _context === void 0 ? void 0 : _context.storageUri) !== undefined) { fs.mkdirSync(_context.storageUri.fsPath, { recursive: true }); const folderHash = crypto.createHash('sha1').update(folder.toString()).digest('hex'); const statusFileName = `dmypy-${folderHash}-${process.pid}.json`; statusFilePath = path.join(_context.storageUri.fsPath, statusFileName); dmypyGlobalArgs = ["--status-file", statusFilePath]; const commandsSupportingLog = ["start", "restart", "run"]; if (commandsSupportingLog.includes(dmypyCommand)) { const logFileName = `dmypy-${folderHash}.log`; const logFilePath = path.join(_context.storageUri.fsPath, logFileName); dmypyCommandArgs = ['--log-file', logFilePath]; } } const activeInterpreter = yield getActiveInterpreter(folder, currentCheck); const mypyConfig = vscode.workspace.getConfiguration('mypy', folder); let executable; const runUsingActiveInterpreter = mypyConfig.get('runUsingActiveInterpreter'); let executionArgs = []; if (runUsingActiveInterpreter) { executable = activeInterpreter; executionArgs = ["-m", "mypy.dmypy"]; if (executable === undefined) { warn("Could not run mypy: no active interpreter. Please activate an interpreter in the " + "Python extension or switch off the mypy.runUsingActiveInterpreter setting.", warnIfFailed, currentCheck); } } else { executable = yield getDmypyExecutable(folder, warnIfFailed, currentCheck); } if (executable === undefined) { return { success: false, stdout: null }; } if (addPythonExecutableArgument && activeInterpreter) { mypyArgs = ['--python-executable', activeInterpreter, ...mypyArgs]; } const args = [...executionArgs, ...dmypyGlobalArgs, dmypyCommand, ...dmypyCommandArgs]; if (mypyArgs.length > 0) { args.push("--", ...mypyArgs); } const command = [executable, ...args].map(shlex_1.quote).join(" "); output(`Running dmypy in folder ${folder.fsPath}\n${command}`, currentCheck); try { const result = yield child_process_promise_1.spawn(executable, args, { cwd: folder.fsPath, capture: ['stdout', 'stderr'], successfulExitCodes }); if (result.code == 1 && result.stderr) { // This might happen when running using `python -m mypy.dmypy` and some error in the // interpreter occurs, such as import error when mypy is not installed. let error = ''; if (runUsingActiveInterpreter && result.stderr.includes('ModuleNotFoundError')) { error = 'Probably mypy is not installed in the active interpreter ' + `(${activeInterpreter}). Either install mypy in this interpreter or switch ` + 'off the mypy.runUsingActiveInterpreter setting. '; } warn(`Error running mypy in ${folder.fsPath}. ${error}See Output panel for details.`, warnIfFailed, currentCheck, true); if (result.stdout) { output(`stdout:\n${result.stdout}`, currentCheck); } output(`stderr:\n${result.stderr}`, currentCheck); return { success: false, stdout: result.stdout }; } return { success: true, stdout: result.stdout }; } catch (exception) { let error = exception.toString(); let showDetailsButton = false; if (exception.name === 'ChildProcessError') { const ex = exception; if (ex.code !== undefined) { let errorString; if (ex.stderr) { // Show only first line of error to user because Newlines are stripped in VSCode // warning messages and it can appear confusing. let mypyError = ex.stderr.split("\n")[0]; if (mypyError.length > 300) { mypyError = mypyError.slice(0, 300) + " [...]"; } errorString = `error: "${mypyError}"`; } else { errorString = `exit code ${ex.code}`; } error = `mypy failed with ${errorString}. See Output panel for details.`; showDetailsButton = true; } if (ex.stdout) { if (ex.code == 2 && !ex.stderr) { // Mypy considers syntax errors as fatal errors (exit code 2). The daemon's // exit code is inconsistent in this case (e.g. for syntax errors it can return // either 1 or 2). return { success: true, stdout: ex.stdout }; } output(`stdout:\n${ex.stdout}`, currentCheck); } if (ex.stderr) { output(`stderr:\n${ex.stderr}`, currentCheck); if (ex.stderr.indexOf('Daemon crashed!') != -1) { error = 'the mypy daemon crashed. This is probably a bug in mypy itself, ' + 'see Output panel for details. The daemon will be restarted automatically.'; showDetailsButton = true; } else if (ex.stderr.indexOf('There are no .py[i] files in directory') != -1) { // Swallow this error. This may happen if one workspace folder contains // Python files and another folder doesn't, or if a workspace contains Python // files that are not reachable from the target directory. return { success: true, stdout: '' }; } else if (ex.stderr.indexOf('Connection refused') != -1 || ex.stderr.indexOf('[Errno 2] No such file') != -1 || ex.stderr.indexOf('Socket operation on non-socket') != -1) { // This can happen if the daemon is stuck, or if the status file is stale due to // e.g. a previous daemon that hasn't been stopped properly. See: // https://github.com/matangover/mypy-vscode/issues/37 // https://github.com/matangover/mypy-vscode/issues/45 // To reproduce the above exceptions: // 1. 'Connection refused': kill daemon process (so that it stops listening on // the socket), and change the pid in status file to any running process. // 2. 'No such file': change connection_name in status file to a non-existent // file. // 3. 'Socket operation on non-socket': change connection_name in status file // to an existing file which is not a socket if (retryIfDaemonStuck) { // Kill the daemon. output("Daemon is stuck or status file is stale. Killing daemon", currentCheck); yield killDaemon(folder, currentCheck, statusFilePath); // Run the same command again, but this time don't retry if it fails. yield sleep(1000); output("Retrying command", currentCheck); return yield runDmypy(folder, dmypyCommand, mypyArgs, warnIfFailed, successfulExitCodes, addPythonExecutableArgument, currentCheck, false); } else { error = 'the mypy daemon is stuck. An attempt to kill it and retry failed. ' + 'This is probably a bug in mypy itself, see Output panel for details.'; showDetailsButton = true; } } } } warn(`Error running mypy in ${folder.fsPath}: ${error}`, warnIfFailed, currentCheck, showDetailsButton); return { success: false, stdout: null }; } }); } function killDaemon(folder, currentCheck, statusFilePath) { return __awaiter(this, void 0, void 0, function* () { const killResult = yield runDmypy(folder, "kill", undefined, undefined, undefined, undefined, currentCheck, false); output(`Ran dmypy kill, stdout: ${killResult.stdout}`, currentCheck); if (killResult.success) { output("Daemon killed successfully", currentCheck); return; } output("Error killing daemon, attempt to delete status file", currentCheck); if (statusFilePath) { try { fs.unlinkSync(statusFilePath); } catch (e) { output(`Error deleting status file: ${errorToString(e)}`, currentCheck); } } else { output("No status file to delete", currentCheck); } }); } function recheckWorkspace() { return __awaiter(this, void 0, void 0, function* () { output("Rechecking workspace"); yield forEachFolder(vscode.workspace.workspaceFolders, folder => checkWorkspace(folder.uri)); output("Recheck complete"); }); } function restartAndRecheckWorkspace() { return __awaiter(this, void 0, void 0, function* () { output("Stopping daemons"); yield forEachFolder(vscode.workspace.workspaceFolders, folder => stopDaemon(folder.uri)); yield recheckWorkspace(); }); } function getDmypyExecutable(folder, warnIfFailed, currentCheck) { var _a; return __awaiter(this, void 0, void 0, function* () { const mypyConfig = vscode.workspace.getConfiguration('mypy', folder); let dmypyExecutable = (_a = mypyConfig.get('dmypyExecutable')) !== null && _a !== void 0 ? _a : 'dmypy'; let helpURL = "https://github.com/matangover/mypy-vscode#installing-mypy"; const isCommand = path.parse(dmypyExecutable).dir === ''; if (isCommand) { const executable = yield lookpath_1.lookpath(dmypyExecutable); if (executable === undefined) { warn(`The mypy daemon executable ('${dmypyExecutable}') was not found on your PATH. ` + `Please install mypy or adjust the mypy.dmypyExecutable setting.`, warnIfFailed, currentCheck, undefined, helpURL); return undefined; } dmypyExecutable = executable; } else { dmypyExecutable = untildify(dmypyExecutable).replace('${workspaceFolder}', folder.fsPath); if (!fs.existsSync(dmypyExecutable)) { warn(`The mypy daemon executable ('${dmypyExecutable}') was not found. ` + `Please install mypy or adjust the mypy.dmypyExecutable setting.`, warnIfFailed, currentCheck, undefined, helpURL); return undefined; } } return dmypyExecutable; }); } function documentSaved(document) { const folder = vscode.workspace.getWorkspaceFolder(document.uri); if (!folder) { return; } if (document.languageId == "python" || isMaybeConfigFile(folder, document.fileName)) { output(`Document saved: ${document.uri.fsPath}`); checkWorkspace(folder.uri); } } function isMaybeConfigFile(folder, file) { if (isConfigFileName(file)) { return true; } let configFile = vscode.workspace.getConfiguration("mypy", folder).get("configFile"); if (configFile === undefined) { return false; } if (!path.isAbsolute(configFile)) { configFile = path.join(folder.uri.fsPath, configFile); } return path.normalize(configFile) == path.normalize(file); } function isConfigFileName(file) { const name = path.basename(file); return name == "mypy.ini" || name == ".mypy.ini" || name == "setup.cfg" || name == "config"; } function configurationChanged(event) { var _a; const folders = (_a = vscode.workspace.workspaceFolders) !== null && _a !== void 0 ? _a : []; const affectedFolders = folders.filter(folder => event.affectsConfiguration("mypy", folder)); if (affectedFolders.length === 0) { return; } const affectedFoldersString = affectedFolders.map(f => f.uri.fsPath).join(", "); output(`Mypy settings changed: ${affectedFoldersString}`); forEachFolder(affectedFolders, folder => checkWorkspace(folder.uri)); } function checkWorkspace(folder) { return __awaiter(this, void 0, void 0, function* () { // Don't check the same workspace folder more than once at the same time. yield lock.acquire(folder.fsPath, () => checkWorkspaceInternal(folder)); }); } function checkWorkspaceInternal(folder) { var _a; return __awaiter(this, void 0, void 0, function* () { if (!activated) { // This can happen if a check was queued right before the extension was deactivated. // We don't want to check in that case since it would cause a zombie daemon. output(`Extension is not activated, not checking: ${folder.fsPath}`); return; } const mypyConfig = vscode.workspace.getConfiguration("mypy", folder); if (!mypyConfig.get("enabled", true)) { output(`Mypy disabled for folder: ${folder.fsPath}`); const folderDiagnostics = diagnostics.get(folder); if (folderDiagnostics) { folderDiagnostics.clear(); } return; } statusBarItem.show(); activeChecks++; const currentCheck = checkIndex; checkIndex++; output(`Check folder: ${folder.fsPath}`, currentCheck); let targets = mypyConfig.get("targets", []); const mypyArgs = [...targets, '--show-error-end', '--no-error-summary', '--no-pretty', '--no-color-output']; const configFile = mypyConfig.get("configFile"); if (configFile) { output(`Using config file: ${configFile}`, currentCheck); mypyArgs.push('--config-file', configFile); } const extraArguments = mypyConfig.get("extraArguments"); if (extraArguments !== undefined && extraArguments.length > 0) { output(`Using extra arguments: ${extraArguments}`, currentCheck); mypyArgs.push(...extraArguments); } const result = yield runDmypy(folder, 'run', mypyArgs, true, [0, 1], true, currentCheck); activeChecks--; if (activeChecks == 0) { statusBarItem.hide(); } if (result.stdout !== null) { output(`Mypy output:\n${(_a = result.stdout) !== null && _a !== void 0 ? _a : "\n"}`, currentCheck); } const folderDiagnostics = getWorkspaceDiagnostics(folder); folderDiagnostics.clear(); if (result.success && result.stdout) { const fileDiagnostics = parseMypyOutput(result.stdout, folder); folderDiagnostics.set(Array.from(fileDiagnostics.entries())); } }); } function parseMypyOutput(stdout, folder) { const outputLines = []; stdout.split(/\r?\n/).forEach(line => { const match = mypy_1.mypyOutputPattern.exec(line); if (match !== null) { const line = match.groups; const previousLine = outputLines[outputLines.length - 1]; if (previousLine && line.type == "note" && previousLine.type == "note" && line.location == previousLine.location) { // This line continues the note on the previous line, merge them. previousLine.message += "\n" + line.message; } else { outputLines.push(line); } } }); let fileDiagnostics = new Map(); for (const line of outputLines) { const diagnostic = createDiagnostic(line); const fileUri = getFileUri(line.file, folder); if (!fileDiagnostics.has(fileUri)) { fileDiagnostics.set(fileUri, []); } const thisFileDiagnostics = fileDiagnostics.get(fileUri); thisFileDiagnostics.push(diagnostic); } return fileDiagnostics; } function getLinkUrl(line) { if (line.type == "note") { const seeLines = line.message.split(/\r?\n/).filter(l => l.startsWith("See https://")); if (seeLines.length > 0) { return seeLines[0].slice(4); } } else { if (line.code) { return `https://mypy.readthedocs.io/en/stable/_refs.html#code-${line.code}`; } } return undefined; } function getFileUri(filePath, folder) { // By default mypy outputs paths relative to the checked folder. If the user specifies // `show_absolute_path = True` in the config file, mypy outputs absolute paths. if (!path.isAbsolute(filePath)) { filePath = path.join(folder.fsPath, filePath); } const fileUri = vscode.Uri.file(filePath); return fileUri; } function createDiagnostic(line) { var _a; // Mypy output is 1-based, VS Code is 0-based. const lineNo = parseInt(line.line) - 1; let column = 0; if (line.column !== undefined) { column = parseInt(line.column) - 1; } let endLineNo = lineNo; let endColumn = column; if (line.endLine !== undefined && line.endColumn !== undefined) { endLineNo = parseInt(line.endLine) - 1; // Mypy's endColumn is inclusive, VS Code's is exclusive. endColumn = parseInt(line.endColumn); if (lineNo == endLineNo && column == endColumn - 1) { // Mypy gave a zero-length range, give a zero-length range for VS Code as well, so that // the error squiggle marks the entire word at that position. endColumn = column; } } const range = new vscode.Range(lineNo, column, endLineNo, endColumn); const diagnostic = new vscode.Diagnostic(range, line.message, line.type === "error" ? vscode.DiagnosticSeverity.Error : vscode.DiagnosticSeverity.Information); diagnostic.source = "mypy"; const errorCode = (_a = line.code) !== null && _a !== void 0 ? _a : "note"; const url = getLinkUrl(line); if (url === undefined) { diagnostic.code = errorCode; } else { diagnostic.code = { value: errorCode, target: vscode.Uri.parse(url), }; } return diagnostic; } function getWorkspaceDiagnostics(folder) { let workspaceDiagnostics = diagnostics.get(folder); if (workspaceDiagnostics) { return workspaceDiagnostics; } else { const workspaceDiagnostics = vscode.languages.createDiagnosticCollection('mypy'); diagnostics.set(folder, workspaceDiagnostics); _context.subscriptions.push(workspaceDiagnostics); return workspaceDiagnostics; } } function getActiveInterpreter(folder, currentCheck) { return __awaiter(this, void 0, void 0, function* () { const path = yield getPythonPathFromPythonExtension(folder, currentCheck); if (path === undefined) { return undefined; } if (!fs.existsSync(path)) { warn(`The selected Python interpreter does not exist: ${path}`, false, currentCheck); return undefined; } return path; }); } // The VS Code Python extension manages its own internal Python interpreter configuration. This // function was originally taken from pyright but modified to work with the new environments API: // https://github.com/microsoft/vscode-python/wiki/Python-Environment-APIs function getPythonPathFromPythonExtension(scopeUri, currentCheck) { return __awaiter(this, void 0, void 0, function* () { try { const api = yield getPythonExtensionAPI(currentCheck); if (api === undefined) { return; } const environmentPath = api.environments.getActiveEnvironmentPath(scopeUri); const environment = yield api.environments.resolveEnvironment(environmentPath); if (environment === undefined) { output('Invalid Python environment returned by Python extension', currentCheck); return; } if (environment.executable.uri === undefined) { output('Invalid Python executable path returned by Python extension', currentCheck); return; } const result = environment.executable.uri.fsPath; output(`Received Python path from Python extension: ${result}`, currentCheck); return result; } catch (error) { output(`Exception when reading Python path from Python extension: ${errorToString(error)}`, currentCheck); } return undefined; }); } function activeInterpreterChanged(e) { var _a; const resource = e.resource; if (resource === undefined) { output(`Active interpreter changed for resource: unknown`); (_a = vscode.workspace.workspaceFolders) === null || _a === void 0 ? void 0 : _a.map(folder => checkWorkspace(folder.uri)); } else { output(`Active interpreter changed for resource: ${resource.uri.fsPath}`); checkWorkspace(resource.uri); } } function getPythonExtensionAPI(currentCheck) { var _a; return __awaiter(this, void 0, void 0, function* () { const extension = vscode.extensions.getExtension('ms-python.python'); if (!extension) { output('Python extension not found', currentCheck); return undefined; } if (!extension.isActive) { output('Waiting for Python extension to load', currentCheck); yield extension.activate(); output('Python extension loaded', currentCheck); } const environmentsAPI = (_a = extension.exports) === null || _a === void 0 ? void 0 : _a.environments; if (!environmentsAPI) { output('Python extension version is too old (it does not expose the environments API). ' + 'Please upgrade the Python extension to the latest version.', currentCheck); return undefined; } return extension.exports; }); } function warn(warning, show = false, currentCheck, detailsButton = false, helpURL) { return __awaiter(this, void 0, void 0, function* () { output(warning, currentCheck); if (show) { const items = []; if (detailsButton) { items.push("Details"); } if (helpURL) { items.push("Help"); } const result = yield vscode.window.showWarningMessage(warning, ...items); if (result === "Details") { outputChannel.show(); } else if (result === "Help") { vscode.env.openExternal(vscode.Uri.parse(helpURL)); } } }); } function filesDeleted(e) { return __awaiter(this, void 0, void 0, function* () { yield filesChanged(e.files); }); } function filesRenamed(e) { return __awaiter(this, void 0, void 0, function* () { const changedUris = e.files.map(f => f.oldUri).concat(...e.files.map(f => f.newUri)); yield filesChanged(changedUris); }); } function filesCreated(e) { return __awaiter(this, void 0, void 0, function* () { yield filesChanged(e.files, true); }); } function filesChanged(files, created = false) { return __awaiter(this, void 0, void 0, function* () { const folders = new Set(); for (let file of files) { const folder = vscode.workspace.getWorkspaceFolder(file); if (folder === undefined) continue; const path = file.fsPath; if (path.endsWith(".py") || path.endsWith(".pyi")) { folders.add(folder.uri); } else if (isMaybeConfigFile(folder, path)) { // Don't trigger mypy run if config file has just been created and is empty, because // mypy would error. Give the user a chance to edit the file. const justCreatedAndEmpty = created && fs.statSync(path).size === 0; if (!justCreatedAndEmpty) { folders.add(folder.uri); } } } if (folders.size === 0) { return; } const foldersString = Array.from(folders).map(f => f.fsPath).join(", "); output(`Files changed in folders: ${foldersString}`); yield forEachFolder(Array.from(folders), folder => checkWorkspace(folder)); }); } function output(line, currentCheck) { if (currentCheck !== undefined) { line = `[${currentCheck}] ${line}`; } if (logFile) { try { var tzoffset = (new Date()).getTimezoneOffset() * 60000; var localISOTime = (new Date(Date.now() - tzoffset)).toISOString().slice(0, -1); fs.appendFileSync(logFile, `${localISOTime} [${process.pid}] ${line}\n`); } catch (e) { // Ignore } } try { outputChannel.appendLine(line); } catch (e) { // Ignore error. This can happen when VS Code is closing and it calls our deactivate // function, and the output channel is already closed. } } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } //# sourceMappingURL=extension.js.map