<commit_before>package com.haskforce.highlighting.annotation.external;

import com.haskforce.actions.RestartGhcModi;
import com.haskforce.highlighting.annotation.Problems;
import com.haskforce.settings.SettingsChangeNotifier;
import com.haskforce.settings.ToolKey;
import com.haskforce.settings.ToolSettings;
import com.haskforce.utils.ExecUtil;
import com.haskforce.utils.NotificationUtil;
import com.haskforce.utils.SystemUtil;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.ParametersList;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleComponent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.Pattern;

/**
 * Process wrapper for GhcModi.  Implements ModuleComponent so destruction of processes coincides with closing projects.
 */
public class GhcModi implements ModuleComponent, SettingsChangeNotifier {
    @SuppressWarnings("UnusedDeclaration")
    private static final Logger LOG = Logger.getInstance(GhcModi.class);

    public @NotNull final Module module;
    public @NotNull String workingDirectory;
    public @Nullable String path;
    public @NotNull String flags;
    private @Nullable Process process;
    private @Nullable BufferedReader input;
    private @Nullable BufferedWriter output;
    private ExecutorService executorService = Executors.newSingleThreadExecutor();
    private boolean enabled = true;
    // Keep track of error messages so we don't output the same ones multiple times.
    public static final Pattern TYPE_SPLIT_REGEX = Pattern.compile(" :: ");

    public static Problems getFutureProblems(@NotNull Project project, @NotNull Future<Problems> problemsFuture) {
        return getFuture(project, problemsFuture);
    }

    public static BrowseItem[] getFutureBrowseItems(@NotNull Project project, @NotNull Future<BrowseItem[]> browseItemsFuture) {
        return getFuture(project, browseItemsFuture);
    }

