--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/eric7/eric7_ide.py Sun Oct 02 11:29:11 2022 +0200 @@ -0,0 +1,434 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Copyright (c) 2002 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +eric Python IDE. + +This is the main Python script that performs the necessary initialization +of the IDE and starts the Qt event loop. +""" + +import contextlib +import io +import logging +import multiprocessing +import os +import sys +import traceback +import time + +originalPathString = os.getenv("PATH") + +# generate list of arguments to be remembered for a restart +restartArgsList = [ + "--no-splash", + "--plugin", + "--debug", + "--config", + "--settings", + "--disable-crash", + "--disable-plugin", +] +restartArgs = [arg for arg in sys.argv[1:] if arg.split("=", 1)[0] in restartArgsList] + +sys.path.insert(1, os.path.dirname(__file__)) + +try: + from PyQt6.QtCore import qWarning, QLibraryInfo, QTimer, QCoreApplication +except ImportError: + try: + from tkinter import messagebox + except ImportError: + sys.exit(100) + messagebox.showerror( + "eric7 Error", + "PyQt could not be imported. Please make sure" + " it is installed and accessible.", + ) + sys.exit(100) + +try: + from PyQt6 import QtWebEngineWidgets # __IGNORE_WARNING__ __IGNORE_EXCEPTION__ + from PyQt6.QtWebEngineCore import QWebEngineUrlScheme + + WEBENGINE_AVAILABLE = True +except ImportError: + WEBENGINE_AVAILABLE = False + +# some global variables needed to start the application +args = None +mainWindow = None +splash = None +inMainLoop = False +app = None + +if "--debug" in sys.argv: + del sys.argv[sys.argv.index("--debug")] + logging.basicConfig(level=logging.DEBUG) + +for arg in sys.argv[:]: + if arg.startswith("--config="): + import Globals + + configDir = arg.replace("--config=", "") + Globals.setConfigDir(configDir) + sys.argv.remove(arg) + elif arg.startswith("--settings="): + from PyQt6.QtCore import QSettings + + settingsDir = os.path.expanduser(arg.replace("--settings=", "")) + if not os.path.isdir(settingsDir): + os.makedirs(settingsDir) + QSettings.setPath( + QSettings.Format.IniFormat, QSettings.Scope.UserScope, settingsDir + ) + sys.argv.remove(arg) + +# make Third-Party package available as a packages repository +sys.path.insert(2, os.path.join(os.path.dirname(__file__), "ThirdParty", "Jasy")) +sys.path.insert(2, os.path.join(os.path.dirname(__file__), "DebugClients", "Python")) + +from EricWidgets.EricApplication import EricApplication + + +def handleSingleApplication(ddindex): + """ + Global function to handle the single application mode. + + @param ddindex index of a '--' option in the options list + """ + from EricWidgets.EricSingleApplication import EricSingleApplicationClient + + client = EricSingleApplicationClient() + res = client.connect() + if res > 0: + if "--no-splash" in sys.argv and sys.argv.index("--no-splash") < ddindex: + sys.argv.remove("--no-splash") + ddindex -= 1 + if "--no-open" in sys.argv and sys.argv.index("--no-open") < ddindex: + sys.argv.remove("--no-open") + ddindex -= 1 + if "--no-crash" in sys.argv and sys.argv.index("--no-crash") < ddindex: + sys.argv.remove("--no-crash") + if ( + "--disable-crash" in sys.argv + and sys.argv.index("--disable-crash") < ddindex + ): + sys.argv.remove("--disable-crash") + ddindex -= 1 + if "--debug" in sys.argv and sys.argv.index("--debug") < ddindex: + sys.argv.remove("--debug") + ddindex -= 1 + for arg in sys.argv: + if arg.startswith("--config=") and sys.argv.index(arg) < ddindex: + sys.argv.remove(arg) + ddindex -= 1 + break + for arg in sys.argv: + if arg.startswith("--plugin=") and sys.argv.index(arg) < ddindex: + sys.argv.remove(arg) + ddindex -= 1 + break + for arg in sys.argv[:]: + if arg.startswith("--disable-plugin=") and sys.argv.index(arg) < ddindex: + sys.argv.remove(arg) + ddindex -= 1 + + if len(sys.argv) > 1: + client.processArgs(sys.argv[1:]) + sys.exit(0) + elif res < 0: + print("eric7: {0}".format(client.errstr())) + # __IGNORE_WARNING_M801__ + sys.exit(res) + + +def excepthook(excType, excValue, tracebackobj): + """ + Global function to catch unhandled exceptions. + + @param excType exception type + @param excValue exception value + @param tracebackobj traceback object + """ + from UI.Info import BugAddress + import Utilities + import Globals + + # Workaround for a strange issue with QScintilla + if str(excValue) == "unable to convert a QVariant back to a Python object": + return + + separator = "-" * 80 + logFile = os.path.join(Globals.getConfigDir(), "eric7_error.log") + notice = ( + """An unhandled exception occurred. Please report the problem\n""" + """using the error reporting dialog or via email to <{0}>.\n""" + """A log has been written to "{1}".\n\nError information:\n""".format( + BugAddress, logFile + ) + ) + timeString = time.strftime("%Y-%m-%d, %H:%M:%S") + + versionInfo = "\n{0}\n{1}".format(separator, Utilities.generateVersionInfo()) + pluginVersionInfo = Utilities.generatePluginsVersionInfo() + if pluginVersionInfo: + versionInfo += "\n{0}\n{1}".format(separator, pluginVersionInfo) + distroInfo = Utilities.generateDistroInfo() + if distroInfo: + versionInfo += "\n{0}\n{1}".format(separator, distroInfo) + + if isinstance(excType, str): + tbinfo = tracebackobj + else: + tbinfofile = io.StringIO() + traceback.print_tb(tracebackobj, None, tbinfofile) + tbinfofile.seek(0) + tbinfo = tbinfofile.read() + errmsg = "{0}: \n{1}".format(str(excType), str(excValue)) + sections = ["", separator, timeString, separator, errmsg, separator, tbinfo] + msg = "\n".join(sections) + with contextlib.suppress(OSError), open(logFile, "w", encoding="utf-8") as f: + f.write(msg) + f.write(versionInfo) + + if inMainLoop: + warning = notice + msg + versionInfo + # Escape &<> otherwise it's not visible in the error dialog + warning = ( + warning.replace("&", "&").replace(">", ">").replace("<", "<") + ) + qWarning(warning) + else: + warning = notice + msg + versionInfo + print(warning) # __IGNORE_WARNING_M801__ + + +def uiStartUp(): + """ + Global function to finalize the start up of the main UI. + + Note: It is activated by a zero timeout single-shot timer. + """ + global args, mainWindow, splash + + if splash: + splash.finish(mainWindow) + del splash + + mainWindow.checkForErrorLog() + mainWindow.processArgs(args) + mainWindow.processInstallInfoFile() + mainWindow.checkProjectsWorkspace() + mainWindow.checkConfigurationStatus() + mainWindow.performVersionCheck() + mainWindow.checkPluginUpdatesAvailable() + mainWindow.autoConnectIrc() + + +def main(): + """ + Main entry point into the application. + """ + from Globals import AppInfo + import Globals + + global app, args, mainWindow, splash, restartArgs, inMainLoop + + sys.excepthook = excepthook + multiprocessing.set_start_method("spawn") + + from PyQt6.QtGui import QGuiApplication + + QGuiApplication.setDesktopFileName("eric7.desktop") + + options = [ + ( + "--config=configDir", + "use the given directory as the one containing the config files", + ), + ("--debug", "activate debugging output to the console"), + ("--no-splash", "don't show the splash screen"), + ("--no-open", "don't open anything at startup except that given in command"), + ("--no-crash", "don't check for a crash session file on startup"), + ("--disable-crash", "disable the support for crash sessions"), + ( + "--disable-plugin=<plug-in name>", + "disable the given plug-in (may be repeated)", + ), + ("--plugin=plugin-file", "load the given plugin file (plugin development)"), + ( + "--settings=settingsDir", + "use the given directory to store the settings files", + ), + ("--small-screen", "adjust the interface for screens smaller than FHD"), + ("--start-file", "load the most recently opened file"), + ("--start-multi", "load the most recently opened multi-project"), + ("--start-project", "load the most recently opened project"), + ("--start-session", "load the global session file"), + ("--", "indicate that there are options for the program to be debugged"), + ("", "(everything after that is considered arguments for this program)"), + ] + appinfo = AppInfo.makeAppInfo( + sys.argv, + "Eric7", + "[project | files... [--] [debug-options]]", + "A Python IDE", + options, + ) + + if "__PYVENV_LAUNCHER__" in os.environ: + del os.environ["__PYVENV_LAUNCHER__"] + + # make sure our executable directory (i.e. that of the used Python + # interpreter) is included in the executable search path + pathList = os.environ["PATH"].split(os.pathsep) + exeDir = os.path.dirname(sys.executable) + if exeDir not in pathList: + pathList.insert(0, exeDir) + os.environ["PATH"] = os.pathsep.join(pathList) + + from Toolbox import Startup + + # set the library paths for plugins + Startup.setLibraryPaths() + + if WEBENGINE_AVAILABLE: + scheme = QWebEngineUrlScheme(b"qthelp") + scheme.setSyntax(QWebEngineUrlScheme.Syntax.Path) + scheme.setFlags(QWebEngineUrlScheme.Flag.SecureScheme) + QWebEngineUrlScheme.registerScheme(scheme) + + app = EricApplication(sys.argv) + ddindex = Startup.handleArgs(sys.argv, appinfo) + + logging.debug("Importing Preferences") + import Preferences + + if Preferences.getUI("SingleApplicationMode"): + handleSingleApplication(ddindex) + + # set the application style sheet + app.setStyleSheetFile(Preferences.getUI("StyleSheet")) + + # set the search path for icons + Startup.initializeResourceSearchPath(app) + + # generate and show a splash window, if not suppressed + from UI.SplashScreen import SplashScreen, NoneSplashScreen + + if "--no-splash" in sys.argv and sys.argv.index("--no-splash") < ddindex: + sys.argv.remove("--no-splash") + ddindex -= 1 + splash = NoneSplashScreen() + elif not Preferences.getUI("ShowSplash"): + splash = NoneSplashScreen() + else: + splash = SplashScreen() + QCoreApplication.processEvents() + + # modify the executable search path for the PyQt5 installer + if Globals.isWindowsPlatform(): + pyqtDataDir = Globals.getPyQt6ModulesDirectory() + if os.path.exists(os.path.join(pyqtDataDir, "bin")): + path = os.path.join(pyqtDataDir, "bin") + else: + path = pyqtDataDir + os.environ["PATH"] = path + os.pathsep + os.environ["PATH"] + + pluginFile = None + noopen = False + nocrash = False + disablecrash = False + disabledPlugins = [] + if "--no-open" in sys.argv and sys.argv.index("--no-open") < ddindex: + sys.argv.remove("--no-open") + ddindex -= 1 + noopen = True + if "--no-crash" in sys.argv and sys.argv.index("--no-crash") < ddindex: + sys.argv.remove("--no-crash") + ddindex -= 1 + nocrash = True + if "--disable-crash" in sys.argv and sys.argv.index("--disable-crash") < ddindex: + sys.argv.remove("--disable-crash") + ddindex -= 1 + disablecrash = True + for arg in sys.argv[:]: + if arg.startswith("--disable-plugin=") and sys.argv.index(arg) < ddindex: + # extract the plug-in name + pluginName = arg.replace("--disable-plugin=", "") + sys.argv.remove(arg) + ddindex -= 1 + disabledPlugins.append(pluginName) + for arg in sys.argv: + if arg.startswith("--plugin=") and sys.argv.index(arg) < ddindex: + # extract the plugin development option + pluginFile = arg.replace("--plugin=", "").replace('"', "") + sys.argv.remove(arg) + ddindex -= 1 + pluginFile = os.path.expanduser(pluginFile) + pluginFile = os.path.abspath(pluginFile) + break + + # is there a set of filenames or options on the command line, + # if so, pass them to the UI + if len(sys.argv) > 1: + args = sys.argv[1:] + + # get the Qt translations directory + qtTransDir = Preferences.getQtTranslationsDir() + if not qtTransDir: + qtTransDir = QLibraryInfo.path(QLibraryInfo.LibraryPath.TranslationsPath) + + # Load translation files and install them + loc = Startup.loadTranslators(qtTransDir, app, ("qscintilla",)) + + # Initialize SSL stuff + from EricNetwork.EricSslUtilities import initSSL + + initSSL() + + splash.showMessage(QCoreApplication.translate("eric7_ide", "Starting...")) + # We can only import these after creating the EricApplication because they + # make Qt calls that need the EricApplication to exist. + from UI.UserInterface import UserInterface + + splash.showMessage( + QCoreApplication.translate("eric7_ide", "Generating Main Window...") + ) + mainWindow = UserInterface( + app, + loc, + splash, + pluginFile, + disabledPlugins, + noopen, + nocrash, + disablecrash, + restartArgs, + originalPathString, + ) + app.lastWindowClosed.connect(app.quit) + mainWindow.show() + + QTimer.singleShot(0, uiStartUp) + + # generate a graphical error handler + from EricWidgets import EricErrorMessage + + eMsg = EricErrorMessage.qtHandler() + eMsg.setMinimumSize(600, 400) + + # start the event loop + inMainLoop = True + res = app.exec() + logging.debug("Shutting down, result %d", res) + logging.shutdown() + sys.exit(res) + + +if __name__ == "__main__": + main()