text_editing.dart 6.26 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
export 'dart:ui' show TextAffinity, TextPosition;
8 9 10

/// A range of characters in a string of text.
class TextRange {
11 12 13 14 15 16 17
  /// 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.
18 19 20 21 22 23 24 25 26 27 28
  const TextRange({ this.start, this.end });

  /// 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.
29 30
  ///
  /// If [start] and [end] are both -1, the text range is empty.
31 32 33
  final int start;

  /// The next index after the characters in this range.
34 35
  ///
  /// If [start] and [end] are both -1, the text range is empty.
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
  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;

  /// Whether the start of this range preceeds the end.
  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);
  }
64

65
  @override
66 67 68 69 70 71 72 73 74 75
  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;
  }

76
  @override
77 78 79 80 81
  int get hashCode => hashValues(
    start.hashCode,
    end.hashCode
  );

82
  @override
83
  String toString() => 'TextRange(start: $start, end: $end)';
84 85 86 87
}

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

103 104 105 106 107 108 109
  /// 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.
110 111
  const TextSelection.collapsed({
    int offset,
112 113 114
    this.affinity: TextAffinity.downstream
  }) : baseOffset = offset, extentOffset = offset, isDirectional = false, super.collapsed(offset);

115 116 117 118 119
  /// 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.
120 121 122 123 124 125
  TextSelection.fromPosition(TextPosition position)
    : baseOffset = position.offset,
      extentOffset = position.offset,
      affinity = position.affinity,
      isDirectional = false,
      super.collapsed(position.offset);
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166

  /// 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;

  /// If the the text range is collpased and has more than one visual location
  /// (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);
167

168
  @override
169 170 171
  String toString() {
    return '$runtimeType(baseOffset: $baseOffset, extentOffset: $extentOffset, affinity: $affinity, isDirectional: $isDirectional)';
  }
172

173
  @override
174 175 176 177 178 179 180 181 182 183 184 185
  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;
  }

186
  @override
187 188 189 190 191 192
  int get hashCode => hashValues(
    baseOffset.hashCode,
    extentOffset.hashCode,
    affinity.hashCode,
    isDirectional.hashCode
  );
193
}