Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit f37a97a

Browse files
authored
Merge pull request #1287 from ckeditor/t/ckeditor5-typing/92
Feature: Add support for the `'word'` unit in the `modifySelection()` helper.
2 parents 4b7d435 + e0a0766 commit f37a97a

File tree

2 files changed

+468
-2
lines changed

2 files changed

+468
-2
lines changed

src/model/utils/modifyselection.js

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import Range from '../range';
1313
import { isInsideSurrogatePair, isInsideCombinedSymbol } from '@ckeditor/ckeditor5-utils/src/unicode';
1414
import DocumentSelection from '../documentselection';
1515

16+
const wordBoundaryCharacters = ' ,.?!:;"-()';
17+
1618
/**
1719
* Modifies the selection. Currently, the supported modifications are:
1820
*
@@ -31,6 +33,7 @@ import DocumentSelection from '../documentselection';
3133
* For example `𨭎` is represented in `String` by `\uD862\uDF4E`. Both `\uD862` and `\uDF4E` do not have any meaning
3234
* outside the pair (are rendered as ? when alone). Position between them would be incorrect. In this case, selection
3335
* extension will include whole "surrogate pair".
36+
* * `'word'` - moves selection by a whole word.
3437
*
3538
* **Note:** if you extend a forward selection in a backward direction you will in fact shrink it.
3639
*
@@ -39,7 +42,7 @@ import DocumentSelection from '../documentselection';
3942
* @param {module:engine/model/selection~Selection} selection The selection to modify.
4043
* @param {Object} [options]
4144
* @param {'forward'|'backward'} [options.direction='forward'] The direction in which the selection should be modified.
42-
* @param {'character'|'codePoint'} [options.unit='character'] The unit by which selection should be modified.
45+
* @param {'character'|'codePoint'|'word'} [options.unit='character'] The unit by which selection should be modified.
4346
*/
4447
export default function modifySelection( model, selection, options = {} ) {
4548
const schema = model.schema;
@@ -79,11 +82,17 @@ export default function modifySelection( model, selection, options = {} ) {
7982
}
8083

8184
// Checks whether the selection can be extended to the the walker's next value (next position).
85+
// @param {{ walker, unit, isForward, schema }} data
86+
// @param {module:engine/view/treewalker~TreeWalkerValue} value
8287
function tryExtendingTo( data, value ) {
8388
// If found text, we can certainly put the focus in it. Let's just find a correct position
8489
// based on the unit.
8590
if ( value.type == 'text' ) {
86-
return getCorrectPosition( data.walker, data.unit );
91+
if ( data.unit === 'word' ) {
92+
return getCorrectWordBreakPosition( data.walker, data.isForward );
93+
}
94+
95+
return getCorrectPosition( data.walker, data.unit, data.isForward );
8796
}
8897

8998
// Entering an element.
@@ -117,6 +126,9 @@ function tryExtendingTo( data, value ) {
117126

118127
// Finds a correct position by walking in a text node and checking whether selection can be extended to given position
119128
// or should be extended further.
129+
//
130+
// @param {module:engine/model/treewalker~TreeWalker} walker
131+
// @param {String} unit The unit by which selection should be modified.
120132
function getCorrectPosition( walker, unit ) {
121133
const textNode = walker.position.textNode;
122134

@@ -134,6 +146,45 @@ function getCorrectPosition( walker, unit ) {
134146
return walker.position;
135147
}
136148

149+
// Finds a correct position of a word break by walking in a text node and checking whether selection can be extended to given position
150+
// or should be extended further.
151+
//
152+
// @param {module:engine/model/treewalker~TreeWalker} walker
153+
// @param {Boolean} isForward Is the direction in which the selection should be modified is forward.
154+
function getCorrectWordBreakPosition( walker, isForward ) {
155+
let textNode = walker.position.textNode;
156+
157+
if ( textNode ) {
158+
let offset = walker.position.offset - textNode.startOffset;
159+
160+
while ( !isAtWordBoundary( textNode.data, offset, isForward ) && !isAtNodeBoundary( textNode, offset, isForward ) ) {
161+
walker.next();
162+
163+
// Check of adjacent text nodes with different attributes (like BOLD).
164+
// Example : 'foofoo []bar<$text bold="true">bar</$text> bazbaz'
165+
// should expand to : 'foofoo [bar<$text bold="true">bar</$text>] bazbaz'.
166+
const nextNode = isForward ? walker.position.nodeAfter : walker.position.nodeBefore;
167+
168+
if ( nextNode ) {
169+
// Check boundary char of an adjacent text node.
170+
const boundaryChar = nextNode.data.charAt( isForward ? 0 : nextNode.data.length - 1 );
171+
172+
// Go to the next node if the character at the boundary of that node belongs to the same word.
173+
if ( !wordBoundaryCharacters.includes( boundaryChar ) ) {
174+
// If adjacent text node belongs to the same word go to it & reset values.
175+
walker.next();
176+
177+
textNode = walker.position.textNode;
178+
}
179+
}
180+
181+
offset = walker.position.offset - textNode.startOffset;
182+
}
183+
}
184+
185+
return walker.position;
186+
}
187+
137188
function getSearchRange( start, isForward ) {
138189
const root = start.root;
139190
const searchEnd = Position.createAt( root, isForward ? 'end' : 0 );
@@ -144,3 +195,24 @@ function getSearchRange( start, isForward ) {
144195
return new Range( searchEnd, start );
145196
}
146197
}
198+
199+
// Checks if selection is on word boundary.
200+
//
201+
// @param {String} data The text node value to investigate.
202+
// @param {Number} offset Position offset.
203+
// @param {Boolean} isForward Is the direction in which the selection should be modified is forward.
204+
function isAtWordBoundary( data, offset, isForward ) {
205+
// The offset to check depends on direction.
206+
const offsetToCheck = offset + ( isForward ? 0 : -1 );
207+
208+
return wordBoundaryCharacters.includes( data.charAt( offsetToCheck ) );
209+
}
210+
211+
// Checks if selection is on node boundary.
212+
//
213+
// @param {module:engine/model/text~Text} textNode The text node to investigate.
214+
// @param {Number} offset Position offset.
215+
// @param {Boolean} isForward Is the direction in which the selection should be modified is forward.
216+
function isAtNodeBoundary( textNode, offset, isForward ) {
217+
return offset === ( isForward ? textNode.endOffset : 0 );
218+
}

0 commit comments

Comments
 (0)