Skip to content

Commit ed18c78

Browse files
authored
Use an append-only buffer for the additions in the piecetable. (#422)
* Use an append-only buffer for the additions in the piecetable. * Leverage increasing character of array to use binarysearch Add test
1 parent 4d104f0 commit ed18c78

File tree

4 files changed

+149
-3
lines changed

4 files changed

+149
-3
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright (C) 2025 Gluon
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
package com.gluonhq.richtextarea.model;
18+
19+
import java.util.Arrays;
20+
import java.util.List;
21+
22+
/**
23+
*
24+
* Append-only buffer used as the PieceTable Addition Buffer
25+
*/
26+
public class AppendOnlyUnitBuffer extends UnitBuffer {
27+
28+
int[] unitLengths = new int[4096];
29+
30+
@Override
31+
public void append(List<Unit> units) {
32+
ensureCapacity(unitList.size() + units.size());
33+
for (Unit u: units) {
34+
doAppend(u);
35+
}
36+
}
37+
38+
@Override
39+
public void append(Unit unit) {
40+
ensureCapacity(unitList.size() + 1);
41+
doAppend(unit);
42+
}
43+
44+
private void doAppend(Unit unit) {
45+
int idx = unitList.size();
46+
int oldLength = idx == 0 ? 0 : unitLengths[idx-1];
47+
unitLengths[idx]= oldLength + unit.length();
48+
unitList.add(unit);
49+
dirty = true;
50+
}
51+
52+
@Override
53+
public void insert(Unit unit, int position) {
54+
throw new UnsupportedOperationException("Do not insert in an append-only buffer");
55+
}
56+
57+
@Override
58+
public void remove(int start, int end) {
59+
throw new UnsupportedOperationException("Do not remove from an append-only buffer");
60+
}
61+
62+
@Override
63+
public Unit getUnitWithRange(int start, int end) {
64+
if (start < 0 || unitList.isEmpty()) return new TextUnit("");
65+
int index = Arrays.binarySearch(unitLengths, 0, unitList.size(), start);
66+
if (index >=0) { // exact start at index
67+
index = index+1;
68+
} else { // no exact item found
69+
index = -(index) -1;
70+
}
71+
return unitList.get(index);
72+
}
73+
74+
@Override
75+
public int length() {
76+
return unitList.isEmpty() ? 0 : unitLengths[unitList.size() - 1];
77+
}
78+
79+
private void ensureCapacity(int required) {
80+
if (required > unitLengths.length) {
81+
int newCapacity = Math.max(unitLengths.length * 2, required);
82+
unitLengths = Arrays.copyOf(unitLengths, newCapacity);
83+
}
84+
}
85+
86+
}

rta/src/main/java/com/gluonhq/richtextarea/model/PieceTable.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
public final class PieceTable extends AbstractTextBuffer {
5353

5454
final UnitBuffer originalText;
55-
UnitBuffer additionBuffer = new UnitBuffer();
55+
final AppendOnlyUnitBuffer additionBuffer = new AppendOnlyUnitBuffer();
5656

5757
final List<Piece> pieces = new ArrayList<>();
5858
private final CommandManager<PieceTable> commander = new CommandManager<>(this);

rta/src/main/java/com/gluonhq/richtextarea/model/UnitBuffer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ public class UnitBuffer {
6161
TextBuffer.ZERO_WIDTH_NO_BREAK_SPACE_TEXT + "([@#])([\\p{L}\\p{N}\\p{P}\\s]*)" + TextBuffer.ZERO_WIDTH_NO_BREAK_SPACE_TEXT,
6262
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);
6363

64-
private final List<Unit> unitList;
65-
private volatile boolean dirty = true;
64+
final List<Unit> unitList;
65+
volatile boolean dirty = true;
6666
private String internalText = "";
6767

6868
public UnitBuffer() {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright (C) 2025 Gluon
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU General Public License
15+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
*
17+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20+
* DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
21+
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23+
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24+
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27+
*/
28+
package com.gluonhq.richtextarea.model;
29+
30+
import org.junit.jupiter.api.Assertions;
31+
import org.junit.jupiter.api.DisplayName;
32+
import org.junit.jupiter.api.Test;
33+
34+
/**
35+
*
36+
* @author johan
37+
*/
38+
public class AppendOnlyUnitBufferTests {
39+
40+
@Test
41+
public void testCapacity() {
42+
final AppendOnlyUnitBuffer additionBuffer = new AppendOnlyUnitBuffer();
43+
int len = additionBuffer.unitLengths.length;
44+
Assertions.assertTrue(len > 0);
45+
int belowMax = (int)(len*.9);
46+
for (int i = 0; i < belowMax; i++) {
47+
TextUnit tu = new TextUnit("a");
48+
additionBuffer.append(tu);
49+
}
50+
int len2 = additionBuffer.unitLengths.length;
51+
Assertions.assertEquals(len, len2);
52+
int aboveMax = (int)(len*1.1);
53+
for (int i = belowMax; i < aboveMax; i++) {
54+
TextUnit tu = new TextUnit("a");
55+
additionBuffer.append(tu);
56+
}
57+
int len3 = additionBuffer.unitLengths.length;
58+
Assertions.assertNotEquals(len, len3);
59+
}
60+
}

0 commit comments

Comments
 (0)