diff --git a/src/main/java/org/netbeans/modules/python/PythonUtility.java b/src/main/java/org/netbeans/modules/python/PythonUtility.java index 08edf93..4f602b1 100644 --- a/src/main/java/org/netbeans/modules/python/PythonUtility.java +++ b/src/main/java/org/netbeans/modules/python/PythonUtility.java @@ -1,583 +1,588 @@ -package org.netbeans.modules.python; - -import com.electronwill.nightconfig.core.file.FileConfig; -import com.google.common.collect.Maps; -import java.awt.event.ActionEvent; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.ExecutionException; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.prefs.Preferences; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.swing.ImageIcon; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.ArrayUtils; -import org.json.JSONObject; -import org.netbeans.api.annotations.common.NonNull; -import org.netbeans.api.extexecution.ExecutionDescriptor; -import org.netbeans.api.extexecution.ExecutionService; -import org.netbeans.api.extexecution.print.LineConvertor; -import org.netbeans.api.extexecution.print.LineConvertors; -import org.netbeans.api.project.FileOwnerQuery; -import org.netbeans.api.project.Project; -import org.netbeans.api.project.ui.OpenProjects; -import org.netbeans.modules.python.options.PythonPlatformManager; -import static org.netbeans.modules.python.options.PythonPlatformManager.getPathFile; -import org.netbeans.modules.python.project.PythonProject; -import org.openide.awt.NotificationDisplayer; -import org.openide.filesystems.FileObject; -import org.openide.filesystems.FileUtil; -import org.openide.util.Exceptions; -import org.openide.util.ImageUtilities; -import org.openide.util.NbBundle; -import org.openide.util.NbPreferences; -import org.openide.util.Pair; -import org.openide.util.RequestProcessor; -import org.openide.util.Utilities; - -/** - * - * @author albilu - */ -public class PythonUtility { - - public static final Logger LOG = Logger.getLogger(PythonUtility.class.getName()); - - public static final RequestProcessor RP = new RequestProcessor("Retry RP", 2); - public static final String PYTHON_MIME_TYPE = "text/x-python"; - public static final File PYLSP_VENV_DIR = Paths.get(System.getProperty("netbeans.user")).resolve(".pythonlsp").toFile(); - - public static final Pattern HOME_PAGE = Pattern.compile(".*Home-page:\\s+(.*)"); - public static final Pattern PAC_VERSIONS = Pattern.compile(".*Available versions:\\s+(.*)"); - public static final Pattern NO_MODULE_PATTERN = Pattern.compile("(.*):\\s+No module named\\s([a-z-]+)"); - public static final Pattern POETRY_PYTHON_PATH = Pattern.compile("(.*)\\s+\\(Activated\\)"); - static final Pattern PYTHON_STACKTRACE_PATTERN = Pattern.compile("^ File \"(.+\\.py)\", line (\\d+).*"); - - public static final LineConvertor HTTP_CONVERTOR = LineConvertors.httpUrl(); - - public static LineConvertors.FileLocator FILE_LOCATOR = (String filename) -> { - FileObject toFileObject = FileUtil.toFileObject(new File(filename.replaceAll("\"", "") - .strip())); - return toFileObject != null ? toFileObject : null; - }; - - public static LineConvertor FILE_CONVERTOR = LineConvertors.filePattern(FILE_LOCATOR, - PYTHON_STACKTRACE_PATTERN, - null, 1, 2); - - public static final String[] IMPORTANT_FILES = { - "Dockerfile", - "Jenkinsfile", - "LICENSE", - "md", - "setup.py", - "pyproject.toml" - }; - - public static String[] EXCLUDED_DIRS = new String[]{ - ".venv", - "venv", - "build", - "dist", - "nbproject", - "py_tests", - "pynguin-report", - "pycache", - ".ropeproject", - "__pycache__", - "poetry.lock", - ".coverage.json" - }; - - public static String[] VENV_DEPS = new String[]{ - "pytest", - "pynguin", - "build" - }; - - public static String[] LSP_DEPS = new String[]{ - "python-lsp-server[all]", - "python-lsp-isort", - "pylsp-mypy", - "pylsp-rope", - "black", - "python-lsp-black", - "isort", - "pyls-memestra", - "ptpython", - "ipython", - "pytest", - //"pynguin", - "build", - "pdoc", - "poetry" - }; - - public static final File SETTINGS = PythonUtility.PYLSP_VENV_DIR.toPath().resolve("settings.json").toFile(); - public static final File SETTINGS_SCHEMA = PythonUtility.PYLSP_VENV_DIR.toPath().resolve("schema.json").toFile(); - public static final File REPOS = PythonUtility.PYLSP_VENV_DIR.toPath().resolve("repos.json").toFile(); - public static final File PLATFORMS = PythonUtility.PYLSP_VENV_DIR.toPath().resolve("platforms.json").toFile(); - public static final File ENVS = PythonUtility.PYLSP_VENV_DIR.toPath().resolve("envs.json").toFile(); - public static final File TEST_RUNNER = PythonUtility.PYLSP_VENV_DIR.toPath().resolve("nb_test_runner.py").toFile(); - public static final File TOML_HANDLER = PythonUtility.PYLSP_VENV_DIR.toPath().resolve("toml_handler.py").toFile(); - - public static Preferences TERMINAL_PREFS = NbPreferences.root() - .node("org/netbeans/modules/terminal/nb"); - - public static int getTermFontSize() { - return TERMINAL_PREFS.getInt("term.fontSize", 18); - } - - public static int geTermFontSt() { - return TERMINAL_PREFS.getInt("term.fontStyle", 0); - } - - public static String getTermFontFam() { - return TERMINAL_PREFS.get("term.fontFamily", "DejaVu Sans Mono"); - } - - public static String getServerVersion() throws IOException { - String commandOutput = getCommandOutput(new String[]{getLspPythonExe(), "-m", "pylsp", "--version"}, null); - if (!commandOutput.startsWith("__main__.py")) { - LOG.log(Level.SEVERE, commandOutput); - return "UNKNOW"; - } - return commandOutput; - - } - - public static String getCommandOutput(String[] cmd, FileObject projectDir) throws IOException { - ProcessBuilder p = new ProcessBuilder(cmd); - if (projectDir != null) { - p.directory(FileUtil.toFile(projectDir)); - } - return IOUtils.toString(p.redirectErrorStream(true).start().getInputStream(), - StandardCharsets.UTF_8).strip(); - } - - public static String getPythonStdLibPath(String path) throws IOException { - return getCommandOutput(new String[]{path, "-c", - "import sysconfig; print(sysconfig.get_paths()['stdlib'])"}, null); - - } - - public static String getLspPythonExe() { - return normalizeVenvPath(PYLSP_VENV_DIR.toPath()); - } - - public static String getProjectPythonExe(FileObject fileObject) - throws IOException { - if (fileObject != null) { - Project owner = FileOwnerQuery.getOwner(fileObject); - if (owner != null) { - FileObject fileObject1 = owner.getProjectDirectory() - .getFileObject("nbproject/project.properties"); - if (fileObject1 != null) { - File toFile = FileUtil.toFile(fileObject1); - Properties appProps = new Properties(); - if (toFile != null) { - appProps.load(new FileInputStream(toFile)); - String property = appProps.getProperty("nbproject.python_path"); - return (property == null || property.isBlank()) - ? (PythonUtility.isPoetry((PythonProject) owner) - /**/ ? PythonUtility.getPoetryPythonPath(PythonUtility.getLspPythonExe(), - owner.getProjectDirectory()) : getPlatformPythonExe()) - : /*appProps.getProperty("nbproject.python_path")*/ property; - } - } - } - } - return getPlatformPythonExe(); - } - - public static List> getPythonExes() throws IOException { - List> versions = new ArrayList<>(); - for (String python : new String[]{"python", "python3", "py"}) { - getCommandOutput(new String[]{Utilities.isWindows() - ? "where" : "which", python}, null).lines().forEach(exe -> { - try { - String striped = exe.strip(); - if (isValidFilePath(striped)) { - String vers = getVersion(striped); - if (!vers.isEmpty()) { - versions.add(Pair.of(vers, striped)); - } - } - } catch (Exception ex) { - Exceptions.printStackTrace(ex); - } - }); - - } - - return versions; - } - - private static boolean isValidFilePath(String filePath) { - try { - // Attempt to create a Path object from the provided string - Path path = Paths.get(filePath); - - // Check if the path is absolute and exists - return path.isAbsolute() && path.toFile().exists() && Files.isExecutable(path); - } catch (Exception e) { - // An exception was thrown when parsing the path - return false; - } - } - - public static String[] getOsShell() { - if (Utilities.isWindows()) { - return new String[]{"cmd.exe", "/c"}; - } else if (Utilities.isMac() || Utilities.isUnix()) { - return new String[]{getEnvs().getOrDefault("SHELL", "/bin/bash"), "-c"}; - } - return null; - } - - public static Map getEnvs() { - Map envs = Maps.newHashMap(System.getenv()); - return envs; - } - - public static String getPlatformPythonExe() throws IOException { - return PythonPlatformManager.getDefault(); - } - - public static ImageIcon getPythonIcon() { - return ImageUtilities.loadImageIcon("org/netbeans/modules/python/python-2.png", false); - - } - - public static ImageIcon getClassIcon() { - return ImageUtilities.loadImageIcon("org/netbeans/modules/python/Classes.png", false); - - } - - public static ImageIcon getMethodIcon() { - return ImageUtilities.loadImageIcon("org/netbeans/modules/python/methodPublic.png", false); - - } - - public static ImageIcon getPytestIcon() { - return ImageUtilities.loadImageIcon("org/netbeans/modules/python/pytest.png", false); - - } - - public static ImageIcon getPackageIcon() { - return ImageUtilities.loadImageIcon("org/netbeans/modules/python/package16.png", false); - - } - - public static ImageIcon getPynguinIcon() { - return ImageUtilities.loadImageIcon("org/netbeans/modules/python/pynguin.png", false); - - } - - public static Properties getProperties(@NonNull Project project, boolean isCreating) throws IOException { - - Properties prop = new Properties(); - if (OpenProjects.getDefault().isProjectOpen(project) || isCreating) { - File file = createProperties(project); - prop.load(new FileInputStream(file)); - } - return prop; - - } - - public static String getVersion(String projectPythonExe) throws IOException { - String commandOutput = getCommandOutput(new String[]{projectPythonExe, "--version"}, null); - if (!commandOutput.startsWith("Py")) { - LOG.log(Level.SEVERE, commandOutput); - return ""; - } - return commandOutput; - } - - public static ImageIcon getErrorIcon() { - return ImageUtilities.loadImageIcon("org/netbeans/modules/python/error-badge.png", false); - - } - - public static ImageIcon getPythonPackageIcon() { - return ImageUtilities.loadImageIcon("org/netbeans/modules/python/libraries.png", false); - - } - - public static int venvPackageInstaller(FileObject projectDirectory) { - String[] cmd = { - normalizeVenvPath(FileUtil.toFile(projectDirectory).toPath()), - "-m", - "pip", - "install" - }; - return processExecutor(ArrayUtils.addAll(cmd, VENV_DEPS), "Installing packages"); - } - - public static String normalizeVenvPath(Path base) { - return Utilities.isWindows() ? base.resolve(".venv\\Scripts\\python.exe").toString() - : base.resolve(".venv/bin/python").toString(); - - } - - @NbBundle.Messages("CTL_Install=Installing packages") - public static void packageInstaller(String errorLline) { - Matcher matcher = NO_MODULE_PATTERN.matcher(errorLline); - while (matcher.find()) { - String[] cmd = { - matcher.group(1), - "-m", - "pip", - "install", - matcher.group(2) - }; - - processExecutor(cmd, Bundle.CTL_Install()); - } - } - - public static int processExecutor(String[] cmd, String message) { - try { - ProcessBuilder pb = new ProcessBuilder(); - pb.command(cmd); - LOG.info(() -> Arrays.toString(cmd)); - - ExecutionDescriptor execDescriptor = new ExecutionDescriptor() - .frontWindowOnError(true) - .showProgress(true) - .outConvertorFactory(new PythonOutputLine()) - .errConvertorFactory(new PythonOutputLine()); - - ExecutionService service = ExecutionService - .newService(() -> pb.start(), execDescriptor, message); - - int waitFor = service.run().get(); - - return waitFor; - } catch (InterruptedException | ExecutionException ex) { - Exceptions.printStackTrace(ex); - } - return 1; - } - - @NbBundle.Messages({ - "CTL_CreatePylsp=Creating pylsp environment", - "CTL_PylspInstallIssue=Issue when creating Pylsp environment.", - "CTL_PylspRetry=Retry" - }) - public static int installLsp(ClassLoader cl) { - try { - if (!PythonUtility.PYLSP_VENV_DIR.exists()) { - PythonUtility.PYLSP_VENV_DIR.mkdir(); - Files.writeString(SETTINGS.toPath(), - IOUtils.resourceToString("org/netbeans/modules/python/settings.json", - StandardCharsets.UTF_8, cl) - ); - Files.writeString(SETTINGS_SCHEMA.toPath(), - IOUtils.resourceToString("org/netbeans/modules/python/schema.json", - StandardCharsets.UTF_8, cl) - ); - Files.writeString(REPOS.toPath(), - IOUtils.resourceToString("org/netbeans/modules/python/repos.json", - StandardCharsets.UTF_8, cl) - ); - Files.writeString(PLATFORMS.toPath(), - IOUtils.resourceToString("org/netbeans/modules/python/platforms.json", - StandardCharsets.UTF_8, cl) - ); - Files.writeString(ENVS.toPath(), - IOUtils.resourceToString("org/netbeans/modules/python/envs.json", - StandardCharsets.UTF_8, cl) - ); - Files.writeString(TEST_RUNNER.toPath(), - IOUtils.resourceToString("org/netbeans/modules/python/nb_test_runner.py", - StandardCharsets.UTF_8, cl) - ); - Files.writeString(TOML_HANDLER.toPath(), - IOUtils.resourceToString("org/netbeans/modules/python/toml_handler.py", - StandardCharsets.UTF_8, cl) - ); - } - String[] cmd = { - PythonUtility.getPlatformPythonExe(), - "-m", - "venv", - PythonUtility.PYLSP_VENV_DIR.toPath().resolve(".venv").toString() - }; - - int processExecutor = PythonUtility.processExecutor(cmd, Bundle.CTL_CreatePylsp()); - if (processExecutor != 0) { - - NotificationDisplayer.getDefault().notify(Bundle.CTL_PylspInstallIssue(), - PythonUtility.getErrorIcon(), - Bundle.CTL_PylspRetry(), - (ActionEvent e) -> { - RP.post(() -> { - PythonUtility.processExecutor(cmd, Bundle.CTL_CreatePylsp()); - finish(); - }); - }, - NotificationDisplayer.Priority.HIGH, - NotificationDisplayer.Category.ERROR); - } - - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - } - - return finish(); - } - - public static String getPipList(String exe) throws IOException { - return getCommandOutput(new String[]{exe, "-m", "pip", "list"}, null); - } - - @NbBundle.Messages({ - "CTL_InstallDeps=Installing pylsp dependencies", - "CTL_MissingDeps=Missing dependency:" - }) - public static int finish() { - String[] cmd = {PythonUtility.getLspPythonExe(), "-m", "pip", "install", "--upgrade"}; - return processExecutor(ArrayUtils.addAll(cmd, LSP_DEPS), Bundle.CTL_InstallDeps()); - } - - public static void noModuleInstalledHandler(String line) { - Matcher matcher = PythonUtility.NO_MODULE_PATTERN.matcher(line); - if (matcher.find()) { - String pyPackage = matcher.group(2); - NotificationDisplayer.getDefault().notify(String.format("%s %s", Bundle.CTL_MissingDeps(), pyPackage), - PythonUtility.getPythonPackageIcon(), - String.format("Install %s", pyPackage), - (ActionEvent e) -> { - RP.post(() -> { - PythonUtility.packageInstaller(line); - }); - }, - NotificationDisplayer.Priority.NORMAL, - NotificationDisplayer.Category.WARNING); - } - } - - public static Map getUserEnvs() throws IOException { - File envsFile = PythonPlatformManager.getPathFile(); - JSONObject jsonObject = new JSONObject(Files.readString(envsFile.toPath())); - return jsonObject.toMap(); - } - - public static void insertUserEnvs(String toString) { - try { - Files.writeString(getPathFile().toPath(), toString); - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - } - } - - public static ExecutionDescriptor getExecutorDescriptor(ExecutionDescriptor.LineConvertorFactory convertorFactory, - Runnable preExeRunnable, Runnable postRunnable, boolean controllable, boolean select) { - ExecutionDescriptor execDescriptor = new ExecutionDescriptor() - .frontWindow(select) - .frontWindowOnError(select) - .controllable(controllable) - .showProgress(true) - .inputVisible(true) - .outConvertorFactory(convertorFactory) - .errConvertorFactory(convertorFactory) - .preExecution(preExeRunnable) - .postExecution(postRunnable); - - return execDescriptor; - } - - public static boolean isRunWithSysEnvs() { - return NbPreferences.root().getBoolean("sysEnv", true); - } - - public static void manageRunEnvs(ProcessBuilder pb) { - try { - Map environment = pb.environment(); - if (!PythonUtility.isRunWithSysEnvs()) { - environment.clear(); - } - Map userEnvs = getUserEnvs(); - environment.putAll(userEnvs); - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - } - - } - - public static String getVenv(PythonProject get) { - try { - return getProperties(get, false).getProperty("nbproject.virtualmanager", "venv"); - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - } - return "venv"; - } - - public static String getProjectType(File toFile) { - try (FileConfig conf = FileConfig.of(toFile)) { - conf.load(); - Object get = conf.get("tool.poetry"); - return get == null ? PythonProject.PYTHON : PythonProject.POETRY; - } - } - - public static boolean isPoetry(PythonProject project) { - FileObject fileObject = project.getProjectDirectory().getFileObject("pyproject.toml"); - - return (fileObject != null && PythonUtility.getProjectType(FileUtil.toFile(fileObject)) - .equals(PythonProject.POETRY)) || project.getProjectDirectory().getFileObject("poetry.lock") != null; - - } - - public static String getPoetryPythonPath(String defaultPython, FileObject projectDir) { - try { - String[] cmd = {defaultPython, "-m", "poetry", "env", "list", "--full-path"}; - - Matcher matcher = PythonUtility.POETRY_PYTHON_PATH.matcher(PythonUtility.getCommandOutput(cmd, projectDir)); - if (matcher.find()) { - String endPath; - if (Utilities.isWindows()) { - endPath = "\\Scripts\\python.exe"; - } else { - endPath = "/bin/python"; - } - return String.format("%s%s", matcher.group(1), endPath); - } - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - } - return defaultPython; - } - - public static File createProperties(Project project) { - File toFile = Paths.get(project.getProjectDirectory().getPath()) - .resolve("nbproject").resolve("project.properties").toFile(); - if (!toFile.exists()) { - try { - FileUtils.createParentDirectories(toFile); - toFile.createNewFile(); - return toFile; - } catch (IOException ex) { - Exceptions.printStackTrace(ex); - } - - } - return toFile; - } - -} +package org.netbeans.modules.python; + +import com.electronwill.nightconfig.core.file.FileConfig; +import com.google.common.collect.Maps; +import java.awt.event.ActionEvent; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.prefs.Preferences; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.ImageIcon; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.json.JSONObject; +import org.netbeans.api.annotations.common.NonNull; +import org.netbeans.api.extexecution.ExecutionDescriptor; +import org.netbeans.api.extexecution.ExecutionService; +import org.netbeans.api.extexecution.print.LineConvertor; +import org.netbeans.api.extexecution.print.LineConvertors; +import org.netbeans.api.project.FileOwnerQuery; +import org.netbeans.api.project.Project; +import org.netbeans.api.project.ui.OpenProjects; +import org.netbeans.modules.python.options.PythonPlatformManager; +import static org.netbeans.modules.python.options.PythonPlatformManager.getPathFile; +import org.netbeans.modules.python.project.PythonProject; +import org.openide.awt.NotificationDisplayer; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; +import org.openide.util.Exceptions; +import org.openide.util.ImageUtilities; +import org.openide.util.NbBundle; +import org.openide.util.NbPreferences; +import org.openide.util.Pair; +import org.openide.util.RequestProcessor; +import org.openide.util.Utilities; + +/** + * + * @author albilu + */ +public class PythonUtility { + + public static final Logger LOG = Logger.getLogger(PythonUtility.class.getName()); + + public static final RequestProcessor RP = new RequestProcessor("Retry RP", 2); + public static final String PYTHON_MIME_TYPE = "text/x-python"; + public static final File PYLSP_VENV_DIR = Paths.get(System.getProperty("netbeans.user")).resolve(".pythonlsp").toFile(); + + public static final Pattern HOME_PAGE = Pattern.compile(".*Home-page:\\s+(.*)"); + public static final Pattern PAC_VERSIONS = Pattern.compile(".*Available versions:\\s+(.*)"); + public static final Pattern NO_MODULE_PATTERN = Pattern.compile("(.*):\\s+No module named\\s([a-z-]+)"); + public static final Pattern POETRY_PYTHON_PATH = Pattern.compile("(.*)\\s+\\(Activated\\)"); + static final Pattern PYTHON_STACKTRACE_PATTERN = Pattern.compile("^ File \"(.+\\.py)\", line (\\d+).*"); + + public static final LineConvertor HTTP_CONVERTOR = LineConvertors.httpUrl(); + + public static LineConvertors.FileLocator FILE_LOCATOR = (String filename) -> { + FileObject toFileObject = FileUtil.toFileObject(new File(filename.replaceAll("\"", "") + .strip())); + return toFileObject != null ? toFileObject : null; + }; + + public static LineConvertor FILE_CONVERTOR = LineConvertors.filePattern(FILE_LOCATOR, + PYTHON_STACKTRACE_PATTERN, + null, 1, 2); + + public static final String[] IMPORTANT_FILES = { + "Dockerfile", + "Jenkinsfile", + "LICENSE", + "md", + "setup.py", + "pyproject.toml" + }; + + public static String[] EXCLUDED_DIRS = new String[]{ + ".venv", + "venv", + "build", + "dist", + "nbproject", + "py_tests", + "pynguin-report", + "pycache", + ".ropeproject", + "__pycache__", + "poetry.lock", + ".coverage.json" + }; + + public static String[] VENV_DEPS = new String[]{ + "pytest", + "pynguin", + "build" + }; + + public static String[] LSP_DEPS = new String[]{ + "python-lsp-server[all]", + "python-lsp-isort", + "pylsp-mypy", + "pylsp-rope", + "black", + "python-lsp-black", + "isort", + "pyls-memestra", + "ptpython", + "ipython", + "pytest", + //"pynguin", + "build", + "pdoc", + "poetry" + }; + + public static final File SETTINGS = PythonUtility.PYLSP_VENV_DIR.toPath().resolve("settings.json").toFile(); + public static final File SETTINGS_SCHEMA = PythonUtility.PYLSP_VENV_DIR.toPath().resolve("schema.json").toFile(); + public static final File REPOS = PythonUtility.PYLSP_VENV_DIR.toPath().resolve("repos.json").toFile(); + public static final File PLATFORMS = PythonUtility.PYLSP_VENV_DIR.toPath().resolve("platforms.json").toFile(); + public static final File ENVS = PythonUtility.PYLSP_VENV_DIR.toPath().resolve("envs.json").toFile(); + public static final File TEST_RUNNER = PythonUtility.PYLSP_VENV_DIR.toPath().resolve("nb_test_runner.py").toFile(); + public static final File TOML_HANDLER = PythonUtility.PYLSP_VENV_DIR.toPath().resolve("toml_handler.py").toFile(); + + public static Preferences TERMINAL_PREFS = NbPreferences.root() + .node("org/netbeans/modules/terminal/nb"); + + public static int getTermFontSize() { + return TERMINAL_PREFS.getInt("term.fontSize", 18); + } + + public static int geTermFontSt() { + return TERMINAL_PREFS.getInt("term.fontStyle", 0); + } + + public static String getTermFontFam() { + return TERMINAL_PREFS.get("term.fontFamily", "DejaVu Sans Mono"); + } + + public static String getServerVersion() throws IOException { + String commandOutput = getCommandOutput(new String[]{getLspPythonExe(), "-m", "pylsp", "--version"}, null); + if (!commandOutput.startsWith("__main__.py")) { + LOG.log(Level.SEVERE, commandOutput); + return "UNKNOW"; + } + return commandOutput; + + } + + public static String getCommandOutput(String[] cmd, FileObject projectDir) throws IOException { + ProcessBuilder p = new ProcessBuilder(cmd); + if (projectDir != null) { + p.directory(FileUtil.toFile(projectDir)); + } + return IOUtils.toString(p.redirectErrorStream(true).start().getInputStream(), + StandardCharsets.UTF_8).strip(); + } + + public static String getPythonStdLibPath(String path) throws IOException { + return getCommandOutput(new String[]{path, "-c", + "import sysconfig; print(sysconfig.get_paths()['stdlib'])"}, null); + + } + + public static String getLspPythonExe() { + return normalizeVenvPath(PYLSP_VENV_DIR.toPath()); + } + + public static String getProjectPythonExe(FileObject fileObject) + throws IOException { + if (fileObject != null) { + Project owner = FileOwnerQuery.getOwner(fileObject); + if (owner != null) { + FileObject fileObject1 = owner.getProjectDirectory() + .getFileObject("nbproject/project.properties"); + if (fileObject1 != null) { + File toFile = FileUtil.toFile(fileObject1); + Properties appProps = new Properties(); + if (toFile != null) { + appProps.load(new FileInputStream(toFile)); + String property = appProps.getProperty("nbproject.python_path"); + return (property == null || property.isBlank()) + ? (PythonUtility.isPoetry((PythonProject) owner) + /**/ ? PythonUtility.getPoetryPythonPath(PythonUtility.getLspPythonExe(), + owner.getProjectDirectory()) : getPlatformPythonExe()) + : /*appProps.getProperty("nbproject.python_path")*/ property; + } + } + } + } + return getPlatformPythonExe(); + } + + public static List> getPythonExes() throws IOException { + List> versions = new ArrayList<>(); + for (String python : new String[]{"python", "python3", "py"}) { + getCommandOutput(new String[]{Utilities.isWindows() + ? "where" : "which", python}, null).lines().forEach(exe -> { + try { + String striped = exe.strip(); + if (isValidFilePath(striped)) { + String vers = getVersion(striped); + if (!vers.isEmpty()) { + versions.add(Pair.of(vers, striped)); + } + } + } catch (Exception ex) { + Exceptions.printStackTrace(ex); + } + }); + + } + + return versions; + } + + private static boolean isValidFilePath(String filePath) { + try { + // Attempt to create a Path object from the provided string + Path path = Paths.get(filePath); + + // Check if the path is absolute and exists + return path.isAbsolute() && path.toFile().exists() && Files.isExecutable(path); + } catch (Exception e) { + // An exception was thrown when parsing the path + return false; + } + } + + public static String[] getOsShell() { + if (Utilities.isWindows()) { + return new String[]{"cmd.exe", "/c"}; + } else if (Utilities.isMac() || Utilities.isUnix()) { + return new String[]{getEnvs().getOrDefault("SHELL", "/bin/bash"), "-c"}; + } + return null; + } + + public static Map getEnvs() { + Map envs = Maps.newHashMap(System.getenv()); + return envs; + } + + public static String getPlatformPythonExe() throws IOException { + return PythonPlatformManager.getDefault(); + } + + public static ImageIcon getPythonIcon() { + return ImageUtilities.loadImageIcon("org/netbeans/modules/python/python-2.png", false); + + } + + public static ImageIcon getClassIcon() { + return ImageUtilities.loadImageIcon("org/netbeans/modules/python/Classes.png", false); + + } + + public static ImageIcon getMethodIcon() { + return ImageUtilities.loadImageIcon("org/netbeans/modules/python/methodPublic.png", false); + + } + + public static ImageIcon getPytestIcon() { + return ImageUtilities.loadImageIcon("org/netbeans/modules/python/pytest.png", false); + + } + + public static ImageIcon getPackageIcon() { + return ImageUtilities.loadImageIcon("org/netbeans/modules/python/package16.png", false); + + } + + public static ImageIcon getPynguinIcon() { + return ImageUtilities.loadImageIcon("org/netbeans/modules/python/pynguin.png", false); + + } + + public static Properties getProperties(@NonNull Project project, boolean isCreating) throws IOException { + + Properties prop = new Properties(); + if (OpenProjects.getDefault().isProjectOpen(project) || isCreating) { + File file = createProperties(project); + prop.load(new FileInputStream(file)); + } + return prop; + + } + + public static String getVersion(String projectPythonExe) throws IOException { + String stripedPath = projectPythonExe.strip(); + if (!isValidFilePath(stripedPath)) { + LOG.log(Level.SEVERE, stripedPath); + return ""; + } + String commandOutput = getCommandOutput(new String[]{stripedPath, "--version"}, null); + if (!commandOutput.startsWith("Py")) { + LOG.log(Level.SEVERE, commandOutput); + return ""; + } + return commandOutput; + } + + public static ImageIcon getErrorIcon() { + return ImageUtilities.loadImageIcon("org/netbeans/modules/python/error-badge.png", false); + + } + + public static ImageIcon getPythonPackageIcon() { + return ImageUtilities.loadImageIcon("org/netbeans/modules/python/libraries.png", false); + + } + + public static int venvPackageInstaller(FileObject projectDirectory) { + String[] cmd = { + normalizeVenvPath(FileUtil.toFile(projectDirectory).toPath()), + "-m", + "pip", + "install" + }; + return processExecutor(ArrayUtils.addAll(cmd, VENV_DEPS), "Installing packages"); + } + + public static String normalizeVenvPath(Path base) { + return Utilities.isWindows() ? base.resolve(".venv\\Scripts\\python.exe").toString() + : base.resolve(".venv/bin/python").toString(); + + } + + @NbBundle.Messages("CTL_Install=Installing packages") + public static void packageInstaller(String errorLline) { + Matcher matcher = NO_MODULE_PATTERN.matcher(errorLline); + while (matcher.find()) { + String[] cmd = { + matcher.group(1), + "-m", + "pip", + "install", + matcher.group(2) + }; + + processExecutor(cmd, Bundle.CTL_Install()); + } + } + + public static int processExecutor(String[] cmd, String message) { + try { + ProcessBuilder pb = new ProcessBuilder(); + pb.command(cmd); + LOG.info(() -> Arrays.toString(cmd)); + + ExecutionDescriptor execDescriptor = new ExecutionDescriptor() + .frontWindowOnError(true) + .showProgress(true) + .outConvertorFactory(new PythonOutputLine()) + .errConvertorFactory(new PythonOutputLine()); + + ExecutionService service = ExecutionService + .newService(() -> pb.start(), execDescriptor, message); + + int waitFor = service.run().get(); + + return waitFor; + } catch (InterruptedException | ExecutionException ex) { + Exceptions.printStackTrace(ex); + } + return 1; + } + + @NbBundle.Messages({ + "CTL_CreatePylsp=Creating pylsp environment", + "CTL_PylspInstallIssue=Issue when creating Pylsp environment.", + "CTL_PylspRetry=Retry" + }) + public static int installLsp(ClassLoader cl) { + try { + if (!PythonUtility.PYLSP_VENV_DIR.exists()) { + PythonUtility.PYLSP_VENV_DIR.mkdir(); + Files.writeString(SETTINGS.toPath(), + IOUtils.resourceToString("org/netbeans/modules/python/settings.json", + StandardCharsets.UTF_8, cl) + ); + Files.writeString(SETTINGS_SCHEMA.toPath(), + IOUtils.resourceToString("org/netbeans/modules/python/schema.json", + StandardCharsets.UTF_8, cl) + ); + Files.writeString(REPOS.toPath(), + IOUtils.resourceToString("org/netbeans/modules/python/repos.json", + StandardCharsets.UTF_8, cl) + ); + Files.writeString(PLATFORMS.toPath(), + IOUtils.resourceToString("org/netbeans/modules/python/platforms.json", + StandardCharsets.UTF_8, cl) + ); + Files.writeString(ENVS.toPath(), + IOUtils.resourceToString("org/netbeans/modules/python/envs.json", + StandardCharsets.UTF_8, cl) + ); + Files.writeString(TEST_RUNNER.toPath(), + IOUtils.resourceToString("org/netbeans/modules/python/nb_test_runner.py", + StandardCharsets.UTF_8, cl) + ); + Files.writeString(TOML_HANDLER.toPath(), + IOUtils.resourceToString("org/netbeans/modules/python/toml_handler.py", + StandardCharsets.UTF_8, cl) + ); + } + String[] cmd = { + PythonUtility.getPlatformPythonExe(), + "-m", + "venv", + PythonUtility.PYLSP_VENV_DIR.toPath().resolve(".venv").toString() + }; + + int processExecutor = PythonUtility.processExecutor(cmd, Bundle.CTL_CreatePylsp()); + if (processExecutor != 0) { + + NotificationDisplayer.getDefault().notify(Bundle.CTL_PylspInstallIssue(), + PythonUtility.getErrorIcon(), + Bundle.CTL_PylspRetry(), + (ActionEvent e) -> { + RP.post(() -> { + PythonUtility.processExecutor(cmd, Bundle.CTL_CreatePylsp()); + finish(); + }); + }, + NotificationDisplayer.Priority.HIGH, + NotificationDisplayer.Category.ERROR); + } + + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + + return finish(); + } + + public static String getPipList(String exe) throws IOException { + return getCommandOutput(new String[]{exe, "-m", "pip", "list"}, null); + } + + @NbBundle.Messages({ + "CTL_InstallDeps=Installing pylsp dependencies", + "CTL_MissingDeps=Missing dependency:" + }) + public static int finish() { + String[] cmd = {PythonUtility.getLspPythonExe(), "-m", "pip", "install", "--upgrade"}; + return processExecutor(ArrayUtils.addAll(cmd, LSP_DEPS), Bundle.CTL_InstallDeps()); + } + + public static void noModuleInstalledHandler(String line) { + Matcher matcher = PythonUtility.NO_MODULE_PATTERN.matcher(line); + if (matcher.find()) { + String pyPackage = matcher.group(2); + NotificationDisplayer.getDefault().notify(String.format("%s %s", Bundle.CTL_MissingDeps(), pyPackage), + PythonUtility.getPythonPackageIcon(), + String.format("Install %s", pyPackage), + (ActionEvent e) -> { + RP.post(() -> { + PythonUtility.packageInstaller(line); + }); + }, + NotificationDisplayer.Priority.NORMAL, + NotificationDisplayer.Category.WARNING); + } + } + + public static Map getUserEnvs() throws IOException { + File envsFile = PythonPlatformManager.getPathFile(); + JSONObject jsonObject = new JSONObject(Files.readString(envsFile.toPath())); + return jsonObject.toMap(); + } + + public static void insertUserEnvs(String toString) { + try { + Files.writeString(getPathFile().toPath(), toString); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + + public static ExecutionDescriptor getExecutorDescriptor(ExecutionDescriptor.LineConvertorFactory convertorFactory, + Runnable preExeRunnable, Runnable postRunnable, boolean controllable, boolean select) { + ExecutionDescriptor execDescriptor = new ExecutionDescriptor() + .frontWindow(select) + .frontWindowOnError(select) + .controllable(controllable) + .showProgress(true) + .inputVisible(true) + .outConvertorFactory(convertorFactory) + .errConvertorFactory(convertorFactory) + .preExecution(preExeRunnable) + .postExecution(postRunnable); + + return execDescriptor; + } + + public static boolean isRunWithSysEnvs() { + return NbPreferences.root().getBoolean("sysEnv", true); + } + + public static void manageRunEnvs(ProcessBuilder pb) { + try { + Map environment = pb.environment(); + if (!PythonUtility.isRunWithSysEnvs()) { + environment.clear(); + } + Map userEnvs = getUserEnvs(); + environment.putAll(userEnvs); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + + } + + public static String getVenv(PythonProject get) { + try { + return getProperties(get, false).getProperty("nbproject.virtualmanager", "venv"); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return "venv"; + } + + public static String getProjectType(File toFile) { + try (FileConfig conf = FileConfig.of(toFile)) { + conf.load(); + Object get = conf.get("tool.poetry"); + return get == null ? PythonProject.PYTHON : PythonProject.POETRY; + } + } + + public static boolean isPoetry(PythonProject project) { + FileObject fileObject = project.getProjectDirectory().getFileObject("pyproject.toml"); + + return (fileObject != null && PythonUtility.getProjectType(FileUtil.toFile(fileObject)) + .equals(PythonProject.POETRY)) || project.getProjectDirectory().getFileObject("poetry.lock") != null; + + } + + public static String getPoetryPythonPath(String defaultPython, FileObject projectDir) { + try { + String[] cmd = {defaultPython, "-m", "poetry", "env", "list", "--full-path"}; + + Matcher matcher = PythonUtility.POETRY_PYTHON_PATH.matcher(PythonUtility.getCommandOutput(cmd, projectDir)); + if (matcher.find()) { + String endPath; + if (Utilities.isWindows()) { + endPath = "\\Scripts\\python.exe"; + } else { + endPath = "/bin/python"; + } + return String.format("%s%s", matcher.group(1), endPath); + } + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + return defaultPython; + } + + public static File createProperties(Project project) { + File toFile = Paths.get(project.getProjectDirectory().getPath()) + .resolve("nbproject").resolve("project.properties").toFile(); + if (!toFile.exists()) { + try { + FileUtils.createParentDirectories(toFile); + toFile.createNewFile(); + return toFile; + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + + } + return toFile; + } + +} diff --git a/src/main/java/org/netbeans/modules/python/options/PythonLspServerConfigsPanel.java b/src/main/java/org/netbeans/modules/python/options/PythonLspServerConfigsPanel.java index 9a77cf9..f13b74e 100644 --- a/src/main/java/org/netbeans/modules/python/options/PythonLspServerConfigsPanel.java +++ b/src/main/java/org/netbeans/modules/python/options/PythonLspServerConfigsPanel.java @@ -42,7 +42,7 @@ final class PythonLspServerConfigsPanel extends javax.swing.JPanel { public static String[] PACKAGES = { "pylsp", - "python-lsp-isort", + "pyls-isort", "pylsp-mypy", "pylsp-rope", "black",