valepakh commented on code in PR #7372:
URL: https://github.com/apache/ignite-3/pull/7372#discussion_r2685403147


##########
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/terminal/PagerSupport.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cli.core.repl.terminal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import org.apache.ignite.internal.cli.config.CliConfigKeys;
+import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
+import org.jline.terminal.Terminal;
+
+/**
+ * Handles pager support in CLI REPL mode.
+ *
+ * <p>Provides functionality to pipe large outputs through a pager (like 
`less` or `more`)
+ * when the output exceeds the terminal height.
+ */
+public class PagerSupport {
+
+    /** Default pager command. Uses less with flags: -R (ANSI colors), -F 
(quit if fits), -X (no clear). */
+    public static final String DEFAULT_PAGER = "less -RFX";
+
+    /** Default pager command for Windows. */
+    public static final String DEFAULT_PAGER_WINDOWS = "more";
+
+    /** Number of lines to reserve for prompt and status. */
+    private static final int TERMINAL_MARGIN = 2;
+
+    private final int terminalHeight;
+    private final boolean pagerEnabled;
+    private final String pagerCommand;
+    private final Terminal terminal;
+
+    /**
+     * Creates PagerSupport for production use.
+     *
+     * @param terminal JLine terminal instance
+     * @param configManagerProvider configuration manager provider
+     */
+    public PagerSupport(Terminal terminal, ConfigManagerProvider 
configManagerProvider) {
+        this.terminal = terminal;
+        this.terminalHeight = terminal.getHeight();
+        this.pagerEnabled = readPagerEnabled(configManagerProvider);
+        this.pagerCommand = readPagerCommand(configManagerProvider);
+    }
+
+    /**
+     * Creates PagerSupport for testing.
+     *
+     * @param terminal JLine terminal (can be null for testing)
+     * @param terminalHeight terminal height in lines
+     * @param pagerEnabled whether pager is enabled
+     * @param pagerCommand pager command (null for default)
+     */
+    PagerSupport(Terminal terminal, int terminalHeight, boolean pagerEnabled, 
String pagerCommand) {
+        this.terminal = terminal;
+        this.terminalHeight = terminalHeight;
+        this.pagerEnabled = pagerEnabled;
+        this.pagerCommand = resolveCommand(pagerCommand);
+    }
+
+    /**
+     * Checks if the pager should be used for the given output.
+     *
+     * @param output the output to check
+     * @return true if the output exceeds terminal height and pager is enabled
+     */
+    public boolean shouldUsePager(String output) {
+        if (!pagerEnabled) {
+            return false;
+        }
+        int lineCount = countLines(output);
+        int threshold = terminalHeight - TERMINAL_MARGIN;
+        return lineCount > threshold;
+    }
+
+    /**
+     * Pipes the output through the configured pager.
+     *
+     * @param output the output to display in the pager
+     */
+    public void pipeToPage(String output) {
+        String command = getPagerCommand();
+        try {
+            ProcessBuilder pb = createPagerProcess(command);
+            Process process = pb.start();
+
+            try (OutputStream os = process.getOutputStream()) {
+                os.write(output.getBytes(StandardCharsets.UTF_8));
+            }
+
+            process.waitFor();
+        } catch (IOException | InterruptedException e) {
+            // Fallback: print directly if pager fails
+            if (terminal != null) {
+                terminal.writer().print(output);
+                terminal.writer().flush();
+            }
+            if (e instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * Returns the pager command to use.
+     *
+     * @return the pager command
+     */
+    public String getPagerCommand() {
+        return pagerCommand;
+    }
+
+    /**
+     * Returns whether the pager is enabled.
+     *
+     * @return true if pager is enabled
+     */
+    public boolean isPagerEnabled() {
+        return pagerEnabled;
+    }
+
+    /**
+     * Counts the number of lines in the output.
+     *
+     * @param output the output to count lines in
+     * @return the number of lines
+     */
+    int countLines(String output) {

Review Comment:
   This method could be made static



##########
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/terminal/PagerSupport.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cli.core.repl.terminal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import org.apache.ignite.internal.cli.config.CliConfigKeys;
+import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
+import org.jline.terminal.Terminal;
+
+/**
+ * Handles pager support in CLI REPL mode.
+ *
+ * <p>Provides functionality to pipe large outputs through a pager (like 
`less` or `more`)
+ * when the output exceeds the terminal height.
+ */
+public class PagerSupport {
+
+    /** Default pager command. Uses less with flags: -R (ANSI colors), -F 
(quit if fits), -X (no clear). */
+    public static final String DEFAULT_PAGER = "less -RFX";
+
+    /** Default pager command for Windows. */
+    public static final String DEFAULT_PAGER_WINDOWS = "more";
+
+    /** Number of lines to reserve for prompt and status. */
+    private static final int TERMINAL_MARGIN = 2;
+
+    private final int terminalHeight;
+    private final boolean pagerEnabled;
+    private final String pagerCommand;
+    private final Terminal terminal;
+
+    /**
+     * Creates PagerSupport for production use.
+     *
+     * @param terminal JLine terminal instance
+     * @param configManagerProvider configuration manager provider
+     */
+    public PagerSupport(Terminal terminal, ConfigManagerProvider 
configManagerProvider) {
+        this.terminal = terminal;
+        this.terminalHeight = terminal.getHeight();
+        this.pagerEnabled = readPagerEnabled(configManagerProvider);
+        this.pagerCommand = readPagerCommand(configManagerProvider);
+    }
+
+    /**
+     * Creates PagerSupport for testing.
+     *
+     * @param terminal JLine terminal (can be null for testing)
+     * @param terminalHeight terminal height in lines
+     * @param pagerEnabled whether pager is enabled
+     * @param pagerCommand pager command (null for default)
+     */
+    PagerSupport(Terminal terminal, int terminalHeight, boolean pagerEnabled, 
String pagerCommand) {
+        this.terminal = terminal;
+        this.terminalHeight = terminalHeight;
+        this.pagerEnabled = pagerEnabled;
+        this.pagerCommand = resolveCommand(pagerCommand);
+    }
+
+    /**
+     * Checks if the pager should be used for the given output.
+     *
+     * @param output the output to check
+     * @return true if the output exceeds terminal height and pager is enabled
+     */
+    public boolean shouldUsePager(String output) {
+        if (!pagerEnabled) {
+            return false;
+        }
+        int lineCount = countLines(output);
+        int threshold = terminalHeight - TERMINAL_MARGIN;
+        return lineCount > threshold;
+    }
+
+    /**
+     * Pipes the output through the configured pager.
+     *
+     * @param output the output to display in the pager
+     */
+    public void pipeToPage(String output) {
+        String command = getPagerCommand();
+        try {
+            ProcessBuilder pb = createPagerProcess(command);
+            Process process = pb.start();
+
+            try (OutputStream os = process.getOutputStream()) {
+                os.write(output.getBytes(StandardCharsets.UTF_8));
+            }
+
+            process.waitFor();
+        } catch (IOException | InterruptedException e) {
+            // Fallback: print directly if pager fails
+            if (terminal != null) {
+                terminal.writer().print(output);
+                terminal.writer().flush();
+            }
+            if (e instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * Returns the pager command to use.
+     *
+     * @return the pager command
+     */
+    public String getPagerCommand() {
+        return pagerCommand;
+    }
+
+    /**
+     * Returns whether the pager is enabled.
+     *
+     * @return true if pager is enabled
+     */
+    public boolean isPagerEnabled() {
+        return pagerEnabled;
+    }
+
+    /**
+     * Counts the number of lines in the output.
+     *
+     * @param output the output to count lines in
+     * @return the number of lines
+     */
+    int countLines(String output) {
+        if (output == null || output.isEmpty()) {
+            return 0;
+        }
+        // Remove trailing newline for accurate count
+        String normalized = output.endsWith("\n") ? output.substring(0, 
output.length() - 1) : output;
+        if (normalized.isEmpty()) {
+            return 0;
+        }
+        int count = 1;
+        for (int i = 0; i < normalized.length(); i++) {
+            if (normalized.charAt(i) == '\n') {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Creates the process builder for the pager.
+     * Package-private for testing.
+     */
+    ProcessBuilder createPagerProcess(String command) {
+        ProcessBuilder pb;
+        if (isWindows()) {
+            pb = new ProcessBuilder("cmd", "/c", command);
+        } else {
+            pb = new ProcessBuilder("sh", "-c", command);
+        }
+        pb.inheritIO();
+        pb.redirectInput(ProcessBuilder.Redirect.PIPE);
+        return pb;
+    }
+
+    private boolean readPagerEnabled(ConfigManagerProvider 
configManagerProvider) {

Review Comment:
   ```suggestion
       private static boolean readPagerEnabled(ConfigManagerProvider 
configManagerProvider) {
   ```



##########
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/terminal/PagerSupport.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cli.core.repl.terminal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import org.apache.ignite.internal.cli.config.CliConfigKeys;
+import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
+import org.jline.terminal.Terminal;
+
+/**
+ * Handles pager support in CLI REPL mode.
+ *
+ * <p>Provides functionality to pipe large outputs through a pager (like 
`less` or `more`)
+ * when the output exceeds the terminal height.
+ */
+public class PagerSupport {
+
+    /** Default pager command. Uses less with flags: -R (ANSI colors), -F 
(quit if fits), -X (no clear). */
+    public static final String DEFAULT_PAGER = "less -RFX";
+
+    /** Default pager command for Windows. */
+    public static final String DEFAULT_PAGER_WINDOWS = "more";
+
+    /** Number of lines to reserve for prompt and status. */
+    private static final int TERMINAL_MARGIN = 2;
+
+    private final int terminalHeight;
+    private final boolean pagerEnabled;
+    private final String pagerCommand;
+    private final Terminal terminal;
+
+    /**
+     * Creates PagerSupport for production use.
+     *
+     * @param terminal JLine terminal instance
+     * @param configManagerProvider configuration manager provider
+     */
+    public PagerSupport(Terminal terminal, ConfigManagerProvider 
configManagerProvider) {
+        this.terminal = terminal;
+        this.terminalHeight = terminal.getHeight();
+        this.pagerEnabled = readPagerEnabled(configManagerProvider);
+        this.pagerCommand = readPagerCommand(configManagerProvider);
+    }
+
+    /**
+     * Creates PagerSupport for testing.
+     *
+     * @param terminal JLine terminal (can be null for testing)
+     * @param terminalHeight terminal height in lines
+     * @param pagerEnabled whether pager is enabled
+     * @param pagerCommand pager command (null for default)
+     */
+    PagerSupport(Terminal terminal, int terminalHeight, boolean pagerEnabled, 
String pagerCommand) {
+        this.terminal = terminal;
+        this.terminalHeight = terminalHeight;
+        this.pagerEnabled = pagerEnabled;
+        this.pagerCommand = resolveCommand(pagerCommand);
+    }
+
+    /**
+     * Checks if the pager should be used for the given output.
+     *
+     * @param output the output to check
+     * @return true if the output exceeds terminal height and pager is enabled
+     */
+    public boolean shouldUsePager(String output) {
+        if (!pagerEnabled) {
+            return false;
+        }
+        int lineCount = countLines(output);
+        int threshold = terminalHeight - TERMINAL_MARGIN;
+        return lineCount > threshold;
+    }
+
+    /**
+     * Pipes the output through the configured pager.
+     *
+     * @param output the output to display in the pager
+     */
+    public void pipeToPage(String output) {
+        String command = getPagerCommand();
+        try {
+            ProcessBuilder pb = createPagerProcess(command);
+            Process process = pb.start();
+
+            try (OutputStream os = process.getOutputStream()) {
+                os.write(output.getBytes(StandardCharsets.UTF_8));
+            }
+
+            process.waitFor();
+        } catch (IOException | InterruptedException e) {
+            // Fallback: print directly if pager fails
+            if (terminal != null) {
+                terminal.writer().print(output);
+                terminal.writer().flush();
+            }
+            if (e instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * Returns the pager command to use.
+     *
+     * @return the pager command
+     */
+    public String getPagerCommand() {
+        return pagerCommand;
+    }
+
+    /**
+     * Returns whether the pager is enabled.
+     *
+     * @return true if pager is enabled
+     */
+    public boolean isPagerEnabled() {
+        return pagerEnabled;
+    }
+
+    /**
+     * Counts the number of lines in the output.
+     *
+     * @param output the output to count lines in
+     * @return the number of lines
+     */
+    int countLines(String output) {
+        if (output == null || output.isEmpty()) {
+            return 0;
+        }
+        // Remove trailing newline for accurate count
+        String normalized = output.endsWith("\n") ? output.substring(0, 
output.length() - 1) : output;
+        if (normalized.isEmpty()) {
+            return 0;
+        }
+        int count = 1;
+        for (int i = 0; i < normalized.length(); i++) {
+            if (normalized.charAt(i) == '\n') {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Creates the process builder for the pager.
+     * Package-private for testing.
+     */
+    ProcessBuilder createPagerProcess(String command) {
+        ProcessBuilder pb;
+        if (isWindows()) {
+            pb = new ProcessBuilder("cmd", "/c", command);
+        } else {
+            pb = new ProcessBuilder("sh", "-c", command);
+        }
+        pb.inheritIO();
+        pb.redirectInput(ProcessBuilder.Redirect.PIPE);
+        return pb;
+    }
+
+    private boolean readPagerEnabled(ConfigManagerProvider 
configManagerProvider) {
+        String value = configManagerProvider.get()
+                .getCurrentProperty(CliConfigKeys.PAGER_ENABLED.value());
+        // Default to true if not set
+        return value == null || value.isEmpty() || Boolean.parseBoolean(value);
+    }
+
+    private String readPagerCommand(ConfigManagerProvider 
configManagerProvider) {
+        String configured = configManagerProvider.get()
+                .getCurrentProperty(CliConfigKeys.PAGER_COMMAND.value());
+        return resolveCommand(configured);
+    }
+
+    private String resolveCommand(String configured) {

Review Comment:
   ```suggestion
       private static String resolveCommand(String configured) {
   ```



##########
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/terminal/PagerSupport.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cli.core.repl.terminal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import org.apache.ignite.internal.cli.config.CliConfigKeys;
+import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
+import org.jline.terminal.Terminal;
+
+/**
+ * Handles pager support in CLI REPL mode.
+ *
+ * <p>Provides functionality to pipe large outputs through a pager (like 
`less` or `more`)
+ * when the output exceeds the terminal height.
+ */
+public class PagerSupport {
+
+    /** Default pager command. Uses less with flags: -R (ANSI colors), -F 
(quit if fits), -X (no clear). */
+    public static final String DEFAULT_PAGER = "less -RFX";
+
+    /** Default pager command for Windows. */
+    public static final String DEFAULT_PAGER_WINDOWS = "more";
+
+    /** Number of lines to reserve for prompt and status. */
+    private static final int TERMINAL_MARGIN = 2;
+
+    private final int terminalHeight;
+    private final boolean pagerEnabled;
+    private final String pagerCommand;
+    private final Terminal terminal;
+
+    /**
+     * Creates PagerSupport for production use.
+     *
+     * @param terminal JLine terminal instance
+     * @param configManagerProvider configuration manager provider
+     */
+    public PagerSupport(Terminal terminal, ConfigManagerProvider 
configManagerProvider) {
+        this.terminal = terminal;
+        this.terminalHeight = terminal.getHeight();
+        this.pagerEnabled = readPagerEnabled(configManagerProvider);
+        this.pagerCommand = readPagerCommand(configManagerProvider);
+    }
+
+    /**
+     * Creates PagerSupport for testing.
+     *
+     * @param terminal JLine terminal (can be null for testing)
+     * @param terminalHeight terminal height in lines
+     * @param pagerEnabled whether pager is enabled
+     * @param pagerCommand pager command (null for default)
+     */
+    PagerSupport(Terminal terminal, int terminalHeight, boolean pagerEnabled, 
String pagerCommand) {
+        this.terminal = terminal;
+        this.terminalHeight = terminalHeight;
+        this.pagerEnabled = pagerEnabled;
+        this.pagerCommand = resolveCommand(pagerCommand);
+    }
+
+    /**
+     * Checks if the pager should be used for the given output.
+     *
+     * @param output the output to check
+     * @return true if the output exceeds terminal height and pager is enabled
+     */
+    public boolean shouldUsePager(String output) {
+        if (!pagerEnabled) {
+            return false;
+        }
+        int lineCount = countLines(output);
+        int threshold = terminalHeight - TERMINAL_MARGIN;
+        return lineCount > threshold;
+    }
+
+    /**
+     * Pipes the output through the configured pager.
+     *
+     * @param output the output to display in the pager
+     */
+    public void pipeToPage(String output) {
+        String command = getPagerCommand();
+        try {
+            ProcessBuilder pb = createPagerProcess(command);
+            Process process = pb.start();
+
+            try (OutputStream os = process.getOutputStream()) {
+                os.write(output.getBytes(StandardCharsets.UTF_8));
+            }
+
+            process.waitFor();
+        } catch (IOException | InterruptedException e) {
+            // Fallback: print directly if pager fails
+            if (terminal != null) {
+                terminal.writer().print(output);
+                terminal.writer().flush();
+            }
+            if (e instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * Returns the pager command to use.
+     *
+     * @return the pager command
+     */
+    public String getPagerCommand() {
+        return pagerCommand;
+    }
+
+    /**
+     * Returns whether the pager is enabled.
+     *
+     * @return true if pager is enabled
+     */
+    public boolean isPagerEnabled() {
+        return pagerEnabled;
+    }
+
+    /**
+     * Counts the number of lines in the output.
+     *
+     * @param output the output to count lines in
+     * @return the number of lines
+     */
+    int countLines(String output) {
+        if (output == null || output.isEmpty()) {
+            return 0;
+        }
+        // Remove trailing newline for accurate count
+        String normalized = output.endsWith("\n") ? output.substring(0, 
output.length() - 1) : output;
+        if (normalized.isEmpty()) {
+            return 0;
+        }
+        int count = 1;
+        for (int i = 0; i < normalized.length(); i++) {
+            if (normalized.charAt(i) == '\n') {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Creates the process builder for the pager.
+     * Package-private for testing.
+     */
+    ProcessBuilder createPagerProcess(String command) {
+        ProcessBuilder pb;
+        if (isWindows()) {
+            pb = new ProcessBuilder("cmd", "/c", command);
+        } else {
+            pb = new ProcessBuilder("sh", "-c", command);
+        }
+        pb.inheritIO();
+        pb.redirectInput(ProcessBuilder.Redirect.PIPE);
+        return pb;
+    }
+
+    private boolean readPagerEnabled(ConfigManagerProvider 
configManagerProvider) {
+        String value = configManagerProvider.get()
+                .getCurrentProperty(CliConfigKeys.PAGER_ENABLED.value());
+        // Default to true if not set
+        return value == null || value.isEmpty() || Boolean.parseBoolean(value);
+    }
+
+    private String readPagerCommand(ConfigManagerProvider 
configManagerProvider) {
+        String configured = configManagerProvider.get()
+                .getCurrentProperty(CliConfigKeys.PAGER_COMMAND.value());
+        return resolveCommand(configured);
+    }
+
+    private String resolveCommand(String configured) {
+        // Priority: configured > $PAGER env > default
+        if (configured != null && !configured.isEmpty()) {
+            return configured;
+        }
+
+        String envPager = System.getenv("PAGER");
+        if (envPager != null && !envPager.isEmpty()) {
+            return envPager;
+        }
+
+        return isWindows() ? DEFAULT_PAGER_WINDOWS : DEFAULT_PAGER;
+    }
+
+    private boolean isWindows() {

Review Comment:
   ```suggestion
       private static boolean isWindows() {
   ```



##########
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/terminal/PagerSupport.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cli.core.repl.terminal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import org.apache.ignite.internal.cli.config.CliConfigKeys;
+import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
+import org.jline.terminal.Terminal;
+
+/**
+ * Handles pager support in CLI REPL mode.
+ *
+ * <p>Provides functionality to pipe large outputs through a pager (like 
`less` or `more`)
+ * when the output exceeds the terminal height.
+ */
+public class PagerSupport {
+
+    /** Default pager command. Uses less with flags: -R (ANSI colors), -F 
(quit if fits), -X (no clear). */
+    public static final String DEFAULT_PAGER = "less -RFX";
+
+    /** Default pager command for Windows. */
+    public static final String DEFAULT_PAGER_WINDOWS = "more";
+
+    /** Number of lines to reserve for prompt and status. */
+    private static final int TERMINAL_MARGIN = 2;
+
+    private final int terminalHeight;
+    private final boolean pagerEnabled;
+    private final String pagerCommand;
+    private final Terminal terminal;
+
+    /**
+     * Creates PagerSupport for production use.
+     *
+     * @param terminal JLine terminal instance
+     * @param configManagerProvider configuration manager provider
+     */
+    public PagerSupport(Terminal terminal, ConfigManagerProvider 
configManagerProvider) {
+        this.terminal = terminal;
+        this.terminalHeight = terminal.getHeight();
+        this.pagerEnabled = readPagerEnabled(configManagerProvider);
+        this.pagerCommand = readPagerCommand(configManagerProvider);
+    }
+
+    /**
+     * Creates PagerSupport for testing.
+     *
+     * @param terminal JLine terminal (can be null for testing)
+     * @param terminalHeight terminal height in lines
+     * @param pagerEnabled whether pager is enabled
+     * @param pagerCommand pager command (null for default)
+     */
+    PagerSupport(Terminal terminal, int terminalHeight, boolean pagerEnabled, 
String pagerCommand) {
+        this.terminal = terminal;
+        this.terminalHeight = terminalHeight;
+        this.pagerEnabled = pagerEnabled;
+        this.pagerCommand = resolveCommand(pagerCommand);
+    }
+
+    /**
+     * Checks if the pager should be used for the given output.
+     *
+     * @param output the output to check
+     * @return true if the output exceeds terminal height and pager is enabled
+     */
+    public boolean shouldUsePager(String output) {
+        if (!pagerEnabled) {
+            return false;
+        }
+        int lineCount = countLines(output);
+        int threshold = terminalHeight - TERMINAL_MARGIN;
+        return lineCount > threshold;
+    }
+
+    /**
+     * Pipes the output through the configured pager.
+     *
+     * @param output the output to display in the pager
+     */
+    public void pipeToPage(String output) {
+        String command = getPagerCommand();
+        try {
+            ProcessBuilder pb = createPagerProcess(command);
+            Process process = pb.start();
+
+            try (OutputStream os = process.getOutputStream()) {
+                os.write(output.getBytes(StandardCharsets.UTF_8));
+            }
+
+            process.waitFor();
+        } catch (IOException | InterruptedException e) {
+            // Fallback: print directly if pager fails
+            if (terminal != null) {
+                terminal.writer().print(output);
+                terminal.writer().flush();
+            }
+            if (e instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * Returns the pager command to use.
+     *
+     * @return the pager command
+     */
+    public String getPagerCommand() {
+        return pagerCommand;
+    }
+
+    /**
+     * Returns whether the pager is enabled.
+     *
+     * @return true if pager is enabled
+     */
+    public boolean isPagerEnabled() {
+        return pagerEnabled;
+    }
+
+    /**
+     * Counts the number of lines in the output.
+     *
+     * @param output the output to count lines in
+     * @return the number of lines
+     */
+    int countLines(String output) {
+        if (output == null || output.isEmpty()) {
+            return 0;
+        }
+        // Remove trailing newline for accurate count
+        String normalized = output.endsWith("\n") ? output.substring(0, 
output.length() - 1) : output;

Review Comment:
   I think `System.lineSeparator` should be used here and everywhere else, 
currently this doesn't seem to work on Windows.



##########
modules/cli/src/test/java/org/apache/ignite/internal/cli/core/repl/terminal/PagerSupportTest.java:
##########
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cli.core.repl.terminal;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link PagerSupport}.
+ */
+class PagerSupportTest {
+
+    @Nested
+    @DisplayName("countLines")
+    class CountLinesTest {
+
+        private final PagerSupport pager = createPagerSupport(24, true, 
"less");
+
+        @Test
+        @DisplayName("returns 0 for null input")
+        void nullInput() {
+            assertThat(pager.countLines(null), is(0));
+        }
+
+        @Test
+        @DisplayName("returns 0 for empty string")
+        void emptyString() {
+            assertThat(pager.countLines(""), is(0));
+        }
+
+        @Test
+        @DisplayName("returns 1 for single line without newline")
+        void singleLineNoNewline() {
+            assertThat(pager.countLines("hello"), is(1));
+        }
+
+        @Test
+        @DisplayName("returns 1 for single line with trailing newline")
+        void singleLineWithNewline() {
+            assertThat(pager.countLines("hello\n"), is(1));
+        }
+
+        @Test
+        @DisplayName("returns 2 for two lines")
+        void twoLines() {
+            assertThat(pager.countLines("hello\nworld"), is(2));
+        }
+
+        @Test
+        @DisplayName("returns 2 for two lines with trailing newline")
+        void twoLinesWithTrailingNewline() {
+            assertThat(pager.countLines("hello\nworld\n"), is(2));
+        }
+
+        @Test
+        @DisplayName("counts multiple lines correctly")
+        void multipleLines() {
+            String output = "line1\nline2\nline3\nline4\nline5";
+            assertThat(pager.countLines(output), is(5));
+        }
+
+        @Test
+        @DisplayName("handles empty lines in the middle")
+        void emptyLinesInMiddle() {
+            String output = "line1\n\nline3";
+            assertThat(pager.countLines(output), is(3));
+        }
+    }
+
+    @Nested
+    @DisplayName("shouldUsePager")
+    class ShouldUsePagerTest {
+
+        @Test
+        @DisplayName("returns false when pager is disabled")
+        void pagerDisabled() {
+            PagerSupport pager = createPagerSupport(24, false, "less");
+            String output = generateLines(100);
+            assertThat(pager.shouldUsePager(output), is(false));
+        }
+
+        @Test
+        @DisplayName("returns false when output fits terminal")
+        void outputFitsTerminal() {
+            PagerSupport pager = createPagerSupport(24, true, "less");
+            String output = generateLines(10);
+            assertThat(pager.shouldUsePager(output), is(false));
+        }
+
+        @Test
+        @DisplayName("returns true when output exceeds terminal height")
+        void outputExceedsTerminal() {
+            PagerSupport pager = createPagerSupport(24, true, "less");
+            String output = generateLines(30);
+            assertThat(pager.shouldUsePager(output), is(true));
+        }
+
+        @Test
+        @DisplayName("returns false when output equals terminal height minus 
margin")
+        void outputEqualsTerminalMinusMargin() {
+            // Terminal height 24, margin 2, so threshold is 22
+            PagerSupport pager = createPagerSupport(24, true, "less");
+            String output = generateLines(22);
+            assertThat(pager.shouldUsePager(output), is(false));
+        }
+
+        @Test
+        @DisplayName("returns true when output is one line over threshold")
+        void outputOneLineOverThreshold() {
+            // Terminal height 24, margin 2, so threshold is 22
+            PagerSupport pager = createPagerSupport(24, true, "less");
+            String output = generateLines(23);
+            assertThat(pager.shouldUsePager(output), is(true));
+        }
+
+        @Test
+        @DisplayName("returns false for null output")
+        void nullOutput() {
+            PagerSupport pager = createPagerSupport(24, true, "less");
+            assertThat(pager.shouldUsePager(null), is(false));
+        }
+
+        @Test
+        @DisplayName("returns false for empty output")
+        void emptyOutput() {
+            PagerSupport pager = createPagerSupport(24, true, "less");
+            assertThat(pager.shouldUsePager(""), is(false));
+        }
+    }
+
+    @Nested
+    @DisplayName("getPagerCommand")
+    class GetPagerCommandTest {
+
+        @Test
+        @DisplayName("returns configured command when set")
+        void configuredCommand() {
+            PagerSupport pager = createPagerSupport(24, true, "more -d");
+            assertThat(pager.getPagerCommand(), is("more -d"));
+        }
+
+        @Test
+        @DisplayName("returns default command when config is null")
+        void defaultWhenConfigNull() {
+            PagerSupport pager = createPagerSupport(24, true, null);
+            assertThat(pager.getPagerCommand(), 
is(PagerSupport.DEFAULT_PAGER));

Review Comment:
   These two tests fail on Windows:
   ```
   Expected: is "less -RFX"
        but: was "more"
   ```
   Probably, it would make sense to make the `isWindows` method `public static` 
and use it here, or use it in the initialization of the `DEFAULT_PAGER` field.



##########
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/terminal/PagerSupport.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cli.core.repl.terminal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import org.apache.ignite.internal.cli.config.CliConfigKeys;
+import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
+import org.jline.terminal.Terminal;
+
+/**
+ * Handles pager support in CLI REPL mode.
+ *
+ * <p>Provides functionality to pipe large outputs through a pager (like 
`less` or `more`)
+ * when the output exceeds the terminal height.
+ */
+public class PagerSupport {
+
+    /** Default pager command. Uses less with flags: -R (ANSI colors), -F 
(quit if fits), -X (no clear). */
+    public static final String DEFAULT_PAGER = "less -RFX";
+
+    /** Default pager command for Windows. */
+    public static final String DEFAULT_PAGER_WINDOWS = "more";
+
+    /** Number of lines to reserve for prompt and status. */
+    private static final int TERMINAL_MARGIN = 2;
+
+    private final int terminalHeight;
+    private final boolean pagerEnabled;
+    private final String pagerCommand;
+    private final Terminal terminal;
+
+    /**
+     * Creates PagerSupport for production use.
+     *
+     * @param terminal JLine terminal instance
+     * @param configManagerProvider configuration manager provider
+     */
+    public PagerSupport(Terminal terminal, ConfigManagerProvider 
configManagerProvider) {
+        this.terminal = terminal;
+        this.terminalHeight = terminal.getHeight();
+        this.pagerEnabled = readPagerEnabled(configManagerProvider);
+        this.pagerCommand = readPagerCommand(configManagerProvider);
+    }
+
+    /**
+     * Creates PagerSupport for testing.
+     *
+     * @param terminal JLine terminal (can be null for testing)
+     * @param terminalHeight terminal height in lines
+     * @param pagerEnabled whether pager is enabled
+     * @param pagerCommand pager command (null for default)
+     */
+    PagerSupport(Terminal terminal, int terminalHeight, boolean pagerEnabled, 
String pagerCommand) {
+        this.terminal = terminal;
+        this.terminalHeight = terminalHeight;
+        this.pagerEnabled = pagerEnabled;
+        this.pagerCommand = resolveCommand(pagerCommand);
+    }
+
+    /**
+     * Checks if the pager should be used for the given output.
+     *
+     * @param output the output to check
+     * @return true if the output exceeds terminal height and pager is enabled
+     */
+    public boolean shouldUsePager(String output) {
+        if (!pagerEnabled) {
+            return false;
+        }
+        int lineCount = countLines(output);
+        int threshold = terminalHeight - TERMINAL_MARGIN;
+        return lineCount > threshold;
+    }
+
+    /**
+     * Pipes the output through the configured pager.
+     *
+     * @param output the output to display in the pager
+     */
+    public void pipeToPage(String output) {
+        String command = getPagerCommand();
+        try {
+            ProcessBuilder pb = createPagerProcess(command);
+            Process process = pb.start();
+
+            try (OutputStream os = process.getOutputStream()) {
+                os.write(output.getBytes(StandardCharsets.UTF_8));
+            }
+
+            process.waitFor();
+        } catch (IOException | InterruptedException e) {
+            // Fallback: print directly if pager fails
+            if (terminal != null) {
+                terminal.writer().print(output);
+                terminal.writer().flush();
+            }
+            if (e instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * Returns the pager command to use.
+     *
+     * @return the pager command
+     */
+    public String getPagerCommand() {
+        return pagerCommand;
+    }
+
+    /**
+     * Returns whether the pager is enabled.
+     *
+     * @return true if pager is enabled
+     */
+    public boolean isPagerEnabled() {
+        return pagerEnabled;
+    }
+
+    /**
+     * Counts the number of lines in the output.
+     *
+     * @param output the output to count lines in
+     * @return the number of lines
+     */
+    int countLines(String output) {
+        if (output == null || output.isEmpty()) {
+            return 0;
+        }
+        // Remove trailing newline for accurate count
+        String normalized = output.endsWith("\n") ? output.substring(0, 
output.length() - 1) : output;
+        if (normalized.isEmpty()) {
+            return 0;
+        }
+        int count = 1;
+        for (int i = 0; i < normalized.length(); i++) {
+            if (normalized.charAt(i) == '\n') {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Creates the process builder for the pager.
+     * Package-private for testing.
+     */
+    ProcessBuilder createPagerProcess(String command) {
+        ProcessBuilder pb;
+        if (isWindows()) {
+            pb = new ProcessBuilder("cmd", "/c", command);
+        } else {
+            pb = new ProcessBuilder("sh", "-c", command);
+        }
+        pb.inheritIO();
+        pb.redirectInput(ProcessBuilder.Redirect.PIPE);
+        return pb;
+    }
+
+    private boolean readPagerEnabled(ConfigManagerProvider 
configManagerProvider) {
+        String value = configManagerProvider.get()
+                .getCurrentProperty(CliConfigKeys.PAGER_ENABLED.value());
+        // Default to true if not set
+        return value == null || value.isEmpty() || Boolean.parseBoolean(value);
+    }
+
+    private String readPagerCommand(ConfigManagerProvider 
configManagerProvider) {

Review Comment:
   ```suggestion
       private static String readPagerCommand(ConfigManagerProvider 
configManagerProvider) {
   ```



##########
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/terminal/PagerSupport.java:
##########
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.cli.core.repl.terminal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import org.apache.ignite.internal.cli.config.CliConfigKeys;
+import org.apache.ignite.internal.cli.config.ConfigManagerProvider;
+import org.jline.terminal.Terminal;
+
+/**
+ * Handles pager support in CLI REPL mode.
+ *
+ * <p>Provides functionality to pipe large outputs through a pager (like 
`less` or `more`)
+ * when the output exceeds the terminal height.
+ */
+public class PagerSupport {
+
+    /** Default pager command. Uses less with flags: -R (ANSI colors), -F 
(quit if fits), -X (no clear). */
+    public static final String DEFAULT_PAGER = "less -RFX";
+
+    /** Default pager command for Windows. */
+    public static final String DEFAULT_PAGER_WINDOWS = "more";
+
+    /** Number of lines to reserve for prompt and status. */
+    private static final int TERMINAL_MARGIN = 2;
+
+    private final int terminalHeight;
+    private final boolean pagerEnabled;
+    private final String pagerCommand;
+    private final Terminal terminal;
+
+    /**
+     * Creates PagerSupport for production use.
+     *
+     * @param terminal JLine terminal instance
+     * @param configManagerProvider configuration manager provider
+     */
+    public PagerSupport(Terminal terminal, ConfigManagerProvider 
configManagerProvider) {
+        this.terminal = terminal;
+        this.terminalHeight = terminal.getHeight();
+        this.pagerEnabled = readPagerEnabled(configManagerProvider);
+        this.pagerCommand = readPagerCommand(configManagerProvider);
+    }
+
+    /**
+     * Creates PagerSupport for testing.
+     *
+     * @param terminal JLine terminal (can be null for testing)
+     * @param terminalHeight terminal height in lines
+     * @param pagerEnabled whether pager is enabled
+     * @param pagerCommand pager command (null for default)
+     */
+    PagerSupport(Terminal terminal, int terminalHeight, boolean pagerEnabled, 
String pagerCommand) {
+        this.terminal = terminal;
+        this.terminalHeight = terminalHeight;
+        this.pagerEnabled = pagerEnabled;
+        this.pagerCommand = resolveCommand(pagerCommand);
+    }
+
+    /**
+     * Checks if the pager should be used for the given output.
+     *
+     * @param output the output to check
+     * @return true if the output exceeds terminal height and pager is enabled
+     */
+    public boolean shouldUsePager(String output) {
+        if (!pagerEnabled) {
+            return false;
+        }
+        int lineCount = countLines(output);
+        int threshold = terminalHeight - TERMINAL_MARGIN;
+        return lineCount > threshold;
+    }
+
+    /**
+     * Pipes the output through the configured pager.
+     *
+     * @param output the output to display in the pager
+     */
+    public void pipeToPage(String output) {
+        String command = getPagerCommand();
+        try {
+            ProcessBuilder pb = createPagerProcess(command);
+            Process process = pb.start();
+
+            try (OutputStream os = process.getOutputStream()) {
+                os.write(output.getBytes(StandardCharsets.UTF_8));
+            }
+
+            process.waitFor();
+        } catch (IOException | InterruptedException e) {
+            // Fallback: print directly if pager fails
+            if (terminal != null) {
+                terminal.writer().print(output);
+                terminal.writer().flush();
+            }
+            if (e instanceof InterruptedException) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * Returns the pager command to use.
+     *
+     * @return the pager command
+     */
+    public String getPagerCommand() {
+        return pagerCommand;
+    }
+
+    /**
+     * Returns whether the pager is enabled.
+     *
+     * @return true if pager is enabled
+     */
+    public boolean isPagerEnabled() {
+        return pagerEnabled;
+    }
+
+    /**
+     * Counts the number of lines in the output.
+     *
+     * @param output the output to count lines in
+     * @return the number of lines
+     */
+    int countLines(String output) {
+        if (output == null || output.isEmpty()) {
+            return 0;
+        }
+        // Remove trailing newline for accurate count
+        String normalized = output.endsWith("\n") ? output.substring(0, 
output.length() - 1) : output;
+        if (normalized.isEmpty()) {
+            return 0;
+        }
+        int count = 1;
+        for (int i = 0; i < normalized.length(); i++) {
+            if (normalized.charAt(i) == '\n') {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * Creates the process builder for the pager.
+     * Package-private for testing.
+     */
+    ProcessBuilder createPagerProcess(String command) {

Review Comment:
   ```suggestion
       private static ProcessBuilder createPagerProcess(String command) {
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to