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
@@ -0,0 +1,86 @@
/*
* Copyright (C) 2025 Gluon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.gluonhq.richtextarea.model;

import java.util.Arrays;
import java.util.List;

/**
*
* Append-only buffer used as the PieceTable Addition Buffer
Comment on lines +23 to +24
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

Missing class-level documentation. The comment is empty and should describe the purpose of this class, particularly explaining that it uses a cumulative length array for optimized lookups and any limitations (such as the hard limit on 4K elements mentioned in the PR description).

Suggested change
*
* Append-only buffer used as the PieceTable Addition Buffer
* An append-only buffer used as the PieceTable Addition Buffer.
* <p>
* This class maintains a cumulative length array ({@code unitLengths}) to enable
* efficient lookup of units by character offset. The cumulative array allows
* for optimized binary search operations when retrieving units that span a given range.
* <p>
* <b>Limitations:</b> This buffer has a hard limit of 4096 elements (4K units),
* as defined by the initial size of the {@code unitLengths} array. Exceeding this
* limit will require resizing, but performance and memory usage are optimized for
* use cases within this bound.

Copilot uses AI. Check for mistakes.
*/
public class AppendOnlyUnitBuffer extends UnitBuffer {

int[] unitLengths = new int[4096];

@Override
public void append(List<Unit> units) {
ensureCapacity(unitList.size() + units.size());
for (Unit u: units) {
doAppend(u);
}
}

@Override
public void append(Unit unit) {
ensureCapacity(unitList.size() + 1);
doAppend(unit);
}

private void doAppend(Unit unit) {
int idx = unitList.size();
int oldLength = idx == 0 ? 0 : unitLengths[idx-1];
unitLengths[idx]= oldLength + unit.length();
unitList.add(unit);
dirty = true;
}

@Override
public void insert(Unit unit, int position) {
throw new UnsupportedOperationException("Do not insert in an append-only buffer");
}

@Override
public void remove(int start, int end) {
throw new UnsupportedOperationException("Do not remove from an append-only buffer");
}

@Override
public Unit getUnitWithRange(int start, int end) {
if (start < 0 || unitList.isEmpty()) return new TextUnit("");
int index = Arrays.binarySearch(unitLengths, 0, unitList.size(), start);
if (index >=0) { // exact start at index
index = index+1;
} else { // no exact item found
index = -(index) -1;
}
return unitList.get(index);
Comment on lines +64 to +71
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

The getUnitWithRange method does not use the 'end' parameter at all and only performs a lookup based on 'start'. This differs from the parent class implementation which checks if the range [start, end] fits within a unit. The current implementation will return the unit at the start position regardless of whether the entire range fits within that unit.

Suggested change
if (start < 0 || unitList.isEmpty()) return new TextUnit("");
int index = Arrays.binarySearch(unitLengths, 0, unitList.size(), start);
if (index >=0) { // exact start at index
index = index+1;
} else { // no exact item found
index = -(index) -1;
}
return unitList.get(index);
if (start < 0 || end <= start || unitList.isEmpty()) return new TextUnit("");
int index = Arrays.binarySearch(unitLengths, 0, unitList.size(), start);
if (index >= 0) { // exact start at index
index = index + 1;
} else { // no exact item found
index = -(index) - 1;
}
// Now, index is the first unit whose end offset is > start
if (index >= unitList.size()) return new TextUnit("");
int unitStart = (index == 0) ? 0 : unitLengths[index - 1];
int unitEnd = unitLengths[index];
if (start >= unitStart && end <= unitEnd) {
return unitList.get(index);
} else {
return new TextUnit("");
}

Copilot uses AI. Check for mistakes.
}

@Override
public int length() {
return unitList.isEmpty() ? 0 : unitLengths[unitList.size() - 1];
}

private void ensureCapacity(int required) {
if (required > unitLengths.length) {
int newCapacity = Math.max(unitLengths.length * 2, required);
unitLengths = Arrays.copyOf(unitLengths, newCapacity);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
public final class PieceTable extends AbstractTextBuffer {

final UnitBuffer originalText;
UnitBuffer additionBuffer = new UnitBuffer();
final AppendOnlyUnitBuffer additionBuffer = new AppendOnlyUnitBuffer();

final List<Piece> pieces = new ArrayList<>();
private final CommandManager<PieceTable> commander = new CommandManager<>(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ public class UnitBuffer {
TextBuffer.ZERO_WIDTH_NO_BREAK_SPACE_TEXT + "([@#])([\\p{L}\\p{N}\\p{P}\\s]*)" + TextBuffer.ZERO_WIDTH_NO_BREAK_SPACE_TEXT,
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL);

private final List<Unit> unitList;
private volatile boolean dirty = true;
final List<Unit> unitList;
volatile boolean dirty = true;
private String internalText = "";

public UnitBuffer() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2025 Gluon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.gluonhq.richtextarea.model;

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

/**
*
* @author johan
*/
public class AppendOnlyUnitBufferTests {

@Test
public void testCapacity() {
final AppendOnlyUnitBuffer additionBuffer = new AppendOnlyUnitBuffer();
int len = additionBuffer.unitLengths.length;
Assertions.assertTrue(len > 0);
int belowMax = (int)(len*.9);
for (int i = 0; i < belowMax; i++) {
TextUnit tu = new TextUnit("a");
additionBuffer.append(tu);
}
int len2 = additionBuffer.unitLengths.length;
Assertions.assertEquals(len, len2);
int aboveMax = (int)(len*1.1);
for (int i = belowMax; i < aboveMax; i++) {
TextUnit tu = new TextUnit("a");
additionBuffer.append(tu);
}
int len3 = additionBuffer.unitLengths.length;
Assertions.assertNotEquals(len, len3);
}
}