mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 06:11:37 +00:00
Bug 389421. Rework word selection, in particular so that layout.word_select.stop_at_punctuation is applied to boundaries between punctuation and non-punctuation, and all Unicode punctuation is treated as punctuation.
This commit is contained in:
parent
171e7e67b5
commit
bbc848d48f
@ -68,7 +68,7 @@ public:
|
||||
virtual PRBool PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset);
|
||||
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset);
|
||||
virtual PRBool PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
|
||||
PRInt32* aOffset, PRBool* aSawBeforeType);
|
||||
PRInt32* aOffset, PeekWordState* aState);
|
||||
|
||||
NS_IMETHOD Reflow(nsPresContext* aPresContext,
|
||||
nsHTMLReflowMetrics& aDesiredSize,
|
||||
@ -261,7 +261,7 @@ BRFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
|
||||
|
||||
PRBool
|
||||
BRFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
|
||||
PRInt32* aOffset, PRBool* aSawBeforeType)
|
||||
PRInt32* aOffset, PeekWordState* aState)
|
||||
{
|
||||
NS_ASSERTION (aOffset && *aOffset <= 1, "aOffset out of range");
|
||||
// Keep going. The actual line jumping will stop us.
|
||||
|
@ -4647,7 +4647,7 @@ nsIFrame::PeekOffset(nsPeekOffsetStruct* aPos)
|
||||
nsContentUtils::GetBoolPref("layout.word_select.eat_space_to_next_word");
|
||||
}
|
||||
|
||||
// sawBeforeType means "we already saw characters of the type
|
||||
// mSawBeforeType means "we already saw characters of the type
|
||||
// before the boundary we're looking for". Examples:
|
||||
// 1. If we're moving forward, looking for a word beginning (i.e. a boundary
|
||||
// between whitespace and non-whitespace), then eatingWS==PR_TRUE means
|
||||
@ -4655,15 +4655,14 @@ nsIFrame::PeekOffset(nsPeekOffsetStruct* aPos)
|
||||
// 2. If we're moving backward, looking for a word beginning (i.e. a boundary
|
||||
// between non-whitespace and whitespace), then eatingWS==PR_TRUE means
|
||||
// "we already saw some non-whitespace".
|
||||
PRBool sawBeforeType = PR_FALSE;
|
||||
|
||||
PeekWordState state;
|
||||
PRBool done = PR_FALSE;
|
||||
while (!done) {
|
||||
PRBool movingInFrameDirection =
|
||||
IsMovingInFrameDirection(current, aPos->mDirection, aPos->mVisual);
|
||||
|
||||
done = current->PeekOffsetWord(movingInFrameDirection, wordSelectEatSpace, aPos->mIsKeyboardSelect,
|
||||
&offset, &sawBeforeType);
|
||||
done = current->PeekOffsetWord(movingInFrameDirection, wordSelectEatSpace,
|
||||
aPos->mIsKeyboardSelect, &offset, &state);
|
||||
|
||||
if (!done) {
|
||||
nsIFrame* nextFrame;
|
||||
@ -4676,14 +4675,14 @@ nsIFrame::PeekOffset(nsPeekOffsetStruct* aPos)
|
||||
// We can't jump lines if we're looking for whitespace following
|
||||
// non-whitespace, and we already encountered non-whitespace.
|
||||
if (NS_FAILED(result) ||
|
||||
jumpedLine && !wordSelectEatSpace && sawBeforeType) {
|
||||
jumpedLine && !wordSelectEatSpace && state.mSawBeforeType) {
|
||||
done = PR_TRUE;
|
||||
} else {
|
||||
current = nextFrame;
|
||||
offset = nextFrameOffset;
|
||||
// Jumping a line is equivalent to encountering whitespace
|
||||
if (wordSelectEatSpace && jumpedLine)
|
||||
sawBeforeType = PR_TRUE;
|
||||
state.SetSawBeforeType();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4889,7 +4888,7 @@ nsFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
|
||||
|
||||
PRBool
|
||||
nsFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
|
||||
PRInt32* aOffset, PRBool* aSawBeforeType)
|
||||
PRInt32* aOffset, PeekWordState* aState)
|
||||
{
|
||||
NS_ASSERTION (aOffset && *aOffset <= 1, "aOffset out of range");
|
||||
PRInt32 startOffset = *aOffset;
|
||||
@ -4898,16 +4897,41 @@ nsFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsK
|
||||
if (aForward == (startOffset == 0)) {
|
||||
// We're before the frame and moving forward, or after it and moving backwards.
|
||||
// If we're looking for non-whitespace, we found it (without skipping this frame).
|
||||
if (aWordSelectEatSpace && *aSawBeforeType)
|
||||
return PR_TRUE;
|
||||
if (!aState->mAtStart) {
|
||||
if (aState->mLastCharWasPunctuation) {
|
||||
// We're not punctuation, so this is a punctuation boundary.
|
||||
if (BreakWordBetweenPunctuation(aForward, aIsKeyboardSelect))
|
||||
return PR_TRUE;
|
||||
} else {
|
||||
// This is not a punctuation boundary.
|
||||
if (aWordSelectEatSpace && aState->mSawBeforeType)
|
||||
return PR_TRUE;
|
||||
}
|
||||
}
|
||||
// Otherwise skip to the other side and note that we encountered non-whitespace.
|
||||
*aOffset = 1 - startOffset;
|
||||
aState->Update(PR_FALSE);
|
||||
if (!aWordSelectEatSpace)
|
||||
*aSawBeforeType = PR_TRUE;
|
||||
aState->SetSawBeforeType();
|
||||
}
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsFrame::BreakWordBetweenPunctuation(PRBool aAfterPunct, PRBool aIsKeyboardSelect)
|
||||
{
|
||||
if (!nsContentUtils::GetBoolPref("layout.word_select.stop_at_punctuation")) {
|
||||
// When this pref is false, we never stop at a punctuation boundary.
|
||||
return PR_FALSE;
|
||||
}
|
||||
if (!aIsKeyboardSelect) {
|
||||
// mouse caret movement (e.g. word selection) always stops at every punctuation boundary
|
||||
return PR_TRUE;
|
||||
}
|
||||
// keyboard caret movement stops after punctuation, not before it
|
||||
return aAfterPunct;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsFrame::CheckVisibility(nsPresContext* , PRInt32 , PRInt32 , PRBool , PRBool *, PRBool *)
|
||||
{
|
||||
|
@ -248,7 +248,13 @@ public:
|
||||
virtual PRBool PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset);
|
||||
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset);
|
||||
virtual PRBool PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
|
||||
PRInt32* aOffset, PRBool* aSawBeforeType);
|
||||
PRInt32* aOffset, PeekWordState *aState);
|
||||
/**
|
||||
* Check whether we should break at a boundary between punctuation and
|
||||
* non-punctuation.
|
||||
* @param aAfterPunct true when this point is logically *after* punctuation.
|
||||
*/
|
||||
PRBool BreakWordBetweenPunctuation(PRBool aAfterPunct, PRBool aIsKeyboardSelect);
|
||||
|
||||
NS_IMETHOD CheckVisibility(nsPresContext* aContext, PRInt32 aStartIndex, PRInt32 aEndIndex, PRBool aRecurse, PRBool *aFinished, PRBool *_retval);
|
||||
|
||||
|
@ -2110,14 +2110,31 @@ protected:
|
||||
* as a word on its own.
|
||||
* @param aOffset [in/out] At what offset into the frame to start looking.
|
||||
* on output - what offset was reached (whether or not we found a place to stop).
|
||||
* @param aSawBeforeType [in/out] Did we encounter a character of the pre-boundary type
|
||||
* (whitespace if aWordSelectEatSpace is true, non-whitespace otherwise).
|
||||
* @param aState [in/out] the state that is carried from frame to frame
|
||||
* @return PR_TRUE: An appropriate offset was found within this frame,
|
||||
* and is given by aOffset.
|
||||
* PR_FALSE: Not found within this frame, need to try the next frame.
|
||||
*/
|
||||
struct PeekWordState {
|
||||
// true when we're still at the start of the search, i.e., we can't return
|
||||
// this point as a valid offset!
|
||||
PRPackedBool mAtStart;
|
||||
// true when we've encountered at least one character of the pre-boundary type
|
||||
// (whitespace if aWordSelectEatSpace is true, non-whitespace otherwise)
|
||||
PRPackedBool mSawBeforeType;
|
||||
// true when the last character encountered was punctuation
|
||||
PRPackedBool mLastCharWasPunctuation;
|
||||
|
||||
PeekWordState() : mAtStart(PR_TRUE), mSawBeforeType(PR_FALSE),
|
||||
mLastCharWasPunctuation(PR_FALSE) {}
|
||||
void SetSawBeforeType() { mSawBeforeType = PR_TRUE; }
|
||||
void Update(PRBool aAfterPunctuation) {
|
||||
mLastCharWasPunctuation = aAfterPunctuation;
|
||||
mAtStart = PR_FALSE;
|
||||
}
|
||||
};
|
||||
virtual PRBool PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
|
||||
PRInt32* aOffset, PRBool* aSawBeforeType) = 0;
|
||||
PRInt32* aOffset, PeekWordState* aState) = 0;
|
||||
|
||||
/**
|
||||
* Search for the first paragraph boundary before or after the given position
|
||||
|
@ -82,6 +82,8 @@
|
||||
#include "nsTextFrameTextRunCache.h"
|
||||
#include "nsExpirationTracker.h"
|
||||
#include "nsICaseConversion.h"
|
||||
#include "nsIUGenCategory.h"
|
||||
#include "nsUnicharUtilCIID.h"
|
||||
|
||||
#include "nsTextFragment.h"
|
||||
#include "nsGkAtoms.h"
|
||||
@ -392,7 +394,7 @@ public:
|
||||
virtual PRBool PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset);
|
||||
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset);
|
||||
virtual PRBool PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
|
||||
PRInt32* aOffset, PRBool* aSawBeforeType);
|
||||
PRInt32* aOffset, PeekWordState* aState);
|
||||
|
||||
NS_IMETHOD CheckVisibility(nsPresContext* aContext, PRInt32 aStartIndex, PRInt32 aEndIndex, PRBool aRecurse, PRBool *aFinished, PRBool *_retval);
|
||||
|
||||
@ -4669,6 +4671,7 @@ public:
|
||||
PRInt32 GetBeforeOffset();
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsIUGenCategory> mCategories;
|
||||
gfxSkipCharsIterator mIterator;
|
||||
const nsTextFragment* mFrag;
|
||||
nsTextFrame* mTextFrame;
|
||||
@ -4742,7 +4745,10 @@ PRBool
|
||||
ClusterIterator::IsPunctuation()
|
||||
{
|
||||
NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
|
||||
return nsTextFrameUtils::IsPunctuationMark(mFrag->CharAt(mCharIndex));
|
||||
if (!mCategories)
|
||||
return PR_FALSE;
|
||||
nsIUGenCategory::nsUGenCategory c = mCategories->Get(mFrag->CharAt(mCharIndex));
|
||||
return c == nsIUGenCategory::kPunctuation || c == nsIUGenCategory::kSymbol;
|
||||
}
|
||||
|
||||
PRBool
|
||||
@ -4810,26 +4816,32 @@ ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, PRInt32 aPosition,
|
||||
}
|
||||
mIterator.SetOriginalOffset(aPosition);
|
||||
|
||||
mCategories = do_GetService(NS_UNICHARCATEGORY_CONTRACTID);
|
||||
|
||||
mFrag = aTextFrame->GetContent()->GetText();
|
||||
mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag, PR_TRUE);
|
||||
|
||||
PRInt32 textLen = aTextFrame->GetContentLength();
|
||||
if (!mWordBreaks.AppendElements(textLen)) {
|
||||
if (!mWordBreaks.AppendElements(textLen + 1)) {
|
||||
mDirection = 0; // signal failure
|
||||
return;
|
||||
}
|
||||
memset(mWordBreaks.Elements(), PR_FALSE, textLen);
|
||||
memset(mWordBreaks.Elements(), PR_FALSE, textLen + 1);
|
||||
nsAutoString text;
|
||||
mFrag->AppendTo(text, aTextFrame->GetContentOffset(), textLen);
|
||||
nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker();
|
||||
PRInt32 i = 0;
|
||||
while ((i = wordBreaker->NextWord(text.get(), textLen, i)) >= 0)
|
||||
while ((i = wordBreaker->NextWord(text.get(), textLen, i)) >= 0) {
|
||||
mWordBreaks[i] = PR_TRUE;
|
||||
}
|
||||
// XXX this never allows word breaks at the start or end of the frame, but to fix
|
||||
// this we would need to rewrite word-break detection to use the text from
|
||||
// textruns or something. Not a regression, at least.
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsTextFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
|
||||
PRInt32* aOffset, PRBool* aSawBeforeType)
|
||||
PRInt32* aOffset, PeekWordState* aState)
|
||||
{
|
||||
PRInt32 contentLength = GetContentLength();
|
||||
NS_ASSERTION (aOffset && *aOffset <= contentLength, "aOffset out of range");
|
||||
@ -4846,24 +4858,25 @@ nsTextFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool
|
||||
if (!cIter.NextCluster())
|
||||
return PR_FALSE;
|
||||
|
||||
PRBool stopAfterPunctuation =
|
||||
nsContentUtils::GetBoolPref("layout.word_select.stop_at_punctuation");
|
||||
PRBool stopBeforePunctuation = stopAfterPunctuation && !aIsKeyboardSelect;
|
||||
do {
|
||||
if (aWordSelectEatSpace == cIter.IsWhitespace() && !*aSawBeforeType) {
|
||||
*aSawBeforeType = PR_TRUE;
|
||||
PRBool isPunctuation = cIter.IsPunctuation();
|
||||
if (aWordSelectEatSpace == cIter.IsWhitespace() && !aState->mSawBeforeType) {
|
||||
aState->SetSawBeforeType();
|
||||
aState->Update(isPunctuation);
|
||||
continue;
|
||||
}
|
||||
if (cIter.GetBeforeOffset() != offset &&
|
||||
(cIter.IsPunctuation() ? stopBeforePunctuation
|
||||
: cIter.HaveWordBreakBefore() && *aSawBeforeType)) {
|
||||
*aOffset = cIter.GetBeforeOffset() - mContentOffset;
|
||||
return PR_TRUE;
|
||||
}
|
||||
if (stopAfterPunctuation && cIter.IsPunctuation()) {
|
||||
*aOffset = cIter.GetAfterOffset() - mContentOffset;
|
||||
return PR_TRUE;
|
||||
// See if we can break before the current cluster
|
||||
if (!aState->mAtStart) {
|
||||
PRBool canBreak = isPunctuation != aState->mLastCharWasPunctuation
|
||||
? BreakWordBetweenPunctuation(aForward ? aState->mLastCharWasPunctuation : isPunctuation,
|
||||
aIsKeyboardSelect)
|
||||
: cIter.HaveWordBreakBefore() && aState->mSawBeforeType;
|
||||
if (canBreak) {
|
||||
*aOffset = cIter.GetBeforeOffset() - mContentOffset;
|
||||
return PR_TRUE;
|
||||
}
|
||||
}
|
||||
aState->Update(isPunctuation);
|
||||
} while (cIter.NextCluster());
|
||||
|
||||
*aOffset = cIter.GetAfterOffset() - mContentOffset;
|
||||
|
Loading…
Reference in New Issue
Block a user