Skip to content
Open
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
Expand Up @@ -8,7 +8,6 @@
* @param <S> the style type
*/
public class StyleSpan<S> {

private final S style;
private final int length;
private int startPos = 0;
Expand All @@ -20,7 +19,6 @@ public StyleSpan(S style, int length) {
if(length < 0) {
throw new IllegalArgumentException("StyleSpan's length cannot be negative");
}

this.style = style;
this.length = length;
}
Expand All @@ -39,10 +37,16 @@ public int getLength() {
return length;
}

// TODO This one is only used in the factory, can't se make the start position immutable and use "moveTo"
// instead (essentially turning this into an immutable style)
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will change that in another PR.

void setStart( int start ) {
startPos = start;
}

StyleSpan<S> moveTo(int start) {
return new StyleSpan<>(style, start, length);
}

int getStart() {
return startPos;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ default StyleSpans<S> subView(int from, int to) {
}

/**
* Same as {@link java.util.List#subList(int, int)}, except that the arguments are two dimensional.
* Same as {@link java.util.List#subList(int, int)}, except that the arguments are two-dimensional.
*/
default StyleSpans<S> subView(Position from, Position to) {
return new SubSpans<>(this, from, to);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,12 @@ public StyleSpans<S> create() {
private void _add(StyleSpan<S> span) {
if(spans.isEmpty()) {
spans.add(span);
} else if(span.getLength() > 0) {
}
else if(span.getLength() > 0) {
if(spans.size() == 1 && spans.get(0).getLength() == 0) {
spans.set(0, span);
} else {
}
else {
StyleSpan<S> prev = spans.get(spans.size() - 1);
if(prev.getStyle().equals(span.getStyle())) {
spans.set(spans.size() - 1, new StyleSpan<>(span.getStyle(), prev.getStart(), prev.getLength() + span.getLength()));
Expand All @@ -177,8 +179,6 @@ private void _add(StyleSpan<S> span) {
spans.add(span);
}
}
} else {
// do nothing, don't add a zero-length span
}
}

Expand Down Expand Up @@ -328,7 +328,7 @@ class AppendedSpans<S> extends StyleSpansBase<S> {

public AppendedSpans(StyleSpans<S> original, StyleSpan<S> appended) {
this.original = original;
this.appended = appended;
this.appended = appended; // .moveTo(original.length());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.fxmisc.richtext.model;

import static org.fxmisc.richtext.model.StyleSpansChecker.checkStyle;
import static org.junit.jupiter.api.Assertions.*;

import java.util.Collection;
Expand All @@ -11,21 +12,6 @@
import org.junit.jupiter.api.Test;

public class ParagraphTest {
private <T> void checkStyle(Paragraph<?, ?, T> paragraph, int length, T[] styles, int... ranges) {
if(ranges.length % 2 == 1 || styles.length != ranges.length / 2) {
throw new IllegalArgumentException("Ranges must come in pair [start;end] and correspond to the style count");
}
StyleSpans<T> styleSpans = paragraph.getStyleSpans();
assertEquals(length, styleSpans.length());
assertEquals(ranges.length/2, styleSpans.getSpanCount(), "Style segment count invalid");
for (int i = 0; i < ranges.length/2 ; i++) {
StyleSpan<T> style = styleSpans.getStyleSpan(i);
assertEquals(ranges[i*2], style.getStart(), "Start not matching for " + i);
assertEquals(ranges[i*2 + 1] - ranges[i*2], style.getLength(), "Length not matching for " + i);
assertEquals(styles[i], style.getStyle(), "Incorrect style for " + i);
}
}

private Paragraph<Void, String, Void> createTextParagraph(TextOps<String, Void> segOps, String text) {
return new Paragraph<>(null, segOps, segOps.create(text), (Void)null);
}
Expand Down Expand Up @@ -241,8 +227,17 @@ public void multipleStyle() {
Paragraph<Void, String, String> p3 = p2.restyle(3, 10, "unknown");
checkStyle(p3, 18, new String[] {"text", "unknown", "keyword", "text"}, 0, 3, 3, 10, 10, 12, 12, 18);

// Restyle up to the end
checkStyle(p3.restyle(11, 17, "out"), 18,
new String[] {"text", "unknown", "keyword", "out", "text"},
0, 3, 3, 10, 10, 11, 11, 17, 17, 18);

// Restyle up to the end
checkStyle(p3.restyle(11, 18, "out"), 18,
new String[] {"text", "unknown", "keyword", "out"},
0, 3, 3, 10, 0, 1, 0, 7);

// Restyle out of bound
// Bug: the styles are totally off
checkStyle(p3.restyle(11, 19, "out"), 19,
new String[] {"text", "unknown", "keyword", "out"},
0, 3, 3, 10, 0, 1, 0, 8);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.fxmisc.richtext.model;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class StyleSpanTest {
private void checkSpan(StyleSpan<String> span, String style, int pos, int len) {
assertEquals(len, span.getLength(), "Incorrect length");
assertEquals(pos, span.getStart(), "Incorrect position");
assertEquals(style, span.getStyle(), "Incorrect style");
}

@Test
@DisplayName("Style do not equal if their length or style is different")
void equals() {
assertEquals(
new StyleSpan<>("myStyle", 2, 3),
new StyleSpan<>("myStyle", 1, 3)
);
assertNotEquals(
new StyleSpan<>("myStyle", 2, 3),
new StyleSpan<>("myStyle", 2, 2)
);
assertNotEquals(
new StyleSpan<>("myStyle", 2, 3),
new StyleSpan<>("other", 2, 3)
);
}

@Test
@DisplayName("Create a valid span")
void createValidSpan() {
StyleSpan<String> a = new StyleSpan<>("myStyle", 10);
StyleSpan<String> b = new StyleSpan<>("myStyle", 0, 10);
checkSpan(a, "myStyle", 0, 10);
checkSpan(b, "myStyle", 0, 10);
assertEquals(a, b);
assertEquals(a.hashCode(), b.hashCode());
}

@Test
@DisplayName("Move span to a different location")
void moveSpan() {
StyleSpan<String> a = new StyleSpan<>("myStyle", 10);
StyleSpan<String> b = new StyleSpan<>("myStyle", 2, 10);
assertEquals(a, b); // position does not matter for equality
assertEquals(a.hashCode(), b.hashCode());
StyleSpan<String> c = a.moveTo(2);
assertEquals(c, b);
checkSpan(a, "myStyle", 0, 10);
checkSpan(b, "myStyle", 2, 10);
checkSpan(c, "myStyle", 2, 10);
}

@Test
@DisplayName("Cannot create a span with a negative length")
void cannotCreateWithNegativeLength() {
// This is bad practice to throw exception in constructor, but that's the way it is. Other solutions would
// have been to use a static create method, a factory, or to change length to 0
assertThrows(IllegalArgumentException.class, () -> new StyleSpan<>("test", -1));
// This is not consistent, as it doesn't throw
checkSpan(new StyleSpan<>("test", 0, -1), "test", 0, -1);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.fxmisc.richtext.model;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.List;

import static org.fxmisc.richtext.model.StyleSpansChecker.checkStyle;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class StyleSpansBuilderTest {
@Test
@DisplayName("Cannot create an empty style span")
void empty() {
assertThrows(IllegalStateException.class, () -> new StyleSpansBuilder<>().create());
}

@Test
@DisplayName("Creating a single-style style span")
void createStyleSpan() {
StyleSpans<String> style = new StyleSpansBuilder<String>().add("alpha", 10).create();
checkStyle(style, 10, new String[] {"alpha"}, 0, 10);
}

@Test
@DisplayName("Adding only empty styles")
void onlyEmptyStyles() {
StyleSpans<String> style = new StyleSpansBuilder<String>()
.add("alpha", 0)
.add("beta", 0)
.add("charlie", 0)
.create();
checkStyle(style, 0, new String[] {"alpha"}, 0, 0);
}

@Test
@DisplayName("Adding style on only empty, overwrites it")
void nonEmptyOnEmpty() {
StyleSpans<String> style = new StyleSpansBuilder<String>()
.add("alpha", 0)
.add("beta", 0)
.add("charlie", 0)
.add("delta", 1)
.create();
checkStyle(style, 1, new String[] {"delta"}, 0, 1);
}

@Test
@DisplayName("Add a list of only one style will merge them all")
void addListOfOneStyle() {
StyleSpans<String> style = new StyleSpansBuilder<String>()
.addAll(List.of(new StyleSpan<>("alpha", 1), new StyleSpan<>("alpha", 2), new StyleSpan<>("alpha", 3)))
.create();
checkStyle(style, 6, new String[] {"alpha"}, 0, 6);
}

@Test
@DisplayName("Creating a multi-style style span")
void multiStyleSpan() {
StyleSpans<String> style = new StyleSpansBuilder<String>()
.add("alpha", 1)
.add("alpha", 2)
.add("beta", 3)
.add("delta", 0)
.add("charlie", 4)
.add("alpha", 5)
.create();
checkStyle(style, 15, new String[] {"alpha", "beta", "charlie", "alpha"}, 0, 3, 3, 6, 6, 10, 10, 15);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.fxmisc.richtext.model;

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

class StyleSpansChecker<T> {
private final StyleSpans<T> styleSpans;

public StyleSpansChecker(StyleSpans<T> styleSpans) {
this.styleSpans = styleSpans;
}

public void check(int length, T[] styles, int... ranges) {
if(ranges.length % 2 == 1 || styles.length != ranges.length / 2) {
throw new IllegalArgumentException("Ranges must come in pair [start;end] and correspond to the style count");
}
assertEquals(length, styleSpans.length());
assertEquals(ranges.length/2, styleSpans.getSpanCount(), "Style segment count invalid");
for (int i = 0; i < ranges.length/2 ; i++) {
StyleSpan<T> style = styleSpans.getStyleSpan(i);
assertEquals(ranges[i*2], style.getStart(), "Start not matching for " + i);
assertEquals(ranges[i*2 + 1] - ranges[i*2], style.getLength(), "Length not matching for " + i);
assertEquals(styles[i], style.getStyle(), "Incorrect style for " + i);
}
}

public static <T> void checkStyle(Paragraph<?, ?, T> paragraph, int length, T[] styles, int... ranges) {
checkStyle(paragraph.getStyleSpans(), length, styles, ranges);
}

public static <T> void checkStyle(StyleSpans<T> styleSpans, int length, T[] styles, int... ranges) {
new StyleSpansChecker<T>(styleSpans).check(length, styles, ranges);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package org.fxmisc.richtext.model;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static org.fxmisc.richtext.model.StyleSpansChecker.checkStyle;

public class StyleSpansTest {
private StyleSpans<String> create(String style, int len) {
StyleSpansBuilder<String> builder = new StyleSpansBuilder<>();
builder.add(style, len);
return builder.create();
}

@Nested
@DisplayName("Extract subview")
class SubViewTest {
@Test
@DisplayName("Extract subview from a single style span should return the same style")
void createSubViewFromSingleStyle() {
StyleSpans<String> subStyle = create("text", 10).subView(2, 8);
checkStyle(subStyle, 6, new String[] {"text"}, 0, 6);
}

@Test
@DisplayName("Extract subview cutting in different styles")
void createSubViewFromMultiple() {
StyleSpansBuilder<String> builder = new StyleSpansBuilder<>();
builder.add("alpha", 6);
builder.add("beta", 7);
builder.add("charlie", 8);
StyleSpans<String> styleSpans = builder.create();
checkStyle(styleSpans, 21, new String[] {"alpha", "beta", "charlie"}, 0, 6, 6, 13, 13, 21);
checkStyle(styleSpans.subView(6, 13), 7, new String[] {"beta"}, 0, 7);
// Empty
checkStyle(styleSpans.subView(7, 7), 0, new String[] {"beta"}, 0, 0); // Strange, why isn't it empty?
// Inverted indexes
checkStyle(styleSpans.subView(14, 5), 0, new String[] {"charlie"}, 0, 0);
// Out of bound
checkStyle(styleSpans.subView(-1,22), 23, new String[] {"alpha", "beta", "charlie"}, 0, 6, 6, 13, 0, 10);
// Bug
checkStyle(styleSpans.subView(6, 14), 8, new String[] {"beta", "charlie"}, 0, 7, 0, 1);
checkStyle(styleSpans.subView(5, 13), 8, new String[] {"alpha", "beta"}, 0, 1, 0, 7);
checkStyle(styleSpans.subView(5, 14), 9, new String[] {"alpha", "beta", "charlie"}, 0, 1, 6, 13, 0, 1);
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of these tests are clear bugs

}
}

@Nested
@DisplayName("Append")
class AppendTest {
private StyleSpans<String> base;

@BeforeEach
void setup() {
base = create("text", 10);
checkStyle(base, 10, new String[]{"text"}, 0, 10);
}

@Test
@DisplayName("Append empty style at the end of an existing style does not do anything")
void appendEmpty() {
checkStyle(base.append("text", 0), 10,
new String[]{"text"}, 0, 10);

}

@Test
@DisplayName("Append on an empty style will create a new style with the provided values")
void appendOnEmpty() {
checkStyle(create("test", 0).append("text", 2), 2,
new String[]{"text"}, 0, 2);
}

@Test
@DisplayName("Append a different style to an existing style span should add it at the end")
void appendDifferentStyle() {
checkStyle(base.append("else", 2), 12,
new String[]{"text", "else"}, 0, 10, 0, 2);
}

@Test
@DisplayName("Append an existing style at the end of a style should extend the existing one")
void appendSameStyle() {
checkStyle(base.append("text", 2), 12,
new String[]{"text"}, 0, 12);
}
}
}