blob: db8cb997445901f3d4a9f4b865fd6d51a7ed7890 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
var Input_removePosition;
var Input_addPosition;
var Input_getPosition;
var Input_textInRange;
var Input_replaceRange;
var Input_selectedTextRange;
var Input_setSelectedTextRange;
var Input_markedTextRange;
var Input_setMarkedText;
var Input_unmarkText;
var Input_forwardSelectionAffinity;
var Input_setForwardSelectionAffinity;
var Input_positionFromPositionOffset;
var Input_positionFromPositionInDirectionOffset;
var Input_comparePositionToPosition;
var Input_offsetFromPositionToPosition;
var Input_positionWithinRangeFarthestInDirection;
var Input_characterRangeByExtendingPositionInDirection;
var Input_firstRectForRange;
var Input_caretRectForPosition;
var Input_closestPositionToPoint;
var Input_closestPositionToPointWithinRange;
var Input_characterRangeAtPoint;
var Input_positionWithinRangeAtCharacterOffset;
var Input_characterOffsetOfPositionWithinRange;
var Input_isAtWordBoundary;
var Input_isAtParagraphBoundary;
var Input_isPositionAtBoundaryGranularityInDirection;
var Input_isPositionWithinTextUnitInDirection;
var Input_toWordBoundary;
var Input_toParagraphBoundary;
var Input_toLineBoundary;
var Input_positionFromPositionToBoundaryInDirection;
var Input_rangeEnclosingPositionWithGranularityInDirection;
// FIXME: ensure updateFormatting() is called after any cursor/selection changes
// FIXME: test capitalisation of on-screen keyboard at start of sentence
(function() {
//function idebug(str)
//{
// debug(str);
//}
var forwardSelection = true;
var positions = new Object();
var BaseIdNull = 0;
var BaseIdDocumentStart = 1;
var BaseIdDocumentEnd = 2;
var BaseIdSelectionStart = 3;
var BaseIdSelectionEnd = 4;
var firstDynamicPosId = 5;
var nextPosId = firstDynamicPosId;
function addPosition(pos)
{
if (pos == null)
return 0;
var copy = new Position(pos.node,pos.offset);
copy.targetX = pos.targetX;
pos = copy;
pos.posId = nextPosId++;
positions[pos.posId] = pos;
Position_track(pos);
return pos.posId;
}
Input_addPosition = addPosition;
function getPosition(posId)
{
if (posId instanceof Position) // for tests
return posId;
if (posId < firstDynamicPosId) {
switch (posId) {
case BaseIdNull: {
return null;
}
case BaseIdDocumentStart: {
var pos = new Position(document.body,0);
pos = Position_closestMatchForwards(pos,Position_okForMovement);
return pos;
}
case BaseIdDocumentEnd: {
var pos = new Position(document.body,document.body.childNodes.length);
pos = Position_closestMatchBackwards(pos,Position_okForMovement);
return pos;
}
case BaseIdSelectionStart: {
var range = Selection_get();
return (range != null) ? range.start : null;
}
case BaseIdSelectionEnd: {
var range = Selection_get();
return (range != null) ? range.end : null;
}
default:
return null;
}
}
if (positions[posId] == null)
throw new Error("No position for pos id "+posId);
return positions[posId];
}
Input_getPosition = getPosition;
// void
Input_removePosition = function(posId)
{
//idebug("Input_removePosition("+posId+")");
var pos = positions[posId];
if (pos == null) {
throw new Error("no position for id "+posId);
}
Position_untrack(pos);
delete positions[posId];
}
// string
Input_textInRange = function(startId,startAdjust,endId,endAdjust)
{
var start = getPosition(startId);
var end = getPosition(endId);
start = positionRight(start,startAdjust);
end = positionRight(end,endAdjust);
if ((start == null) || (end == null))
return "";
var range = new Range(start.node,start.offset,end.node,end.offset);
var result = Range_getText(range);
//idebug("Input_textInRange("+startId+","+startAdjust+","+endId+","+endAdjust+") = "+
// JSON.stringify(result));
return result;
}
// void
Input_replaceRange = function(startId,endId,text)
{
//idebug("Input_replaceRange("+startId+","+endId+","+JSON.stringify(text)+")");
var start = getPosition(startId);
var end = getPosition(endId);
if (start == null)
throw new Error("start is null");
if (end == null)
throw new Error("end is null");
var range = new Range(start.node,start.offset,end.node,end.offset);
Range_trackWhileExecuting(range,function() {
Selection_deleteRangeContents(range,true);
});
range.start = Position_preferTextPosition(range.start);
var node = range.start.node;
var offset = range.start.offset;
if (node.nodeType == Node.TEXT_NODE) {
DOM_insertCharacters(node,offset,text);
Cursor_set(node,offset+text.length);
}
else if (node.nodeType == Node.ELEMENT_NODE) {
var textNode = DOM_createTextNode(document,text);
DOM_insertBefore(node,textNode,node.childNodes[offset]);
Cursor_set(node,offset+1);
}
}
// { startId, endId }
Input_selectedTextRange = function()
{
var range = Selection_get();
if (range == null) {
//idebug("Input_selectedTextRange = null");
return null;
}
else {
var startId = addPosition(range.start);
var endId = addPosition(range.end);
//idebug("Input_selectedTextRange = "+startId+", "+endId);
return { startId: startId,
endId: endId };
}
}
// void
Input_setSelectedTextRange = function(startId,endId)
{
//idebug("Input_setSelectedTextRange("+startId+","+endId+")");
var start = getPosition(startId);
var end = getPosition(endId);
var oldSelection = Selection_get();
var oldStart = (oldSelection != null) ? oldSelection.start : null;
var oldEnd = (oldSelection != null) ? oldSelection.end : null;
Selection_set(start.node,start.offset,end.node,end.offset);
// The positions may have changed as a result of spans being added/removed
var newRange = Selection_get();
start = newRange.start;
end = newRange.end;
if (Position_equal(start,end))
Cursor_ensurePositionVisible(end);
else if (Position_equal(oldStart,start) && !Position_equal(oldEnd,end))
Cursor_ensurePositionVisible(end);
else if (Position_equal(oldEnd,end) && !Position_equal(oldStart,start))
Cursor_ensurePositionVisible(start);
}
// { startId, endId }
Input_markedTextRange = function()
{
//idebug("Input_markedTextRange");
return null;
}
// void
Input_setMarkedText = function(text,startOffset,endOffset)
{
Selection_deleteContents(true);
var oldSel = Selection_get();
Range_trackWhileExecuting(oldSel,function() {
Cursor_insertCharacter(text,false,false,true);
});
var newSel = Selection_get();
Selection_set(oldSel.start.node,oldSel.start.offset,
newSel.end.node,newSel.end.offset,false,true);
}
// void
Input_unmarkText = function()
{
var range = Selection_get();
Cursor_set(range.end.node,range.end.offset);
//idebug("Input_unmarkText");
}
// boolean
Input_forwardSelectionAffinity = function()
{
//idebug("Input_forwardSelectionAffinity");
return forwardSelection;
}
// void
Input_setForwardSelectionAffinity = function(value)
{
//idebug("Input_setForwardSelectionAffinity");
forwardSelection = value;
}
function positionRight(pos,offset)
{
if (offset > 0) {
for (; offset > 0; offset--) {
var next = Position_nextMatch(pos,Position_okForMovement);
if (next == null)
return pos;
pos = next;
}
}
else {
for (; offset < 0; offset++) {
var prev = Position_prevMatch(pos,Position_okForMovement);
if (prev == null)
return pos;
pos = prev;
}
}
return pos;
}
function positionDown(pos,offset)
{
if (offset > 0) {
for (; offset > 0; offset--) {
var below = Text_posBelow(pos);
if (below == null)
return pos;
pos = below;
}
}
else {
for (; offset < 0; offset++) {
var above = Text_posAbove(pos);
if (above == null)
return pos;
pos = above;
}
}
return pos;
}
// posId
Input_positionFromPositionOffset = function(posId,offset)
{
var pos = getPosition(posId);
var res = addPosition(positionRight(pos,offset));
//idebug("Input_positionFromPositionOffset("+posId+","+offset+") = "+res);
return res;
}
// posId
Input_positionFromPositionInDirectionOffset = function(posId,direction,offset)
{
//idebug("Input_positionFromPositionInDirectionOffset("+posId+","+direction+","+offset+")");
var pos = getPosition(posId);
if (direction == "left")
return addPosition(positionRight(pos,-offset));
else if (direction == "right")
return addPosition(positionRight(pos,offset));
else if (direction == "up")
return addPosition(positionDown(pos,-offset));
else if (direction == "down")
return addPosition(positionDown(pos,offset));
else
throw new Error("unknown direction: "+direction);
}
// int
Input_comparePositionToPosition = function(posId1,posId2)
{
//idebug("Input_comparePositionToPosition("+posId1+","+posId2+")");
var pos1 = getPosition(posId1);
var pos2 = getPosition(posId2);
if (pos1 == null)
throw new Error("pos1 is null");
if (pos2 == null)
throw new Error("pos2 is null");
return Position_compare(pos1,pos2);
}
// int
Input_offsetFromPositionToPosition = function(fromId,toId)
{
//idebug("Input_offsetFromPositionToPosition("+fromId+","+toId+")");
throw new Error("offsetFromPositionToPosition: not implemented");
}
Input_positionWithinRangeFarthestInDirection = function(startId,endId,direction)
{
//idebug("Input_positionWithinRangeFarthestInDirection("+startId+","+endId+","+direction);
throw new Error("positionWithinRangeFarthestInDirection: not implemented");
}
// { startId, endId }
Input_characterRangeByExtendingPositionInDirection = function(posId,direction)
{
//idebug("Input_characterRangeByExtendingPositionInDirection("+posId+","+direction);
throw new Error("characterRangeByExtendingPositionInDirection: not implemented");
}
Input_firstRectForRange = function(startId,endId)
{
//idebug("Input_firstRectForRange("+startId+","+endId+")");
var start = getPosition(startId);
var end = getPosition(endId);
var range = new Range(start.node,start.offset,end.node,end.offset);
var rects = Range_getClientRects(range);
if (rects.length == 0)
return { x: 0, y: 0, width: 0, height: 0 };
else
return { x: rects[0].left, y: rects[0].top,
width: rects[0].width, height: rects[0].height };
}
Input_caretRectForPosition = function(posId)
{
//idebug("Input_caretRectForPosition("+posId+")");
var pos = getPosition(posId);
var rect = Position_rectAtPos(pos);
if (rect == null)
return { x: 0, y: 0, width: 0, height: 0 };
else
return { x: rect.left, y: rect.top, width: rect.width, height: rect.height };
}
// posId
Input_closestPositionToPoint = function(x,y)
{
//idebug("Input_closestPositionToPoint("+x+","+y+")");
throw new Error("closestPositionToPoint: not implemented");
}
// posId
Input_closestPositionToPointWithinRange = function(x,y,startId,endId)
{
//idebug("Input_closestPositionToPointWithinRange("+x+","+y+")");
throw new Error("closestPositionToPointWithinRange: not implemented");
}
// { startId, endId }
Input_characterRangeAtPoint = function(x,y)
{
//idebug("Input_characterRangeAtPoint("+x+","+y+")");
throw new Error("characterRangeAtPoint: not implemented");
}
// posId
Input_positionWithinRangeAtCharacterOffset = function(startId,endId,offset)
{
//idebug("Input_positionWithinRangeAtCharacterOffset("+startId+","+endId+","+offset+")");
throw new Error("positionWithinRangeAtCharacterOffset: not implemented");
}
// int
Input_characterOffsetOfPositionWithinRange = function(posId,startId,endId)
{
//idebug("Input_characterOffsetOfPositionWithinRange("+posId+","+startId+","+endId+")");
throw new Error("characterOffsetOfPositionWithinRange: not implemented");
}
// UITextInputTokenizer methods
var punctuation = "!\"#%&',-/:;<=>@`~\\^\\$\\\\\\.\\*\\+\\?\\(\\)\\[\\]\\{\\}\\|";
var letterRE = new RegExp("[^\\s"+punctuation+"]");
var wordAtStartRE = new RegExp("^[^\\s"+punctuation+"]+");
var nonWordAtStartRE = new RegExp("^[\\s"+punctuation+"]+");
var wordAtEndRE = new RegExp("[^\\s"+punctuation+"]+$");
var nonWordAtEndRE = new RegExp("[\\s"+punctuation+"]+$");
function isForward(direction)
{
return ((direction == "forward") ||
(direction == "right") ||
(direction == "down"));
}
Input_isAtWordBoundary = function(pos,direction)
{
if (pos.node.nodeType != Node.TEXT_NODE)
return false;
var paragraph = Text_analyseParagraph(pos);
if (paragraph == null)
return false;
var offset = Paragraph_offsetAtPosition(paragraph,pos);
var before = paragraph.text.substring(0,offset);
var after = paragraph.text.substring(offset);
var text = paragraph.text;
var afterMatch = (offset < text.length) && (text.charAt(offset).match(letterRE));
var beforeMatch = (offset > 0) && (text.charAt(offset-1).match(letterRE));
// coerce to boolean
afterMatch = !!afterMatch;
beforeMatch = !!beforeMatch;
if (isForward(direction))
return beforeMatch && !afterMatch;
else
return !beforeMatch;
}
Input_isAtParagraphBoundary = function(pos,direction)
{
}
Input_isPositionAtBoundaryGranularityInDirection = function(posId,granularity,direction)
{
//idebug("Input_isPositionAtBoundaryGranularityInDirection("+
// posId+","+granularity+","+direction+")");
var pos = getPosition(posId);
if (pos == null)
return false;
// FIXME: Temporary hack to avoid exceptions when running under iOS 8
if ((granularity == "sentence") || (granularity == "document"))
return false;
if (granularity == "character") {
return true;
}
else if (granularity == "word") {
return Input_isAtWordBoundary(pos,direction);
}
else if ((granularity == "paragraph") || (granularity == "line")) {
if (isForward(direction))
return Position_equal(pos,Text_toEndOfBoundary(pos,granularity));
else
return Position_equal(pos,Text_toStartOfBoundary(pos,granularity));
}
else if (granularity == "sentence") {
}
else if (granularity == "document") {
}
throw new Error("unsupported granularity: "+granularity);
}
Input_isPositionWithinTextUnitInDirection = function(posId,granularity,direction)
{
//idebug("Input_isPositionWithinTextUnitInDirection("+
// posId+","+granularity+","+direction+")");
var pos = getPosition(posId);
if (pos == null)
return false;
// FIXME: Temporary hack to avoid exceptions when running under iOS 8
if ((granularity == "sentence") || (granularity == "document"))
return true;
if (granularity == "character") {
return true;
}
else if (granularity == "word") {
pos = Text_closestPosInDirection(pos,direction);
if (pos == null)
return false;
var paragraph = Text_analyseParagraph(pos);
if (paragraph == null)
return false;
if ((pos != null) && (pos.node.nodeType == Node.TEXT_NODE)) {
var offset = Paragraph_offsetAtPosition(paragraph,pos);
var text = paragraph.text;
if (isForward(direction))
return !!((offset < text.length) && (text.charAt(offset).match(letterRE)));
else
return !!((offset > 0) && (text.charAt(offset-1).match(letterRE)));
}
else {
return false;
}
}
else if (granularity == "sentence") {
}
else if ((granularity == "paragraph") || (granularity == "line")) {
var start = Text_toStartOfBoundary(pos,granularity);
var end = Text_toEndOfBoundary(pos,granularity);
start = start ? start : pos;
end = end ? end : pos;
if (isForward(direction)) {
return ((Position_compare(start,pos) <= 0) &&
(Position_compare(pos,end) < 0));
}
else {
return ((Position_compare(start,pos) < 0) &&
(Position_compare(pos,end) <= 0));
}
}
else if (granularity == "document") {
}
throw new Error("unsupported granularity: "+granularity);
}
Input_toWordBoundary = function(pos,direction)
{
pos = Text_closestPosInDirection(pos,direction);
if (pos == null)
return null;
var paragraph = Text_analyseParagraph(pos);
if (paragraph == null)
return null;
var run = Paragraph_runFromNode(paragraph,pos.node);
var offset = pos.offset + run.start;
if (isForward(direction)) {
var remaining = paragraph.text.substring(offset);
var afterWord = remaining.replace(wordAtStartRE,"");
var afterNonWord = remaining.replace(nonWordAtStartRE,"");
if (remaining.length == 0) {
return pos;
}
else if (afterWord.length < remaining.length) {
var newOffset = offset + (remaining.length - afterWord.length);
return Paragraph_positionAtOffset(paragraph,newOffset);
}
else {
var newOffset = offset + (remaining.length - afterNonWord.length);
return Paragraph_positionAtOffset(paragraph,newOffset);
}
}
else {
var remaining = paragraph.text.substring(0,offset);
var beforeWord = remaining.replace(wordAtEndRE,"");
var beforeNonWord = remaining.replace(nonWordAtEndRE,"");
if (remaining.length == 0) {
return pos;
}
else if (beforeWord.length < remaining.length) {
var newOffset = offset - (remaining.length - beforeWord.length);
return Paragraph_positionAtOffset(paragraph,newOffset);
}
else {
var newOffset = offset - (remaining.length - beforeNonWord.length);
return Paragraph_positionAtOffset(paragraph,newOffset);
}
}
}
Input_toParagraphBoundary = function(pos,direction)
{
if (isForward(direction)) {
var end = Text_toEndOfBoundary(pos,"paragraph");
if (Position_equal(pos,end)) {
end = Position_nextMatch(end,Position_okForMovement);
end = Text_toEndOfBoundary(end,"paragraph");
end = Text_toStartOfBoundary(end,"paragraph");
}
return end ? end : pos;
}
else {
var start = Text_toStartOfBoundary(pos,"paragraph");
if (Position_equal(pos,start)) {
start = Position_prevMatch(start,Position_okForMovement);
start = Text_toStartOfBoundary(start,"paragraph");
start = Text_toEndOfBoundary(start,"paragraph");
}
return start ? start : pos;
}
}
Input_toLineBoundary = function(pos,direction)
{
if (isForward(direction)) {
var end = Text_toEndOfBoundary(pos,"line");
return end ? end : pos;
}
else {
var start = Text_toStartOfBoundary(pos,"line");
return start ? start : pos;
}
}
Input_positionFromPositionToBoundaryInDirection = function(posId,granularity,direction)
{
//idebug("Input_positionFromPositionToBoundaryInDirection("+
// posId+","+granularity+","+direction+")");
var pos = getPosition(posId);
if (pos == null)
return null;
// FIXME: Temporary hack to avoid exceptions when running under iOS 8
if (granularity == "sentence")
granularity = "paragraph";
if (granularity == "word")
return addPosition(Input_toWordBoundary(pos,direction));
else if (granularity == "paragraph")
return addPosition(Input_toParagraphBoundary(pos,direction));
else if (granularity == "line")
return addPosition(Input_toLineBoundary(pos,direction));
else if (granularity == "character")
return Input_positionFromPositionInDirectionOffset(posId,direction,1);
else if (granularity == "document")
return isForward(direction) ? BaseIdDocumentEnd : BaseIdDocumentStart;
else
throw new Error("unsupported granularity: "+granularity);
}
Input_rangeEnclosingPositionWithGranularityInDirection = function(posId,granularity,direction)
{
//idebug("Input_rangeEnclosingPositionWithGranularityInDirection("+
// posId+","+granularity+","+direction);
var pos = getPosition(posId);
if (pos == null)
return null;
// FIXME: Temporary hack to avoid exceptions when running under iOS 8
if (granularity == "sentence")
granularity = "paragraph";
if (granularity == "word") {
pos = Text_closestPosInDirection(pos,direction);
if (pos == null)
return null;
var paragraph = Text_analyseParagraph(pos);
if (pos == null)
return addPosition(null);
if (paragraph == null)
return addPosition(null);
var run = Paragraph_runFromNode(paragraph,pos.node);
var offset = pos.offset + run.start;
var before = paragraph.text.substring(0,offset);
var after = paragraph.text.substring(offset);
var beforeWord = before.replace(wordAtEndRE,"");
var afterWord = after.replace(wordAtStartRE,"");
var ok;
if (isForward(direction))
ok = (afterWord.length < after.length);
else
ok = (beforeWord.length < before.length);
if (ok) {
var charsBefore = (before.length - beforeWord.length);
var charsAfter = (after.length - afterWord.length);
var startOffset = offset - charsBefore;
var endOffset = offset + charsAfter;
var startPos = Paragraph_positionAtOffset(paragraph,startOffset);
var endPos = Paragraph_positionAtOffset(paragraph,endOffset);
return { startId: addPosition(startPos),
endId: addPosition(endPos) };
}
else {
return null;
}
}
else if ((granularity == "paragraph") || (granularity == "line")) {
var start = Text_toStartOfBoundary(pos,granularity);
var end = Text_toEndOfBoundary(pos,granularity);
start = start ? start : pos;
end = end ? end : pos;
if ((granularity == "paragraph") || !isForward(direction)) {
if (isForward(direction)) {
if (Position_equal(pos,Text_toEndOfBoundary(pos,granularity)))
return null;
}
else {
if (Position_equal(pos,Text_toStartOfBoundary(pos,granularity)))
return null;
}
}
return { startId: addPosition(start),
endId: addPosition(end) };
}
else {
throw new Error("unsupported granularity: "+granularity);
}
}
})();