Skip to content
2 changes: 2 additions & 0 deletions lib/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export 'src/core/buffer/cell_flags.dart';
export 'src/core/buffer/cell_offset.dart';
export 'src/core/buffer/line.dart';
export 'src/core/buffer/range.dart';
export 'src/core/buffer/range_block.dart';
export 'src/core/buffer/range_line.dart';
export 'src/core/buffer/segment.dart';
export 'src/core/cell.dart';
export 'src/core/color.dart';
Expand Down
29 changes: 12 additions & 17 deletions lib/src/core/buffer/buffer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:math' show max, min;
import 'package:flutter/material.dart';
import 'package:xterm/src/core/buffer/cell_offset.dart';
import 'package:xterm/src/core/buffer/line.dart';
import 'package:xterm/src/core/buffer/range_line.dart';
import 'package:xterm/src/core/buffer/range.dart';
import 'package:xterm/src/core/charset.dart';
import 'package:xterm/src/core/cursor.dart';
Expand Down Expand Up @@ -465,7 +466,7 @@ class Buffer {
r'\'.codeUnitAt(0),
};

BufferRange? getWordBoundary(CellOffset position) {
BufferRangeLine? getWordBoundary(CellOffset position) {
if (position.y >= lines.length) {
return null;
}
Expand Down Expand Up @@ -500,7 +501,7 @@ class Buffer {
return null;
}

return BufferRange(
return BufferRangeLine(
CellOffset(start, position.y),
CellOffset(end, position.y),
);
Expand All @@ -509,7 +510,7 @@ class Buffer {
/// Get the plain text content of the buffer including the scrollback.
/// Accepts an optional [range] to get a specific part of the buffer.
String getText([BufferRange? range]) {
range ??= BufferRange(
range ??= BufferRangeLine(
CellOffset(0, 0),
CellOffset(viewWidth - 1, height - 1),
);
Expand All @@ -518,23 +519,17 @@ class Buffer {

final builder = StringBuffer();

final firstLine = range.begin.y.clamp(0, height - 1);
final lastLine = range.end.y.clamp(firstLine, height - 1);

for (var i = firstLine; i <= lastLine; i++) {
if (i < 0 || i >= lines.length) {
for (var segment in range.toSegments()) {
if (segment.line < 0 || segment.line >= height) {
continue;
}

final line = lines[i];
final start = i == firstLine ? range.begin.x : 0;
final end = i == lastLine ? range.end.x : line.length;

if (!(i == firstLine || line.isWrapped)) {
builder.write('\n');
final line = lines[segment.line];
if (!(segment.line == range.begin.y ||
segment.line == 0 ||
line.isWrapped)) {
builder.write("\n");
}

builder.write(line.getText(start, end));
builder.write(line.getText(segment.start, segment.end));
}

return builder.toString();
Expand Down
2 changes: 1 addition & 1 deletion lib/src/core/buffer/cell_offset.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class CellOffset {
}

bool isWithin(BufferRange range) {
return range.begin.isBeforeOrSame(this) && range.end.isAfterOrSame(this);
return range.contains(this);
}

@override
Expand Down
50 changes: 13 additions & 37 deletions lib/src/core/buffer/range.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import 'package:xterm/src/core/buffer/cell_offset.dart';
import 'package:xterm/src/core/buffer/segment.dart';

class BufferRange {
abstract class BufferRange {
final CellOffset begin;

final CellOffset end;

BufferRange(this.begin, this.end);
const BufferRange(this.begin, this.end);

BufferRange.collapsed(this.begin) : end = begin;

Expand All @@ -18,45 +18,21 @@ class BufferRange {
return begin.isEqual(end);
}

BufferRange get normalized {
if (isNormalized) {
return this;
} else {
return BufferRange(end, begin);
}
}

Iterable<BufferSegment> toSegments() sync* {
var begin = this.begin;
var end = this.end;

if (!isNormalized) {
end = this.begin;
begin = this.end;
}
BufferRange get normalized;

for (var i = begin.y; i <= end.y; i++) {
var startX = i == begin.y ? begin.x : null;
var endX = i == end.y ? end.x : null;
yield BufferSegment(this, i, startX, endX);
}
}
/// Convert this range to segments of single lines.
Iterable<BufferSegment> toSegments();

bool contains(CellOffset position) {
return begin.isBeforeOrSame(position) && end.isAfterOrSame(position);
}
/// Returns true if the given[position] is within this range.
bool contains(CellOffset position);

BufferRange merge(BufferRange range) {
final begin = this.begin.isBefore(range.begin) ? this.begin : range.begin;
final end = this.end.isAfter(range.end) ? this.end : range.end;
return BufferRange(begin, end);
}
/// Returns the smallest range that contains both this range and the given
/// [range].
BufferRange merge(BufferRange range);

BufferRange extend(CellOffset position) {
final begin = this.begin.isBefore(position) ? position : this.begin;
final end = this.end.isAfter(position) ? position : this.end;
return BufferRange(begin, end);
}
/// Returns the smallest range that contains both this range and the given
/// [position].
BufferRange extend(CellOffset position);

@override
operator ==(Object other) {
Expand Down
111 changes: 111 additions & 0 deletions lib/src/core/buffer/range_block.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import 'dart:math';

import 'package:xterm/src/core/buffer/cell_offset.dart';
import 'package:xterm/src/core/buffer/range.dart';
import 'package:xterm/src/core/buffer/segment.dart';

class BufferRangeBlock extends BufferRange {
BufferRangeBlock(super.begin, super.end);

BufferRangeBlock.collapsed(CellOffset begin) : super.collapsed(begin);

@override
bool get isNormalized {
// A block range is normalized if begin is the top left corner of the range
// and end the bottom right corner.
return (begin.isBefore(end) && begin.x <= end.x) || begin.isEqual(end);
}

@override
BufferRangeBlock get normalized {
if (isNormalized) {
return this;
}
// Determine new normalized begin and end offset, such that begin is the
// top left corner and end is the bottom right corner of the block.
final normalBegin = CellOffset(min(begin.x, end.x), min(begin.y, end.y));
final normalEnd = CellOffset(max(begin.x, end.x), max(begin.y, end.y));
return BufferRangeBlock(normalBegin, normalEnd);
}

@override
Iterable<BufferSegment> toSegments() sync* {
var begin = this.begin;
var end = this.end;

if (!isNormalized) {
end = this.begin;
begin = this.end;
}

final startX = min(begin.x, end.x);
final endX = max(begin.x, end.x);
for (var i = begin.y; i <= end.y; i++) {
yield BufferSegment(this, i, startX, endX);
}
}

@override
bool contains(CellOffset position) {
var begin = this.begin;
var end = this.end;

if (!isNormalized) {
end = this.begin;
begin = this.end;
}
if (!(begin.y <= position.y && position.y <= end.y)) {
return false;
}

final startX = min(begin.x, end.x);
final endX = max(begin.x, end.x);
return startX <= position.x && position.x <= endX;
}

@override
BufferRangeBlock merge(BufferRange range) {
// Enlarge the block such that both borders of the range
// are within the selected block.
return extend(range.begin).extend(range.end);
}

@override
BufferRangeBlock extend(CellOffset position) {
// If the position is within the block, there is nothing to do.
if (contains(position)) {
return this;
}
// Otherwise normalize the block and push the borders outside up to
// the position to which the block has to extended.
final normal = normalized;
final extendBegin = CellOffset(
min(normal.begin.x, position.x),
min(normal.begin.y, position.y),
);
final extendEnd = CellOffset(
max(normal.end.x, position.x),
max(normal.end.y, position.y),
);
return BufferRangeBlock(extendBegin, extendEnd);
}

@override
operator ==(Object other) {
if (identical(this, other)) {
return true;
}

if (other is! BufferRangeBlock) {
return false;
}

return begin == other.begin && end == other.end;
}

@override
int get hashCode => begin.hashCode ^ end.hashCode;

@override
String toString() => 'Block Range($begin, $end)';
}
66 changes: 66 additions & 0 deletions lib/src/core/buffer/range_line.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import 'package:xterm/src/core/buffer/cell_offset.dart';
import 'package:xterm/src/core/buffer/range.dart';
import 'package:xterm/src/core/buffer/segment.dart';

class BufferRangeLine extends BufferRange {
BufferRangeLine(super.begin, super.end);

BufferRangeLine.collapsed(CellOffset begin) : super.collapsed(begin);

@override
BufferRangeLine get normalized {
return isNormalized ? this : BufferRangeLine(end, begin);
}

@override
Iterable<BufferSegment> toSegments() sync* {
final self = normalized;
for (var i = self.begin.y; i <= self.end.y; i++) {
var startX = i == self.begin.y ? self.begin.x : null;
var endX = i == self.end.y ? self.end.x : null;
yield BufferSegment(this, i, startX, endX);
}
}

@override
bool contains(CellOffset position) {
final self = normalized;
return self.begin.isBeforeOrSame(position) &&
self.end.isAfterOrSame(position);
}

@override
BufferRangeLine merge(BufferRange range) {
final self = normalized;
final begin = self.begin.isBefore(range.begin) ? self.begin : range.begin;
final end = self.end.isAfter(range.end) ? self.end : range.end;
return BufferRangeLine(begin, end);
}

@override
BufferRangeLine extend(CellOffset position) {
final self = normalized;
final begin = self.begin.isAfter(position) ? position : self.begin;
final end = self.end.isBefore(position) ? position : self.end;
return BufferRangeLine(begin, end);
}

@override
operator ==(Object other) {
if (identical(this, other)) {
return true;
}

if (other is! BufferRangeLine) {
return false;
}

return begin == other.begin && end == other.end;
}

@override
int get hashCode => begin.hashCode ^ end.hashCode;

@override
String toString() => 'Line Range($begin, $end)';
}
14 changes: 10 additions & 4 deletions lib/src/core/buffer/segment.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ class BufferSegment {
/// The line that this segment resides on.
final int line;

/// The start position of this segment.
/// The start position of this segment. [null] means the start of the line.
final int? start;

/// The end position of this segment. [null] if this segment is not closed.
/// The end position of this segment. [null] means the end of the line.
/// Should be greater than or equal to [start].
final int? end;

const BufferSegment(this.range, this.line, this.start, this.end);
const BufferSegment(this.range, this.line, this.start, this.end)
: assert((start != null && end != null) ? start <= end : true);

bool isWithin(CellOffset position) {
if (position.y != line) {
Expand All @@ -33,7 +35,11 @@ class BufferSegment {
}

@override
String toString() => 'Segment($line, $start, $end)';
String toString() {
final start = this.start != null ? this.start.toString() : 'start';
final end = this.end != null ? this.end.toString() : 'end';
return 'Segment($line, $start -> $end)';
}

@override
int get hashCode =>
Expand Down
Loading