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
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package org.cyclops.integratedscripting.client.gui.component.input;

import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;

import java.util.function.IntToDoubleFunction;

/**
* Code editing operations on the state of a text area.
* @author rubensworks
Expand Down Expand Up @@ -305,4 +310,66 @@ public static TextHistory.State moveLines(TextHistory.State state, int direction
}
}


private static final String BREAK_AFTER = ",;([{";
private static final String OPERATORS = "+-*/%=&|<>!?:";

private static boolean canBreakBefore(String value, int pos) {
char prev = value.charAt(pos - 1);
char cur = value.charAt(pos);
if (cur == ' ') {
return false;
}
if (prev == ' ' || BREAK_AFTER.indexOf(prev) >= 0) {
return true;
}
// Don't split multi-character operators such as && and >=
if ((OPERATORS.indexOf(prev) >= 0) != (OPERATORS.indexOf(cur) >= 0)) {
return true;
}
// Keep member access chains readable, but don't split numbers
return cur == '.' && !Character.isDigit(prev);
}

/**
* Wrap lines to the given width, preferably at spaces and punctuation, so identifiers are not split.
* Trailing spaces may exceed the width.
* @param value A text.
* @param maxWidth The maximum line width.
* @param charWidth The width of a character.
* @return The start positions of all wrapped lines.
*/
public static int[] wrapLines(String value, float maxWidth, IntToDoubleFunction charWidth) {
IntList starts = new IntArrayList();
int start = 0;
while (true) {
starts.add(start);
int end = value.indexOf('\n', start);
if (end < 0) {
end = value.length();
}
float width = 0;
int lastBreak = -1;
for (int i = start; i < end; i++) {
if (i > start && canBreakBefore(value, i)) {
lastBreak = i;
}
char c = value.charAt(i);
width += (float) charWidth.applyAsDouble(c);
if (width > maxWidth && i > start && c != ' ') {
start = lastBreak > start ? lastBreak : i;
starts.add(start);
width = 0;
lastBreak = -1;
i = start - 1;
}
}
if (end >= value.length() - 1) {
break;
}
start = end + 1;
}
return starts.toIntArray();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,10 @@ public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float
false,
Font.DisplayMode.NORMAL
);
} else if (this.showLineNumbers) {
// Mark wrapped continuation lines
RenderHelpers.drawScaledString(guiGraphics, font, "\u21B3", this.getX() + 4, line.y - offsetY + 2, 0.5f,
Helpers.RGBToInt(160, 160, 160), false, Font.DisplayMode.NORMAL);
}
lastLineNumber = line.lineNumber;
}
Expand Down Expand Up @@ -1012,17 +1016,19 @@ private TextLayout getLayout(String s) {
IntList lineNumbers = new IntArrayList();
MutableBoolean trailingNewline = new MutableBoolean();
MutableInt lineNumber = new MutableInt();
this.font.getSplitter().splitLines(s, getTextWidth() /* changed */, Style.EMPTY, true, (style, startPos, endPos) -> {
String stringPart = s.substring(startPos, endPos);
StringSplitter splitter = this.font.getSplitter();
int[] starts = TextEditing.wrapLines(s, getTextWidth(), c -> splitter.stringWidth(String.valueOf((char) c)));
for (int i = 0; i < starts.length; i++) {
String stringPart = s.substring(starts[i], i + 1 < starts.length ? starts[i + 1] : s.length());
boolean hasNewLine = stringPart.endsWith("\n");
trailingNewline.setValue(hasNewLine);
lineStarts.add(startPos);
lineStarts.add(starts[i]);
contents.add(StringUtils.stripEnd(stringPart, " \n"));
lineNumbers.add(lineNumber.intValue());
if (hasNewLine) {
lineNumber.increment();
}
});
}
// Add an empty line after a trailing newline, so the cursor can be placed and scrolled there
if (trailingNewline.isTrue()) {
lineStarts.add(s.length());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,41 @@ public void testNewlineTwoSpaces() {
assertThat(TextEditing.newline(s(" if (a) {", 10), TWO), equalTo(s(" if (a) {\n ", 15)));
}


private static int[] wrap(String value, int width) {
return TextEditing.wrapLines(value, width, c -> 1);
}

@Test
public void testWrapLinesFits() {
assertThat(wrap("", 10), equalTo(new int[]{0}));
assertThat(wrap("abc\ndef", 10), equalTo(new int[]{0, 4}));
assertThat(wrap("abc\n", 10), equalTo(new int[]{0}));
assertThat(wrap("a\n\nb", 10), equalTo(new int[]{0, 2, 3}));
}

@Test
public void testWrapLinesAtSpace() {
// Trailing spaces stay on the line
assertThat(wrap("return abc", 8), equalTo(new int[]{0, 7}));
assertThat(wrap("a bc", 3), equalTo(new int[]{0, 4}));
}

@Test
public void testWrapLinesAtPunctuation() {
assertThat(wrap("ctx.ops.size(item)", 10), equalTo(new int[]{0, 7, 13}));
assertThat(wrap("abc,def", 5), equalTo(new int[]{0, 4}));
}

@Test
public void testWrapLinesKeepsOperatorsAndNumbers() {
assertThat(wrap("a>=b", 2), equalTo(new int[]{0, 1, 3}));
assertThat(wrap("x=12.5", 5), equalTo(new int[]{0, 2}));
}

@Test
public void testWrapLinesSplitsLongTokens() {
assertThat(wrap("abcdefgh", 3), equalTo(new int[]{0, 3, 6}));
}

}
Loading