moveLinesAtSelections method
Moves every touched line up or down by one row, keeping contiguous runs together.
Implementation
bool moveLinesAtSelections({required bool down}) {
return _runEditFrame(() {
_beginHistoryAction(_TextAreaHistoryAction.transform, breakChain: true);
_refreshDocumentSnapshot();
final indexes = _selectedLineIndexes().toList()..sort();
if (indexes.isEmpty) return false;
final runs = _contiguousLineRuns(indexes);
final lines = [for (var i = 0; i < lineCount; i++) _document.lineAt(i)];
var changed = false;
final ordered = down ? runs.reversed : runs;
for (final run in ordered) {
if (down) {
if (run.end >= lines.length - 1) continue;
final block = lines.sublist(run.start, run.end + 1);
lines.removeRange(run.start, run.end + 1);
lines.insertAll(run.start + 1, block);
changed = true;
} else {
if (run.start <= 0) continue;
final block = lines.sublist(run.start, run.end + 1);
lines.removeRange(run.start, run.end + 1);
lines.insertAll(run.start - 1, block);
changed = true;
}
}
if (!changed) return false;
final delta = down ? 1 : -1;
final mapped = [
for (final range in selections.ranges)
(
start: _document.positionForOffset(range.startOffset),
end: _document.positionForOffset(range.endOffset),
isReversed: range.isReversed,
),
];
final primaryIndex = selections.ranges.indexOf(selections.primary!);
_recordUndoSnapshot();
// A trailing newline is represented by the final empty document line,
// so joining all logical lines already preserves it.
_replaceText(lines.join('\n'));
TextSelectionRange relocate(
({TextPosition start, TextPosition end, bool isReversed}) positions,
) {
final startLine = (positions.start.line + delta).clamp(
0,
lineCount - 1,
);
final endLine = (positions.end.line + delta).clamp(0, lineCount - 1);
return TextSelectionRange(
startOffset: _document.offsetForPosition(
TextPosition(
line: startLine,
column: positions.start.column.clamp(
0,
_document.lineLength(startLine),
),
),
),
endOffset: _document.offsetForPosition(
TextPosition(
line: endLine,
column: positions.end.column.clamp(
0,
_document.lineLength(endLine),
),
),
),
isReversed: positions.start != positions.end && positions.isReversed,
);
}
final relocated = [for (final positions in mapped) relocate(positions)];
setSelections(
TextSelectionSet(
relocated,
primaryOffset: relocated.isEmpty
? 0
: relocated[primaryIndex].activeOffset,
),
);
return true;
});
}