Skip to content

Commit

Permalink
IndexOutOfBoundsException in console-ui when header exceeds size of t…
Browse files Browse the repository at this point in the history
…he terminal, fixes #1025 (#1026)

* console-ui crash when header exceeds size of the terminal, fixes #1025
* Add a unit test

---------

Co-authored-by: Guillaume Nodet <[email protected]>
  • Loading branch information
mattirn and gnodet authored Jul 17, 2024
1 parent 89a99fc commit 89db557
Show file tree
Hide file tree
Showing 2 changed files with 234 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,14 @@ public AbstractPrompt(
ConsolePrompt.UiConfig cfg) {
this.terminal = terminal;
this.bindingReader = new BindingReader(terminal.reader());
this.header = header;
this.size.copy(terminal.getSize());
int listSpace = Math.min(size.getRows(), 10);
this.header = header.size() > size.getRows() - listSpace
? header.subList(header.size() - size.getRows() + listSpace, header.size())
: header;
this.message = message;
this.items = items;
this.firstItemRow = header.size() + 1;
this.firstItemRow = this.header.size() + 1;
this.config = cfg;
}

Expand Down Expand Up @@ -148,7 +152,7 @@ private List<AttributedString> displayLines(
asb.append(buffer);
out.add(asb.toAttributedString());
int listStart;
if (cursorRow - firstItemRow >= 0) {
if (cursorRow - firstItemRow >= 0 && !candidates.isEmpty() && cursorRow - firstItemRow < candidates.size()) {
String dc = candidates.get(cursorRow - firstItemRow).displ();
listStart = candidatesColumn
+ buffer.columnLength()
Expand All @@ -165,6 +169,9 @@ private List<AttributedString> displayLines(
.orElse(20),
20);
for (int i = range.first; i < range.last - 1; i++) {
if (candidates.isEmpty() || i > candidates.size() - 1) {
break;
}
Candidate c = candidates.get(i);
asb = new AttributedStringBuilder();
AttributedStringBuilder tmp = new AttributedStringBuilder();
Expand Down Expand Up @@ -199,6 +206,9 @@ private List<AttributedString> displayLines(int cursorRow, Set<String> selected)
asb.append(message);
out.add(asb.toAttributedString());
for (int i = range.first; i < range.last - 1; i++) {
if (items.isEmpty() || i > items.size() - 1) {
break;
}
ConsoleUIItemIF s = items.get(i);
asb = new AttributedStringBuilder();
if (s.isSelectable()) {
Expand Down
221 changes: 221 additions & 0 deletions console-ui/src/test/java/org/jline/consoleui/Issue1025.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/*
* Copyright (c) 2024, the original author(s).
*
* This software is distributable under the BSD license. See the terms of the
* BSD license in the documentation provided with this software.
*
* https://opensource.org/licenses/BSD-3-Clause
*/
package org.jline.consoleui;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.jline.consoleui.elements.ConfirmChoice;
import org.jline.consoleui.prompt.ConfirmResult;
import org.jline.consoleui.prompt.ConsolePrompt;
import org.jline.consoleui.prompt.PromptResultItemIF;
import org.jline.consoleui.prompt.builder.PromptBuilder;
import org.jline.reader.LineReader;
import org.jline.reader.LineReaderBuilder;
import org.jline.reader.impl.completer.StringsCompleter;
import org.jline.terminal.Attributes;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
import org.jline.utils.AttributedString;
import org.jline.utils.AttributedStringBuilder;
import org.jline.utils.AttributedStyle;
import org.junit.jupiter.api.Test;

public class Issue1025 {

private static void addInHeader(List<AttributedString> header, String text) {
addInHeader(header, AttributedStyle.DEFAULT, text);
}

private static void addInHeader(List<AttributedString> header, AttributedStyle style, String text) {
AttributedStringBuilder asb = new AttributedStringBuilder();
asb.style(style).append(text);
header.add(asb.toAttributedString());
}

@Test
void testIssue1025() throws Exception {
EofPipedInputStream in = new EofPipedInputStream();
in.setIn(new ByteArrayInputStream("\r\r\r\r\r\r".getBytes()));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (Terminal terminal = TerminalBuilder.builder()
.attributes(new Attributes())
.type("xterm")
.streams(in, baos)
.build()) {
test(terminal);
}
}

public static void main(String[] args) throws Exception {
try (Terminal terminal = TerminalBuilder.terminal()) {
test(terminal);
}
}

private static void test(Terminal terminal) throws IOException {
List<AttributedString> header = new ArrayList<>();
AttributedStyle style = new AttributedStyle();
addInHeader(header, style.italic().foreground(2), "Hello World!");
addInHeader(
header, "This is a demonstration of ConsoleUI java library. It provides a simple console interface");
addInHeader(
header,
"for querying information from the user. ConsoleUI is inspired by Inquirer.js which is written");
addInHeader(header, "in JavaScript.");

// If the header exceeds the height of the display, this will lead to a crash.
for (int i = 0; i < 80; i++) {
addInHeader(header, "Extra line: " + i);
}

ConsolePrompt.UiConfig config;
config = new ConsolePrompt.UiConfig("\u276F", "\u25EF ", "\u25C9 ", "\u25EF ");
//
// LineReader is needed only if you are adding JLine Completers in your prompts.
// If you are not using Completers you do not need to create LineReader.
//
LineReader reader = LineReaderBuilder.builder().terminal(terminal).build();
ConsolePrompt prompt = new ConsolePrompt(reader, terminal, config);
PromptBuilder promptBuilder = prompt.getPromptBuilder();

promptBuilder
.createInputPrompt()
.name("name")
.message("Please enter your name")
.defaultValue("John Doe")
// .mask('*')
.addCompleter(
// new Completers.FilesCompleter(() -> Paths.get(System.getProperty("user.dir"))))
new StringsCompleter("Jim", "Jack", "John", "Donald", "Dock"))
.addPrompt();

promptBuilder
.createListPrompt()
.name("pizzatype")
.message("Which pizza do you want?")
.newItem()
.text("Margherita")
.add() // without name (name defaults to text)
.newItem("veneziana")
.text("Veneziana")
.add()
.newItem("hawai")
.text("Hawai")
.add()
.newItem("quattro")
.text("Quattro Stagioni")
.add()
.addPrompt();

promptBuilder
.createCheckboxPrompt()
.name("topping")
.message("Please select additional toppings:")
.newSeparator("standard toppings")
.add()
.newItem()
.name("cheese")
.text("Cheese")
.add()
.newItem("bacon")
.text("Bacon")
.add()
.newItem("onions")
.text("Onions")
.disabledText("Sorry. Out of stock.")
.add()
.newSeparator()
.text("special toppings")
.add()
.newItem("salami")
.text("Very hot salami")
.check()
.add()
.newItem("salmon")
.text("Smoked Salmon")
.add()
.newSeparator("and our speciality...")
.add()
.newItem("special")
.text("Anchovies, and olives")
.checked(true)
.add()
.addPrompt();

promptBuilder
.createChoicePrompt()
.name("payment")
.message("How do you want to pay?")
.newItem()
.name("cash")
.message("Cash")
.key('c')
.asDefault()
.add()
.newItem("visa")
.message("Visa Card")
.key('v')
.add()
.newItem("master")
.message("Master Card")
.key('m')
.add()
.newSeparator("online payment")
.add()
.newItem("paypal")
.message("Paypal")
.key('p')
.add()
.addPrompt();

promptBuilder
.createConfirmPromp()
.name("delivery")
.message("Is this pizza for delivery?")
.defaultValue(ConfirmChoice.ConfirmationValue.YES)
.addPrompt();

Map<String, ? extends PromptResultItemIF> result = prompt.prompt(header, promptBuilder.build());
System.out.println("result = " + result);

ConfirmResult delivery = (ConfirmResult) result.get("delivery");
if (delivery.getConfirmed() == ConfirmChoice.ConfirmationValue.YES) {
System.out.println("We will deliver the pizza in 5 minutes");
}
}

public static class EofPipedInputStream extends InputStream {

private InputStream in;

public InputStream getIn() {
return in;
}

public void setIn(InputStream in) {
this.in = in;
}

@Override
public int read() throws IOException {
return in != null ? in.read() : -1;
}

@Override
public int available() throws IOException {
return in != null ? in.available() : 0;
}
}
}

0 comments on commit 89db557

Please sign in to comment.