Bug 1890689 apply drift correction to input rate instead of output rate r=pehrsons
[gecko.git] / editor / spellchecker / TextServicesDocument.h
blob0b72bda977eb4bca5f896f5aab36363a60f084c5
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"
11 #include "nsCOMPtr.h"
12 #include "nsCycleCollectionParticipant.h"
13 #include "nsIEditActionListener.h"
14 #include "nsISupportsImpl.h"
15 #include "nsStringFwd.h"
16 #include "nsTArray.h"
17 #include "nscore.h"
19 class nsIContent;
20 class nsIEditor;
21 class nsINode;
22 class nsISelectionController;
23 class nsRange;
25 namespace mozilla {
27 class EditorBase;
28 class FilteredContentIterator;
29 class OffsetEntry;
31 namespace dom {
32 class AbstractRange;
33 class Document;
34 class Element;
35 class StaticRange;
36 }; // namespace dom
38 /**
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 {
43 private:
44 enum class IteratorStatus : uint8_t {
45 // No iterator (I), or iterator doesn't point to anything valid.
46 eDone = 0,
47 // I points to first text node (TN) in current block (CB).
48 eValid,
49 // No TN in CB, I points to first TN in prev block.
50 ePrev,
51 // No TN in CB, I points to first TN in next block.
52 eNext,
55 class OffsetEntryArray final : public nsTArray<UniquePtr<OffsetEntry>> {
56 public:
57 /**
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);
68 /**
69 * Returns index of first `OffsetEntry` which manages aTextNode.
71 Maybe<size_t> FirstIndexOf(const dom::Text& aTextNode) const;
73 /**
74 * FindWordRange() returns a word range starting from aStartPointToScan
75 * in aAllTextInBlock.
77 Result<EditorDOMRangeInTexts, nsresult> FindWordRange(
78 nsAString& aAllTextInBlock, const EditorRawDOMPoint& aStartPointToScan);
80 /**
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);
91 /**
92 * Remove all `OffsetEntry` elements whose `mIsValid` is set to false.
94 void RemoveInvalidElements();
96 /**
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 {
119 public:
120 size_t StartIndex() const {
121 MOZ_ASSERT(IsIndexesSet());
122 return *mStartIndex;
124 size_t EndIndex() const {
125 MOZ_ASSERT(IsIndexesSet());
126 return *mEndIndex;
129 uint32_t StartOffsetInTextInBlock() const {
130 MOZ_ASSERT(IsSet());
131 return *mStartOffsetInTextInBlock;
133 uint32_t EndOffsetInTextInBlock() const {
134 MOZ_ASSERT(IsSet());
135 return *mEndOffsetInTextInBlock;
137 uint32_t LengthInTextInBlock() const {
138 MOZ_ASSERT(IsSet());
139 return *mEndOffsetInTextInBlock - *mStartOffsetInTextInBlock;
142 bool IsCollapsed() {
143 return !IsSet() || (IsInSameElement() && StartOffsetInTextInBlock() ==
144 EndOffsetInTextInBlock());
147 bool IsIndexesSet() const {
148 return mStartIndex.isSome() && mEndIndex.isSome();
150 bool IsSet() const {
151 return IsIndexesSet() && mStartOffsetInTextInBlock.isSome() &&
152 mEndOffsetInTextInBlock.isSome();
154 bool IsInSameElement() const {
155 return IsIndexesSet() && StartIndex() == EndIndex();
158 void Reset() {
159 mStartIndex.reset();
160 mEndIndex.reset();
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;
192 private:
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;
214 protected:
215 virtual ~TextServicesDocument() = default;
217 public:
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
257 * content.
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).
276 eBlockNotFound = 0,
277 // No TB in S, but found one before/after S.
278 eBlockOutside,
279 // S extends beyond the start and end of TB.
280 eBlockInside,
281 // TB contains entire S.
282 eBlockContains,
283 // S begins or ends in TB but extends outside of TB.
284 eBlockPartial,
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
292 * selection status.
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.
299 MOZ_CAN_RUN_SCRIPT
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
315 * in the document.
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.
351 MOZ_CAN_RUN_SCRIPT
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.
358 MOZ_CAN_RUN_SCRIPT
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);
378 private:
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,
422 uint32_t aLength,
423 bool aDoUpdate);
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