blob: 8177aaa16fde6978529e72896fd2e33483c397e9 [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.
*/
package org.netbeans.modules.editor.actions;
import java.awt.event.ActionEvent;
import java.util.Map;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.JTextComponent;
import javax.swing.text.Position;
import org.netbeans.api.editor.EditorActionRegistration;
import org.netbeans.api.editor.caret.CaretInfo;
import org.netbeans.api.editor.caret.CaretMoveContext;
import org.netbeans.api.editor.caret.EditorCaret;
import org.netbeans.editor.BaseCaret;
import org.netbeans.editor.BaseDocument;
import org.netbeans.editor.BaseKit;
import org.netbeans.editor.Utilities;
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
import org.netbeans.modules.editor.lib2.RectangularSelectionUtils;
import org.netbeans.modules.editor.lib2.typinghooks.CamelCaseInterceptorsManager;
import org.netbeans.spi.editor.AbstractEditorAction;
import org.netbeans.spi.editor.caret.CaretMoveHandler;
public class CamelCaseActions {
/* package */ static final String deleteNextCamelCasePosition = "delete-next-camel-case-position"; //NOI18N
static final String SYSTEM_ACTION_CLASS_NAME_PROPERTY = "systemActionClassName";
public abstract static class CamelCaseAction extends AbstractEditorAction {
public CamelCaseAction(Map<String, ?> attrs) {
super(attrs);
}
public CamelCaseAction() {
}
@Override
public void actionPerformed(final ActionEvent evt, final JTextComponent target) {
if (target != null) {
if (!target.isEditable() || !target.isEnabled()) {
target.getToolkit().beep();
return;
}
final Caret caret = target.getCaret();
final BaseDocument doc = (BaseDocument)target.getDocument();
if(caret instanceof EditorCaret) {
final EditorCaret editorCaret = (EditorCaret) caret;
// Document's lock must come before carets' lock
doc.runAtomicAsUser(new Runnable() {
@Override
public void run() {
editorCaret.moveCarets(new CaretMoveHandler() {
@Override
public void moveCarets(final CaretMoveContext context) {
for (final CaretInfo caretInfo : editorCaret.getSortedCarets()) {
// Do a transaction for each modification
// TBD beforeChange() and afterChange() are called under doc-lock but imho this semantics of interceptors should be revisited anyway
final CamelCaseInterceptorsManager.Transaction t = CamelCaseInterceptorsManager.getInstance().openTransaction(target, caretInfo.getDot(), !isForward());
try {
if (!t.beforeChange()) {
boolean result = false;
if (doesTypingModification()) {
DocumentUtilities.setTypingModification(doc, true);
}
Object[] r = t.change();
try {
int dotPos = caretInfo.getDot();
int wsPos;
if (r == null) {
if (isForward()) {
int eolPos = Utilities.getRowEnd(doc, dotPos);
wsPos = Utilities.getNextWord(target, dotPos);
wsPos = (dotPos == eolPos) ? wsPos : Math.min(eolPos, wsPos);
} else {
int bolPos = Utilities.getRowStart(doc, dotPos);
wsPos = Utilities.getPreviousWord(target, dotPos);
wsPos = (dotPos == bolPos) ? wsPos : Math.max(bolPos, wsPos);
}
} else {
wsPos = (Integer) r[0];
}
if (isForward()) {
moveToNewOffset(context, caretInfo, dotPos, wsPos - dotPos);
} else {
moveToNewOffset(context, caretInfo, wsPos, dotPos - wsPos);
}
result = true;
} catch (BadLocationException e) {
target.getToolkit().beep();
} finally {
if (doesTypingModification()) {
DocumentUtilities.setTypingModification(doc, false);
}
}
if (result) {
t.afterChange();
}
}
} finally {
t.close();
}
}
}
});
}
});
} else {
final CamelCaseInterceptorsManager.Transaction t = CamelCaseInterceptorsManager.getInstance().openTransaction(target, caret.getDot(), !isForward());
try {
if (!t.beforeChange()) {
final Boolean [] result = new Boolean [] { Boolean.FALSE };
doc.runAtomicAsUser(new Runnable() {
@Override
public void run() {
if (doesTypingModification()) {
DocumentUtilities.setTypingModification(doc, true);
}
Object[] r = t.change();
try {
int dotPos = caret.getDot();
int wsPos;
if (r == null) {
if (isForward()) {
int eolPos = Utilities.getRowEnd(doc, dotPos);
wsPos = Utilities.getNextWord(target, dotPos);
wsPos = (dotPos == eolPos) ? wsPos : Math.min(eolPos, wsPos);
} else {
int bolPos = Utilities.getRowStart(doc, dotPos);
wsPos = Utilities.getPreviousWord(target, dotPos);
wsPos = (dotPos == bolPos) ? wsPos : Math.max(bolPos, wsPos);
}
} else {
wsPos = (Integer) r[0];
}
if (isForward()) {
moveToNewOffset(target, dotPos, wsPos - dotPos);
} else {
moveToNewOffset(target, wsPos, dotPos - wsPos);
}
result[0] = Boolean.TRUE;
} catch (BadLocationException e) {
target.getToolkit().beep();
} finally {
if (doesTypingModification()) {
DocumentUtilities.setTypingModification(doc, false);
}
}
}
});
if(result[0].booleanValue()) {
t.afterChange();
}
}
} finally {
t.close();
}
}
}
}
protected abstract boolean isForward();
protected boolean doesTypingModification() {
return false;
}
protected abstract void moveToNewOffset(JTextComponent target, int offset, int length) throws BadLocationException;
protected abstract void moveToNewOffset(CaretMoveContext context, CaretInfo caretInfo, int offset, int length) throws BadLocationException;
}
@EditorActionRegistration(name = BaseKit.removeNextWordAction)
public static class RemoveWordNextAction extends CamelCaseAction {
@Override
protected boolean isForward() {
return true;
}
@Override
protected void moveToNewOffset(JTextComponent target, int offset, int length) throws BadLocationException {
target.getDocument().remove(offset, length);
}
@Override
protected void moveToNewOffset(CaretMoveContext context, CaretInfo caretInfo, int offset, int length) throws BadLocationException {
context.getDocument().remove(offset, length);
}
@Override
protected boolean doesTypingModification() {
return true;
}
}
@EditorActionRegistration(name = BaseKit.removePreviousWordAction)
public static class RemoveWordPreviousAction extends CamelCaseAction {
@Override
protected boolean isForward() {
return false;
}
@Override
protected void moveToNewOffset(JTextComponent target, int offset, int length) throws BadLocationException {
target.getDocument().remove(offset, length);
}
@Override
protected void moveToNewOffset(CaretMoveContext context, CaretInfo caretInfo, int offset, int length) throws BadLocationException {
context.getDocument().remove(offset, length);
}
@Override
protected boolean doesTypingModification() {
return true;
}
}
@EditorActionRegistration(name = DefaultEditorKit.nextWordAction)
public static class NextCamelCasePosition extends CamelCaseAction {
@Override
protected boolean isForward() {
return true;
}
@Override
protected void moveToNewOffset(JTextComponent target, int offset, int length) throws BadLocationException {
target.setCaretPosition(offset+length);
}
@Override
protected void moveToNewOffset(CaretMoveContext context, CaretInfo caretInfo, int offset, int length) throws BadLocationException {
Position pos = context.getDocument().createPosition(offset + length);
context.setDot(caretInfo, pos, Position.Bias.Forward);
}
}
@EditorActionRegistration(name = DefaultEditorKit.previousWordAction)
public static class PreviousCamelCasePosition extends CamelCaseAction {
@Override
protected boolean isForward() {
return false;
}
@Override
protected void moveToNewOffset(JTextComponent target, int offset, int length) throws BadLocationException {
target.setCaretPosition(offset);
}
@Override
protected void moveToNewOffset(CaretMoveContext context, CaretInfo caretInfo, int offset, int length) throws BadLocationException {
Position pos = context.getDocument().createPosition(offset);
context.setDot(caretInfo, pos, Position.Bias.Forward);
}
}
@EditorActionRegistration(name = DefaultEditorKit.selectionNextWordAction)
public static class SelectNextCamelCasePosition extends CamelCaseAction {
@Override
protected boolean isForward() {
return true;
}
protected void moveToNewOffset(JTextComponent target, int offset, int length) throws BadLocationException {
Caret caret = target.getCaret();
if (caret instanceof BaseCaret && RectangularSelectionUtils.isRectangularSelection(target)) {
((BaseCaret) caret).extendRectangularSelection(true, true);
} else {
target.getCaret().moveDot(offset + length);
}
}
@Override
protected void moveToNewOffset(CaretMoveContext context, CaretInfo caretInfo, int offset, int length) throws BadLocationException {
Position pos = context.getDocument().createPosition(offset + length);
context.moveDot(caretInfo, pos, Position.Bias.Forward);
}
}
@EditorActionRegistration(name = DefaultEditorKit.selectionPreviousWordAction)
public static class SelectPreviousCamelCasePosition extends CamelCaseAction {
@Override
protected boolean isForward() {
return false;
}
protected void moveToNewOffset(JTextComponent target, int offset, int length) throws BadLocationException {
Caret caret = target.getCaret();
if (caret instanceof BaseCaret && RectangularSelectionUtils.isRectangularSelection(target)) {
((BaseCaret) caret).extendRectangularSelection(false, true);
} else {
target.getCaret().moveDot(offset);
}
}
@Override
protected void moveToNewOffset(CaretMoveContext context, CaretInfo caretInfo, int offset, int length) throws BadLocationException {
Position pos = context.getDocument().createPosition(offset);
context.moveDot(caretInfo, pos, Position.Bias.Forward);
}
}
}