text_editing.dart 6.35 KB
Newer Older
1 2 3 4
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:ui' show hashValues, TextAffinity, TextPosition;
6

7 8
import 'package:meta/meta.dart';

9 10
export 'dart:ui' show TextAffinity, TextPosition;

11 12
/// A range of characters in a string of text.
class TextRange {
13 14 15 16 17 18 19
  /// Creates a text range.
  ///
  /// The [start] and [end] arguments must not be null. Both the [start] and
  /// [end] must either be greater than or equal to zero or both exactly -1.
  ///
  /// Instead of creating an empty text range, consider using the [empty]
  /// constant.
20 21 22 23
  const TextRange({
    @required this.start,
    @required this.end
  });
24 25 26 27 28 29 30 31 32 33

  /// A text range that starts and ends at offset.
  const TextRange.collapsed(int offset)
    : start = offset,
      end = offset;

  /// A text range that contains nothing and is not in the text.
  static const TextRange empty = const TextRange(start: -1, end: -1);

  /// The index of the first character in the range.
34 35
  ///
  /// If [start] and [end] are both -1, the text range is empty.
36 37 38
  final int start;

  /// The next index after the characters in this range.
39 40
  ///
  /// If [start] and [end] are both -1, the text range is empty.
41 42 43 44 45 46 47 48
  final int end;

  /// Whether this range represents a valid position in the text.
  bool get isValid => start >= 0 && end >= 0;

  /// Whether this range is empty (but still potentially placed inside the text).
  bool get isCollapsed => start == end;

49
  /// Whether the start of this range precedes the end.
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
  bool get isNormalized => end >= start;

  /// The text before this range.
  String textBefore(String text) {
    assert(isNormalized);
    return text.substring(0, start);
  }

  /// The text after this range.
  String textAfter(String text) {
    assert(isNormalized);
    return text.substring(end);
  }

  /// The text inside this range.
  String textInside(String text) {
    assert(isNormalized);
    return text.substring(start, end);
  }
69

70
  @override
71 72 73 74 75 76 77 78 79 80
  bool operator ==(dynamic other) {
    if (identical(this, other))
      return true;
    if (other is! TextRange)
      return false;
    TextRange typedOther = other;
    return typedOther.start == start
        && typedOther.end == end;
  }

81
  @override
82 83 84 85 86
  int get hashCode => hashValues(
    start.hashCode,
    end.hashCode
  );

87
  @override
88
  String toString() => 'TextRange(start: $start, end: $end)';
89 90 91 92
}

/// A range of text that represents a selection.
class TextSelection extends TextRange {
93 94 95
  /// Creates a text selection.
  ///
  /// The [baseOffset] and [extentOffset] arguments must not be null.
96
  const TextSelection({
97 98
    @required int baseOffset,
    @required int extentOffset,
99 100 101 102 103 104 105 106 107
    this.affinity: TextAffinity.downstream,
    this.isDirectional: false
  }) : baseOffset = baseOffset,
       extentOffset = extentOffset,
       super(
         start: baseOffset < extentOffset ? baseOffset : extentOffset,
         end: baseOffset < extentOffset ? extentOffset : baseOffset
       );

108 109 110 111 112 113 114
  /// Creates a collapsed selection at the given offset.
  ///
  /// A collapsed selection starts and ends at the same offset, which means it
  /// contains zero characters but instead serves as an insertion point in the
  /// text.
  ///
  /// The [offset] argument must not be null.
115
  const TextSelection.collapsed({
116
    @required int offset,
117 118 119
    this.affinity: TextAffinity.downstream
  }) : baseOffset = offset, extentOffset = offset, isDirectional = false, super.collapsed(offset);

120 121 122 123 124
  /// Creates a collapsed selection at the given text position.
  ///
  /// A collapsed selection starts and ends at the same offset, which means it
  /// contains zero characters but instead serves as an insertion point in the
  /// text.
125 126 127 128 129 130
  TextSelection.fromPosition(TextPosition position)
    : baseOffset = position.offset,
      extentOffset = position.offset,
      affinity = position.affinity,
      isDirectional = false,
      super.collapsed(position.offset);
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145

  /// The offset at which the selection originates.
  ///
  /// Might be larger than, smaller than, or equal to extent.
  final int baseOffset;

  /// The offset at which the selection terminates.
  ///
  /// When the user uses the arrow keys to adjust the selection, this is the
  /// value that changes. Similarly, if the current theme paints a caret on one
  /// side of the selection, this is the location at which to paint the caret.
  ///
  /// Might be larger than, smaller than, or equal to base.
  final int extentOffset;

146
  /// If the the text range is collapsed and has more than one visual location
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
  /// (e.g., occurs at a line break), which of the two locations to use when
  /// painting the caret.
  final TextAffinity affinity;

  /// Whether this selection has disambiguated its base and extent.
  ///
  /// On some platforms, the base and extent are not disambiguated until the
  /// first time the user adjusts the selection. At that point, either the start
  /// or the end of the selection becomes the base and the other one becomes the
  /// extent and is adjusted.
  final bool isDirectional;

  /// The position at which the selection originates.
  ///
  /// Might be larger than, smaller than, or equal to extent.
  TextPosition get base => new TextPosition(offset: baseOffset, affinity: affinity);

  /// The position at which the selection terminates.
  ///
  /// When the user uses the arrow keys to adjust the selection, this is the
  /// value that changes. Similarly, if the current theme paints a caret on one
  /// side of the selection, this is the location at which to paint the caret.
  ///
  /// Might be larger than, smaller than, or equal to base.
  TextPosition get extent => new TextPosition(offset: extentOffset, affinity: affinity);
172

173
  @override
174 175 176
  String toString() {
    return '$runtimeType(baseOffset: $baseOffset, extentOffset: $extentOffset, affinity: $affinity, isDirectional: $isDirectional)';
  }
177

178
  @override
179 180 181 182 183 184 185 186 187 188 189 190
  bool operator ==(dynamic other) {
    if (identical(this, other))
      return true;
    if (other is! TextSelection)
      return false;
    TextSelection typedOther = other;
    return typedOther.baseOffset == baseOffset
        && typedOther.extentOffset == extentOffset
        && typedOther.affinity == affinity
        && typedOther.isDirectional == isDirectional;
  }

191
  @override
192 193 194 195 196 197
  int get hashCode => hashValues(
    baseOffset.hashCode,
    extentOffset.hashCode,
    affinity.hashCode,
    isDirectional.hashCode
  );
198
}