Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,176 changes: 1,018 additions & 158 deletions builtins/src/main/java/org/jline/builtins/Nano.java

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion builtins/src/main/java/org/jline/builtins/Tmux.java
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ private void inputLoop() {
first = true;
}
if (b == Binding.Mouse) {
MouseEvent event = terminal.readMouseEvent();
MouseEvent event = terminal.readMouseEvent(reader::readCharacter, reader.getLastBinding());
// System.err.println(event.toString());
} else if (b instanceof String || b instanceof String[]) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Expand Down
128 changes: 128 additions & 0 deletions builtins/src/test/java/org/jline/builtins/NanoSuggestionBoxTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* Copyright (c) 2025, 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.builtins;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import org.jline.terminal.Size;
import org.jline.terminal.Terminal;
import org.jline.terminal.impl.DumbTerminal;
import org.jline.utils.AttributedString;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertNotNull;

/**
* Test for the suggestion box placement in Nano editor.
*/
public class NanoSuggestionBoxTest {

/**
* A custom Nano implementation that exposes the buildSuggestionBox method for testing.
*/
static class TestableNano extends Nano {
public TestableNano(Terminal terminal) {
super(terminal, Paths.get("."));
}

/**
* Expose the buildSuggestionBox method for testing.
*/
public Nano.Box buildSuggestionBoxForTest(
List<AttributedString> suggestions,
List<AttributedString> screenLines,
int cursorLine,
int firstLineToDisplay) {
// Set up the buffer with the cursor at the specified line
buffer = new Buffer(null);
buffer.lines = new ArrayList<>();
for (int i = 0; i < 30; i++) {
buffer.lines.add("Line " + (i + 1));
}
buffer.line = cursorLine;
buffer.column = 3;
buffer.firstLineToDisplay = firstLineToDisplay;
buffer.offsetInLine = 0;
buffer.offsetInLineToDisplay = 0;
buffer.computeAllOffsets(); // Initialize offsets

// Set the terminal size
size.copy(terminal.getSize());

// Call the protected method using reflection
try {
java.lang.reflect.Method method =
Nano.class.getDeclaredMethod("buildSuggestionBox", List.class, List.class);
method.setAccessible(true);
return (Nano.Box) method.invoke(this, suggestions, screenLines);
} catch (Exception e) {
throw new RuntimeException("Failed to call buildSuggestionBox", e);
}
}
}

@Test
public void testSuggestionBoxPlacement() throws IOException {
// Create a dumb terminal for testing
ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
ByteArrayOutputStream out = new ByteArrayOutputStream();
Terminal terminal = new DumbTerminal("name", "dumb", in, out, Charset.forName("UTF-8"));
terminal.setSize(new Size(80, 24));

TestableNano nano = new TestableNano(terminal);

// Create suggestions
List<AttributedString> suggestions = new ArrayList<>();
suggestions.add(new AttributedString("Suggestion 1"));
suggestions.add(new AttributedString("Suggestion 2"));
suggestions.add(new AttributedString("Suggestion 3"));

// Create screen lines
List<AttributedString> screenLines = new ArrayList<>();
for (int i = 0; i < 20; i++) {
screenLines.add(new AttributedString("Screen line " + i));
}

// Test with cursor at different positions

// 1. Test with cursor at the top of the screen
Nano.Box box1 = nano.buildSuggestionBoxForTest(suggestions, screenLines, 0, 0);
assertNotNull(box1, "Box should be created when cursor is at the top");

// 2. Test with cursor in the middle of the screen
Nano.Box box2 = nano.buildSuggestionBoxForTest(suggestions, screenLines, 10, 0);
assertNotNull(box2, "Box should be created when cursor is in the middle");

// 3. Test with cursor at the bottom of the screen
Nano.Box box3 = nano.buildSuggestionBoxForTest(suggestions, screenLines, 19, 0);
assertNotNull(box3, "Box should be created when cursor is at the bottom");

// 4. Test with scrolled view (firstLineToDisplay > 0)
Nano.Box box4 = nano.buildSuggestionBoxForTest(suggestions, screenLines, 15, 10);
assertNotNull(box4, "Box should be created when view is scrolled");

// 5. Test with cursor at the top of a scrolled view
Nano.Box box5 = nano.buildSuggestionBoxForTest(suggestions, screenLines, 10, 10);
assertNotNull(box5, "Box should be created when cursor is at the top of a scrolled view");

// 6. Test with cursor at the bottom of a scrolled view
Nano.Box box6 = nano.buildSuggestionBoxForTest(suggestions, screenLines, 29, 10);
assertNotNull(box6, "Box should be created when cursor is at the bottom of a scrolled view");

// The test passes if all boxes are created successfully
// Visual verification would require manual testing
System.out.println("All suggestion boxes were created successfully");
}
}
3 changes: 2 additions & 1 deletion curses/src/main/java/org/jline/curses/impl/GUIImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ public void run() {
handleInput(bindingReader.getLastBinding());
break;
case Mouse:
handleMouse(terminal.readMouseEvent(bindingReader::readCharacter));
handleMouse(
terminal.readMouseEvent(bindingReader::readCharacter, bindingReader.getLastBinding()));
break;
}
redraw();
Expand Down
44 changes: 44 additions & 0 deletions terminal/src/main/java/org/jline/terminal/Terminal.java
Original file line number Diff line number Diff line change
Expand Up @@ -1171,6 +1171,50 @@ enum MouseTracking {
*/
MouseEvent readMouseEvent(IntSupplier reader);

/**
* Reads and decodes a mouse event with a specified prefix that has already been consumed.
*
* <p>
* This method is similar to {@link #readMouseEvent()}, but it allows specifying a prefix
* that has already been consumed. This is useful when the mouse event prefix (e.g., "\033[<"
* or "\033[M") has been consumed by the key binding detection, and we need to continue
* parsing from the current position.
* </p>
*
* <p>
* This method is primarily intended for advanced use cases where the standard
* {@link #readMouseEvent()} method is not sufficient, particularly when dealing with
* key binding systems that may consume part of the mouse event sequence.
* </p>
*
* @param prefix the prefix that has already been consumed, or null if none
* @return the decoded mouse event containing event type, button, modifiers, and coordinates
* @see #readMouseEvent()
* @see MouseEvent
*/
MouseEvent readMouseEvent(String prefix);

/**
* Reads and decodes a mouse event using the provided input supplier with a specified prefix
* that has already been consumed.
*
* <p>
* This method combines the functionality of {@link #readMouseEvent(IntSupplier)} and
* {@link #readMouseEvent(String)}, allowing both a custom input supplier and a prefix
* to be specified. This is useful for advanced input handling scenarios where both
* customization of the input source and handling of partially consumed sequences are needed.
* </p>
*
* @param reader the input supplier that provides the raw bytes of the mouse event data
* @param prefix the prefix that has already been consumed, or null if none
* @return the decoded mouse event containing event type, button, modifiers, and coordinates
* @see #readMouseEvent()
* @see #readMouseEvent(IntSupplier)
* @see #readMouseEvent(String)
* @see MouseEvent
*/
MouseEvent readMouseEvent(IntSupplier reader, String prefix);

/**
* Returns whether the terminal has support for focus tracking.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,12 +277,22 @@ public boolean trackMouse(MouseTracking tracking) {

@Override
public MouseEvent readMouseEvent() {
return lastMouseEvent = MouseSupport.readMouse(this, lastMouseEvent);
return readMouseEvent(getStringCapability(Capability.key_mouse));
}

@Override
public MouseEvent readMouseEvent(IntSupplier reader) {
return lastMouseEvent = MouseSupport.readMouse(reader, lastMouseEvent);
return readMouseEvent(reader, getStringCapability(Capability.key_mouse));
}

@Override
public MouseEvent readMouseEvent(String prefix) {
return lastMouseEvent = MouseSupport.readMouse(this, lastMouseEvent, prefix);
}

@Override
public MouseEvent readMouseEvent(IntSupplier reader, String prefix) {
return lastMouseEvent = MouseSupport.readMouse(reader, lastMouseEvent, prefix);
}

@Override
Expand Down
Loading
Loading