// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed 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.
package com.google.collide.client.code.autocomplete;
import static com.google.collide.shared.document.util.PositionUtils.getPosition;
import com.google.collide.client.editor.Editor;
import com.google.collide.client.editor.EditorDocumentMutator;
import com.google.collide.client.editor.selection.SelectionModel;
import com.google.collide.client.util.logging.Log;
import com.google.collide.shared.document.Position;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
/**
* Implementation that allows to apply most common autocompletions.
*
*/
public class DefaultAutocompleteResult implements AutocompleteResult {
/**
* Empty result.
*
* <ul>
* <li>if there is no selection - does nothing
* <li>if something is selected - deletes selected text
* </ul>
*/
public static final DefaultAutocompleteResult EMPTY = new DefaultAutocompleteResult(
"", 0, 0, 0, 0, PopupAction.CLOSE, "");
/**
* Result that moves cursor to the right on 1 character and closes popup.
*
* <p>This result is used to bypass unintended user input. For example, when
* user enters quote twice, first quote is explicitly doubled, and the
* second one must be bypassed.
*/
public static final DefaultAutocompleteResult PASS_CHAR = new DefaultAutocompleteResult(
"", 1, 0, 0, 0, PopupAction.CLOSE, "");
/**
* Number of symbols to expand selection to the left before replacement.
*/
private final int backspaceCount;
/**
* Number of symbols to expand selection to the right before replacement.
*/
private final int deleteCount;
/**
* Text to be inserted at cursor position.
*/
private final String autocompletionText;
/**
* Number of chars, relative to beginning of replacement to move cursor right.
*/
private final int jumpLength;
/**
* Length of selection (in chars) before cursor position after jump.
*/
private final int selectionCount;
/**
* String that guards from applying result when context has changed.
*
* <p>{@link Autocompleter#applyChanges} checks that text before selection
* (cursor) is the same as {@link #preContentSuffix} and refuses to apply
* result if it's not truth.
*
* <p>If suffix is matched, then it is removed. That way one can replace
* template shortcut with real template content.
*/
private final String preContentSuffix;
/**
* Action over popup when completion is applied.
*/
private final PopupAction popupAction;
public DefaultAutocompleteResult(String autocompletionText, int jumpLength, int backspaceCount,
int selectionCount, int deleteCount, PopupAction popupAction, String preContentSuffix) {
Preconditions.checkState(jumpLength >= 0, "negative jump length");
Preconditions.checkState(backspaceCount >= 0, "negative backspace count");
Preconditions.checkState(selectionCount >= 0, "negative select count");
Preconditions.checkState(deleteCount >= 0, "negative delete count");
Preconditions.checkState(selectionCount <= jumpLength, "select count > jump length");
this.autocompletionText = autocompletionText;
this.jumpLength = jumpLength;
this.backspaceCount = backspaceCount;
this.selectionCount = selectionCount;
this.deleteCount = deleteCount;
this.popupAction = popupAction;
this.preContentSuffix = preContentSuffix;
}
/**
* Creates simple textual insertion result.
*
* <p>Created instance describes insertion of specified text with matching
* (see {@link #preContentSuffix}), without additional deletions and without
* selection; after applying insertion popup is closed.
*/
public DefaultAutocompleteResult(String autocompletionText, String preContentSuffix,
int jumpLength) {
this(autocompletionText, jumpLength, 0, 0, 0, PopupAction.CLOSE, preContentSuffix);
}
@VisibleForTesting
public String getAutocompletionText() {
return autocompletionText;
}
@VisibleForTesting
public int getJumpLength() {
return jumpLength;
}
@VisibleForTesting
public int getBackspaceCount() {
return backspaceCount;
}
@VisibleForTesting
public int getDeleteCount() {
return deleteCount;
}
@Override
public PopupAction getPopupAction() {
return popupAction;
}
@Override
public void apply(Editor editor) {
SelectionModel selection = editor.getSelection();
Position[] selectionRange = selection.getSelectionRange(false);
boolean selectionChanged = false;
// 1) New beginning of selection based on suffix-matching and
// backspaceCount
Position selectionStart = selectionRange[0];
int selectionStartColumn = selectionStart.getColumn();
String textBefore = selectionStart.getLine().getText().substring(0, selectionStartColumn);
if (!textBefore.endsWith(preContentSuffix)) {
Log.warn(getClass(),
"expected suffix [" + preContentSuffix + "] do not match [" + textBefore + "]");
return;
}
int matchCount = preContentSuffix.length();
int leftOffset = backspaceCount + matchCount;
if (leftOffset > 0) {
selectionStart = getPosition(selectionStart, -leftOffset);
selectionChanged = true;
}
// 2) Calculate end of selection
Position selectionEnd = selectionRange[1];
if (deleteCount > 0) {
selectionEnd = getPosition(selectionEnd, deleteCount);
selectionChanged = true;
}
// 3) Set selection it was changed.
if (selectionChanged) {
selection.setSelection(selectionStart.getLineInfo(), selectionStart.getColumn(),
selectionEnd.getLineInfo(), selectionEnd.getColumn());
}
// 4) Replace selection
EditorDocumentMutator mutator = editor.getEditorDocumentMutator();
if (selection.hasSelection() || autocompletionText.length() > 0) {
mutator.insertText(selectionStart.getLine(), selectionStart.getLineNumber(),
selectionStart.getColumn(), autocompletionText);
}
// 5) Move cursor / set final selection
selectionEnd = getPosition(selectionStart, jumpLength);
if (selectionCount == 0) {
selection.setCursorPosition(selectionEnd.getLineInfo(), selectionEnd.getColumn());
} else {
selectionStart = getPosition(selectionStart, jumpLength - selectionCount);
selection.setSelection(selectionStart.getLineInfo(), selectionStart.getColumn(),
selectionEnd.getLineInfo(), selectionEnd.getColumn());
}
}
@Override
public String toString() {
return "SimpleAutocompleteResult{" +
"backspaceCount=" + backspaceCount +
", deleteCount=" + deleteCount +
", autocompletionText='" + autocompletionText + '\'' +
", jumpLength=" + jumpLength +
", selectionCount=" + selectionCount +
", expectedSuffix='" + preContentSuffix + '\'' +
", popupAction=" + popupAction +
'}';
}
}