1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #ifndef mozilla_TextServicesDocument_h
7 #define mozilla_TextServicesDocument_h
9 #include "mozilla/Maybe.h"
10 #include "mozilla/UniquePtr.h"
12 #include "nsCycleCollectionParticipant.h"
13 #include "nsIEditActionListener.h"
14 #include "nsISupportsImpl.h"
15 #include "nsStringFwd.h"
22 class nsISelectionController
;
28 class FilteredContentIterator
;
39 * The TextServicesDocument presents the document in as a bunch of flattened
40 * text blocks. Each text block can be retrieved as an nsString.
42 class TextServicesDocument final
: public nsIEditActionListener
{
44 enum class IteratorStatus
: uint8_t {
45 // No iterator (I), or iterator doesn't point to anything valid.
47 // I points to first text node (TN) in current block (CB).
49 // No TN in CB, I points to first TN in prev block.
51 // No TN in CB, I points to first TN in next block.
55 class OffsetEntryArray final
: public nsTArray
<UniquePtr
<OffsetEntry
>> {
58 * Init() initializes this array with aFilteredIter.
60 * @param[in] aIterRange Can be nullptr.
61 * @param[out] aAllTextInBlock
62 * Returns all text in the block.
64 Result
<IteratorStatus
, nsresult
> Init(
65 FilteredContentIterator
& aFilteredIter
, IteratorStatus aIteratorStatus
,
66 nsRange
* aIterRange
, nsAString
* aAllTextInBlock
= nullptr);
69 * Returns index of first `OffsetEntry` which manages aTextNode.
71 Maybe
<size_t> FirstIndexOf(const dom::Text
& aTextNode
) const;
74 * FindWordRange() returns a word range starting from aStartPointToScan
77 Result
<EditorDOMRangeInTexts
, nsresult
> FindWordRange(
78 nsAString
& aAllTextInBlock
, const EditorRawDOMPoint
& aStartPointToScan
);
81 * SplitElementAt() splits an `OffsetEntry` at aIndex if aOffsetInTextNode
82 * is middle of the range in the text node.
84 * @param aIndex Index of the entry which you want to split.
85 * @param aOffsetInTextNode
86 * Offset in the text node. I.e., the offset should be
87 * greater than 0 and less than `mLength`.
89 nsresult
SplitElementAt(size_t aIndex
, uint32_t aOffsetInTextNode
);
92 * Remove all `OffsetEntry` elements whose `mIsValid` is set to false.
94 void RemoveInvalidElements();
97 * Called when non-collapsed selection will be deleted.
99 nsresult
WillDeleteSelection();
102 * Called when non-collapsed selection is deleteded.
104 OffsetEntry
* DidDeleteSelection();
107 * Called when aInsertedText is inserted.
109 MOZ_CAN_RUN_SCRIPT nsresult
DidInsertText(dom::Selection
* aSelection
,
110 const nsAString
& aInsertedString
);
113 * Called when selection range will be applied to the DOM Selection.
115 Result
<EditorRawDOMRangeInTexts
, nsresult
> WillSetSelection(
116 uint32_t aOffsetInTextInBlock
, uint32_t aLength
);
118 class Selection final
{
120 size_t StartIndex() const {
121 MOZ_ASSERT(IsIndexesSet());
124 size_t EndIndex() const {
125 MOZ_ASSERT(IsIndexesSet());
129 uint32_t StartOffsetInTextInBlock() const {
131 return *mStartOffsetInTextInBlock
;
133 uint32_t EndOffsetInTextInBlock() const {
135 return *mEndOffsetInTextInBlock
;
137 uint32_t LengthInTextInBlock() const {
139 return *mEndOffsetInTextInBlock
- *mStartOffsetInTextInBlock
;
143 return !IsSet() || (IsInSameElement() && StartOffsetInTextInBlock() ==
144 EndOffsetInTextInBlock());
147 bool IsIndexesSet() const {
148 return mStartIndex
.isSome() && mEndIndex
.isSome();
151 return IsIndexesSet() && mStartOffsetInTextInBlock
.isSome() &&
152 mEndOffsetInTextInBlock
.isSome();
154 bool IsInSameElement() const {
155 return IsIndexesSet() && StartIndex() == EndIndex();
161 mStartOffsetInTextInBlock
.reset();
162 mEndOffsetInTextInBlock
.reset();
164 void SetIndex(size_t aIndex
) { mEndIndex
= mStartIndex
= Some(aIndex
); }
165 void Set(size_t aIndex
, uint32_t aOffsetInTextInBlock
) {
166 mEndIndex
= mStartIndex
= Some(aIndex
);
167 mStartOffsetInTextInBlock
= mEndOffsetInTextInBlock
=
168 Some(aOffsetInTextInBlock
);
170 void SetIndexes(size_t aStartIndex
, size_t aEndIndex
) {
171 MOZ_DIAGNOSTIC_ASSERT(aStartIndex
<= aEndIndex
);
172 mStartIndex
= Some(aStartIndex
);
173 mEndIndex
= Some(aEndIndex
);
175 void Set(size_t aStartIndex
, size_t aEndIndex
,
176 uint32_t aStartOffsetInTextInBlock
,
177 uint32_t aEndOffsetInTextInBlock
) {
178 MOZ_DIAGNOSTIC_ASSERT(aStartIndex
<= aEndIndex
);
179 mStartIndex
= Some(aStartIndex
);
180 mEndIndex
= Some(aEndIndex
);
181 mStartOffsetInTextInBlock
= Some(aStartOffsetInTextInBlock
);
182 mEndOffsetInTextInBlock
= Some(aEndOffsetInTextInBlock
);
185 void CollapseToStart() {
186 MOZ_ASSERT(mStartIndex
.isSome());
187 MOZ_ASSERT(mStartOffsetInTextInBlock
.isSome());
188 mEndIndex
= mStartIndex
;
189 mEndOffsetInTextInBlock
= mStartOffsetInTextInBlock
;
193 Maybe
<size_t> mStartIndex
;
194 Maybe
<size_t> mEndIndex
;
195 // Selected start and end offset in all text in a block element.
196 Maybe
<uint32_t> mStartOffsetInTextInBlock
;
197 Maybe
<uint32_t> mEndOffsetInTextInBlock
;
199 Selection mSelection
;
202 RefPtr
<dom::Document
> mDocument
;
203 nsCOMPtr
<nsISelectionController
> mSelCon
;
204 RefPtr
<EditorBase
> mEditorBase
;
205 RefPtr
<FilteredContentIterator
> mFilteredIter
;
206 nsCOMPtr
<nsIContent
> mPrevTextBlock
;
207 nsCOMPtr
<nsIContent
> mNextTextBlock
;
208 OffsetEntryArray mOffsetTable
;
209 RefPtr
<nsRange
> mExtent
;
211 uint32_t mTxtSvcFilterType
;
212 IteratorStatus mIteratorStatus
;
215 virtual ~TextServicesDocument() = default;
218 TextServicesDocument();
220 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
221 NS_DECL_CYCLE_COLLECTION_CLASS(TextServicesDocument
)
224 * Initializes the text services document to use a particular editor. The
225 * text services document will use the DOM document and presentation shell
226 * used by the editor.
228 * @param aEditor The editor to use.
230 nsresult
InitWithEditor(nsIEditor
* aEditor
);
233 * Sets the range/extent over which the text services document will iterate.
234 * Note that InitWithEditor() should have been called prior to calling this
235 * method. If this method is never called, the text services defaults to
236 * iterating over the entire document.
238 * @param aAbstractRange The range to use. aAbstractRange must point to a
239 * valid range object.
241 nsresult
SetExtent(const dom::AbstractRange
* aAbstractRange
);
244 * Expands the end points of the range so that it spans complete words. This
245 * call does not change any internal state of the text services document.
247 * @param aStaticRange [in/out] The range to be expanded/adjusted.
249 nsresult
ExpandRangeToWordBoundaries(dom::StaticRange
* aStaticRange
);
252 * Sets the filter type to be used while iterating over content.
253 * This will clear the current filter type if it's not either
254 * FILTERTYPE_NORMAL or FILTERTYPE_MAIL.
256 * @param aFilterType The filter type to be used while iterating over
259 nsresult
SetFilterType(uint32_t aFilterType
);
262 * Returns the text in the current text block.
264 * @param aStr [OUT] This will contain the text.
266 nsresult
GetCurrentTextBlock(nsAString
& aStr
);
269 * Tells the document to point to the first text block in the document. This
270 * method does not adjust the current cursor position or selection.
272 nsresult
FirstBlock();
274 enum class BlockSelectionStatus
{
275 // There is no text block (TB) in or before the selection (S).
277 // No TB in S, but found one before/after S.
279 // S extends beyond the start and end of TB.
281 // TB contains entire S.
283 // S begins or ends in TB but extends outside of TB.
288 * Tells the document to point to the last text block that contains the
289 * current selection or caret.
291 * @param aSelectionStatus [OUT] This will contain the text block
293 * @param aSelectionOffset [OUT] This will contain the offset into the
294 * string returned by GetCurrentTextBlock() where
295 * the selection begins.
296 * @param aLength [OUT] This will contain the number of
297 * characters that are selected in the string.
300 nsresult
LastSelectedBlock(BlockSelectionStatus
* aSelStatus
,
301 uint32_t* aSelOffset
, uint32_t* aSelLength
);
304 * Tells the document to point to the text block before the current one.
305 * This method will return NS_OK, even if there is no previous block.
306 * Callers should call IsDone() to check if we have gone beyond the first
307 * text block in the document.
309 nsresult
PrevBlock();
312 * Tells the document to point to the text block after the current one.
313 * This method will return NS_OK, even if there is no next block. Callers
314 * should call IsDone() to check if we have gone beyond the last text block
317 nsresult
NextBlock();
320 * IsDone() will always set aIsDone == false unless the document contains
321 * no text, PrevBlock() was called while the document was already pointing
322 * to the first text block in the document, or NextBlock() was called while
323 * the document was already pointing to the last text block in the document.
325 * @param aIsDone [OUT] This will contain the result.
327 nsresult
IsDone(bool* aIsDone
);
330 * SetSelection() allows the caller to set the selection based on an offset
331 * into the string returned by GetCurrentTextBlock(). A length of zero
332 * places the cursor at that offset. A positive non-zero length "n" selects
333 * n characters in the string.
335 * @param aOffset Offset into string returned by
336 * GetCurrentTextBlock().
337 * @param aLength Number of characters selected.
339 MOZ_CAN_RUN_SCRIPT nsresult
SetSelection(uint32_t aOffset
, uint32_t aLength
);
342 * Scrolls the document so that the current selection is visible.
344 nsresult
ScrollSelectionIntoView();
347 * Deletes the text selected by SetSelection(). Calling DeleteSelection()
348 * with nothing selected, or with a collapsed selection (cursor) does
349 * nothing and returns NS_OK.
352 nsresult
DeleteSelection();
355 * Inserts the given text at the current cursor position. If there is a
356 * selection, it will be deleted before the text is inserted.
359 nsresult
InsertText(const nsAString
& aText
);
362 * nsIEditActionListener method implementations.
364 NS_DECL_NSIEDITACTIONLISTENER
367 * Actual edit action listeners. When you add new method here for listening
368 * to new edit action, you need to make it called by EditorBase.
369 * Additionally, you need to call it from proper method of
370 * nsIEditActionListener too because if this is created not for inline
371 * spell checker of the editor, edit actions will be notified via
372 * nsIEditActionListener (slow path, though).
374 void DidDeleteContent(const nsIContent
& aChildContent
);
375 void DidJoinContents(const EditorRawDOMPoint
& aJoinedPoint
,
376 const nsIContent
& aRemovedContent
);
379 // TODO: We should get rid of this method since `aAbstractRange` has
380 // enough simple API to get them.
381 static nsresult
GetRangeEndPoints(const dom::AbstractRange
* aAbstractRange
,
382 nsINode
** aStartContainer
,
383 uint32_t* aStartOffset
,
384 nsINode
** aEndContainer
,
385 uint32_t* aEndOffset
);
387 nsresult
CreateFilteredContentIterator(
388 const dom::AbstractRange
* aAbstractRange
,
389 FilteredContentIterator
** aFilteredIter
);
391 dom::Element
* GetDocumentContentRootNode() const;
392 already_AddRefed
<nsRange
> CreateDocumentContentRange();
393 already_AddRefed
<nsRange
> CreateDocumentContentRootToNodeOffsetRange(
394 nsINode
* aParent
, uint32_t aOffset
, bool aToStart
);
395 nsresult
CreateDocumentContentIterator(
396 FilteredContentIterator
** aFilteredIter
);
398 nsresult
AdjustContentIterator();
400 static nsresult
FirstTextNode(FilteredContentIterator
* aFilteredIter
,
401 IteratorStatus
* aIteratorStatus
);
402 static nsresult
LastTextNode(FilteredContentIterator
* aFilteredIter
,
403 IteratorStatus
* aIteratorStatus
);
405 static nsresult
FirstTextNodeInCurrentBlock(
406 FilteredContentIterator
* aFilteredIter
);
407 static nsresult
FirstTextNodeInPrevBlock(
408 FilteredContentIterator
* aFilteredIter
);
409 static nsresult
FirstTextNodeInNextBlock(
410 FilteredContentIterator
* aFilteredIter
);
412 nsresult
GetFirstTextNodeInPrevBlock(nsIContent
** aContent
);
413 nsresult
GetFirstTextNodeInNextBlock(nsIContent
** aContent
);
415 static bool DidSkip(FilteredContentIterator
* aFilteredIter
);
416 static void ClearDidSkip(FilteredContentIterator
* aFilteredIter
);
418 static bool HasSameBlockNodeParent(dom::Text
& aTextNode1
,
419 dom::Text
& aTextNode2
);
421 MOZ_CAN_RUN_SCRIPT nsresult
SetSelectionInternal(uint32_t aOffset
,
424 MOZ_CAN_RUN_SCRIPT nsresult
GetSelection(BlockSelectionStatus
* aSelStatus
,
425 uint32_t* aSelOffset
,
426 uint32_t* aSelLength
);
427 MOZ_CAN_RUN_SCRIPT nsresult
428 GetCollapsedSelection(BlockSelectionStatus
* aSelStatus
, uint32_t* aSelOffset
,
429 uint32_t* aSelLength
);
430 nsresult
GetUncollapsedSelection(BlockSelectionStatus
* aSelStatus
,
431 uint32_t* aSelOffset
, uint32_t* aSelLength
);
434 } // namespace mozilla
436 #endif // #ifndef mozilla_TextServicesDocument_h