Files
clide/lib/src/terminal/src/utils/circular_buffer.dart
T
jpmschweitzerandClaude 1fb4786f18
test / unit + widget + golden + a11y (push) Failing after 35s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m0s
sweep remaining analyze infos to zero
Clears the 19 misc lint hits left after the test_app print sweep
+ libc.dart suppression. By rule:

- `withOpacity(α)` → `withValues(alpha: α)` (deprecated_member_use)
  in `painter.dart:187` and `terminal_view.dart:318`.
- `Pointer.elementAt(n)` → `Pointer + n` (deprecated_member_use)
  in `native_pty.dart:306` and `session.dart:187`.
- Brace single-statement for/if bodies in `native_pty.dart`
  (×3) and `decisions_view.dart` (curly_braces_in_flow_control_
  structures).
- `IsolateClient` and `InProcessClient` constructors switched to
  `super.log` / `super.events` parameters (use_super_parameters);
  associated unused imports of `kernel/src/log.dart` and
  `kernel/src/events/bus.dart` removed in the same files.
- `InProcessClient._dispatcher` field + getter/setter pair folded
  into a single mutable public `dispatcher` field
  (unnecessary_getters_setters).
- `_buildDispatcher` local in `lib/main.dart` renamed to
  `buildDispatcher` (no_leading_underscores_for_local_identifiers).
- `_onTapDown(_)` in `terminal_view.dart` typed as
  `TapDownDetails _` (strict_top_level_inference).
- `operator []=(...)` in `circular_buffer.dart` given an explicit
  `void` return type (strict_top_level_inference).
- `CustomKeyboardListener` and `TerminalGestureDetector` callsites
  reordered so `child:` lands last (sort_child_properties_last).
- `CustomTextEdit` constructor declared `const`
  (prefer_const_constructors_in_immutables).
- `LinkedHashMap<K, V>()` in `paragraph_cache.dart` collapsed to a
  `<K, V>{}` literal (prefer_collection_literals); the now-unused
  `dart:collection` import dropped.

Project analyze: 19 → 0 issues. `make test` stays green; coverage
unchanged at 52.72%.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 23:51:46 +02:00

317 lines
8.9 KiB
Dart

