Skip to content

Commit

Permalink
When adding spaces in place of tabs, calculate from start of line
Browse files Browse the repository at this point in the history
Maintain the length of the last line in AttributedStringBuilder so that
future appends also calculate tab size correctly.

If there are line breaks in a string, a tab character should add spaces
up until the next tabstop from the beginning of the line not from the
start of the string.
  • Loading branch information
tpoliaw committed Jun 16, 2017
1 parent bf6e24c commit 1fdc15c
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public class AttributedStringBuilder extends AttributedCharSequence implements A
private int[] style;
private int length;
private int tabs = 0;
private int lastLineLength = 0;
private AttributedStyle current = AttributedStyle.DEFAULT;

public static AttributedString append(CharSequence... strings) {
Expand Down Expand Up @@ -149,6 +150,11 @@ public AttributedStringBuilder append(AttributedCharSequence str, int start, int
ensureCapacity(length + 1);
buffer[length] = c;
style[length] = s;
if (c == '\n') {
lastLineLength = 0;
} else {
lastLineLength++;
}
length++;
}
}
Expand Down Expand Up @@ -321,28 +327,48 @@ public AttributedStringBuilder appendAnsi(String ansi) {
ensureCapacity(length + 1);
buffer[length] = c;
style[length] = this.current.getStyle();
if (c == '\n') {
lastLineLength = 0;
} else {
lastLineLength++;
}
length++;
}
}
return this;
}

protected void insertTab(AttributedStyle s) {
int nb = tabs - length % tabs;
int nb = tabs - lastLineLength % tabs;
ensureCapacity(length + nb);
for (int i = 0; i < nb; i++) {
buffer[length] = ' ';
style[length] = s.getStyle();
length++;
}
lastLineLength += nb;
}

public void setLength(int l) {
length = l;
}

public AttributedStringBuilder tabs(int tabs) {
this.tabs = tabs;
/**
* Set the number of spaces a tab is expanded to. Tab size cannot be changed
* after text has been added to prevent inconsistent indentation.
*
* If tab size is set to 0, tabs are not expanded (the default).
* @param tabsize Spaces per tab or 0 for no tab expansion. Must be non-negative
* @return
*/
public AttributedStringBuilder tabs(int tabsize) {
if (length > 0) {
throw new IllegalStateException("Cannot change tab size after appending text");
}
if (tabsize < 0) {
throw new IllegalArgumentException("Tab size must be non negative");
}
this.tabs = tabsize;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package org.jline.utils;

import org.junit.Test;

import static org.junit.Assert.*;

public class AttributedStringBuilderTest {
private static String TAB_SIZE_ERR_MSG = "Incorrect tab size";

/**
* Test single line with tabs in
*/
@Test
public void testTabSize() {
AttributedStringBuilder sb;
sb = new AttributedStringBuilder().tabs(4);
sb.append("hello\tWorld");
assertEquals(TAB_SIZE_ERR_MSG, "hello World", sb.toString());

sb = new AttributedStringBuilder().tabs(5);
sb.append("hello\tWorld");
assertEquals(TAB_SIZE_ERR_MSG, "hello World", sb.toString());
}

/**
* Test multiple lines with tabs in
*/
@Test
public void testSplitLineTabSize() {
AttributedStringBuilder sb;
sb = new AttributedStringBuilder().tabs(4);
sb.append("hello\n\tWorld");
assertEquals(TAB_SIZE_ERR_MSG, "hello\n World", sb.toString());

sb = new AttributedStringBuilder().tabs(4);
sb.append("hello\tWorld\n\tfoo\tbar");
assertEquals(TAB_SIZE_ERR_MSG, "hello World\n foo bar", sb.toString());

sb = new AttributedStringBuilder().tabs(5);
sb.append("hello\n\tWorld");
assertEquals(TAB_SIZE_ERR_MSG, "hello\n World", sb.toString());

sb = new AttributedStringBuilder().tabs(5);
sb.append("hello\tWorld\n\tfoo\tbar");
assertEquals(TAB_SIZE_ERR_MSG, "hello World\n foo bar", sb.toString());
}

@Test
public void testAppendToString() {
AttributedStringBuilder sb;
String expected = "";
sb = new AttributedStringBuilder().tabs(4);

sb.append("hello"); expected += "hello";
sb.append("\tWorld"); expected += " World"; //append to first line
assertEquals(TAB_SIZE_ERR_MSG, expected, sb.toString());

sb.append("\nfoo\tbar"); expected += "\nfoo bar"; //append new line
assertEquals(TAB_SIZE_ERR_MSG, expected, sb.toString());

sb.append("lorem\tipsum"); expected += "lorem ipsum"; //append to second line
assertEquals(TAB_SIZE_ERR_MSG, expected, sb.toString());
}

@Test
public void testFromAnsiWithTabs() {
AttributedStringBuilder sb;
String expected = "";
sb = new AttributedStringBuilder().tabs(4);

sb.appendAnsi("hello\tWorld"); expected += "hello World";
assertEquals(TAB_SIZE_ERR_MSG, expected, sb.toString());

sb.appendAnsi("\033[38;5;120mgreen\tfoo\033[39m"); expected += "green foo";
assertEquals(TAB_SIZE_ERR_MSG, expected, sb.toString());
sb.appendAnsi("\n\033[38;5;120mbar\tbaz\033[39m"); expected += "\nbar baz";
assertEquals(TAB_SIZE_ERR_MSG, expected, sb.toString());
}

/**
* Test that tabs are not expanded in strings if tab size has not been set
*/
@Test
public void testUnsetTabSize() {
AttributedStringBuilder sb;
String expected = "";
sb = new AttributedStringBuilder();

sb.append("hello\tWorld"); expected += "hello\tWorld";
assertEquals(TAB_SIZE_ERR_MSG, expected, sb.toString());
}

@Test(expected=IllegalStateException.class)
public void testChangingExistingTabSize() throws Exception {
AttributedStringBuilder sb = new AttributedStringBuilder();
sb.append("helloWorld");
sb.tabs(4);
}

@Test(expected=IllegalArgumentException.class)
public void testNegativeTabSize() throws Exception {
@SuppressWarnings("unused")
AttributedStringBuilder sb = new AttributedStringBuilder().tabs(-1);
}
}

0 comments on commit 1fdc15c

Please sign in to comment.