ステップ数カウント

<details><summary>ロジック</summary>

```java
import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import java.awt.*;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class LineCounterUI {

    // 設定オプション（初期値）
    private static boolean includeComments = true; // コメント行を含めるかどうか
    private static boolean includeBlankLines = true; // 空白行を含めるかどうか
    private static boolean autoSave = false; // 自動的に結果を保存するかどうか
    private static String defaultPath = System.getProperty("user.dir"); // 初期フォルダパス
    private static String outputFilePath = defaultPath + File.separator + "計測結果.txt"; // 結果保存先（デフォルト値）
    private static Set<String> extensions = new HashSet<>(List.of("java", "xml")); // 計測対象の拡張子（初期値）
    private static Path lastFolderPath = null; // 最後に選択したフォルダ
    private static String lastResult = ""; // 最後の計測結果

    public static void main(String[] args) {
        // Swingを使ってGUIを表示
        SwingUtilities.invokeLater(LineCounterUI::createAndShowGUI);
    }

    private static void createAndShowGUI() {
        // メインウィンドウの作成
        JFrame frame = new JFrame("ファイル行数計測ツール");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(800, 500);
        frame.setLayout(new BorderLayout());

        // 左側のオプションパネル
        JPanel optionsPanel = new JPanel();
        optionsPanel.setLayout(new BoxLayout(optionsPanel, BoxLayout.Y_AXIS));

        // オプション設定用のチェックボックス（コメント行・空白行・自動保存）
        JCheckBox includeCommentsCheckBox = new JCheckBox("コメント行を含める", includeComments);
        JCheckBox includeBlankLinesCheckBox = new JCheckBox("空白行を含める", includeBlankLines);
        JCheckBox autoSaveCheckBox = new JCheckBox("結果を自動保存する", autoSave);

        // チェックボックスの動作を設定
        includeCommentsCheckBox.addActionListener(e -> includeComments = includeCommentsCheckBox.isSelected());
        includeBlankLinesCheckBox.addActionListener(e -> includeBlankLines = includeBlankLinesCheckBox.isSelected());
        autoSaveCheckBox.addActionListener(e -> autoSave = autoSaveCheckBox.isSelected());

        // 拡張子フィルタリングのラベルと入力フィールド
        JLabel extensionLabel = new JLabel("計測する拡張子（カンマ区切り）:");
        JTextField extensionField = new JTextField(String.join(",", extensions));
        extensionField.setMaximumSize(new Dimension(Integer.MAX_VALUE, extensionField.getPreferredSize().height)); // 1行の高さに調整
        extensionField.addActionListener(e -> updateExtensions(extensionField.getText()));

        // 結果保存先のラベルと入力フィールド
        JLabel outputFileLabel = new JLabel("結果保存先（ファイルパス指定）:");
        JTextField outputFileField = new JTextField(outputFilePath, 30);
        outputFileField.addActionListener(e -> outputFilePath = outputFileField.getText());

        // オプションパネルに各要素を追加
        optionsPanel.add(includeCommentsCheckBox);
        optionsPanel.add(includeBlankLinesCheckBox);
        optionsPanel.add(autoSaveCheckBox);
        optionsPanel.add(extensionLabel);
        optionsPanel.add(extensionField);
        optionsPanel.add(outputFileLabel);
        optionsPanel.add(outputFileField);

        // メインフレームにオプションパネルを追加
        frame.add(optionsPanel, BorderLayout.WEST);

        // 中央のアクションパネル
        JPanel actionPanel = new JPanel();
        actionPanel.setLayout(new BorderLayout());

        // 結果表示用のテキストエリア（スクロール可能）
        JTextArea resultArea = new JTextArea(8, 50);
        resultArea.setEditable(false);
        JScrollPane scrollPane = new JScrollPane(resultArea);
        actionPanel.add(scrollPane, BorderLayout.CENTER);

        // ボタンパネル（フォルダ選択、再計測、保存）
        JPanel buttonPanel = new JPanel();
        JButton selectFolderButton = new JButton("フォルダを選択");
        JButton reMeasureButton = new JButton("再計測");
        JButton saveResultsButton = new JButton("結果を保存");
        reMeasureButton.setEnabled(false); // 初期状態では無効
        saveResultsButton.setEnabled(false); // 初期状態では無効

        // 選択したフォルダのラベル表示
        JLabel selectedFolderLabel = new JLabel("選択したフォルダ: ");

        // フォルダ選択ボタンの動作
        selectFolderButton.addActionListener(e -> {
            JFileChooser fileChooser = new JFileChooser(defaultPath); // 初期パスを設定
            fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); // ディレクトリ選択のみ許可
            int returnValue = fileChooser.showOpenDialog(null);

            if (returnValue == JFileChooser.APPROVE_OPTION) {
                lastFolderPath = fileChooser.getSelectedFile().toPath(); // 選択したフォルダを取得
            } else {
                // ダイアログがキャンセルされた場合に直接入力を促す
                String inputPath = JOptionPane.showInputDialog("フォルダパスを入力してください:");
                if (inputPath != null) {
                    lastFolderPath = Paths.get(inputPath).toAbsolutePath(); // 入力されたパスを絶対パスに変換
                }
            }

            // フォルダが正しく選択された場合、計測を実行
            if (lastFolderPath != null && Files.isDirectory(lastFolderPath)) {
                selectedFolderLabel.setText("選択したフォルダ: " + lastFolderPath);
                resultArea.setText(""); // 結果表示エリアをクリア

                try {
                    List<String> results = countLinesInFolder(lastFolderPath);
                    lastResult = String.join("\n", results); // 計測結果を保持
                    for (String line : results) {
                        resultArea.append(line + "\n");
                    }
                    reMeasureButton.setEnabled(true); // 再計測ボタンを有効化
                    saveResultsButton.setEnabled(true); // 保存ボタンを有効化

                    if (autoSave) {
                        saveResultsToFile(results); // 自動保存が有効なら保存
                        resultArea.append("\n結果を " + outputFilePath + " に保存しました。\n");
                    }
                } catch (IOException ex) {
                    resultArea.setText("エラー: " + ex.getMessage());
                }
            } else {
                selectedFolderLabel.setText("フォルダの選択に失敗しました。");
            }
        });

        // 再計測ボタンの動作
        reMeasureButton.addActionListener(e -> {
            if (lastFolderPath != null) {
                try {
                    List<String> results = countLinesInFolder(lastFolderPath);
                    lastResult = String.join("\n", results); // 結果を更新
                    resultArea.setText(lastResult); // テキストエリアに表示
                    if (autoSave) {
                        saveResultsToFile(results);
                    }
                } catch (IOException ex) {
                    resultArea.setText("エラー: " + ex.getMessage());
                }
            }
        });

        // 結果保存ボタンの動作
        saveResultsButton.addActionListener(e -> {
            if (!lastResult.isEmpty()) {
                try {
                    saveResultsToFile(List.of(lastResult.split("\n"))); // 保存処理
                    JOptionPane.showMessageDialog(frame, "結果を保存しました。", "保存成功", JOptionPane.INFORMATION_MESSAGE);
                } catch (IOException ex) {
                    JOptionPane.showMessageDialog(frame, "保存に失敗しました: " + ex.getMessage(), "保存失敗", JOptionPane.ERROR_MESSAGE);
                }
            }
        });

        // ボタンパネルにボタンとラベルを追加
        buttonPanel.add(selectFolderButton);
        buttonPanel.add(reMeasureButton);
        buttonPanel.add(saveResultsButton);
        buttonPanel.add(selectedFolderLabel);

        // アクションパネルにボタンパネルを追加
        actionPanel.add(buttonPanel, BorderLayout.SOUTH);
        frame.add(actionPanel, BorderLayout.CENTER);

        // メインウィンドウを表示
        frame.setVisible(true);
    }

    private static void updateExtensions(String text) {
        // 拡張子フィルタリング用リストを更新
        extensions = Arrays.stream(text.split(","))
                .map(String::trim)
                .filter(ext -> !ext.isEmpty())
                .collect(Collectors.toSet());
    }

    public static List<String> countLinesInFolder(Path folderPath) throws IOException {
        // フォルダ内の全ファイルを検索し、条件に合うファイルの行数を計測
        try (Stream<Path> paths = Files.walk(folderPath)) {
            return paths.filter(Files::isRegularFile) // 通常ファイルのみ
                    .filter(file -> extensions.contains(getFileExtension(file))) // 拡張子フィルタリング
                    .map(LineCounterUI::processFile) // 各ファイルの行数を計測
                    .collect(Collectors.toList());
        }
    }

    private static String getFileExtension(Path file) {
        // ファイル名から拡張子を取得
        String name = file.getFileName().toString();
        int lastDot = name.lastIndexOf(".");
        return (lastDot == -1) ? "" : name.substring(lastDot + 1).toLowerCase();
    }

    public static String processFile(Path filePath) {
        // ファイルの行数を計測
        long lineCount = 0;
        try (BufferedReader reader = Files.newBufferedReader(filePath)) {
            lineCount = reader.lines()
                    .filter(line -> includeBlankLines || !line.trim().isEmpty()) // 空白行の除外
                    .filter(line -> includeComments || isNotComment(line, filePath)) // コメント行の除外
                    .count();
        } catch (IOException e) {
            return filePath.getFileName() + "\tエラー: " + e.getMessage();
        }
        return filePath.getFileName() + "\t" + lineCount;
    }

    private static boolean isNotComment(String line, Path filePath) {
        // コメント行かどうかを判定（拡張子による条件分岐）
        String ext = getFileExtension(filePath);
        line = line.trim();
        switch (ext) {
            case "java":
                return !(line.startsWith("//") || line.startsWith("/*") || line.startsWith("*") || line.endsWith("*/"));
            case "xml":
                return !(line.startsWith("<!--") || line.endsWith("-->"));
            default:
                return true;
        }
    }

    public static void saveResultsToFile(List<String> results) throws IOException {
        // 計測結果をファイルに保存
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(outputFilePath))) {
            for (String line : results) {
                writer.write(line);
                writer.newLine();
            }
        }
    }
}

```
</details>