// Based on xterm.dart v4.0.0 by xuty (MIT). See LICENSE in this directory.
/// A circular buffer in which elements know their index in the buffer.
class IndexAwareCircularBuffer<T extends IndexedItem> {
/// Creates a new circular list with the specified [maxLength].
IndexAwareCircularBuffer(int maxLength) : _array = List<T?>.filled(maxLength, null);
/// The backing array for this list. Length is always equal to [maxLength].
late List<T?> _array;
/// The number of elements in the list. This is always less than or equal to
/// [maxLength].
var _length = 0;
/// The index of the first element in [_array].
var _startIndex = 0;
/// The start index of this list, including items that has been dropped in
/// overflow
var _absoluteStartIndex = 0;
/// Gets the cyclic index for the specified regular index. The cyclic index
/// can then be used on the backing array to get the element associated with
/// the regular index.
@pragma('vm:prefer-inline')
int _getCyclicIndex(int index) {
return (_startIndex + index) % _array.length;
}
/// Removes the element at [index] from the list.
@pragma('vm:prefer-inline')
void _dropChild(int index) {
final cyclicIndex = _getCyclicIndex(index);
_array[cyclicIndex]?._detach();
_array[cyclicIndex] = null;
}
/// Adds the specified [child] to the list at the specified [index].
@pragma('vm:prefer-inline')
void _adoptChild(int index, T child) {
final cyclicIndex = _getCyclicIndex(index);
_array[cyclicIndex]?._detach();
_array[cyclicIndex] = child.._attach(this, index);
}
/// Moves the element at [fromIndex] to [toIndex]. Both indexes should be
/// less than [maxLength].
@pragma('vm:prefer-inline')
void _moveChild(int fromIndex, int toIndex) {
final fromCyclicIndex = _getCyclicIndex(fromIndex);
final toCyclicIndex = _getCyclicIndex(toIndex);
_array[toCyclicIndex]?._detach();
_array[toCyclicIndex] = _array[fromCyclicIndex]?.._move(toIndex);
_array[fromCyclicIndex] = null;
}
/// Gets the element at the specified [index] in the list.
@pragma('vm:prefer-inline')
T? _getChild(int index) {
return _array[_getCyclicIndex(index)];
}
/// The number of elements that can be stored in the list.
int get maxLength {
return _array.length;
}
/// Sets the number of elements that can be stored in the list. This operation
/// is relatively expensive, as it requires the backing array to be
/// reallocated.
set maxLength(int value) {
if (value <= 0) {
throw ArgumentError.value(value, 'value', "maxLength can't be negative!");
}
if (value == _array.length) return;
// Reconstruct array, starting at index 0. Only transfer values from the
// indexes 0 to length.
final newArray = List<T?>.generate(
value,
(index) => index < _length ? _getChild(index) : null,
);
_startIndex = 0;
_array = newArray;
}
/// Number of elements in the list.
int get length {
return _length;
}
/// Iterates over the list and calls [callback] for each element.
void forEach(void Function(T item) callback) {
final length = _length;
for (int i = 0; i < length; i++) {
callback(_getChild(i)!);
}
}
/// Gets the element at the specified [index] in the list. Throws if the
/// index is out of bounds.
T operator [](int index) {
RangeError.checkValueInInterval(index, 0, length - 1, 'index');
return _getChild(index)!;
}
/// Sets the element at the specified [index] in the list. Throws if the
/// index is out of bounds.
void operator []=(int index, T value) {
RangeError.checkValueInInterval(index, 0, length - 1, 'index');
_adoptChild(index, value);
}
/// Removes all elements from the list.
void clear() {
for (var i = 0; i < _length; i++) {
_dropChild(i);
}
_startIndex = 0;
_length = 0;
}
/// Adds all elements in [items] to the list.
void pushAll(Iterable<T> items) {
for (var element in items) {
push(element);
}
}
/// Adds [value] to the end of the list. May cause the first element to be
/// trimmed if the list is full.
void push(T value) {
_adoptChild(_length, value);
if (_length == _array.length) {
// When the list is full, we trim the first element
_startIndex++;
_absoluteStartIndex++;
if (_startIndex == _array.length) {
_startIndex = 0;
}
} else {
// When the list is not full, we just increase the length
_length++;
}
}
/// Removes and returns the last value on the list, throws if the list is
/// empty.
T pop() {
assert(_length > 0, 'Cannot pop from an empty list');
final result = _getChild(_length - 1);
_dropChild(_length - 1);
_length--;
return result!;
}
/// Deletes [count] elements starting at [index], shifting all elements after
/// [index] to the left.
void remove(int index, [int count = 1]) {
if (count > 0) {
if (index + count >= _length) {
count = _length - index;
}
for (var i = index; i < _length - count; i++) {
_moveChild(i + count, i);
}
for (var i = _length - count; i < _length; i++) {
_dropChild(i);
}
_length -= count;
}
}
/// Inserts [item] at [index], shifting all elements after [index] to the
/// right. May cause the first element to be trimmed if the list is full.
void insert(int index, T item) {
RangeError.checkValueInInterval(index, 0, _length, 'index');
if (index == _length) {
return push(item);
}
if (index == 0 && _length >= _array.length) {
// when something is inserted at index 0 and the list is full then
// the new value immediately gets removed => nothing changes
return;
}
for (var i = _length - 1; i >= index; i--) {
_moveChild(i, i + 1);
}
_adoptChild(index, item);
if (_length >= _array.length) {
_startIndex += 1;
_absoluteStartIndex += 1;
} else {
_length++;
}
}
/// Inserts [items] at [index] in order.
void insertAll(int index, List<T> items) {
for (var i = items.length - 1; i >= 0; i--) {
insert(index, items[i]);
// when the list is full then we have to move the index down
// as newly inserted values remove values with a lower index
if (_length >= _array.length) {
index--;
if (index < 0) {
return;
}
}
}
}
/// Removes [count] elements starting at [index], shifting all elements after
/// [index] to the left.
///
/// This method is cheap since it does not actually modify the list, but
/// instead just adjusts the start index and length.
void trimStart(int count) {
if (count > _length) count = _length;
_startIndex += count;
_startIndex %= _array.length;
_length -= count;
}
/// Replaces all elements in the list with [replacement].
void replaceWith(List<T> replacement) {
for (var i = 0; i < _length; i++) {
_dropChild(i);
}
var copyStart = 0;
if (replacement.length > maxLength) {
copyStart = replacement.length - maxLength;
}
for (var i = 0; i < copyStart; i++) {
_dropChild(i);
}
final copyLength = replacement.length - copyStart;
for (var i = 0; i < copyLength; i++) {
_adoptChild(i, replacement[copyStart + i]);
}
_startIndex = 0;
_length = copyLength;
}
/// Replaces the element at [index] with [value] and returns the replaced
/// item.
T swap(int index, T value) {
final result = _getChild(index);
_adoptChild(index, value);
return result!;
}
/// Whether adding another element would cause the first element to be
/// trimmed.
bool get isFull => length == maxLength;
/// Returns a list containing all elements in the list.
List<T> toList() {
return List<T>.generate(length, (index) => this[index]);
}
String debugDump() {
final buffer = StringBuffer();
buffer.writeln('CircularList:');
for (var i = 0; i < _length; i++) {
final child = _getChild(i);
buffer.writeln(' $i: $child');
}
return buffer.toString();
}
}
mixin IndexedItem {
IndexAwareCircularBuffer? _owner;
int? _absoluteIndex;
/// The index of this item in the buffer. Must only be accessed when
/// [attached] is true.
int get index => _absoluteIndex! - _owner!._absoluteStartIndex;
/// Whether this item is currently stored in a buffer.
bool get attached => _owner != null;
/// Sets the owner and index of this item. This is called by the buffer when
/// the item is adopted.
void _attach(IndexAwareCircularBuffer owner, int index) {
_owner = owner;
_absoluteIndex = owner._absoluteStartIndex + index;
}
/// Marks this item as detached from a buffer. This is called after the item
/// has been removed from the buffer.
void _detach() {
_owner = null;
_absoluteIndex = null;
}
/// Moves this item to [newIndex] in the buffer.
void _move(int newIndex) {
assert(attached);
_absoluteIndex = _owner!._absoluteStartIndex + newIndex;
}
}