    @Nullable
    private static <T> T getFuture(@NotNull Project project, @NotNull Future<T> future) {
        long timeout = ToolKey.getGhcModiTimeout(project);
        try {
            return future.get(timeout, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            LOG.warn(e);
            displayError(project, "ghc-modi was interrupted: " + e);
        } catch (java.util.concurrent.ExecutionException e) {
            LOG.warn(e);
            displayError(project, "ghc-modi execution was aborted: " + e);
        } catch (TimeoutException e) {
            LOG.warn("ghc-modi took too long to respond (waited " + timeout + " milliseconds): " + e);
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Checks the module with ghc-modi and returns Problems to be annotated in the source.
     */
    @Nullable
    public Future<Problems> check(final @NotNull String file) {
        return handleErrors(new GhcModiCallable<Problems>() {
            @Override
            public Problems call() throws GhcModiError {
                final String stdout = simpleExec("check " + file);
                return stdout == null ? new Problems() : handleCheck(file, stdout);
            }
        });
    }

    @Nullable
    private static Problems handleCheck(@NotNull String file, @NotNull String stdout) throws GhcModiError {
        final Problems problems = GhcMod.parseProblems(new Scanner(stdout));
        if (problems == null) {
            // parseProblems should have returned something, so let's just dump the output to the user.
            throw new CheckParseError(stdout);
        } else if (problems.size() == 1) {
            final GhcMod.Problem problem = (GhcMod.Problem)problems.get(0);
            if (problem.startLine == 0 && problem.startColumn == 0) {
                throw new CheckError(file, problem.message);
            }
        }
        return problems;
    }

    /**
     * Returns an array of browse information for a given module.
     */
    @Nullable
    public Future<BrowseItem[]> browse(@NotNull final String module) {
        return handleErrors(new GhcModiCallable<BrowseItem[]>() {
            @Override
            public BrowseItem[] call() throws GhcModiError {
                String[] lines = simpleExecToLines("browse -d " + module);
                if (lines == null) {
                    return null;
                }
                BrowseItem[] result = new BrowseItem[lines.length];
                for (int i = 0; i < lines.length; ++i) {
                    final String[] parts = TYPE_SPLIT_REGEX.split(lines[i], 2);
                    //noinspection ObjectAllocationInLoop
                    result[i] = new BrowseItem(parts[0], module, parts.length == 2 ? parts[1] : "");
                }
                return result;
            }
        });
    }

    /**
     * Wrapper class for the output of `ghc-modi browse` command.
     */
    public static class BrowseItem {
        public final @NotNull String name;
        public final @NotNull String module;
        public final @NotNull String type;

        public BrowseItem(@NotNull String name, @NotNull String module, @NotNull String type) {
            this.name = name;
            this.module = module;
            this.type = type;
        }

    }

    /**
     * A wrapper to exec that checks stdout and returns null if there was no output.
     */
    @Nullable
    public String simpleExec(@NotNull String command) throws GhcModiError {
        final String stdout = exec(command);
        if (stdout == null || stdout.length() == 0) {
            return null;
        }
        return stdout;
    }

    /**
     * Same as simpleExec, except returns an array of Strings for each line in the output.
     */
    @Nullable
    public String[] simpleExecToLines(@NotNull String command) throws GhcModiError {
        final String result = simpleExec(command);
        return result == null ? null : StringUtil.splitByLines(result);
    }

    /**
     * Lazily spawns a process if needed and executes the given command.  If this fails, returns null and displays
     * the error to the user.  If the path to ghc-modi is not set, this simply returns null with no error message.
     */
    @Nullable
    public synchronized String exec(@NotNull String command) throws GhcModiError {
        if (!enabled) { return null; }
        if (path == null) { return null; }
        if (process == null) { spawnProcess(); }<commit_msg>Removed redundant spawnProcess.<commit_after>package com.haskforce.highlighting.annotation.external;

import com.haskforce.actions.RestartGhcModi;
import com.haskforce.highlighting.annotation.Problems;
import com.haskforce.settings.SettingsChangeNotifier;
import com.haskforce.settings.ToolKey;
import com.haskforce.settings.ToolSettings;
import com.haskforce.utils.ExecUtil;
import com.haskforce.utils.NotificationUtil;
import com.haskforce.utils.SystemUtil;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.ParametersList;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleComponent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.regex.Pattern;

/**
 * Process wrapper for GhcModi.  Implements ModuleComponent so destruction of processes coincides with closing projects.
 */
public class GhcModi implements ModuleComponent, SettingsChangeNotifier {
    @SuppressWarnings("UnusedDeclaration")
    private static final Logger LOG = Logger.getInstance(GhcModi.class);

    public @NotNull final Module module;
    public @NotNull String workingDirectory;
    public @Nullable String path;
    public @NotNull String flags;
    private @Nullable Process process;
    private @Nullable BufferedReader input;
    private @Nullable BufferedWriter output;
    private ExecutorService executorService = Executors.newSingleThreadExecutor();
    private boolean enabled = true;
    // Keep track of error messages so we don't output the same ones multiple times.
    public static final Pattern TYPE_SPLIT_REGEX = Pattern.compile(" :: ");

    public static Problems getFutureProblems(@NotNull Project project, @NotNull Future<Problems> problemsFuture) {
        return getFuture(project, problemsFuture);
    }

    public static BrowseItem[] getFutureBrowseItems(@NotNull Project project, @NotNull Future<BrowseItem[]> browseItemsFuture) {
        return getFuture(project, browseItemsFuture);
    }

    @Nullable
    private static <T> T getFuture(@NotNull Project project, @NotNull Future<T> future) {
        long timeout = ToolKey.getGhcModiTimeout(project);
        try {
            return future.get(timeout, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            LOG.warn(e);
            displayError(project, "ghc-modi was interrupted: " + e);
        } catch (java.util.concurrent.ExecutionException e) {
            LOG.warn(e);
            displayError(project, "ghc-modi execution was aborted: " + e);
        } catch (TimeoutException e) {
            LOG.warn("ghc-modi took too long to respond (waited " + timeout + " milliseconds): " + e);
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Checks the module with ghc-modi and returns Problems to be annotated in the source.
     */
    @Nullable
    public Future<Problems> check(final @NotNull String file) {
        return handleErrors(new GhcModiCallable<Problems>() {
            @Override
            public Problems call() throws GhcModiError {
                final String stdout = simpleExec("check " + file);
                return stdout == null ? new Problems() : handleCheck(file, stdout);
            }
        });
    }

    @Nullable
    private static Problems handleCheck(@NotNull String file, @NotNull String stdout) throws GhcModiError {
        final Problems problems = GhcMod.parseProblems(new Scanner(stdout));
        if (problems == null) {
            // parseProblems should have returned something, so let's just dump the output to the user.
            throw new CheckParseError(stdout);
        } else if (problems.size() == 1) {
            final GhcMod.Problem problem = (GhcMod.Problem)problems.get(0);
            if (problem.startLine == 0 && problem.startColumn == 0) {
                throw new CheckError(file, problem.message);
            }
        }
        return problems;
    }

    /**
     * Returns an array of browse information for a given module.
     */
    @Nullable
    public Future<BrowseItem[]> browse(@NotNull final String module) {
        return handleErrors(new GhcModiCallable<BrowseItem[]>() {
            @Override
            public BrowseItem[] call() throws GhcModiError {
                String[] lines = simpleExecToLines("browse -d " + module);
                if (lines == null) {
                    return null;
                }
                BrowseItem[] result = new BrowseItem[lines.length];
                for (int i = 0; i < lines.length; ++i) {
                    final String[] parts = TYPE_SPLIT_REGEX.split(lines[i], 2);
                    //noinspection ObjectAllocationInLoop
                    result[i] = new BrowseItem(parts[0], module, parts.length == 2 ? parts[1] : "");
                }
                return result;
            }
        });
    }

    /**
     * Wrapper class for the output of `ghc-modi browse` command.
     */
    public static class BrowseItem {
        public final @NotNull String name;
        public final @NotNull String module;
        public final @NotNull String type;

        public BrowseItem(@NotNull String name, @NotNull String module, @NotNull String type) {
            this.name = name;
            this.module = module;
            this.type = type;
        }

    }

    /**
     * A wrapper to exec that checks stdout and returns null if there was no output.
     */
    @Nullable
    public String simpleExec(@NotNull String command) throws GhcModiError {
        final String stdout = exec(command);
        if (stdout == null || stdout.length() == 0) {
            return null;
        }
        return stdout;
    }

    /**
     * Same as simpleExec, except returns an array of Strings for each line in the output.
     */
    @Nullable
    public String[] simpleExecToLines(@NotNull String command) throws GhcModiError {
        final String result = simpleExec(command);
        return result == null ? null : StringUtil.splitByLines(result);
    }

    /**
     * Lazily spawns a process if needed and executes the given command.  If this fails, returns null and displays
     * the error to the user.  If the path to ghc-modi is not set, this simply returns null with no error message.
     */
    @Nullable
    public synchronized String exec(@NotNull String command) throws GhcModiError {
        if (!enabled) { return null; }
        if (path == null) { return null; }
{'fraction_non_alphanumeric': 0.09204537244418756, 'fraction_numerical': 0.0014449822989668377, 'mean_word_length': 2.719967750604676, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5999617', 'n_tokens_mistral': 3788, 'n_tokens_neox': 3637, 'n_words': 1092}