This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 257893
Collapse All | Expand All

(-)a/.hgtags (+2 lines)
Lines 962-965 Link Here
962
37fc29481ec42daf8347ecc28cdf38079401b033 release81_beta_base
962
37fc29481ec42daf8347ecc28cdf38079401b033 release81_beta_base
963
ca20d6d4ed09fed2af119a1fd21a2eee31751d9e build_27
963
ca20d6d4ed09fed2af119a1fd21a2eee31751d9e build_27
964
294dca090459562fe1bbe59c5a4e61ab95d24d42 release81_base
964
294dca090459562fe1bbe59c5a4e61ab95d24d42 release81_base
965
5cf4b63c4b9823e6fc4a8137827c99b8a753d773 editor_multi_caret_stable
965
8d2520b6f9672304f6326258c34ed8d2509e6d2a jdk9ea
966
8d2520b6f9672304f6326258c34ed8d2509e6d2a jdk9ea
967
(-)a/editor.actions/src/org/netbeans/modules/editor/actions/AddSelectionElseCaretAction.java (+162 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2016 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.modules.editor.actions;
43
44
import java.awt.Point;
45
import java.awt.Rectangle;
46
import java.awt.event.ActionEvent;
47
import java.util.ArrayList;
48
import java.util.List;
49
import javax.swing.text.BadLocationException;
50
import javax.swing.text.Caret;
51
import javax.swing.text.JTextComponent;
52
import javax.swing.text.Position;
53
import org.netbeans.api.editor.EditorActionNames;
54
import org.netbeans.api.editor.EditorActionRegistration;
55
import org.netbeans.api.editor.EditorActionRegistrations;
56
import org.netbeans.api.editor.caret.EditorCaret;
57
import org.netbeans.api.editor.caret.CaretMoveContext;
58
import org.netbeans.editor.BaseDocument;
59
import org.netbeans.editor.Utilities;
60
import org.netbeans.spi.editor.AbstractEditorAction;
61
import org.netbeans.spi.editor.caret.CaretMoveHandler;
62
63
/**
64
 *
65
 * @author Ralph Ruijs
66
 */
67
@EditorActionRegistrations({
68
    @EditorActionRegistration(name = EditorActionNames.addSelectionElseCaretUpAction),
69
    @EditorActionRegistration(name = EditorActionNames.addSelectionElseCaretDownAction)
70
})
71
public class AddSelectionElseCaretAction extends AbstractEditorAction {
72
73
    @Override
74
    protected void actionPerformed(ActionEvent evt, final JTextComponent target) {
75
        if (target != null) {
76
            Caret caret = target.getCaret();
77
            if (caret != null && caret instanceof EditorCaret) {
78
                final EditorCaret editorCaret = (EditorCaret) caret;
79
                final BaseDocument doc = (BaseDocument) target.getDocument();
80
                if(EditorActionNames.addSelectionElseCaretUpAction.equals(actionName())) {
81
                    doc.runAtomicAsUser(new Runnable() {
82
                        @Override
83
                        public void run() {
84
                            final List<Position> dots = new ArrayList<>(editorCaret.getCarets().size() << 1);
85
                            editorCaret.moveCarets(new CaretMoveHandler() {
86
                                @Override
87
                                public void moveCarets(CaretMoveContext context) {
88
                                    for (org.netbeans.api.editor.caret.CaretInfo caretInfo : context.getOriginalCarets()) {
89
                                        try {
90
                                            int dot = caretInfo.getDot();
91
                                            Point p = caretInfo.getMagicCaretPosition();
92
                                            if (p == null) {
93
                                                Rectangle r = target.modelToView(dot);
94
                                                if (r != null) {
95
                                                    p = new Point(r.x, r.y);
96
                                                    context.setMagicCaretPosition(caretInfo, p);
97
                                                } else {
98
                                                    return; // model to view failed
99
                                                }
100
                                            }
101
                                            try {
102
                                                dot = Utilities.getPositionAbove(target, dot, p.x);
103
                                                Position dotPos = doc.createPosition(dot);
104
                                                dots.add(dotPos);
105
                                                dots.add(dotPos);
106
                                            } catch (BadLocationException e) {
107
                                                // the position stays the same
108
                                            }
109
                                        } catch (BadLocationException ex) {
110
                                            target.getToolkit().beep();
111
                                        }
112
                                    }
113
                                }
114
                            });
115
116
                            editorCaret.addCarets(dots);
117
                        }
118
                    });
119
                } else {
120
                    doc.runAtomicAsUser(new Runnable() {
121
                        @Override
122
                        public void run() {
123
                            final List<Position> dots = new ArrayList<>(editorCaret.getCarets().size() << 1);
124
                            editorCaret.moveCarets(new CaretMoveHandler() {
125
                                @Override
126
                                public void moveCarets(CaretMoveContext context) {
127
                                    for (org.netbeans.api.editor.caret.CaretInfo caretInfo : context.getOriginalCarets()) {
128
                                        try {
129
                                            int dot = caretInfo.getDot();
130
                                            Point p = caretInfo.getMagicCaretPosition();
131
                                            if (p == null) {
132
                                                Rectangle r = target.modelToView(dot);
133
                                                if (r != null) {
134
                                                    p = new Point(r.x, r.y);
135
                                                    context.setMagicCaretPosition(caretInfo, p);
136
                                                } else {
137
                                                    return; // model to view failed
138
                                                }
139
                                            }
140
                                            try {
141
                                                dot = Utilities.getPositionBelow(target, dot, p.x);
142
                                                Position dotPos = doc.createPosition(dot);
143
                                                dots.add(dotPos);
144
                                                dots.add(dotPos);
145
                                            } catch (BadLocationException e) {
146
                                                // position stays the same
147
                                            }
148
                                        } catch (BadLocationException ex) {
149
                                            target.getToolkit().beep();
150
                                        }
151
                                    }
152
                                }
153
                            });
154
                            editorCaret.addCarets(dots);
155
                        }
156
                    });
157
                }
158
            }
159
        }
160
    }
161
162
}
(-)a/editor.actions/src/org/netbeans/modules/editor/actions/Bundle.properties (-1 / +6 lines)
Lines 52-57 Link Here
52
toggle-line-numbers_menu_text=&Show Line Numbers
52
toggle-line-numbers_menu_text=&Show Line Numbers
53
toggle-non-printable-characters=Toggle Non-printable Characters
53
toggle-non-printable-characters=Toggle Non-printable Characters
54
toggle-non-printable-characters_menu_text=Show &Non-printable Characters
54
toggle-non-printable-characters_menu_text=Show &Non-printable Characters
55
toggle-typing-mode=Toggle Typing Mode
55
transpose-letters=Transpose Letters
56
transpose-letters=Transpose Letters
56
goto-declaration=Go to Declaration
57
goto-declaration=Go to Declaration
57
goto-declaration_menu_text=Go to &Declaration
58
goto-declaration_menu_text=Go to &Declaration
Lines 68-71 Link Here
68
caret-previous-word=Insertion Point to Previous Word
69
caret-previous-word=Insertion Point to Previous Word
69
selection-next-word=Extend Selection to Next Word
70
selection-next-word=Extend Selection to Next Word
70
selection-previous-word=Extend Selection to Previous Word
71
selection-previous-word=Extend Selection to Previous Word
71
toggle-lines-view=Show &Indent Guide Lines
72
toggle-lines-view=Show &Indent Guide Lines
73
remove-last-caret=Remove Last Caret
74
remove-last-caret_menu_text=Remove Last Caret
75
add-selection-else-caret-up=Duplicate Insertion Point Up
76
add-selection-else-caret-down=Duplicate Insertion Point Down
(-)a/editor.actions/src/org/netbeans/modules/editor/actions/RemoveLastCaretAction.java (+74 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2016 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.modules.editor.actions;
43
44
import java.awt.event.ActionEvent;
45
import javax.swing.text.Caret;
46
import javax.swing.text.JTextComponent;
47
import org.netbeans.api.editor.EditorActionNames;
48
import org.netbeans.api.editor.EditorActionRegistration;
49
import org.netbeans.api.editor.EditorActionRegistrations;
50
import org.netbeans.api.editor.caret.EditorCaret;
51
import org.netbeans.spi.editor.AbstractEditorAction;
52
53
/**
54
 *
55
 * @author Ralph Ruijs <ralphbenjamin@netbeans.org>
56
 */
57
@EditorActionRegistrations({
58
    @EditorActionRegistration(name = EditorActionNames.removeLastCaret,
59
                              menuPath = "Edit",
60
                              menuPosition = 840,
61
                              menuText = "#" + EditorActionNames.removeLastCaret + "_menu_text")
62
})
63
public class RemoveLastCaretAction extends AbstractEditorAction {
64
65
    @Override
66
    protected void actionPerformed(ActionEvent evt, JTextComponent component) {
67
        Caret caret = component.getCaret();
68
        if(caret instanceof EditorCaret) {
69
            EditorCaret editorCaret = (EditorCaret) caret;
70
            editorCaret.removeLastCaret();
71
        }
72
    }
73
    
74
}
(-)a/editor.actions/src/org/netbeans/modules/editor/actions/ToggleTypingModeAction.java (+76 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2015 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2015 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.modules.editor.actions;
43
44
import java.awt.event.ActionEvent;
45
import javax.swing.text.JTextComponent;
46
import org.netbeans.api.editor.EditorActionNames;
47
import org.netbeans.api.editor.EditorActionRegistration;
48
import org.netbeans.api.editor.EditorUtilities;
49
import org.netbeans.spi.editor.AbstractEditorAction;
50
51
/**
52
 * Switch caret from insert mode to overwrite mode or vice versa.
53
 *
54
 * @author Miloslav Metelka
55
 */
56
57
@EditorActionRegistration(name = EditorActionNames.toggleTypingMode)
58
public final class ToggleTypingModeAction extends AbstractEditorAction {
59
60
    private static final long serialVersionUID = 1L;
61
62
    public ToggleTypingModeAction() {
63
        super();
64
    }
65
66
    @Override
67
    public void actionPerformed(ActionEvent evt, JTextComponent target) {
68
        if (target != null) {
69
            Boolean overwriteMode = (Boolean) target.getClientProperty(EditorUtilities.CARET_OVERWRITE_MODE_PROPERTY);
70
            // Now toggle
71
            overwriteMode = (overwriteMode == null || !overwriteMode) ? Boolean.TRUE : Boolean.FALSE;
72
            target.putClientProperty(EditorUtilities.CARET_OVERWRITE_MODE_PROPERTY, overwriteMode);
73
        }
74
    }
75
76
}
(-)a/editor.document/apichanges.xml (+18 lines)
Lines 112-117 Link Here
112
<!-- ACTUAL CHANGES BEGIN HERE: -->
112
<!-- ACTUAL CHANGES BEGIN HERE: -->
113
113
114
  <changes>
114
  <changes>
115
      <change id="ShiftPositions">
116
          <api name="api"/>
117
          <summary>Added ShiftPositions</summary>
118
          <version major="1" minor="7"/>
119
          <date day="22" month="2" year="2016"/>
120
          <author login="mmetelka"/>
121
          <compatibility source="compatible" binary="compatible" semantic="compatible" addition="yes"/>
122
          <description>
123
              <p>
124
                  Added ShiftPositions class to create and read ShiftPositions.
125
                  The implementation ShiftPos is a position together with a 
126
                  shift of extra columns. This allows for positions behind
127
                  line's last character (newline) or within a tab character.
128
              </p>
129
          </description>
130
          <class name="ShiftPositions" package="org.netbeans.api.editor.document"/>
131
          <issue number="257889"/>
132
      </change>
115
      <change id="TextSearchUtils-getPreviousWordStart">
133
      <change id="TextSearchUtils-getPreviousWordStart">
116
          <api name="api"/>
134
          <api name="api"/>
117
          <summary>Added TextSearchUtils.getPreviousWordStart() method</summary>
135
          <summary>Added TextSearchUtils.getPreviousWordStart() method</summary>
(-)a/editor.document/nbproject/project.properties (-1 / +1 lines)
Lines 1-6 Link Here
1
javac.source=1.7
1
javac.source=1.7
2
javac.compilerargs=-Xlint -Xlint:-serial
2
javac.compilerargs=-Xlint -Xlint:-serial
3
spec.version.base=1.6.0
3
spec.version.base=1.7.0
4
javadoc.arch=${basedir}/arch.xml
4
javadoc.arch=${basedir}/arch.xml
5
javadoc.apichanges=${basedir}/apichanges.xml
5
javadoc.apichanges=${basedir}/apichanges.xml
6
is.autoload=true
6
is.autoload=true
(-)a/editor.document/src/org/netbeans/api/editor/document/ShiftPositions.java (+135 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2015 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2015 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.api.editor.document;
43
44
import javax.swing.text.Position;
45
import org.netbeans.api.annotations.common.NonNull;
46
import org.netbeans.modules.editor.lib2.document.ShiftPos;
47
import org.openide.util.Parameters;
48
49
/**
50
 * A position together with a shift of extra columns.
51
 * This allows for positions behind line's last character (newline) or within a tab character.
52
 *
53
 * @author Miloslav Metelka
54
 * @since 1.7
55
 */
56
public final class ShiftPositions {
57
58
    private ShiftPositions() {
59
        // No instances
60
    }
61
    
62
    /**
63
     * Produce an immutable shift position.
64
     * The returned position acts like the original position and it does not handle in any way
65
     * any subsequent document modifications.
66
     *
67
     * @param pos non-null position. If this is already a shift position its shift
68
     *  gets added to the shift parameter passed to this method.
69
     * @param shift >= 0 number of extra columns added to the position.
70
     *  For 0 the original position gets returned. Negative value throws an IllegalArgumentException.
71
     * @return virtual position whose {@link Position#getOffset()} returns the same value
72
     *  like the getPosition parameter.
73
     */
74
    public static Position create(@NonNull Position pos, int shift) {
75
        Parameters.notNull("pos", pos);   //NOI18N
76
        if (shift > 0) {
77
            if (pos.getClass() == ShiftPos.class) {
78
                return new ShiftPos((ShiftPos)pos, shift);
79
            } else {
80
                return new ShiftPos(pos, shift);
81
            }
82
        } else if (shift == 0) {
83
            return pos;
84
        } else {
85
            throw new IllegalArgumentException("shift=" + shift + " < 0");
86
        }
87
    }
88
89
    /**
90
     * Return shift of a passed virtual position or zero for regular positions.
91
     * @param pos non-null position.
92
     * @return >=0 shift or zero for regular positions.
93
     */
94
    public static int getShift(@NonNull Position pos) {
95
        return getShiftImpl(pos);
96
    }
97
98
    /**
99
     * Compare positions.
100
     * @param pos1 non-null position.
101
     * @param pos2 non-null position.
102
     * @return offset of pos1 minus offset of pos2 or diff of their shifts in case
103
     *  both positions have the same offset.
104
     * @NullPointerException if any passed position is null unless both positions are null
105
     *  in which case the method would return 0.
106
     */
107
    public static int compare(@NonNull Position pos1, @NonNull Position pos2) {
108
        if (pos1 == pos2) {
109
            return 0;
110
        }
111
        int offsetDiff = pos1.getOffset() - pos2.getOffset();
112
        return (offsetDiff != 0) ? offsetDiff : getShiftImpl(pos1) - getShiftImpl(pos2);
113
    }
114
    
115
    /**
116
     * Compare positions by providing their offsets and shifts obtained earlier.
117
     * @param offset1 offset of first position.
118
     * @param shift1 shift of first position.
119
     * @param offset2 offset of second position.
120
     * @param shift2 shift of second position.
121
     * @return offset1 minus offset2 or shift1 minus shift2 in case
122
     *  offset1 and offset2 are equal.
123
     */
124
    public static int compare(int offset1, int shift1, int offset2, int shift2) {
125
        int offsetDiff = offset1 - offset2;
126
        return (offsetDiff != 0) ? offsetDiff : shift1 - shift2;
127
    }
128
    
129
    private static int getShiftImpl(Position pos) {
130
        return (pos.getClass() == ShiftPos.class)
131
                ? ((ShiftPos)pos).getShift()
132
                : 0;
133
    }
134
    
135
}
(-)a/editor.document/src/org/netbeans/modules/editor/lib2/document/ShiftPos.java (+83 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2015 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2015 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.modules.editor.lib2.document;
43
44
import javax.swing.text.Position;
45
46
47
/**
48
 * Implementation of a shift position. The clients should only use methods
49
 * in {@link org.netbeans.api.editor.document.ShiftPositions} and never check
50
 * for an instance of this particular class.
51
 *
52
 * @author Miloslav Metelka
53
 */
54
public final class ShiftPos implements Position {
55
56
    private final Position pos;
57
    
58
    private final int shift;
59
60
    public ShiftPos(Position pos, int shift) {
61
        this.pos = pos;
62
        this.shift = shift;
63
    }
64
    
65
    public ShiftPos(ShiftPos shiftPos, int shift) {
66
        this.pos = shiftPos.pos;
67
        this.shift = shiftPos.shift + shift;
68
    }
69
70
    public Position getPosition() {
71
        return pos;
72
    }
73
    
74
    public int getShift() {
75
        return shift;
76
    }
77
78
    @Override
79
    public int getOffset() {
80
        return pos.getOffset();
81
    }
82
83
}
(-)a/editor.document/test/unit/src/org/netbeans/api/editor/document/ShiftPositionsTest.java (+100 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2016 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.api.editor.document;
43
44
import javax.swing.text.Document;
45
import javax.swing.text.PlainDocument;
46
import javax.swing.text.Position;
47
import org.junit.Test;
48
import static org.junit.Assert.*;
49
50
/**
51
 *
52
 * @author Miloslav Metelka
53
 */
54
public class ShiftPositionsTest {
55
    
56
    public ShiftPositionsTest() {
57
    }
58
59
    @Test
60
    public void testPos() throws Exception {
61
        Document doc = new PlainDocument();
62
        doc.insertString(0, "\t\t\n\n", null);
63
        Position pos1 = doc.createPosition(1);
64
        Position pos2 = doc.createPosition(2);
65
        
66
        Position pos10 = ShiftPositions.create(pos1, 0);
67
        Position pos11 = ShiftPositions.create(pos1, 1);
68
        Position pos20 = ShiftPositions.create(pos2, 0);
69
        Position pos21 = ShiftPositions.create(pos2, 1);
70
        
71
        assertEquals(0, ShiftPositions.getShift(pos1));
72
        assertEquals(0, ShiftPositions.getShift(pos10));
73
        assertEquals(1, ShiftPositions.getShift(pos11));
74
        comparePos(pos1, pos10, 0);
75
        comparePos(pos10, pos11, -1);
76
        comparePos(pos1, pos2, -1);
77
        comparePos(pos10, pos20, -1);
78
        comparePos(pos20, pos21, -1);
79
    }
80
    
81
    private void comparePos(Position pos1, Position pos2, int expectedResult) {
82
        comparePosImpl(pos1, pos2, expectedResult, true);
83
    }
84
85
    private void comparePosImpl(Position pos1, Position pos2, int expectedResult, boolean reverseCompare) {
86
        int result = ShiftPositions.compare(pos1, pos2);
87
        assertEquals("Invalid result=" + result + " when comparing positions pos1=" +
88
                pos1 + " to pos2=" + pos2, expectedResult, result);
89
90
        result = ShiftPositions.compare(pos1.getOffset(), ShiftPositions.getShift(pos1),
91
                pos2.getOffset(), ShiftPositions.getShift(pos2));
92
        assertEquals("Invalid result=" + result + " when comparing positions pos1=" +
93
                pos1 + " to pos2=" + pos2, expectedResult, result);
94
95
        if (reverseCompare) {
96
            comparePosImpl(pos2, pos1, -expectedResult, false);
97
        }
98
    }
99
100
}
(-)a/editor.errorstripe/nbproject/project.xml (+9 lines)
Lines 84-89 Link Here
84
                    </run-dependency>
84
                    </run-dependency>
85
                </dependency>
85
                </dependency>
86
                <dependency>
86
                <dependency>
87
                    <code-name-base>org.netbeans.modules.editor.lib2</code-name-base>
88
                    <build-prerequisite/>
89
                    <compile-dependency/>
90
                    <run-dependency>
91
                        <release-version>1</release-version>
92
                        <specification-version>2.5</specification-version>
93
                    </run-dependency>
94
                </dependency>
95
                <dependency>
87
                    <code-name-base>org.netbeans.modules.editor.mimelookup</code-name-base>
96
                    <code-name-base>org.netbeans.modules.editor.mimelookup</code-name-base>
88
                    <build-prerequisite/>
97
                    <build-prerequisite/>
89
                    <compile-dependency/>
98
                    <compile-dependency/>
(-)a/editor.errorstripe/src/org/netbeans/modules/editor/errorstripe/caret/CaretMarkProvider.java (-12 / +29 lines)
Lines 44-55 Link Here
44
44
45
package org.netbeans.modules.editor.errorstripe.caret;
45
package org.netbeans.modules.editor.errorstripe.caret;
46
import java.util.Collections;
46
import java.util.Collections;
47
import java.util.LinkedList;
47
import java.util.List;
48
import java.util.List;
48
import javax.swing.event.CaretEvent;
49
import javax.swing.event.CaretEvent;
49
import javax.swing.event.CaretListener;
50
import javax.swing.event.CaretListener;
51
import javax.swing.text.Caret;
50
import javax.swing.text.Document;
52
import javax.swing.text.Document;
51
import javax.swing.text.JTextComponent;
53
import javax.swing.text.JTextComponent;
52
import javax.swing.text.StyledDocument;
54
import javax.swing.text.StyledDocument;
55
import org.netbeans.api.editor.caret.CaretInfo;
56
import org.netbeans.api.editor.caret.EditorCaret;
53
import org.netbeans.modules.editor.errorstripe.privatespi.Mark;
57
import org.netbeans.modules.editor.errorstripe.privatespi.Mark;
54
import org.netbeans.modules.editor.errorstripe.privatespi.MarkProvider;
58
import org.netbeans.modules.editor.errorstripe.privatespi.MarkProvider;
55
import org.openide.text.NbDocument;
59
import org.openide.text.NbDocument;
Lines 64-105 Link Here
64
    
68
    
65
    private static final RequestProcessor RP = new RequestProcessor("CaretMarkProvider");
69
    private static final RequestProcessor RP = new RequestProcessor("CaretMarkProvider");
66
    
70
    
67
    private Mark mark;
71
    private List<Mark> marks;
68
    private JTextComponent component;
72
    private JTextComponent component;
69
    
73
    
70
    /** Creates a new instance of AnnotationMarkProvider */
74
    /** Creates a new instance of AnnotationMarkProvider */
71
    public CaretMarkProvider(JTextComponent component) {
75
    public CaretMarkProvider(JTextComponent component) {
72
        this.component = component;
76
        this.component = component;
73
        component.addCaretListener(this);
77
        component.addCaretListener(this);
74
        mark = createMark();
78
        marks = createMarks();
75
    }
79
    }
76
80
77
    private Mark createMark() {
81
    private List<Mark> createMarks() {
78
        int offset = component.getCaretPosition(); //TODO: AWT?
79
        Document doc = component.getDocument();
82
        Document doc = component.getDocument();
80
        int line = 0;
83
        if(!(doc instanceof StyledDocument)) {
81
        
84
            return Collections.singletonList((Mark)new CaretMark(0));
82
        if (doc instanceof StyledDocument) {
83
            line = NbDocument.findLineNumber((StyledDocument) doc, offset);
84
        }
85
        }
85
        
86
        List<Mark> lines = new LinkedList<>();
86
        return new CaretMark(line);
87
        Caret caret = component.getCaret();
88
        if(caret instanceof EditorCaret) {
89
            EditorCaret editorCaret = (EditorCaret) caret;
90
            for (CaretInfo caretInfo : editorCaret.getCarets()) {
91
                int offset = caretInfo.getDot();
92
                int line = NbDocument.findLineNumber((StyledDocument) doc, offset);
93
                lines.add(new CaretMark(line));
94
            }
95
        } else {
96
            int offset = component.getCaretPosition(); //TODO: AWT?
97
            int line = NbDocument.findLineNumber((StyledDocument) doc, offset);
98
            lines.add(new CaretMark(line));
99
        }
100
        return lines;
87
    }
101
    }
88
    
102
    
103
    @Override
89
    public synchronized List<Mark> getMarks() {
104
    public synchronized List<Mark> getMarks() {
90
        return Collections.singletonList(mark);
105
        return Collections.unmodifiableList(marks);
91
    }
106
    }
92
107
108
    @Override
93
    public void caretUpdate(CaretEvent e) {
109
    public void caretUpdate(CaretEvent e) {
94
        final List<Mark> old = getMarks();
110
        final List<Mark> old = getMarks();
95
        
111
        
96
        mark = createMark();
112
        marks = createMarks();
97
        
113
        
98
        final List<Mark> nue = getMarks();
114
        final List<Mark> nue = getMarks();
99
        
115
        
100
        //Do not fire this event under the document's write lock
116
        //Do not fire this event under the document's write lock
101
        //may deadlock with other providers:
117
        //may deadlock with other providers:
102
        RP.post(new Runnable() {
118
        RP.post(new Runnable() {
119
            @Override
103
            public void run() {
120
            public void run() {
104
                firePropertyChange(PROP_MARKS, old, nue);
121
                firePropertyChange(PROP_MARKS, old, nue);
105
            }
122
            }
(-)a/editor.lib/src/org/netbeans/editor/ActionFactory.java (-5 / +13 lines)
Lines 46-51 Link Here
46
46
47
import java.awt.Component;
47
import java.awt.Component;
48
import java.awt.Cursor;
48
import java.awt.Cursor;
49
import java.awt.Point;
49
import java.awt.Rectangle;
50
import java.awt.Rectangle;
50
import java.awt.Toolkit;
51
import java.awt.Toolkit;
51
import java.awt.event.ActionEvent;
52
import java.awt.event.ActionEvent;
Lines 56-61 Link Here
56
import java.lang.ref.Reference;
57
import java.lang.ref.Reference;
57
import java.lang.ref.WeakReference;
58
import java.lang.ref.WeakReference;
58
import java.text.SimpleDateFormat;
59
import java.text.SimpleDateFormat;
60
import java.util.ArrayList;
59
import java.util.Arrays;
61
import java.util.Arrays;
60
import java.util.Date;
62
import java.util.Date;
61
import java.util.HashSet;
63
import java.util.HashSet;
Lines 87-95 Link Here
87
import javax.swing.text.AbstractDocument;
89
import javax.swing.text.AbstractDocument;
88
import javax.swing.text.View;
90
import javax.swing.text.View;
89
import javax.swing.undo.UndoManager;
91
import javax.swing.undo.UndoManager;
92
import org.netbeans.api.editor.caret.CaretInfo;
90
import org.netbeans.api.editor.EditorActionNames;
93
import org.netbeans.api.editor.EditorActionNames;
91
import org.netbeans.api.editor.EditorActionRegistration;
94
import org.netbeans.api.editor.EditorActionRegistration;
92
import org.netbeans.api.editor.EditorActionRegistrations;
95
import org.netbeans.api.editor.EditorActionRegistrations;
96
import org.netbeans.api.editor.EditorUtilities;
97
import org.netbeans.api.editor.caret.EditorCaret;
93
import org.netbeans.api.editor.fold.FoldHierarchy;
98
import org.netbeans.api.editor.fold.FoldHierarchy;
94
import org.netbeans.api.lexer.TokenHierarchy;
99
import org.netbeans.api.lexer.TokenHierarchy;
95
import org.netbeans.api.progress.ProgressUtils;
100
import org.netbeans.api.progress.ProgressUtils;
Lines 98-103 Link Here
98
import org.netbeans.modules.editor.indent.api.Indent;
103
import org.netbeans.modules.editor.indent.api.Indent;
99
import org.netbeans.modules.editor.indent.api.Reformat;
104
import org.netbeans.modules.editor.indent.api.Reformat;
100
import org.netbeans.api.editor.NavigationHistory;
105
import org.netbeans.api.editor.NavigationHistory;
106
import org.netbeans.api.editor.caret.CaretMoveContext;
107
import org.netbeans.spi.editor.caret.CaretMoveHandler;
101
import org.netbeans.modules.editor.lib2.RectangularSelectionUtils;
108
import org.netbeans.modules.editor.lib2.RectangularSelectionUtils;
102
import org.netbeans.modules.editor.lib2.view.DocumentView;
109
import org.netbeans.modules.editor.lib2.view.DocumentView;
103
import org.netbeans.spi.editor.typinghooks.CamelCaseInterceptor;
110
import org.netbeans.spi.editor.typinghooks.CamelCaseInterceptor;
Lines 105-110 Link Here
105
import org.openide.util.ImageUtilities;
112
import org.openide.util.ImageUtilities;
106
import org.openide.util.Lookup;
113
import org.openide.util.Lookup;
107
import org.openide.util.NbBundle;
114
import org.openide.util.NbBundle;
115
import org.openide.util.Pair;
108
import org.openide.util.actions.Presenter;
116
import org.openide.util.actions.Presenter;
109
117
110
/**
118
/**
Lines 854-861 Link Here
854
        }
862
        }
855
    }
863
    }
856
864
857
    /** Switch to overwrite mode or back to insert mode */
865
    /** Switch to overwrite mode or back to insert mode
858
    @EditorActionRegistration(name = BaseKit.toggleTypingModeAction)
866
     * @deprecated Replaced by ToggleTypingModeAction in editor.actions module
867
     */
859
    public static class ToggleTypingModeAction extends LocalBaseAction {
868
    public static class ToggleTypingModeAction extends LocalBaseAction {
860
869
861
        static final long serialVersionUID =-2431132686507799723L;
870
        static final long serialVersionUID =-2431132686507799723L;
Lines 866-877 Link Here
866
875
867
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
876
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
868
            if (target != null) {
877
            if (target != null) {
869
                EditorUI editorUI = Utilities.getEditorUI(target);
878
                Boolean overwriteMode = (Boolean) target.getClientProperty(EditorUtilities.CARET_OVERWRITE_MODE_PROPERTY);
870
                Boolean overwriteMode = (Boolean)editorUI.getProperty(EditorUI.OVERWRITE_MODE_PROPERTY);
871
                // Now toggle
879
                // Now toggle
872
                overwriteMode = (overwriteMode == null || !overwriteMode.booleanValue())
880
                overwriteMode = (overwriteMode == null || !overwriteMode.booleanValue())
873
                                ? Boolean.TRUE : Boolean.FALSE;
881
                                ? Boolean.TRUE : Boolean.FALSE;
874
                editorUI.putProperty(EditorUI.OVERWRITE_MODE_PROPERTY, overwriteMode);
882
                target.putClientProperty(EditorUtilities.CARET_OVERWRITE_MODE_PROPERTY, overwriteMode);
875
            }
883
            }
876
        }
884
        }
877
    }
885
    }
(-)a/editor.lib/src/org/netbeans/editor/BaseCaret.java (-5 / +5 lines)
Lines 137-150 Link Here
137
    /** Caret type representing block covering current character */
137
    /** Caret type representing block covering current character */
138
    public static final String BLOCK_CARET = EditorPreferencesDefaults.BLOCK_CARET; // NOI18N
138
    public static final String BLOCK_CARET = EditorPreferencesDefaults.BLOCK_CARET; // NOI18N
139
139
140
    /** Default caret type */
141
    public static final String LINE_CARET = EditorPreferencesDefaults.LINE_CARET; // NOI18N
142
143
    /** One dot thin line compatible with Swing default caret */
140
    /** One dot thin line compatible with Swing default caret */
144
    public static final String THIN_LINE_CARET = "thin-line-caret"; // NOI18N
141
    public static final String THIN_LINE_CARET = EditorPreferencesDefaults.THIN_LINE_CARET; // NOI18N
145
142
146
    /** @since 1.23 */
143
    /** @since 1.23 */
147
    public static final String THICK_LINE_CARET = "thick-line-caret"; // NOI18N
144
    public static final String THICK_LINE_CARET = EditorPreferencesDefaults.THICK_LINE_CARET; // NOI18N
145
146
    /** Default caret type */
147
    public static final String LINE_CARET = THICK_LINE_CARET; // NOI18N
148
148
149
    /** Boolean property defining whether selection is being rectangular in a particular text component. */
149
    /** Boolean property defining whether selection is being rectangular in a particular text component. */
150
    private static final String RECTANGULAR_SELECTION_PROPERTY = "rectangular-selection"; // NOI18N
150
    private static final String RECTANGULAR_SELECTION_PROPERTY = "rectangular-selection"; // NOI18N
(-)a/editor.lib/src/org/netbeans/editor/BaseKit.java (-165 / +938 lines)
Lines 59-64 Link Here
59
import java.util.Iterator;
59
import java.util.Iterator;
60
import java.util.List;
60
import java.util.List;
61
import java.util.ArrayList;
61
import java.util.ArrayList;
62
import java.util.LinkedList;
62
import java.util.prefs.PreferenceChangeEvent;
63
import java.util.prefs.PreferenceChangeEvent;
63
import javax.swing.Action;
64
import javax.swing.Action;
64
import javax.swing.InputMap;
65
import javax.swing.InputMap;
Lines 88-98 Link Here
88
import javax.swing.event.ChangeListener;
89
import javax.swing.event.ChangeListener;
89
import javax.swing.plaf.TextUI;
90
import javax.swing.plaf.TextUI;
90
import javax.swing.text.AbstractDocument;
91
import javax.swing.text.AbstractDocument;
92
import static javax.swing.text.DefaultEditorKit.selectionBackwardAction;
93
import static javax.swing.text.DefaultEditorKit.selectionBeginLineAction;
94
import static javax.swing.text.DefaultEditorKit.selectionDownAction;
95
import static javax.swing.text.DefaultEditorKit.selectionEndLineAction;
96
import static javax.swing.text.DefaultEditorKit.selectionForwardAction;
97
import static javax.swing.text.DefaultEditorKit.selectionUpAction;
91
import javax.swing.text.EditorKit;
98
import javax.swing.text.EditorKit;
92
import javax.swing.text.Position;
99
import javax.swing.text.Position;
93
import javax.swing.text.View;
100
import javax.swing.text.View;
101
import javax.swing.undo.AbstractUndoableEdit;
102
import javax.swing.undo.CannotRedoException;
103
import javax.swing.undo.CannotUndoException;
104
import org.netbeans.api.editor.caret.CaretInfo;
94
import org.netbeans.api.editor.EditorActionRegistration;
105
import org.netbeans.api.editor.EditorActionRegistration;
95
import org.netbeans.api.editor.EditorActionRegistrations;
106
import org.netbeans.api.editor.EditorActionRegistrations;
107
import org.netbeans.api.editor.caret.EditorCaret;
96
import org.netbeans.api.editor.mimelookup.MimeLookup;
108
import org.netbeans.api.editor.mimelookup.MimeLookup;
97
import org.netbeans.api.editor.mimelookup.MimePath;
109
import org.netbeans.api.editor.mimelookup.MimePath;
98
import org.netbeans.api.editor.settings.KeyBindingSettings;
110
import org.netbeans.api.editor.settings.KeyBindingSettings;
Lines 108-115 Link Here
108
import org.netbeans.modules.editor.lib2.EditorPreferencesKeys;
120
import org.netbeans.modules.editor.lib2.EditorPreferencesKeys;
109
import org.netbeans.modules.editor.lib.KitsTracker;
121
import org.netbeans.modules.editor.lib.KitsTracker;
110
import org.netbeans.api.editor.NavigationHistory;
122
import org.netbeans.api.editor.NavigationHistory;
123
import org.netbeans.api.editor.caret.CaretMoveContext;
124
import org.netbeans.spi.editor.caret.CaretMoveHandler;
111
import org.netbeans.lib.editor.util.swing.PositionRegion;
125
import org.netbeans.lib.editor.util.swing.PositionRegion;
112
import org.netbeans.modules.editor.lib.SettingsConversions;
126
import org.netbeans.modules.editor.lib.SettingsConversions;
127
import org.netbeans.modules.editor.lib2.RectangularSelectionCaretAccessor;
113
import org.netbeans.modules.editor.lib2.RectangularSelectionUtils;
128
import org.netbeans.modules.editor.lib2.RectangularSelectionUtils;
114
import org.netbeans.modules.editor.lib2.actions.KeyBindingsUpdater;
129
import org.netbeans.modules.editor.lib2.actions.KeyBindingsUpdater;
115
import org.netbeans.modules.editor.lib2.typinghooks.DeletedTextInterceptorsManager;
130
import org.netbeans.modules.editor.lib2.typinghooks.DeletedTextInterceptorsManager;
Lines 122-127 Link Here
122
import org.openide.util.LookupEvent;
137
import org.openide.util.LookupEvent;
123
import org.openide.util.LookupListener;
138
import org.openide.util.LookupListener;
124
import org.openide.util.NbBundle;
139
import org.openide.util.NbBundle;
140
import org.openide.util.Pair;
125
import org.openide.util.WeakListeners;
141
import org.openide.util.WeakListeners;
126
import org.openide.util.WeakSet;
142
import org.openide.util.WeakSet;
127
143
Lines 545-551 Link Here
545
561
546
    /** Create caret to navigate through document */
562
    /** Create caret to navigate through document */
547
    public @Override Caret createCaret() {
563
    public @Override Caret createCaret() {
548
        return new BaseCaret();
564
        return new EditorCaret();
549
    }
565
    }
550
566
551
    /** Create empty document */
567
    /** Create empty document */
Lines 1111-1118 Link Here
1111
                            }
1127
                            }
1112
                        });
1128
                        });
1113
                        Caret caret = target.getCaret();
1129
                        Caret caret = target.getCaret();
1114
                        if (caret instanceof BaseCaret) {
1130
                        if (caret instanceof EditorCaret) {
1115
                            ((BaseCaret)caret).setRectangularSelectionToDotAndMark();
1131
                            RectangularSelectionCaretAccessor.get().setRectangularSelectionToDotAndMark((EditorCaret)caret);
1116
                        }
1132
                        }
1117
                        if (changed[0]) {
1133
                        if (changed[0]) {
1118
                            return;
1134
                            return;
Lines 1120-1185 Link Here
1120
                    }
1136
                    }
1121
1137
1122
                    try {
1138
                    try {
1123
                    final Position insertionOffset = doc.createPosition(computeInsertionOffset(target.getCaret()), Position.Bias.Backward);
1139
//                        for (final CaretInfo caret : ((EditorCaret)target.getCaret()).getCarets()) {
1124
                    String replacedText = "";
1140
//                    final Position insertionOffset = doc.createPosition(computeInsertionOffset(caret), Position.Bias.Backward);
1125
                    if (Utilities.isSelectionShowing(target.getCaret())) {
1141
//                    String replacedText = "";
1126
                        int p0 = Math.min(target.getCaret().getDot(), target.getCaret().getMark());
1142
//                    if (target.getCaret().isSelectionVisible() && caret.getDotPosition() != caret.getMarkPosition()) {
1127
                        int p1 = Math.max(target.getCaret().getDot(), target.getCaret().getMark());
1143
//                        int p0 = Math.min(caret.getDot(), caret.getMark());
1128
                        replacedText = doc.getText(p0, p1 - p0);
1144
//                        int p1 = Math.max(caret.getDot(), caret.getMark());
1129
                    }
1145
//                        replacedText = doc.getText(p0, p1 - p0);
1130
                    final TypedTextInterceptorsManager.Transaction transaction = TypedTextInterceptorsManager.getInstance().openTransaction(
1146
//                    }
1131
                            target, insertionOffset, cmd, replacedText);
1147
//                    final TypedTextInterceptorsManager.Transaction transaction = TypedTextInterceptorsManager.getInstance().openTransaction(
1132
                    
1148
//                            target, insertionOffset, cmd, replacedText);
1133
                    try {
1149
//                    
1134
                        if (!transaction.beforeInsertion()) {
1150
//                    try {
1135
                            final Object [] result = new Object [] { Boolean.FALSE, "" }; //NOI18N
1151
//                        if (!transaction.beforeInsertion()) {
1136
                            doc.runAtomicAsUser (new Runnable () {
1152
//                            final Object [] result = new Object [] { Boolean.FALSE, "" }; //NOI18N
1137
                                public void run () {
1153
//                            doc.runAtomicAsUser (new Runnable () {
1138
                                    boolean alreadyBeeped = false;
1154
//                                public void run () {
1139
                                    if (Utilities.isSelectionShowing(target.getCaret())) { // valid selection
1155
//                                    boolean alreadyBeeped = false;
1140
                                        EditorUI editorUI = Utilities.getEditorUI(target);
1156
//                                    if (target.getCaret().isSelectionVisible() && caret.getDot() != caret.getMark()) { // valid selection
1141
                                        Boolean overwriteMode = (Boolean) editorUI.getProperty(EditorUI.OVERWRITE_MODE_PROPERTY);
1157
//                                        EditorUI editorUI = Utilities.getEditorUI(target);
1142
                                        boolean ovr = (overwriteMode != null && overwriteMode.booleanValue());
1158
//                                        Boolean overwriteMode = (Boolean) editorUI.getProperty(EditorUI.OVERWRITE_MODE_PROPERTY);
1159
//                                        boolean ovr = (overwriteMode != null && overwriteMode.booleanValue());
1160
                        final Caret[] caret = {target.getCaret()};
1161
                        
1162
                        if(caret[0] instanceof EditorCaret) {
1163
                            EditorCaret editorCaret = (EditorCaret) caret[0];
1164
                            final List<CaretInfo> carets = editorCaret.getCarets();
1165
                            if(carets.size() > 1) {
1166
                                doc.runAtomicAsUser(new Runnable() {
1167
                                    public void run() {
1168
                                        boolean alreadyBeeped = false;
1169
                                        DocumentUtilities.setTypingModification(doc, true);
1143
                                        try {
1170
                                        try {
1144
                                            doc.putProperty(DOC_REPLACE_SELECTION_PROPERTY, true);
1171
                                        List<Position> dotAndMarkPosPairs = new ArrayList<>(carets.size() << 1);
1145
                                            replaceSelection(target, insertionOffset.getOffset(), target.getCaret(), "", ovr);
1172
                                        for (CaretInfo c : carets) {
1173
                                            dotAndMarkPosPairs.add(c.getDotPosition());
1174
                                            dotAndMarkPosPairs.add(c.getMarkPosition());
1175
                                            if (c.isSelection()) { // valid selection
1176
                                                int p0 = Math.min(c.getDot(), c.getMark());
1177
                                                int p1 = Math.max(c.getDot(), c.getMark());
1178
                                                String replacedText = null;
1179
                                                try {
1180
                                                    replacedText = doc.getText(p0, p1 - p0);
1181
                                                } catch (BadLocationException ble) {
1182
                                                    LOG.log(Level.FINE, null, ble);
1183
                                                    if (!alreadyBeeped) {
1184
                                                        target.getToolkit().beep();
1185
                                                    }
1186
                                                    alreadyBeeped = true;
1187
                                                }
1188
                                                EditorUI editorUI = Utilities.getEditorUI(target);
1189
                                                Boolean overwriteMode = (Boolean) editorUI.getProperty(EditorUI.OVERWRITE_MODE_PROPERTY);
1190
                                                boolean ovr = (overwriteMode != null && overwriteMode.booleanValue());
1191
                                                try {
1192
                                                    doc.putProperty(DOC_REPLACE_SELECTION_PROPERTY, true);
1193
                                                    doc.remove(p0, p1 - p0);
1194
                                                } catch (BadLocationException ble) {
1195
                                                    LOG.log(Level.FINE, null, ble);
1196
                                                    if (!alreadyBeeped) {
1197
                                                        target.getToolkit().beep();
1198
                                                    }
1199
                                                    alreadyBeeped = true;
1200
                                                } finally {
1201
                                                    doc.putProperty(DOC_REPLACE_SELECTION_PROPERTY, null);
1202
                                                }
1203
                                            }
1204
                                            try {
1205
//                                                TBD: Mark for the last caret?
1206
//                                                try {
1207
//                                                    NavigationHistory.getEdits().markWaypoint(target, insertionOffset, false, true);
1208
//                                                } catch (BadLocationException e) {
1209
//                                                    LOG.log(Level.WARNING, "Can't add position to the history of edits.", e); //NOI18N
1210
//                                                }
1211
                                                
1212
                                                final BaseDocument doc = (BaseDocument) target.getDocument();
1213
                                                EditorUI editorUI = Utilities.getEditorUI(target);
1214
                                                editorUI.getWordMatch().clear(); // reset word matching
1215
                                                
1216
                                                int insertionOffset = c.getDot();
1217
                                                Boolean overwriteMode = (Boolean) editorUI.getProperty(EditorUI.OVERWRITE_MODE_PROPERTY);
1218
                                                boolean ovr = (overwriteMode != null && overwriteMode.booleanValue());
1219
                                                if (ovr && insertionOffset < doc.getLength() && doc.getChars(insertionOffset, 1)[0] != '\n') { //NOI18N
1220
                                                    // overwrite current char
1221
                                                    doc.remove(insertionOffset, 1);
1222
                                                    doc.insertString(insertionOffset, cmd, null);
1223
                                                } else { // insert mode
1224
                                                    doc.insertString(insertionOffset, cmd, null);
1225
                                                }
1226
                                            } catch (BadLocationException ble) {
1227
                                                LOG.log(Level.FINE, null, ble);
1228
                                                if (!alreadyBeeped) {
1229
                                                    target.getToolkit().beep();
1230
                                                }
1231
                                            }
1232
                                        }
1233
                                        doc.addUndoableEdit(new CaretEdit(dotAndMarkPosPairs, target));
1234
                                        } finally {
1235
                                            DocumentUtilities.setTypingModification(doc, false);
1236
                                        }
1237
                                    }
1238
                                });
1239
                                return;
1240
                            }
1241
                        }
1242
                        
1243
                        final Position insertionOffset = doc.createPosition(computeInsertionOffset(caret[0]), Position.Bias.Backward);
1244
                        String replacedText = "";
1245
                        if (target.getCaret().isSelectionVisible() && caret[0].getDot() != caret[0].getMark()) {
1246
                            int p0 = Math.min(caret[0].getDot(), caret[0].getMark());
1247
                            int p1 = Math.max(caret[0].getDot(), caret[0].getMark());
1248
                            replacedText = doc.getText(p0, p1 - p0);
1249
                        }
1250
                        final TypedTextInterceptorsManager.Transaction transaction = TypedTextInterceptorsManager.getInstance().openTransaction(
1251
                                target, insertionOffset, cmd, replacedText);
1252
1253
                        try {
1254
                            if (!transaction.beforeInsertion()) {
1255
                                final Object[] result = new Object[]{Boolean.FALSE, ""}; //NOI18N
1256
                                doc.runAtomicAsUser(new Runnable() {
1257
                                    public void run() {
1258
                                        boolean alreadyBeeped = false;
1259
                                        if (target.getCaret().isSelectionVisible() && caret[0].getDot() != caret[0].getMark()) { // valid selection
1260
                                            EditorUI editorUI = Utilities.getEditorUI(target);
1261
                                            Boolean overwriteMode = (Boolean) editorUI.getProperty(EditorUI.OVERWRITE_MODE_PROPERTY);
1262
                                            boolean ovr = (overwriteMode != null && overwriteMode.booleanValue());
1263
                                            try {
1264
                                                doc.putProperty(DOC_REPLACE_SELECTION_PROPERTY, true);
1265
                                                replaceSelection(target, insertionOffset.getOffset(), target.getCaret(), "", ovr);
1266
                                            } catch (BadLocationException ble) {
1267
                                                LOG.log(Level.FINE, null, ble);
1268
                                                target.getToolkit().beep();
1269
                                                alreadyBeeped = true;
1270
                                            } finally {
1271
                                                doc.putProperty(DOC_REPLACE_SELECTION_PROPERTY, null);
1272
                                            }
1273
                                        }
1274
                                        Object[] r = transaction.textTyped();
1275
                                        String insertionText = r == null ? cmd : (String) r[0];
1276
                                        int caretPosition = r == null ? -1 : (Integer) r[1];
1277
1278
                                        try {
1279
                                            performTextInsertion(target, insertionOffset.getOffset(), insertionText, caretPosition);
1280
                                            result[0] = Boolean.TRUE;
1281
                                            result[1] = insertionText;
1146
                                        } catch (BadLocationException ble) {
1282
                                        } catch (BadLocationException ble) {
1147
                                            LOG.log(Level.FINE, null, ble);
1283
                                            LOG.log(Level.FINE, null, ble);
1148
                                            target.getToolkit().beep();
1284
                                            if (!alreadyBeeped) {
1149
                                            alreadyBeeped = true;
1285
                                                target.getToolkit().beep();
1150
                                        }
1286
                                            }
1151
                                        finally {
1152
                                            doc.putProperty(DOC_REPLACE_SELECTION_PROPERTY, null);
1153
                                        }
1287
                                        }
1154
                                    }
1288
                                    }
1155
                                    Object [] r = transaction.textTyped();
1289
                                });
1156
                                    String insertionText = r == null ? cmd : (String) r[0];
1290
1157
                                    int caretPosition = r == null ? -1 : (Integer) r[1];
1291
                                if (((Boolean) result[0]).booleanValue()) {
1158
                                    
1292
                                    transaction.afterInsertion();
1159
                                    try {
1293
1160
                                        performTextInsertion(target, insertionOffset.getOffset(), insertionText, caretPosition);
1294
                                    // XXX: this is potentially wrong and we may need to call this with
1161
                                        result[0] = Boolean.TRUE;
1295
                                    // the original cmd; or maybe only if insertionText == cmd; but maybe
1162
                                        result[1] = insertionText;
1296
                                    // it does not matter, because nobody seems to be overwriting this method anyway
1163
                                    } catch (BadLocationException ble) {
1297
                                    checkIndent(target, (String) result[1]);
1164
                                        LOG.log(Level.FINE, null, ble);
1298
                                } // else text insertion failed
1165
                                        if (!alreadyBeeped)
1299
                            }
1166
                                            target.getToolkit().beep();
1300
                        } finally {
1167
                                    }
1301
                            transaction.close();
1168
                                }
1169
                            });
1170
                            
1171
                            if (((Boolean)result[0]).booleanValue()) {
1172
                                transaction.afterInsertion();
1173
1174
                                // XXX: this is potentially wrong and we may need to call this with
1175
                                // the original cmd; or maybe only if insertionText == cmd; but maybe
1176
                                // it does not matter, because nobody seems to be overwriting this method anyway
1177
                                checkIndent(target, (String)result[1]);
1178
                            } // else text insertion failed
1179
                        }
1302
                        }
1180
                    } finally {
1181
                        transaction.close();
1182
                    }
1183
                    } catch (BadLocationException ble) {
1303
                    } catch (BadLocationException ble) {
1184
                        LOG.log(Level.FINE, null, ble);
1304
                        LOG.log(Level.FINE, null, ble);
1185
                        target.getToolkit().beep();
1305
                        target.getToolkit().beep();
Lines 1548-1553 Link Here
1548
                        public void run () {
1668
                        public void run () {
1549
                            DocumentUtilities.setTypingModification(doc, true);
1669
                            DocumentUtilities.setTypingModification(doc, true);
1550
                            try {
1670
                            try {
1671
                                if(caret instanceof EditorCaret) {
1672
                                    EditorCaret editorCaret = (EditorCaret) caret;
1673
                                    editorCaret.moveCarets(new CaretMoveHandler() {
1674
                                        @Override
1675
                                        public void moveCarets(CaretMoveContext context) {
1676
                                            for (CaretInfo caretInfo : context.getOriginalSortedCarets()) {
1677
                                                if (caretInfo.isSelection()) { // block selected
1678
                                                    try {
1679
                                                        int start = Math.min(caretInfo.getDot(), caretInfo.getMark());
1680
                                                        int end = Math.max(caretInfo.getDot(), caretInfo.getMark());
1681
                                                        String replacedText = doc.getText(start, end - start);
1682
                                                        if (replacedText.trim().isEmpty()) {
1683
                                                            doc.remove(start, end - start);
1684
                                                            insertTabString(doc, start);
1685
                                                        } else {
1686
                                                            boolean selectionAtLineStart = Utilities.getRowStart(doc, start) == start;
1687
                                                            changeBlockIndent(doc, start, end, +1);
1688
                                                            if (selectionAtLineStart) {
1689
                                                                int newSelectionStartOffset = start;
1690
                                                                int lineStartOffset = Utilities.getRowStart(doc, start);
1691
                                                                if (lineStartOffset != newSelectionStartOffset) {
1692
                                                                    target.select(lineStartOffset, end);
1693
                                                                }
1694
                                                            }
1695
                                                        }
1696
                                                    } catch (GuardedException ge) {
1697
                                                        LOG.log(Level.FINE, null, ge);
1698
                                                        target.getToolkit().beep();
1699
                                                    } catch (BadLocationException e) {
1700
                                                        LOG.log(Level.WARNING, null, e);
1701
                                                    }
1702
                                                } else { // no selected text
1703
                                                    int dotOffset = caretInfo.getDot();
1704
                                                    try {
1705
                                                        // is there any char on this line before cursor?
1706
                                                        int indent = Utilities.getRowIndent(doc, dotOffset);
1707
                                                        // test whether we should indent
1708
                                                        if (indent == -1) {
1709
                                                            // find caret column
1710
                                                            int caretCol = Utilities.getVisualColumn(doc, dotOffset);
1711
                                                            // find next tab column
1712
                                                            int nextTabCol = Utilities.getNextTabColumn(doc, dotOffset);
1713
1714
                                                            indenter.reindent(dotOffset);
1715
1716
                                                            dotOffset = caretInfo.getDot();
1717
                                                            int newCaretCol = Utilities.getVisualColumn(doc, dotOffset);
1718
                                                            if (newCaretCol <= caretCol) {
1719
                                                                // find indent of the first previous non-white row
1720
                                                                int upperCol = Utilities.getRowIndent(doc, dotOffset, false);
1721
                                                                changeRowIndent(doc, dotOffset, upperCol > nextTabCol ? upperCol : nextTabCol);
1722
                                                                // Fix of #32240
1723
                                                                dotOffset = caretInfo.getDot();
1724
                                                                Position newDotPos = doc.createPosition(Utilities.getRowEnd(doc, dotOffset));
1725
                                                                context.setDot(caretInfo, newDotPos);
1726
                                                            }
1727
                                                        } else { // already chars on the line
1728
                                                            insertTabString(doc, dotOffset);
1729
                                                        }
1730
                                                    } catch (GuardedException ge) {
1731
                                                        LOG.log(Level.FINE, null, ge);
1732
                                                        target.getToolkit().beep();
1733
                                                    } catch (BadLocationException e) {
1734
                                                        // use the same pos
1735
                                                        target.getToolkit().beep();
1736
                                                        LOG.log(Level.FINE, null, e);
1737
                                                    }
1738
                                                }
1739
                                            }
1740
                                        }
1741
                                    });
1742
                                } else {
1551
                                if (Utilities.isSelectionShowing(caret)) { // block selected
1743
                                if (Utilities.isSelectionShowing(caret)) { // block selected
1552
                                    try {
1744
                                    try {
1553
                                        if (target.getSelectedText().trim().isEmpty()) {
1745
                                        if (target.getSelectedText().trim().isEmpty()) {
Lines 1605-1610 Link Here
1605
                                        LOG.log(Level.FINE, null, e);
1797
                                        LOG.log(Level.FINE, null, e);
1606
                                    }
1798
                                    }
1607
                                }
1799
                                }
1800
                                }
1608
                            } finally {
1801
                            } finally {
1609
                                DocumentUtilities.setTypingModification(doc, false);
1802
                                DocumentUtilities.setTypingModification(doc, false);
1610
                            }
1803
                            }
Lines 1802-1807 Link Here
1802
1995
1803
		final BaseDocument doc = (BaseDocument)target.getDocument();
1996
		final BaseDocument doc = (BaseDocument)target.getDocument();
1804
		final Caret caret = target.getCaret();
1997
		final Caret caret = target.getCaret();
1998
                if (caret instanceof EditorCaret) {
1999
                    final EditorCaret editorCaret = (EditorCaret) caret;
2000
                    final List<CaretInfo> carets = editorCaret.getSortedCarets();
2001
                    if (carets.size() > 1) {
2002
                        doc.runAtomicAsUser(new Runnable() {
2003
                            public void run() {
2004
                                    boolean alreadyBeeped = false;
2005
                                    DocumentUtilities.setTypingModification(doc, true);
2006
                                    try {
2007
                                        List<Position> caretsDotAndMarkPositions = new ArrayList<>(carets.size() << 1);
2008
                                        for (CaretInfo c : carets) {
2009
                                            caretsDotAndMarkPositions.add(c.getDotPosition());
2010
                                            caretsDotAndMarkPositions.add(c.getMarkPosition());
2011
                                            if (c.isSelection()) {
2012
                                                // remove selection
2013
                                                final int dot = c.getDot();
2014
                                                final int mark = c.getMark();
2015
                                                try {
2016
                                                    if (RectangularSelectionUtils.isRectangularSelection(target)) {
2017
                                                        if (!RectangularSelectionUtils.removeSelection(target)) {
2018
                                                            RectangularSelectionUtils.removeChar(target, nextChar);
2019
                                                        }
2020
                                                        RectangularSelectionCaretAccessor.get().setRectangularSelectionToDotAndMark((EditorCaret)caret);
2021
                                                    } else {
2022
                                                        doc.remove(Math.min(dot, mark), Math.abs(dot - mark));
2023
                                                    }
2024
                                                } catch (BadLocationException ble) {
2025
                                                    LOG.log(Level.FINE, null, ble);
2026
                                                    if (!alreadyBeeped) {
2027
                                                        target.getToolkit().beep();
2028
                                                    }
2029
                                                    alreadyBeeped = true;
2030
                                                }
2031
                                            } else {
2032
                                                final int dot = c.getDot();
2033
                                                char[] removedChar = null;
2034
2035
                                                try {
2036
                                                    removedChar = nextChar
2037
                                                            ? dot < doc.getLength() ? doc.getChars(dot, 1) : null
2038
                                                            : dot > 0 ? doc.getChars(dot - 1, 1) : null;
2039
                                                } catch (BadLocationException ble) {
2040
                                                    LOG.log(Level.FINE, null, ble);
2041
                                                    if (!alreadyBeeped) {
2042
                                                        target.getToolkit().beep();
2043
                                                    }
2044
                                                    alreadyBeeped = true;
2045
                                                }
2046
2047
                                                if (removedChar != null) {
2048
                                                    final String removedText = String.valueOf(removedChar);
2049
                                                    try {
2050
                                                        if (nextChar) { // remove next char
2051
                                                            doc.remove(dot, 1);
2052
                                                        } else { // remove previous char
2053
                                                            doc.remove(dot - 1, 1);
2054
                                                        }
2055
                                                    } catch (BadLocationException ble) {
2056
                                                        LOG.log(Level.FINE, null, ble);
2057
                                                        if (!alreadyBeeped) {
2058
                                                            target.getToolkit().beep();
2059
                                                        }
2060
                                                        alreadyBeeped = true;
2061
                                                    }
2062
                                                }
2063
                                            }
2064
                                        }
2065
                                        doc.addUndoableEdit(new CaretEdit(caretsDotAndMarkPositions, target));
2066
                                    } finally {
2067
                                        DocumentUtilities.setTypingModification(doc, false);
2068
                                    }
2069
                                }
2070
                            }
2071
                        );
2072
                        return;
2073
                    }
2074
                }
2075
                    
2076
                // Non-multicaret case
1805
		final int dot = caret.getDot();
2077
		final int dot = caret.getDot();
1806
		final int mark = caret.getMark();
2078
		final int mark = caret.getMark();
1807
                
2079
                
Lines 1811-1826 Link Here
1811
                        public void run () {
2083
                        public void run () {
1812
                            DocumentUtilities.setTypingModification(doc, true);
2084
                            DocumentUtilities.setTypingModification(doc, true);
1813
                            try {
2085
                            try {
2086
                                List<Position> dotAndMarkPosPairs = new ArrayList<>(2);
2087
                                dotAndMarkPosPairs.add(doc.createPosition(caret.getDot()));
2088
                                dotAndMarkPosPairs.add(doc.createPosition(caret.getMark()));
1814
                                if (RectangularSelectionUtils.isRectangularSelection(target)) {
2089
                                if (RectangularSelectionUtils.isRectangularSelection(target)) {
1815
                                    if (!RectangularSelectionUtils.removeSelection(target)) {
2090
                                    if (!RectangularSelectionUtils.removeSelection(target)) {
1816
                                        RectangularSelectionUtils.removeChar(target, nextChar);
2091
                                        RectangularSelectionUtils.removeChar(target, nextChar);
1817
                                    }
2092
                                    }
1818
                                    if (caret instanceof BaseCaret) {
2093
                                    if (caret instanceof EditorCaret) {
1819
                                        ((BaseCaret)caret).setRectangularSelectionToDotAndMark();
2094
                                        RectangularSelectionCaretAccessor.get().setRectangularSelectionToDotAndMark((EditorCaret)caret);
1820
                                    }
2095
                                    }
1821
                                } else {
2096
                                } else {
1822
                                    doc.remove(Math.min(dot, mark), Math.abs(dot - mark));
2097
                                    doc.remove(Math.min(dot, mark), Math.abs(dot - mark));
1823
                                }
2098
                                }
2099
                                doc.addUndoableEdit(new CaretEdit(dotAndMarkPosPairs, target));
1824
                            } catch (BadLocationException e) {
2100
                            } catch (BadLocationException e) {
1825
                                target.getToolkit().beep();
2101
                                target.getToolkit().beep();
1826
                            } finally {
2102
                            } finally {
Lines 1972-1994 Link Here
1972
                        // If on last line (without newline) then insert newline at very end temporarily
2248
                        // If on last line (without newline) then insert newline at very end temporarily
1973
                        // then cut and remove previous newline.
2249
                        // then cut and remove previous newline.
1974
                        int removeNewlineOffset = -1;
2250
                        int removeNewlineOffset = -1;
1975
                        if (!Utilities.isSelectionShowing(target)) {
2251
                        Caret caret = target.getCaret();
1976
                            Element elem = ((AbstractDocument) target.getDocument()).getParagraphElement(
2252
                        boolean disableNoSelectionCopy =
1977
                                    target.getCaretPosition());
2253
                            Boolean.getBoolean("org.netbeans.editor.disable.no.selection.copy");
1978
                            int lineStartOffset = elem.getStartOffset();
2254
                        if(!disableNoSelectionCopy &&
1979
                            int lineEndOffset = elem.getEndOffset();
2255
                                (!(caret instanceof EditorCaret)) || !(((EditorCaret)caret).getCarets().size() > 1)) {
1980
                            if (lineEndOffset == doc.getLength() + 1) { // Very end
2256
                            if (!Utilities.isSelectionShowing(target)) {
1981
                                // Temporarily insert extra newline
2257
                                Element elem = ((AbstractDocument) target.getDocument()).getParagraphElement(
1982
                                try {
2258
                                        target.getCaretPosition());
1983
                                    doc.insertString(lineEndOffset - 1, "\n", null);
2259
                                int lineStartOffset = elem.getStartOffset();
1984
                                    if (lineStartOffset > 0) { // Only when not on first line
2260
                                int lineEndOffset = elem.getEndOffset();
1985
                                        removeNewlineOffset = lineStartOffset - 1;
2261
                                if (lineEndOffset == doc.getLength() + 1) { // Very end
2262
                                    // Temporarily insert extra newline
2263
                                    try {
2264
                                        doc.insertString(lineEndOffset - 1, "\n", null);
2265
                                        if (lineStartOffset > 0) { // Only when not on first line
2266
                                            removeNewlineOffset = lineStartOffset - 1;
2267
                                        }
2268
                                    } catch (BadLocationException e) {
2269
                                        // could not insert extra newline
1986
                                    }
2270
                                    }
1987
                                } catch (BadLocationException e) {
1988
                                    // could not insert extra newline
1989
                                }
2271
                                }
2272
                                target.select(lineStartOffset, lineEndOffset);
1990
                            }
2273
                            }
1991
                            target.select(lineStartOffset, lineEndOffset);
1992
                        }
2274
                        }
1993
2275
1994
                        target.cut();
2276
                        target.cut();
Lines 2023-2038 Link Here
2023
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
2305
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
2024
            if (target != null) {
2306
            if (target != null) {
2025
                try {
2307
                try {
2026
                    int caretPosition = target.getCaretPosition();
2308
                    Caret caret = target.getCaret();
2027
                    boolean emptySelection = !Utilities.isSelectionShowing(target);
2309
                    boolean emptySelection = false;
2028
                    boolean disableNoSelectionCopy =
2310
                    boolean disableNoSelectionCopy =
2029
                            Boolean.getBoolean("org.netbeans.editor.disable.no.selection.copy");
2311
                            Boolean.getBoolean("org.netbeans.editor.disable.no.selection.copy");
2030
                    // If there is no selection then pre-select a current line including newline
2312
                    int caretPosition = caret.getDot();
2031
                    if (emptySelection && !disableNoSelectionCopy) {
2313
                    if(!disableNoSelectionCopy &&
2032
                        Element elem = ((AbstractDocument) target.getDocument()).getParagraphElement(
2314
                            (!(caret instanceof EditorCaret)) || !(((EditorCaret)caret).getCarets().size() > 1)) {
2033
                                caretPosition);
2315
                        emptySelection = !Utilities.isSelectionShowing(target);
2034
                        if (!Utilities.isRowWhite((BaseDocument) target.getDocument(), elem.getStartOffset())) {
2316
                        // If there is no selection then pre-select a current line including newline
2035
                            target.select(elem.getStartOffset(), elem.getEndOffset());
2317
                        if (emptySelection && !disableNoSelectionCopy) {
2318
                            Element elem = ((AbstractDocument) target.getDocument()).getParagraphElement(
2319
                                    caretPosition);
2320
                            if (!Utilities.isRowWhite((BaseDocument) target.getDocument(), elem.getStartOffset())) {
2321
                                target.select(elem.getStartOffset(), elem.getEndOffset());
2322
                            }
2036
                        }
2323
                        }
2037
                    }
2324
                    }
2038
                    target.copy();
2325
                    target.copy();
Lines 2214-2223 Link Here
2214
            super(ABBREV_RESET | UNDO_MERGE_RESET | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
2501
            super(ABBREV_RESET | UNDO_MERGE_RESET | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
2215
        }
2502
        }
2216
2503
2217
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
2504
        public void actionPerformed(ActionEvent evt, final JTextComponent target) {
2218
            if (target != null) {
2505
            if (target != null) {
2506
                Caret caret = target.getCaret();
2507
                final Document doc = target.getDocument();
2508
                if(doc != null && caret instanceof EditorCaret) {
2509
                    final EditorCaret editorCaret = (EditorCaret) caret;
2510
                    editorCaret.moveCarets(new CaretMoveHandler() {
2511
                        @Override
2512
                        public void moveCarets(CaretMoveContext context) {
2513
                            for (CaretInfo caretInfo : context.getOriginalSortedCarets()) {
2514
                                try {
2515
                                    int dot = caretInfo.getDot();
2516
                                    Point p = caretInfo.getMagicCaretPosition();
2517
                                    if (p == null) {
2518
                                        Rectangle r = target.modelToView(dot);
2519
                                        if (r != null) {
2520
                                            p = new Point(r.x, r.y);
2521
                                            context.setMagicCaretPosition(caretInfo, p);
2522
                                        } else {
2523
                                            return; // model to view failed
2524
                                        }
2525
                                    }
2526
                                    try {
2527
                                        dot = Utilities.getPositionAbove(target, dot, p.x);
2528
                                        Position dotPos = doc.createPosition(dot);
2529
                                        boolean select = selectionUpAction.equals(getValue(Action.NAME));
2530
                                        if (select) {
2531
                                            context.moveDot(caretInfo, dotPos);
2532
                                            if (RectangularSelectionUtils.isRectangularSelection(target)) {
2533
                                                RectangularSelectionCaretAccessor.get().updateRectangularUpDownSelection(editorCaret);
2534
                                            }
2535
                                        } else {
2536
                                            context.setDot(caretInfo, dotPos);
2537
                                        }
2538
                                    } catch (BadLocationException e) {
2539
                                        // the position stays the same
2540
                                    }
2541
                                } catch (BadLocationException ex) {
2542
                                    target.getToolkit().beep();
2543
                                }
2544
                            }
2545
                        }
2546
                    });
2547
                } else {
2219
                try {
2548
                try {
2220
                    Caret caret = target.getCaret();
2221
                    int dot = caret.getDot();
2549
                    int dot = caret.getDot();
2222
                    Point p = caret.getMagicCaretPosition();
2550
                    Point p = caret.getMagicCaretPosition();
2223
                    if (p == null) {
2551
                    if (p == null) {
Lines 2235-2242 Link Here
2235
                        if (select) {
2563
                        if (select) {
2236
                            caret.moveDot(dot);
2564
                            caret.moveDot(dot);
2237
                            if (RectangularSelectionUtils.isRectangularSelection(target)) {
2565
                            if (RectangularSelectionUtils.isRectangularSelection(target)) {
2238
                                if (caret instanceof BaseCaret) {
2566
                                if (caret instanceof EditorCaret) {
2239
                                    ((BaseCaret) caret).updateRectangularUpDownSelection();
2567
                                    RectangularSelectionCaretAccessor.get().updateRectangularUpDownSelection((EditorCaret)caret);
2240
                                }
2568
                                }
2241
                            }
2569
                            }
2242
                        } else {
2570
                        } else {
Lines 2249-2254 Link Here
2249
                    target.getToolkit().beep();
2577
                    target.getToolkit().beep();
2250
                }
2578
                }
2251
            }
2579
            }
2580
            }
2252
        }
2581
        }
2253
    }
2582
    }
2254
2583
Lines 2264-2273 Link Here
2264
            super(ABBREV_RESET | UNDO_MERGE_RESET | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
2593
            super(ABBREV_RESET | UNDO_MERGE_RESET | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
2265
        }
2594
        }
2266
2595
2267
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
2596
        public void actionPerformed(ActionEvent evt, final JTextComponent target) {
2268
            if (target != null) {
2597
            if (target != null) {
2598
                Caret caret = target.getCaret();
2599
                final Document doc = target.getDocument();
2600
                if (doc != null && caret instanceof EditorCaret) {
2601
                    final EditorCaret editorCaret = (EditorCaret) caret;
2602
                    editorCaret.moveCarets(new CaretMoveHandler() {
2603
                        @Override
2604
                        public void moveCarets(CaretMoveContext context) {
2605
                            for (CaretInfo caretInfo : context.getOriginalSortedCarets()) {
2606
                                try {
2607
                                    int dot = caretInfo.getDot();
2608
                                    Point p = caretInfo.getMagicCaretPosition();
2609
                                    if (p == null) {
2610
                                        Rectangle r = target.modelToView(dot);
2611
                                        if (r != null) {
2612
                                            p = new Point(r.x, r.y);
2613
                                            context.setMagicCaretPosition(caretInfo, p);
2614
                                        } else {
2615
                                            return; // model to view failed
2616
                                        }
2617
                                    }
2618
                                    try {
2619
                                        dot = Utilities.getPositionBelow(target, dot, p.x);
2620
                                        Position dotPos = doc.createPosition(dot);
2621
                                        boolean select = selectionDownAction.equals(getValue(Action.NAME));
2622
                                        if (select) {
2623
                                            context.moveDot(caretInfo, dotPos);
2624
                                            if (RectangularSelectionUtils.isRectangularSelection(target)) {
2625
                                                RectangularSelectionCaretAccessor.get().updateRectangularUpDownSelection(editorCaret);
2626
                                            }
2627
                                        } else {
2628
                                            context.setDot(caretInfo, dotPos);
2629
                                        }
2630
                                    } catch (BadLocationException e) {
2631
                                        // position stays the same
2632
                                    }
2633
                                } catch (BadLocationException ex) {
2634
                                    target.getToolkit().beep();
2635
                                }
2636
                            }
2637
                        }
2638
                    });
2639
                } else {
2269
                try {
2640
                try {
2270
                    Caret caret = target.getCaret();
2271
                    int dot = caret.getDot();
2641
                    int dot = caret.getDot();
2272
                    Point p = caret.getMagicCaretPosition();
2642
                    Point p = caret.getMagicCaretPosition();
2273
                    if (p == null) {
2643
                    if (p == null) {
Lines 2285-2292 Link Here
2285
                        if (select) {
2655
                        if (select) {
2286
                            caret.moveDot(dot);
2656
                            caret.moveDot(dot);
2287
                            if (RectangularSelectionUtils.isRectangularSelection(target)) {
2657
                            if (RectangularSelectionUtils.isRectangularSelection(target)) {
2288
                                if (caret instanceof BaseCaret) {
2658
                                if (caret instanceof EditorCaret) {
2289
                                    ((BaseCaret)caret).updateRectangularUpDownSelection();
2659
                                    RectangularSelectionCaretAccessor.get().updateRectangularUpDownSelection((EditorCaret)caret);
2290
                                }
2660
                                }
2291
                            }
2661
                            }
2292
                        } else {
2662
                        } else {
Lines 2298-2303 Link Here
2298
                } catch (BadLocationException ex) {
2668
                } catch (BadLocationException ex) {
2299
                    target.getToolkit().beep();
2669
                    target.getToolkit().beep();
2300
                }
2670
                }
2671
                }
2301
            }
2672
            }
2302
        }
2673
        }
2303
    }
2674
    }
Lines 2315-2325 Link Here
2315
            super(ABBREV_RESET | UNDO_MERGE_RESET | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
2686
            super(ABBREV_RESET | UNDO_MERGE_RESET | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
2316
        }
2687
        }
2317
2688
2318
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
2689
        public void actionPerformed(ActionEvent evt, final JTextComponent target) {
2319
            if (target != null) {
2690
            if (target != null) {
2320
                try {
2691
                try {
2321
                    Caret caret = target.getCaret();
2692
                    Caret caret = target.getCaret();
2322
                    BaseDocument doc = (BaseDocument)target.getDocument();
2693
                    final Document doc = target.getDocument();
2694
                    if(doc != null && caret instanceof EditorCaret) {
2695
                        final EditorCaret editorCaret = (EditorCaret) caret;
2696
                        editorCaret.moveCarets(new CaretMoveHandler() {
2697
                            @Override
2698
                            public void moveCarets(CaretMoveContext context) {
2699
                                try {
2700
                                    for (CaretInfo caretInfo : context.getOriginalSortedCarets()) {
2701
                                        int caretOffset = caretInfo.getDot();
2702
                                        Rectangle caretBounds = ((BaseTextUI) target.getUI()).modelToView(target, caretOffset);
2703
                                        if (caretBounds == null) {
2704
                                            return; // Cannot continue reasonably
2705
                                        }
2706
2707
                                        // Retrieve caret magic position and attempt to retain
2708
                                        // the x-coordinate information and use it
2709
                                        // for setting of the new caret position
2710
                                        Point magicCaretPosition = caretInfo.getMagicCaretPosition();
2711
                                        if (magicCaretPosition == null) {
2712
                                            magicCaretPosition = new Point(caretBounds.x, caretBounds.y);
2713
                                        }
2714
2715
                                        Rectangle visibleBounds = target.getVisibleRect();
2716
                                        int newCaretOffset;
2717
                                        Rectangle newCaretBounds;
2718
2719
                                        // Check whether caret was contained in the original visible window
2720
                                        if (visibleBounds.contains(caretBounds)) {
2721
                                            // Clone present view bounds
2722
                                            Rectangle newVisibleBounds = new Rectangle(visibleBounds);
2723
                                            // Do viewToModel() and modelToView() with the left top corner
2724
                                            // of the currently visible view. If that line is not fully visible
2725
                                            // then it should be the bottom line of the previous page
2726
                                            // (if it's fully visible then the line above it).
2727
                                            int topLeftOffset = target.viewToModel(new Point(
2728
                                                    visibleBounds.x, visibleBounds.y));
2729
                                            Rectangle topLeftLineBounds = target.modelToView(topLeftOffset);
2730
2731
                                            // newVisibleBounds.y will hold bottom of new view
2732
                                            if (topLeftLineBounds.y != visibleBounds.y) {
2733
                                                newVisibleBounds.y = topLeftLineBounds.y + topLeftLineBounds.height;
2734
                                            } // Component view starts right at the line boundary
2735
                                            // Go back by the view height
2736
                                            newVisibleBounds.y -= visibleBounds.height;
2737
2738
                                            // Find the new caret bounds by using relative y position
2739
                                            // on the original caret bounds. If the caret's new relative bounds
2740
                                            // would be visually above the old bounds
2741
                                            // the view should be shifted so that the relative bounds
2742
                                            // are the same (user's eyes do not need to move).
2743
                                            int caretRelY = caretBounds.y - visibleBounds.y;
2744
                                            int caretNewY = newVisibleBounds.y + caretRelY;
2745
                                            newCaretOffset = target.viewToModel(new Point(magicCaretPosition.x, caretNewY));
2746
                                            newCaretBounds = target.modelToView(newCaretOffset);
2747
                                            if (newCaretBounds.y < caretNewY) {
2748
                                                // Need to go one line down to retain the top line
2749
                                                // of the present newVisibleBounds to be fully visible.
2750
                                                // Attempt to go forward by height of caret
2751
                                                newCaretOffset = target.viewToModel(new Point(magicCaretPosition.x,
2752
                                                        newCaretBounds.y + newCaretBounds.height));
2753
                                                newCaretBounds = target.modelToView(newCaretOffset);
2754
                                            }
2755
2756
                                            // Shift the new visible bounds so that the caret
2757
                                            // does not visually move
2758
                                            newVisibleBounds.y = newCaretBounds.y - caretRelY;
2759
2760
                                            // Scroll the window to the requested rectangle
2761
                                            target.scrollRectToVisible(newVisibleBounds);
2762
2763
                                        } else { // Caret outside of originally visible window
2764
                                            // Shift the dot by the visible bounds height
2765
                                            Point newCaretPoint = new Point(magicCaretPosition.x,
2766
                                                    caretBounds.y - visibleBounds.height);
2767
                                            newCaretOffset = target.viewToModel(newCaretPoint);
2768
                                            newCaretBounds = target.modelToView(newCaretOffset);
2769
                                        }
2770
2771
                                        boolean select = selectionPageUpAction.equals(getValue(Action.NAME));
2772
                                        Position newCaretPos = doc.createPosition(newCaretOffset);
2773
                                        if (select) {
2774
                                            context.moveDot(caretInfo, newCaretPos);
2775
                                        } else {
2776
                                            context.setDot(caretInfo, newCaretPos);
2777
                                        }
2778
2779
                                        // Update magic caret position
2780
                                        magicCaretPosition.y = newCaretBounds.y;
2781
                                        context.setMagicCaretPosition(caretInfo, magicCaretPosition);
2782
                                    }
2783
                                } catch (BadLocationException ex) {
2784
                                    target.getToolkit().beep();
2785
                                }
2786
                            }
2787
                        });
2788
                    } else {
2323
                    int caretOffset = caret.getDot();
2789
                    int caretOffset = caret.getDot();
2324
                    Rectangle caretBounds = ((BaseTextUI)target.getUI()).modelToView(target, caretOffset);
2790
                    Rectangle caretBounds = ((BaseTextUI)target.getUI()).modelToView(target, caretOffset);
2325
                    if (caretBounds == null) {
2791
                    if (caretBounds == null) {
Lines 2400-2406 Link Here
2400
                    // Update magic caret position
2866
                    // Update magic caret position
2401
                    magicCaretPosition.y = newCaretBounds.y;
2867
                    magicCaretPosition.y = newCaretBounds.y;
2402
                    caret.setMagicCaretPosition(magicCaretPosition);
2868
                    caret.setMagicCaretPosition(magicCaretPosition);
2403
                    
2869
                    }
2404
                } catch (BadLocationException ex) {
2870
                } catch (BadLocationException ex) {
2405
                    target.getToolkit().beep();
2871
                    target.getToolkit().beep();
2406
                }
2872
                }
Lines 2421-2429 Link Here
2421
                | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
2887
                | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
2422
        }
2888
        }
2423
2889
2424
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
2890
        public void actionPerformed(ActionEvent evt, final JTextComponent target) {
2425
            if (target != null) {
2891
            if (target != null) {
2426
                Caret caret = target.getCaret();
2892
                Caret caret = target.getCaret();
2893
                final Document doc = target.getDocument();
2894
                if (doc != null && caret instanceof EditorCaret) {
2895
                    final EditorCaret editorCaret = (EditorCaret) caret;
2896
                    editorCaret.moveCarets(new CaretMoveHandler() {
2897
                        @Override
2898
                        public void moveCarets(CaretMoveContext context) {
2899
                            for (CaretInfo caretInfo : context.getOriginalSortedCarets()) {
2900
                                try {
2901
                                    int offset;
2902
                                    boolean select = selectionForwardAction.equals(getValue(Action.NAME));
2903
                                    if (!select && Utilities.isSelectionShowing(editorCaret)) {
2904
                                        offset = target.getSelectionEnd();
2905
                                        if (offset != caretInfo.getDot()) {
2906
                                            offset--;
2907
                                        } else {
2908
                                            // clear the selection, but do not move the cursor
2909
                                            Position pos = doc.createPosition(offset);
2910
                                            context.setDot(caretInfo, pos);
2911
                                            return;
2912
                                        }
2913
                                    } else {
2914
                                        offset = caretInfo.getDot();
2915
                                    }
2916
                                    int dot = target.getUI().getNextVisualPositionFrom(target,
2917
                                            offset, Position.Bias.Forward, SwingConstants.EAST, null);
2918
                                    Position dotPos = doc.createPosition(dot);
2919
                                    if (select) {
2920
                                        if (RectangularSelectionUtils.isRectangularSelection(target)) {
2921
                                            RectangularSelectionCaretAccessor.get().extendRectangularSelection(editorCaret, true, false);
2922
                                        } else {
2923
                                            context.moveDot(caretInfo, dotPos);
2924
                                        }
2925
                                    } else {
2926
                                        context.setDot(caretInfo, dotPos);
2927
                                    }
2928
                                } catch (BadLocationException ex) {
2929
                                    target.getToolkit().beep();
2930
                                }
2931
                            }
2932
                        }
2933
                    });
2934
                } else {
2427
                try {
2935
                try {
2428
                    int pos;
2936
                    int pos;
2429
                    boolean select = selectionForwardAction.equals(getValue(Action.NAME));
2937
                    boolean select = selectionForwardAction.equals(getValue(Action.NAME));
Lines 2443-2450 Link Here
2443
                    int dot = target.getUI().getNextVisualPositionFrom(target,
2951
                    int dot = target.getUI().getNextVisualPositionFrom(target,
2444
                              pos, Position.Bias.Forward, SwingConstants.EAST, null);
2952
                              pos, Position.Bias.Forward, SwingConstants.EAST, null);
2445
                    if (select) {
2953
                    if (select) {
2446
                        if (caret instanceof BaseCaret && RectangularSelectionUtils.isRectangularSelection(target)) {
2954
                        if (caret instanceof EditorCaret && RectangularSelectionUtils.isRectangularSelection(target)) {
2447
                            ((BaseCaret)caret).extendRectangularSelection(true, false);
2955
                            RectangularSelectionCaretAccessor.get().extendRectangularSelection((EditorCaret)caret, true, false);
2448
                        } else {
2956
                        } else {
2449
                            caret.moveDot(dot);
2957
                            caret.moveDot(dot);
2450
                        }
2958
                        }
Lines 2455-2460 Link Here
2455
                    target.getToolkit().beep();
2963
                    target.getToolkit().beep();
2456
                }
2964
                }
2457
            }
2965
            }
2966
            }
2458
        }
2967
        }
2459
    }
2968
    }
2460
2969
Lines 2472-2481 Link Here
2472
                | CLEAR_STATUS_TEXT);
2981
                | CLEAR_STATUS_TEXT);
2473
        }
2982
        }
2474
2983
2475
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
2984
        public void actionPerformed(ActionEvent evt, final JTextComponent target) {
2476
            if (target != null) {
2985
            if (target != null) {
2477
                try {
2986
                try {
2478
                    Caret caret = target.getCaret();
2987
                    Caret caret = target.getCaret();
2988
                    final Document doc = target.getDocument();
2989
                    if(doc != null && caret instanceof EditorCaret) {
2990
                        final EditorCaret editorCaret = (EditorCaret) caret;
2991
                        editorCaret.moveCarets(new CaretMoveHandler() {
2992
                            @Override
2993
                            public void moveCarets(CaretMoveContext context) {
2994
                                try {
2995
                                    for (CaretInfo caretInfo : context.getOriginalSortedCarets()) {
2996
                                        int caretOffset = caretInfo.getDot();
2997
                                        Rectangle caretBounds = ((BaseTextUI) target.getUI()).modelToView(target, caretOffset);
2998
                                        if (caretBounds == null) {
2999
                                            return; // Cannot continue reasonably
3000
                                        }
3001
3002
                                        // Retrieve caret magic position and attempt to retain
3003
                                        // the x-coordinate information and use it
3004
                                        // for setting of the new caret position
3005
                                        Point magicCaretPosition = caretInfo.getMagicCaretPosition();
3006
                                        if (magicCaretPosition == null) {
3007
                                            magicCaretPosition = new Point(caretBounds.x, caretBounds.y);
3008
                                        }
3009
3010
                                        Rectangle visibleBounds = target.getVisibleRect();
3011
                                        int newCaretOffset;
3012
                                        Rectangle newCaretBounds;
3013
3014
                                        // Check whether caret was contained in the original visible window
3015
                                        if (visibleBounds.contains(caretBounds)) {
3016
                                            // Clone present view bounds
3017
                                            Rectangle newVisibleBounds = new Rectangle(visibleBounds);
3018
                                            // Do viewToModel() and modelToView() with the left bottom corner
3019
                                            // of the currently visible view.
3020
                                            // That line should be the top line of the next page.
3021
                                            int bottomLeftOffset = target.viewToModel(new Point(
3022
                                                    visibleBounds.x, visibleBounds.y + visibleBounds.height));
3023
                                            Rectangle bottomLeftLineBounds = target.modelToView(bottomLeftOffset);
3024
3025
                                            // newVisibleBounds.y will hold bottom of new view
3026
                                            newVisibleBounds.y = bottomLeftLineBounds.y;
3027
3028
                                            // Find the new caret bounds by using relative y position
3029
                                            // on the original caret bounds. If the caret's new relative bounds
3030
                                            // would be visually below the old bounds
3031
                                            // the view should be shifted so that the relative bounds
3032
                                            // are the same (user's eyes do not need to move).
3033
                                            int caretRelY = caretBounds.y - visibleBounds.y;
3034
                                            int caretNewY = newVisibleBounds.y + caretRelY;
3035
                                            newCaretOffset = target.viewToModel(new Point(magicCaretPosition.x, caretNewY));
3036
                                            newCaretBounds = target.modelToView(newCaretOffset);
3037
                                            if (newCaretBounds.y > caretNewY) {
3038
                                                // Need to go one line above to retain the top line
3039
                                                // of the present newVisibleBounds to be fully visible.
3040
                                                // Attempt to go up by height of caret.
3041
                                                newCaretOffset = target.viewToModel(new Point(magicCaretPosition.x,
3042
                                                        newCaretBounds.y - newCaretBounds.height));
3043
                                                newCaretBounds = target.modelToView(newCaretOffset);
3044
                                            }
3045
3046
                                            // Shift the new visible bounds so that the caret
3047
                                            // does not visually move
3048
                                            newVisibleBounds.y = newCaretBounds.y - caretRelY;
3049
3050
                                            // Scroll the window to the requested rectangle
3051
                                            target.scrollRectToVisible(newVisibleBounds);
3052
3053
                                        } else { // Caret outside of originally visible window
3054
                                            // Shift the dot by the visible bounds height
3055
                                            Point newCaretPoint = new Point(magicCaretPosition.x,
3056
                                                    caretBounds.y + visibleBounds.height);
3057
                                            newCaretOffset = target.viewToModel(newCaretPoint);
3058
                                            newCaretBounds = target.modelToView(newCaretOffset);
3059
                                        }
3060
3061
                                        boolean select = selectionPageDownAction.equals(getValue(Action.NAME));
3062
                                        Position newCaretPos = doc.createPosition(newCaretOffset);
3063
                                        if (select) {
3064
                                            context.moveDot(caretInfo, newCaretPos);
3065
                                        } else {
3066
                                            context.setDot(caretInfo, newCaretPos);
3067
                                        }
3068
3069
                                        // Update magic caret position
3070
                                        magicCaretPosition.y = newCaretBounds.y;
3071
                                        context.setMagicCaretPosition(caretInfo, magicCaretPosition);
3072
                                    }
3073
                                } catch (BadLocationException ex) {
3074
                                    target.getToolkit().beep();
3075
                                }
3076
                            }
3077
                        });
3078
                    } else {
2479
                    int caretOffset = caret.getDot();
3079
                    int caretOffset = caret.getDot();
2480
                    Rectangle caretBounds = ((BaseTextUI)target.getUI()).modelToView(target, caretOffset);
3080
                    Rectangle caretBounds = ((BaseTextUI)target.getUI()).modelToView(target, caretOffset);
2481
                    if (caretBounds == null) {
3081
                    if (caretBounds == null) {
Lines 2551-2606 Link Here
2551
                    // Update magic caret position
3151
                    // Update magic caret position
2552
                    magicCaretPosition.y = newCaretBounds.y;
3152
                    magicCaretPosition.y = newCaretBounds.y;
2553
                    caret.setMagicCaretPosition(magicCaretPosition);
3153
                    caret.setMagicCaretPosition(magicCaretPosition);
2554
                    
2555
                } catch (BadLocationException ex) {
2556
                    target.getToolkit().beep();
2557
                }
2558
            }
2559
        }
2560
    }
2561
2562
    @EditorActionRegistrations({
2563
        @EditorActionRegistration(name = backwardAction),
2564
        @EditorActionRegistration(name = selectionBackwardAction)
2565
    })
2566
    public static class BackwardAction extends LocalBaseAction {
2567
2568
        static final long serialVersionUID =-3048379822817847356L;
2569
2570
        public BackwardAction() {
2571
            super(MAGIC_POSITION_RESET | ABBREV_RESET | UNDO_MERGE_RESET
2572
                  | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
2573
        }
2574
2575
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
2576
            if (target != null) {
2577
                Caret caret = target.getCaret();
2578
                try {
2579
                    int pos;
2580
                    boolean select = selectionBackwardAction.equals(getValue(Action.NAME));
2581
                    if (!select && Utilities.isSelectionShowing(caret))
2582
                    {
2583
                        pos = target.getSelectionStart(); 
2584
                        if (pos != caret.getDot()) {
2585
                            pos++;
2586
                        } else {
2587
                            // clear the selection, but do not move the cursor
2588
                            caret.setDot(pos);
2589
                            return;
2590
                        }
2591
                    }
2592
                    else
2593
                        pos = caret.getDot();
2594
                    int dot = target.getUI().getNextVisualPositionFrom(target,
2595
                              pos, Position.Bias.Backward, SwingConstants.WEST, null);
2596
                    if (select) {
2597
                        if (caret instanceof BaseCaret && RectangularSelectionUtils.isRectangularSelection(target)) {
2598
                            ((BaseCaret)caret).extendRectangularSelection(false, false);
2599
                        } else {
2600
                            caret.moveDot(dot);
2601
                        }
2602
                    } else {
2603
                        caret.setDot(dot);
2604
                    }
3154
                    }
2605
                } catch (BadLocationException ex) {
3155
                } catch (BadLocationException ex) {
2606
                    target.getToolkit().beep();
3156
                    target.getToolkit().beep();
Lines 2609-2614 Link Here
2609
        }
3159
        }
2610
    }
3160
    }
2611
3161
3162
    @EditorActionRegistrations({
3163
        @EditorActionRegistration(name = backwardAction),
3164
        @EditorActionRegistration(name = selectionBackwardAction)
3165
    })
3166
    public static class BackwardAction extends LocalBaseAction {
3167
3168
        static final long serialVersionUID = -3048379822817847356L;
3169
3170
        public BackwardAction() {
3171
            super(MAGIC_POSITION_RESET | ABBREV_RESET | UNDO_MERGE_RESET
3172
                    | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
3173
        }
3174
3175
        public void actionPerformed(ActionEvent evt, final JTextComponent target) {
3176
            if (target != null) {
3177
                Caret caret = target.getCaret();
3178
                final Document doc = target.getDocument();
3179
                if (doc != null && caret instanceof EditorCaret) {
3180
                    EditorCaret editorCaret = (EditorCaret) caret;
3181
                    editorCaret.moveCarets(new CaretMoveHandler() {
3182
                        @Override
3183
                        public void moveCarets(CaretMoveContext context) {
3184
                            for (CaretInfo caretInfo : context.getOriginalSortedCarets()) {
3185
                                try {
3186
                                    int offset;
3187
                                    boolean select = selectionBackwardAction.equals(getValue(Action.NAME));
3188
                                    if (!select && caretInfo.isSelectionShowing()) {
3189
                                        offset = caretInfo.getSelectionStart();
3190
                                        if (offset != caretInfo.getDot()) {
3191
                                            offset++;
3192
                                        } else {
3193
                                            // clear the selection, but do not move the cursor
3194
                                            context.setDot(caretInfo, doc.createPosition(offset));
3195
                                            return;
3196
                                        }
3197
                                    } else {
3198
                                        offset = caretInfo.getDot();
3199
                                    }
3200
                                    int dot = target.getUI().getNextVisualPositionFrom(target,
3201
                                            offset, Position.Bias.Backward, SwingConstants.WEST, null);
3202
                                    Position dotPos = doc.createPosition(dot);
3203
                                    if (select) {
3204
                                        context.moveDot(caretInfo, dotPos);
3205
                                    } else {
3206
                                        context.setDot(caretInfo, dotPos);
3207
                                    }
3208
                                } catch (BadLocationException ex) {
3209
                                    target.getToolkit().beep();
3210
                                }
3211
                            }
3212
                        }
3213
                    });
3214
                } else {
3215
                    try {
3216
                        int pos;
3217
                        boolean select = selectionBackwardAction.equals(getValue(Action.NAME));
3218
                        if (!select && Utilities.isSelectionShowing(caret)) {
3219
                            pos = target.getSelectionStart();
3220
                            if (pos != caret.getDot()) {
3221
                                pos++;
3222
                            } else {
3223
                                // clear the selection, but do not move the cursor
3224
                                caret.setDot(pos);
3225
                                return;
3226
                            }
3227
                        } else {
3228
                            pos = caret.getDot();
3229
                        }
3230
                        int dot = target.getUI().getNextVisualPositionFrom(target,
3231
                                pos, Position.Bias.Backward, SwingConstants.WEST, null);
3232
                        if (select) {
3233
                            if (caret instanceof EditorCaret && RectangularSelectionUtils.isRectangularSelection(target)) {
3234
                                RectangularSelectionCaretAccessor.get().extendRectangularSelection((EditorCaret)caret, false, false);
3235
                            } else {
3236
                                caret.moveDot(dot);
3237
                            }
3238
                        } else {
3239
                            caret.setDot(dot);
3240
                        }
3241
                    } catch (BadLocationException ex) {
3242
                        target.getToolkit().beep();
3243
                    }
3244
                }
3245
            }
3246
        }
3247
    }
3248
2612
    public static class BeginLineAction extends LocalBaseAction {
3249
    public static class BeginLineAction extends LocalBaseAction {
2613
3250
2614
        @EditorActionRegistrations({
3251
        @EditorActionRegistrations({
Lines 2639-2648 Link Here
2639
            homeKeyColumnOne = columnOne;
3276
            homeKeyColumnOne = columnOne;
2640
        }
3277
        }
2641
3278
2642
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
3279
        public void actionPerformed(ActionEvent evt, final JTextComponent target) {
2643
            if (target != null) {
3280
            if (target != null) {
2644
                Caret caret = target.getCaret();
3281
                Caret caret = target.getCaret();
2645
                BaseDocument doc = (BaseDocument)target.getDocument();
3282
                final Document doc = target.getDocument();
3283
                if (doc != null && caret instanceof EditorCaret) {
3284
                    EditorCaret editorCaret = (EditorCaret) caret;
3285
                    editorCaret.moveCarets(new CaretMoveHandler() {
3286
                        @Override
3287
                        public void moveCarets(CaretMoveContext context) {
3288
                            for (CaretInfo caretInfo : context.getOriginalSortedCarets()) {
3289
                                try {
3290
                                    int dot = caretInfo.getDot();
3291
                                    // #232675: if bounds are defined, use them rather than line start/end
3292
                                    Object o = target.getClientProperty(PROP_NAVIGATE_BOUNDARIES);
3293
                                    PositionRegion bounds = null;
3294
                                    if (o instanceof PositionRegion) {
3295
                                        bounds = (PositionRegion) o;
3296
                                        int start = bounds.getStartOffset();
3297
                                        int end = bounds.getEndOffset();
3298
                                        if (dot > start && dot <= end) {
3299
                                            // move to the region start
3300
                                            dot = start;
3301
                                        } else {
3302
                                            bounds = null;
3303
                                        }
3304
                                    }
3305
3306
                                    if (bounds == null) {
3307
                                        int lineStartPos = Utilities.getRowStart(target, dot);
3308
                                        if (homeKeyColumnOne) { // to first column
3309
                                            dot = lineStartPos;
3310
                                        } else { // either to line start or text start
3311
                                            BaseDocument doc = (BaseDocument) target.getDocument();
3312
                                            int textStartPos = Utilities.getRowFirstNonWhite(doc, lineStartPos);
3313
                                            if (textStartPos < 0) { // no text on the line
3314
                                                textStartPos = Utilities.getRowEnd(target, lineStartPos);
3315
                                            }
3316
                                            if (dot == lineStartPos) { // go to the text start pos
3317
                                                dot = textStartPos;
3318
                                            } else if (dot <= textStartPos) {
3319
                                                dot = lineStartPos;
3320
                                            } else {
3321
                                                dot = textStartPos;
3322
                                            }
3323
                                        }
3324
                                    }
3325
                                    // For partial view hierarchy check bounds
3326
                                    dot = Math.max(dot, target.getUI().getRootView(target).getStartOffset());
3327
                                    String actionName = (String) getValue(Action.NAME);
3328
                                    boolean select = selectionBeginLineAction.equals(actionName)
3329
                                            || selectionLineFirstColumnAction.equals(actionName);
3330
3331
                                    // If possible scroll the view to its begining horizontally
3332
                                    // to ease user's orientation in the code.
3333
                                    Rectangle r = target.modelToView(dot);
3334
                                    Rectangle visRect = target.getVisibleRect();
3335
                                    if (r.getMaxX() < visRect.getWidth()) {
3336
                                        r.x = 0;
3337
                                        target.scrollRectToVisible(r);
3338
                                    }
3339
3340
                                    Position dotPos = doc.createPosition(dot);
3341
                                    if (select) {
3342
                                        context.moveDot(caretInfo, dotPos);
3343
                                    } else {
3344
                                        context.setDot(caretInfo, dotPos);
3345
                                    }
3346
                                } catch (BadLocationException e) {
3347
                                    target.getToolkit().beep();
3348
                                }
3349
                            }
3350
                        }
3351
                    });
3352
                } else {
2646
                try {
3353
                try {
2647
                    int dot = caret.getDot();
3354
                    int dot = caret.getDot();
2648
                    // #232675: if bounds are defined, use them rather than line start/end
3355
                    // #232675: if bounds are defined, use them rather than line start/end
Lines 2665-2671 Link Here
2665
                        if (homeKeyColumnOne) { // to first column
3372
                        if (homeKeyColumnOne) { // to first column
2666
                            dot = lineStartPos;
3373
                            dot = lineStartPos;
2667
                        } else { // either to line start or text start
3374
                        } else { // either to line start or text start
2668
                            int textStartPos = Utilities.getRowFirstNonWhite(doc, lineStartPos);
3375
                            int textStartPos = Utilities.getRowFirstNonWhite(((BaseDocument)doc), lineStartPos);
2669
                            if (textStartPos < 0) { // no text on the line
3376
                            if (textStartPos < 0) { // no text on the line
2670
                                textStartPos = Utilities.getRowEnd(target, lineStartPos);
3377
                                textStartPos = Utilities.getRowEnd(target, lineStartPos);
2671
                            }
3378
                            }
Lines 2702-2707 Link Here
2702
                    target.getToolkit().beep();
3409
                    target.getToolkit().beep();
2703
                }
3410
                }
2704
            }
3411
            }
3412
            }
2705
        }
3413
        }
2706
    }
3414
    }
2707
3415
Lines 2718-2726 Link Here
2718
                  | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
3426
                  | WORD_MATCH_RESET | CLEAR_STATUS_TEXT);
2719
        }
3427
        }
2720
3428
2721
        public void actionPerformed(ActionEvent evt, JTextComponent target) {
3429
        public void actionPerformed(ActionEvent evt, final JTextComponent target) {
2722
            if (target != null) {
3430
            if (target != null) {
2723
                Caret caret = target.getCaret();
3431
                Caret caret = target.getCaret();
3432
                if(caret instanceof EditorCaret) {
3433
                    ((EditorCaret) caret).moveCarets(new org.netbeans.spi.editor.caret.CaretMoveHandler() {
3434
                        @Override
3435
                        public void moveCarets(CaretMoveContext context) {
3436
                            for (CaretInfo caretInfo : context.getOriginalSortedCarets()) {
3437
                                try {
3438
                                    // #232675: if bounds are defined, use them rather than line start/end
3439
                                    Object o = target.getClientProperty(PROP_NAVIGATE_BOUNDARIES);
3440
                                    int dot = -1;
3441
3442
                                    if (o instanceof PositionRegion) {
3443
                                        PositionRegion bounds = (PositionRegion) o;
3444
                                        int start = bounds.getStartOffset();
3445
                                        int end = bounds.getEndOffset();
3446
                                        int d = caretInfo.getDot();
3447
                                        if (d >= start && d < end) {
3448
                                            // move to the region start
3449
                                            dot = end;
3450
                                        }
3451
                                    }
3452
3453
                                    if (dot == -1) {
3454
                                        dot = Utilities.getRowEnd(target, caretInfo.getDot());
3455
                                    }
3456
3457
                                    // For partial view hierarchy check bounds
3458
                                    dot = Math.min(dot, target.getUI().getRootView(target).getEndOffset());
3459
                                    boolean select = selectionEndLineAction.equals(getValue(Action.NAME));
3460
                                    Position dotPos = context.getDocument().createPosition(dot);
3461
                                    if (select) {
3462
                                        context.moveDot(caretInfo, dotPos);
3463
                                    } else {
3464
                                        context.setDot(caretInfo, dotPos);
3465
                                    }
3466
                                    // now move the magic caret position far to the right
3467
                                    Rectangle r = target.modelToView(dot);
3468
                                    if (r != null) {
3469
                                        Point p = new Point(MAGIC_POSITION_MAX, r.y);
3470
                                        context.setMagicCaretPosition(caretInfo, p);
3471
                                    }
3472
                                } catch (BadLocationException e) {
3473
                                    e.printStackTrace();
3474
                                    target.getToolkit().beep();
3475
                                }
3476
                            }
3477
                        }
3478
                    });
3479
                    
3480
                } else {
2724
                try {
3481
                try {
2725
                    // #232675: if bounds are defined, use them rather than line start/end
3482
                    // #232675: if bounds are defined, use them rather than line start/end
2726
                    Object o = target.getClientProperty(PROP_NAVIGATE_BOUNDARIES);
3483
                    Object o = target.getClientProperty(PROP_NAVIGATE_BOUNDARIES);
Lines 2760-2765 Link Here
2760
                    target.getToolkit().beep();
3517
                    target.getToolkit().beep();
2761
                }
3518
                }
2762
            }
3519
            }
3520
            }
2763
        }
3521
        }
2764
    }
3522
    }
2765
3523
Lines 2852-2868 Link Here
2852
                    int newDotOffset = getNextWordOffset(target);
3610
                    int newDotOffset = getNextWordOffset(target);
2853
                    boolean select = selectionNextWordAction.equals(getValue(Action.NAME));
3611
                    boolean select = selectionNextWordAction.equals(getValue(Action.NAME));
2854
                    if (select) {
3612
                    if (select) {
2855
                        if (caret instanceof BaseCaret && RectangularSelectionUtils.isRectangularSelection(target)) {
3613
                        if (caret instanceof EditorCaret && RectangularSelectionUtils.isRectangularSelection(target)) {
2856
                            ((BaseCaret) caret).extendRectangularSelection(true, true);
3614
                            RectangularSelectionCaretAccessor.get().extendRectangularSelection((EditorCaret)caret, true, true);
2857
                        } else {
3615
                        } else {
2858
                            caret.moveDot(newDotOffset);
3616
                            caret.moveDot(newDotOffset);
2859
                        }
3617
                        }
2860
                    } else {
3618
                    } else {
2861
                        if (caret instanceof BaseCaret) {
3619
                        caret.setDot(newDotOffset);
2862
                            ((BaseCaret)caret).setDot(newDotOffset, false);
2863
                        } else {
2864
                            caret.setDot(newDotOffset);
2865
                        }
2866
                    }
3620
                    }
2867
                } catch (BadLocationException ex) {
3621
                } catch (BadLocationException ex) {
2868
                    target.getToolkit().beep();
3622
                    target.getToolkit().beep();
Lines 2898-2914 Link Here
2898
                    int newDotOffset = getPreviousWordOffset(target);
3652
                    int newDotOffset = getPreviousWordOffset(target);
2899
                    boolean select = selectionPreviousWordAction.equals(getValue(Action.NAME));
3653
                    boolean select = selectionPreviousWordAction.equals(getValue(Action.NAME));
2900
                    if (select) {
3654
                    if (select) {
2901
                        if (caret instanceof BaseCaret && RectangularSelectionUtils.isRectangularSelection(target)) {
3655
                        if (caret instanceof EditorCaret && RectangularSelectionUtils.isRectangularSelection(target)) {
2902
                            ((BaseCaret) caret).extendRectangularSelection(false, true);
3656
                            RectangularSelectionCaretAccessor.get().extendRectangularSelection((EditorCaret)caret, false, true);
2903
                        } else {
3657
                        } else {
2904
                            caret.moveDot(newDotOffset);
3658
                            caret.moveDot(newDotOffset);
2905
                        }
3659
                        }
2906
                    } else {
3660
                    } else {
2907
                        if (caret instanceof BaseCaret) {
3661
                        caret.setDot(newDotOffset);
2908
                            ((BaseCaret)caret).setDot(newDotOffset, false);
2909
                        } else {
2910
                            caret.setDot(newDotOffset);
2911
                        }
2912
                    }
3662
                    }
2913
                } catch (BadLocationException ex) {
3663
                } catch (BadLocationException ex) {
2914
                    target.getToolkit().beep();
3664
                    target.getToolkit().beep();
Lines 3008-3014 Link Here
3008
            if (target != null) {
3758
            if (target != null) {
3009
                final Caret caret = target.getCaret();
3759
                final Caret caret = target.getCaret();
3010
                final BaseDocument doc = (BaseDocument)target.getDocument();
3760
                final BaseDocument doc = (BaseDocument)target.getDocument();
3011
                doc.runAtomicAsUser (new Runnable () {
3761
                doc.render(new Runnable () {
3012
                    public void run () {
3762
                    public void run () {
3013
                        DocumentUtilities.setTypingModification(doc, true);
3763
                        DocumentUtilities.setTypingModification(doc, true);
3014
                        try {
3764
                        try {
Lines 3171-3176 Link Here
3171
            }
3921
            }
3172
        }
3922
        }
3173
    } // End of DefaultSyntaxTokenContext class
3923
    } // End of DefaultSyntaxTokenContext class
3924
3925
    private static class CaretEdit extends AbstractUndoableEdit {
3926
3927
        private final JTextComponent target;
3928
        private final List<Position> offsets;
3929
3930
        public CaretEdit(List<Position> offsets, JTextComponent target) {
3931
            this.target = target;
3932
            this.offsets = offsets;
3933
        }
3934
3935
        @Override
3936
        public void undo() throws CannotUndoException {
3937
            EditorCaret caret = (EditorCaret) target.getCaret();
3938
            caret.replaceCarets(offsets);
3939
        }
3940
3941
        @Override
3942
        public void redo() throws CannotRedoException {
3943
            EditorCaret caret = (EditorCaret) target.getCaret();
3944
            caret.replaceCarets(offsets);
3945
        }
3946
    }
3174
    
3947
    
3175
    private class KeybindingsAndPreferencesTracker implements LookupListener, PreferenceChangeListener {
3948
    private class KeybindingsAndPreferencesTracker implements LookupListener, PreferenceChangeListener {
3176
        
3949
        
(-)a/editor.lib/src/org/netbeans/editor/EditorUI.java (-1 / +5 lines)
Lines 82-87 Link Here
82
import javax.swing.plaf.TextUI;
82
import javax.swing.plaf.TextUI;
83
import javax.swing.text.*;
83
import javax.swing.text.*;
84
import javax.swing.undo.UndoManager;
84
import javax.swing.undo.UndoManager;
85
import org.netbeans.api.editor.EditorUtilities;
85
import org.netbeans.api.editor.mimelookup.MimeLookup;
86
import org.netbeans.api.editor.mimelookup.MimeLookup;
86
import org.netbeans.api.editor.mimelookup.MimePath;
87
import org.netbeans.api.editor.mimelookup.MimePath;
87
import org.netbeans.api.editor.settings.EditorStyleConstants;
88
import org.netbeans.api.editor.settings.EditorStyleConstants;
Lines 655-661 Link Here
655
            if (!component.isEnabled()) {
656
            if (!component.isEnabled()) {
656
                component.getCaret().setVisible(false);
657
                component.getCaret().setVisible(false);
657
            }
658
            }
658
        } 
659
        } else if (EditorUtilities.CARET_OVERWRITE_MODE_PROPERTY.equals(propName)) {
660
            // Mirror the property into EditorUI
661
            putProperty(OVERWRITE_MODE_PROPERTY, component.getClientProperty(EditorUtilities.CARET_OVERWRITE_MODE_PROPERTY));
662
        }
659
        
663
        
660
        if (propName == null || ColoringMap.PROP_COLORING_MAP.equals(propName)) {
664
        if (propName == null || ColoringMap.PROP_COLORING_MAP.equals(propName)) {
661
            listener.preferenceChange(null);
665
            listener.preferenceChange(null);
(-)a/editor.lib/src/org/netbeans/editor/ext/ExtKit.java (-5 lines)
Lines 177-187 Link Here
177
    public ExtKit() {
177
    public ExtKit() {
178
    }
178
    }
179
179
180
    /** Create caret to navigate through document */
181
    public @Override Caret createCaret() {
182
        return new ExtCaret();
183
    }
184
185
    public @Override SyntaxSupport createSyntaxSupport(BaseDocument doc) {
180
    public @Override SyntaxSupport createSyntaxSupport(BaseDocument doc) {
186
        return new ExtSyntaxSupport(doc);
181
        return new ExtSyntaxSupport(doc);
187
    }
182
    }
(-)a/editor.lib2/apichanges.xml (-1 / +35 lines)
Lines 105-112 Link Here
105
    </apidefs>
105
    </apidefs>
106
106
107
    <!-- ACTUAL CHANGES BEGIN HERE: -->
107
    <!-- ACTUAL CHANGES BEGIN HERE: -->
108
108
    
109
    <changes>
109
    <changes>
110
        <change id="editor-caret">
111
            <summary>Caret API introduced</summary>
112
            <version major="2" minor="7"/>
113
            <date day="22" month="2" year="2016"/>
114
            <author login="mmetelka"/>
115
            <author login="ralphbenjamin"/>
116
            <compatibility binary="compatible" semantic="compatible" source="compatible" addition="yes" deprecation="no" deletion="no"/>
117
            <description>
118
                <p>
119
                    The Caret API was introduced to allow working with multiple
120
                    carets within one document.
121
                </p>
122
            </description>
123
            <class name="EditorActionNames" package="org.netbeans.api.editor" />
124
            <class name="EditorUtilities" package="org.netbeans.api.editor" />
125
            <package name="org.netbeans.api.editor.caret" />
126
            <issue number="257893"/>
127
        </change>
110
        <change id="occurrences-names">
128
        <change id="occurrences-names">
111
            <summary>Added IDs for goto prev/next occurrence actions</summary>
129
            <summary>Added IDs for goto prev/next occurrence actions</summary>
112
            <version major="2" minor="6"/>
130
            <version major="2" minor="6"/>
Lines 122-127 Link Here
122
            <class name="EditorActionNames" package="org.netbeans.api.editor"/>
140
            <class name="EditorActionNames" package="org.netbeans.api.editor"/>
123
            <issue number="194356"/>
141
            <issue number="194356"/>
124
        </change>
142
        </change>
143
        <change id="shift-highlights-sequence">
144
            <summary>Extended HightlightsSequence by ShiftHighlightsSequence</summary>
145
            <version major="2" minor="5"/>
146
            <date day="22" month="2" year="2016"/>
147
            <author login="mmetelka"/>
148
            <compatibility binary="compatible" semantic="compatible" source="compatible" addition="yes" deprecation="no" deletion="no"/>
149
            <description>
150
                <p>
151
        Highlights sequence that supports shifts in addition to regular offsets.
152
        This allows to color individual spaces within a tab character
153
        or to color extra virtual characters beyond a newline character.
154
                </p>
155
            </description>
156
            <class name="ShiftHighlightsSequence" package="org.netbeans.spi.editor.highlighting"/>
157
            <issue number="XXX"/>
158
        </change>
125
        <change id="releasable-highlights-container">
159
        <change id="releasable-highlights-container">
126
            <summary>ReleasableHighlightsContainer added</summary>
160
            <summary>ReleasableHighlightsContainer added</summary>
127
            <version major="2" minor="4"/>
161
            <version major="2" minor="4"/>
(-)a/editor.lib2/arch.xml (-3 / +12 lines)
Lines 87-93 Link Here
87
</li>    
87
</li>    
88
<li>
88
<li>
89
    <api name="editor-typing-hooks" group="java" type="export" category="official" url="@org-netbeans-modules-editor-lib2@/org/netbeans/spi/editor/typinghooks/package-summary.html">Typing Hooks SPI</api>
89
    <api name="editor-typing-hooks" group="java" type="export" category="official" url="@org-netbeans-modules-editor-lib2@/org/netbeans/spi/editor/typinghooks/package-summary.html">Typing Hooks SPI</api>
90
</li>    
90
</li>
91
<li>
92
    <api name="editor-caret" group="java" type="export" category="official" url="@org-netbeans-modules-editor-lib2@/org/netbeans/api/editor/caret/package-summary.html">Editor Caret API</api>
93
</li>
91
</ul>
94
</ul>
92
</answer>
95
</answer>
93
96
Lines 126-133 Link Here
126
        </question>
129
        </question>
127
-->
130
-->
128
<answer id="arch-usecases">
131
<answer id="arch-usecases">
129
    At the moment the Editor Library 2 module contains three distinct SPIs. Each SPI lives
132
    At the moment the Editor Library 2 module contains distinct APIs/SPIs. They live
130
    in its own package and the usecases can be found in the packages overview.
133
    in their own package and the usecases can be found in the packages overview.
131
    <ul>
134
    <ul>
132
    <li>Code Generator SPI -
135
    <li>Code Generator SPI -
133
        <a href="@org-netbeans-modules-editor-lib2@/org/netbeans/spi/editor/codegen/package-summary.html#usecases">org.netbeans.spi.editor.codegen</a>
136
        <a href="@org-netbeans-modules-editor-lib2@/org/netbeans/spi/editor/codegen/package-summary.html#usecases">org.netbeans.spi.editor.codegen</a>
Lines 138-143 Link Here
138
    <li>Typing Hooks SPI -
141
    <li>Typing Hooks SPI -
139
        <a href="@org-netbeans-modules-editor-lib2@/org/netbeans/spi/editor/typinghooks/package-summary.html#usecases">org.netbeans.spi.editor.typinghooks</a>
142
        <a href="@org-netbeans-modules-editor-lib2@/org/netbeans/spi/editor/typinghooks/package-summary.html#usecases">org.netbeans.spi.editor.typinghooks</a>
140
    </li>
143
    </li>
144
    <li>Caret API -
145
        <a href="@org-netbeans-modules-editor-lib2@/org/netbeans/api/editor/caret/package-summary.html#usecases">org.netbeans.api.editor.caret</a>
146
    </li>
147
    <li>Caret SPI -
148
        <a href="@org-netbeans-modules-editor-lib2@/org/netbeans/spi/editor/caret/package-summary.html#usecases">org.netbeans.spi.editor.caret</a>
149
    </li>
141
    </ul>
150
    </ul>
142
</answer>
151
</answer>
143
152
(-)a/editor.lib2/nbproject/project.xml (-10 / +11 lines)
Lines 90-96 Link Here
90
                    <compile-dependency/>
90
                    <compile-dependency/>
91
                    <run-dependency>
91
                    <run-dependency>
92
                        <release-version>1</release-version>
92
                        <release-version>1</release-version>
93
                        <specification-version>1.44</specification-version>
93
                        <specification-version>1.63</specification-version>
94
                    </run-dependency>
94
                    </run-dependency>
95
                </dependency>
95
                </dependency>
96
                <dependency>
96
                <dependency>
Lines 127-140 Link Here
127
                    </run-dependency>
127
                    </run-dependency>
128
                </dependency>
128
                </dependency>
129
                <dependency>
129
                <dependency>
130
                    <code-name-base>org.openide.util.ui</code-name-base>
131
                    <build-prerequisite/>
132
                    <compile-dependency/>
133
                    <run-dependency>
134
                        <specification-version>9.3</specification-version>
135
                    </run-dependency>
136
                </dependency>
137
                <dependency>
138
                    <code-name-base>org.openide.util</code-name-base>
130
                    <code-name-base>org.openide.util</code-name-base>
139
                    <build-prerequisite/>
131
                    <build-prerequisite/>
140
                    <compile-dependency/>
132
                    <compile-dependency/>
Lines 150-155 Link Here
150
                        <specification-version>8.0</specification-version>
142
                        <specification-version>8.0</specification-version>
151
                    </run-dependency>
143
                    </run-dependency>
152
                </dependency>
144
                </dependency>
145
                <dependency>
146
                    <code-name-base>org.openide.util.ui</code-name-base>
147
                    <build-prerequisite/>
148
                    <compile-dependency/>
149
                    <run-dependency>
150
                        <specification-version>9.3</specification-version>
151
                    </run-dependency>
152
                </dependency>
153
            </module-dependencies>
153
            </module-dependencies>
154
            <test-dependencies>
154
            <test-dependencies>
155
                <test-type>
155
                <test-type>
Lines 209-216 Link Here
209
            </test-dependencies>
209
            </test-dependencies>
210
            <public-packages>
210
            <public-packages>
211
                <package>org.netbeans.api.editor</package>
211
                <package>org.netbeans.api.editor</package>
212
                <package>org.netbeans.api.editor.document</package>
212
                <package>org.netbeans.api.editor.caret</package>
213
                <package>org.netbeans.spi.editor</package>
213
                <package>org.netbeans.spi.editor</package>
214
                <package>org.netbeans.spi.editor.caret</package>
214
                <package>org.netbeans.spi.editor.codegen</package>
215
                <package>org.netbeans.spi.editor.codegen</package>
215
                <package>org.netbeans.spi.editor.document</package>
216
                <package>org.netbeans.spi.editor.document</package>
216
                <package>org.netbeans.spi.editor.highlighting</package>
217
                <package>org.netbeans.spi.editor.highlighting</package>
(-)a/editor.lib2/src/org/netbeans/api/editor/EditorActionNames.java (-1 / +27 lines)
Lines 81-87 Link Here
81
81
82
    /**
82
    /**
83
     * Zoom text in by increasing default font size.
83
     * Zoom text in by increasing default font size.
84
     * <br/>
84
     * <br>
85
     * textComponent.getClientProperty("text-zoom") contains positive (or negative)
85
     * textComponent.getClientProperty("text-zoom") contains positive (or negative)
86
     * integer of how many points the font size should be increased (decreased).
86
     * integer of how many points the font size should be increased (decreased).
87
     * @since 1.45
87
     * @since 1.45
Lines 136-141 Link Here
136
     * @since 1.64
136
     * @since 1.64
137
     */
137
     */
138
    public static final String organizeMembers = "organize-members"; // NOI18N
138
    public static final String organizeMembers = "organize-members"; // NOI18N
139
140
141
    /**
142
     * Toggle caret between regular insert mode and overwrite mode.
143
     * @since 1.31
144
     */
145
    public static final String toggleTypingMode = "toggle-typing-mode"; // NOI18N
146
    
147
    /**
148
     * Remove the last added caret.
149
     * @since 2.6
150
     */
151
    public static final String removeLastCaret = "remove-last-caret"; // NOI18N
139
    
152
    
140
    /**
153
    /**
141
     * Navigates to the previous occurence of the symbol under the caret. The action
154
     * Navigates to the previous occurence of the symbol under the caret. The action
Lines 150-153 Link Here
150
     * @since 2.3
163
     * @since 2.3
151
     */
164
     */
152
    public static final String gotoNextOccurrence = "next-marked-occurrence"; // NOI18N
165
    public static final String gotoNextOccurrence = "next-marked-occurrence"; // NOI18N
166
    
167
    /**
168
     * Add a caret to the line above at the same position.
169
     * @since 2.6
170
     */
171
    public static final String addSelectionElseCaretUpAction = "add-selection-else-caret-up"; // NOI18N
172
    
173
    /**
174
     * Add a caret to the line below at the same position.
175
     * @since 2.6
176
     */
177
    public static final String addSelectionElseCaretDownAction = "add-selection-else-caret-down"; // NOI18N
178
153
}
179
}
(-)a/editor.lib2/src/org/netbeans/api/editor/EditorActionRegistration.java (-14 / +14 lines)
Lines 52-58 Link Here
52
/**
52
/**
53
 * Registration of an editor action so that it's automatically added into the list
53
 * Registration of an editor action so that it's automatically added into the list
54
 * of editor actions even without being explicitly created by <code>BaseKit.createActions()</code>.
54
 * of editor actions even without being explicitly created by <code>BaseKit.createActions()</code>.
55
 * <br/>
55
 * <br>
56
 * The corresponding annotation processor will build a xml-layer entry file
56
 * The corresponding annotation processor will build a xml-layer entry file
57
 * in the corresponding <i>/Editors/&lt;mime-type&gt;/Actions</code> folder.
57
 * in the corresponding <i>/Editors/&lt;mime-type&gt;/Actions</code> folder.
58
 *
58
 *
Lines 65-71 Link Here
65
65
66
    /**
66
    /**
67
     * Name of the action that will appear as <code>Action.NAME</code> attribute's value.
67
     * Name of the action that will appear as <code>Action.NAME</code> attribute's value.
68
     * <br/>
68
     * <br>
69
     * The Swing's text package actions use convention of lowercase letters with hyphens
69
     * The Swing's text package actions use convention of lowercase letters with hyphens
70
     * e.g. "caret-end-word" - see String constants in {@link javax.swing.text.DefaultEditorKit}.
70
     * e.g. "caret-end-word" - see String constants in {@link javax.swing.text.DefaultEditorKit}.
71
     *
71
     *
Lines 75-81 Link Here
75
75
76
    /**
76
    /**
77
     * Mime type for which the action will be registered.
77
     * Mime type for which the action will be registered.
78
     * <br/>
78
     * <br>
79
     * It implies the target folder of the registration <i>/Editors/&lt;mime-type&gt;/Actions</code>.
79
     * It implies the target folder of the registration <i>/Editors/&lt;mime-type&gt;/Actions</code>.
80
     *
80
     *
81
     * @return mime-type of the action registration (for example "text/x-java"
81
     * @return mime-type of the action registration (for example "text/x-java"
Lines 117-126 Link Here
117
     * Menu text bundle key of the registered action.
117
     * Menu text bundle key of the registered action.
118
     * If an empty string is used (the default) it will be set to the same value
118
     * If an empty string is used (the default) it will be set to the same value
119
     * like action's short description.
119
     * like action's short description.
120
     * <br/>
120
     * <br>
121
     * Value starting with a hash "#key" searches in a bundle in the same package
121
     * Value starting with a hash "#key" searches in a bundle in the same package
122
     * as the action's class.
122
     * as the action's class.
123
     * <br/>
123
     * <br>
124
     * "bundle#key" allows specification of both bundle and a corresponding key.
124
     * "bundle#key" allows specification of both bundle and a corresponding key.
125
     */
125
     */
126
    String menuText() default "";
126
    String menuText() default "";
Lines 129-138 Link Here
129
     * Popup menu text bundle key of the registered action.
129
     * Popup menu text bundle key of the registered action.
130
     * If an empty string is used (the default) it will be set to the same value
130
     * If an empty string is used (the default) it will be set to the same value
131
     * like menu text.
131
     * like menu text.
132
     * <br/>
132
     * <br>
133
     * Value starting with a hash "#key" searches in a bundle in the same package
133
     * Value starting with a hash "#key" searches in a bundle in the same package
134
     * as the action's class.
134
     * as the action's class.
135
     * <br/>
135
     * <br>
136
     * "bundle#key" allows specification of both bundle and a corresponding key.
136
     * "bundle#key" allows specification of both bundle and a corresponding key.
137
     */
137
     */
138
    String popupText() default "";
138
    String popupText() default "";
Lines 144-150 Link Here
144
144
145
    /**
145
    /**
146
     * Integer position of the main menu item among the other menu items.
146
     * Integer position of the main menu item among the other menu items.
147
     * <br/>
147
     * <br>
148
     * The default Integer.MAX_VALUE value means no menu representation.
148
     * The default Integer.MAX_VALUE value means no menu representation.
149
     */
149
     */
150
    int menuPosition() default Integer.MAX_VALUE;
150
    int menuPosition() default Integer.MAX_VALUE;
Lines 157-177 Link Here
157
157
158
    /**
158
    /**
159
     * Integer position of the popup menu item among the other popup menu (or submenu) items.
159
     * Integer position of the popup menu item among the other popup menu (or submenu) items.
160
     * <br/>
160
     * <br>
161
     * The default Integer.MAX_VALUE value means no popup menu representation.
161
     * The default Integer.MAX_VALUE value means no popup menu representation.
162
     */
162
     */
163
    int popupPosition() default Integer.MAX_VALUE;
163
    int popupPosition() default Integer.MAX_VALUE;
164
164
165
    /**
165
    /**
166
     * Integer position of this action in editor toolbar.
166
     * Integer position of this action in editor toolbar.
167
     * <br/>
167
     * <br>
168
     * The default Integer.MAX_VALUE value means no toolbar representation.
168
     * The default Integer.MAX_VALUE value means no toolbar representation.
169
     */
169
     */
170
    int toolBarPosition() default Integer.MAX_VALUE;
170
    int toolBarPosition() default Integer.MAX_VALUE;
171
    
171
    
172
    /**
172
    /**
173
     * True if the action should not display its icon in menu.
173
     * True if the action should not display its icon in menu.
174
     * <br/>
174
     * <br>
175
     * False by default (icon visible in menu).
175
     * False by default (icon visible in menu).
176
     * @since 1.74
176
     * @since 1.74
177
     */
177
     */
Lines 179-185 Link Here
179
    
179
    
180
    /**
180
    /**
181
     * True if the action should not be displayed in customizer for key bindings assignment.
181
     * True if the action should not be displayed in customizer for key bindings assignment.
182
     * <br/>
182
     * <br>
183
     * False by default (key binding can be configured for the action).
183
     * False by default (key binding can be configured for the action).
184
     * @since 1.74
184
     * @since 1.74
185
     */
185
     */
Lines 187-193 Link Here
187
187
188
    /**
188
    /**
189
     * Boolean key in preferences that corresponds to action's selected state.
189
     * Boolean key in preferences that corresponds to action's selected state.
190
     * <br/>
190
     * <br>
191
     * If set to non-empty string the action will be represented by a check-box
191
     * If set to non-empty string the action will be represented by a check-box
192
     * in menu and popup menu and the corresponding key will be set in
192
     * in menu and popup menu and the corresponding key will be set in
193
     * global mime-lookup <code>MimeLookup.getLookup(MimePath.EMPTY)</code>.
193
     * global mime-lookup <code>MimeLookup.getLookup(MimePath.EMPTY)</code>.
Lines 196-202 Link Here
196
    
196
    
197
    /**
197
    /**
198
     * Whether or not the action should be in checked state by default.
198
     * Whether or not the action should be in checked state by default.
199
     * <br/>
199
     * <br>
200
     * If the preference should default to true or to false. Only valid in conjunction
200
     * If the preference should default to true or to false. Only valid in conjunction
201
     * with {@link #preferencesKey() }.
201
     * with {@link #preferencesKey() }.
202
     * 
202
     * 
(-)a/editor.lib2/src/org/netbeans/api/editor/EditorActionRegistrations.java (-1 / +1 lines)
Lines 52-58 Link Here
52
/**
52
/**
53
 * Annotation allowing to annotate one action class by multiple
53
 * Annotation allowing to annotate one action class by multiple
54
 * {@link EditorActionRegistration} annotations.
54
 * {@link EditorActionRegistration} annotations.
55
 * <br/>
55
 * <br>
56
 * Example:
56
 * Example:
57
 * <pre>
57
 * <pre>
58
 * @EditorActionRegistrations({ @EditorActionRegistration(name = "name1", ...),
58
 * @EditorActionRegistrations({ @EditorActionRegistration(name = "name1", ...),
(-)a/editor.lib2/src/org/netbeans/api/editor/EditorRegistry.java (-17 / +17 lines)
Lines 72-83 Link Here
72
72
73
/**
73
/**
74
 * Registry maintaining {@link JTextComponent}s in most-recently-used order.
74
 * Registry maintaining {@link JTextComponent}s in most-recently-used order.
75
 * <br/>
75
 * <br>
76
 * The particular text component needs to register itself first (to avoid dealing
76
 * The particular text component needs to register itself first (to avoid dealing
77
 * with all the JTextFields etc.). Then the registry will attach
77
 * with all the JTextFields etc.). Then the registry will attach
78
 * a focus listener to the text component and once the component gains
78
 * a focus listener to the text component and once the component gains
79
 * the focus it will move to the head of the components list.
79
 * the focus it will move to the head of the components list.
80
 * <br/>
80
 * <br>
81
 * The registry will also fire a change in case a document property
81
 * The registry will also fire a change in case a document property
82
 * of the focused component changes (by calling component.setDocument()).
82
 * of the focused component changes (by calling component.setDocument()).
83
 *
83
 *
Lines 98-106 Link Here
98
98
99
    /**
99
    /**
100
     * Fired when focus was delivered to a registered text component.
100
     * Fired when focus was delivered to a registered text component.
101
     * <br/>
101
     * <br>
102
     * The focused component will become the first in the components list.
102
     * The focused component will become the first in the components list.
103
     * <br/>
103
     * <br>
104
     * The {@link java.beans.PropertyChangeEvent#getOldValue()} will be a component
104
     * The {@link java.beans.PropertyChangeEvent#getOldValue()} will be a component
105
     * losing the focus {@link FocusEvent#getOppositeComponent()}.
105
     * losing the focus {@link FocusEvent#getOppositeComponent()}.
106
     * The {@link java.beans.PropertyChangeEvent#getNewValue()} will be the text component gaining the focus.
106
     * The {@link java.beans.PropertyChangeEvent#getNewValue()} will be the text component gaining the focus.
Lines 109-117 Link Here
109
    
109
    
110
    /**
110
    /**
111
     * Fired when a registered focused component has lost the focus.
111
     * Fired when a registered focused component has lost the focus.
112
     * <br/>
112
     * <br>
113
     * The focused component will remain the first in the components list.
113
     * The focused component will remain the first in the components list.
114
     * <br/>
114
     * <br>
115
     * The {@link java.beans.PropertyChangeEvent#getOldValue()} will be the text component
115
     * The {@link java.beans.PropertyChangeEvent#getOldValue()} will be the text component
116
     * losing the focus and the {@link java.beans.PropertyChangeEvent#getNewValue()}
116
     * losing the focus and the {@link java.beans.PropertyChangeEvent#getNewValue()}
117
     * will be the component gaining the focus {@link FocusEvent#getOppositeComponent()}.
117
     * will be the component gaining the focus {@link FocusEvent#getOppositeComponent()}.
Lines 121-127 Link Here
121
    /**
121
    /**
122
     * Fired when document property of the focused component changes
122
     * Fired when document property of the focused component changes
123
     * i.e. someone has called {@link JTextComponent#setDocument(Document)}.
123
     * i.e. someone has called {@link JTextComponent#setDocument(Document)}.
124
     * <br/>
124
     * <br>
125
     * The {@link java.beans.PropertyChangeEvent#getOldValue()} will be the original document
125
     * The {@link java.beans.PropertyChangeEvent#getOldValue()} will be the original document
126
     * of the focused text component and the {@link java.beans.PropertyChangeEvent#getNewValue()}
126
     * of the focused text component and the {@link java.beans.PropertyChangeEvent#getNewValue()}
127
     * will be the new document set to the focused text component.
127
     * will be the new document set to the focused text component.
Lines 132-144 Link Here
132
     * Fired when a component (returned previously from {@link #componentList()})
132
     * Fired when a component (returned previously from {@link #componentList()})
133
     * is removed from component hierarchy (so it's likely that the component will be released completely
133
     * is removed from component hierarchy (so it's likely that the component will be released completely
134
     * and garbage-collected).
134
     * and garbage-collected).
135
     * <br/>
135
     * <br>
136
     * Such component will no longer be returned from {@link #componentList()}
136
     * Such component will no longer be returned from {@link #componentList()}
137
     * or {@link #lastFocusedComponent()}.
137
     * or {@link #lastFocusedComponent()}.
138
     * <br/>
138
     * <br>
139
     * The {@link java.beans.PropertyChangeEvent#getOldValue()} will be the removed
139
     * The {@link java.beans.PropertyChangeEvent#getOldValue()} will be the removed
140
     * component.
140
     * component.
141
     * <br/>
141
     * <br>
142
     * The {@link java.beans.PropertyChangeEvent#getNewValue()} returns <code>null</code>
142
     * The {@link java.beans.PropertyChangeEvent#getNewValue()} returns <code>null</code>
143
     */
143
     */
144
    public static final String COMPONENT_REMOVED_PROPERTY = "componentRemoved"; //NOI18N
144
    public static final String COMPONENT_REMOVED_PROPERTY = "componentRemoved"; //NOI18N
Lines 147-160 Link Here
147
     * Fired when the last focused component (returned previously from {@link #lastFocusedComponent()})
147
     * Fired when the last focused component (returned previously from {@link #lastFocusedComponent()})
148
     * was removed from component hierarchy (so it's likely that the component will be released completely
148
     * was removed from component hierarchy (so it's likely that the component will be released completely
149
     * and garbage-collected).
149
     * and garbage-collected).
150
     * <br/>
150
     * <br>
151
     * Such component will no longer be returned from {@link #componentList()}
151
     * Such component will no longer be returned from {@link #componentList()}
152
     * or {@link #lastFocusedComponent()}.
152
     * or {@link #lastFocusedComponent()}.
153
     * <br/>
153
     * <br>
154
     * The {@link java.beans.PropertyChangeEvent#getOldValue()} will be the removed
154
     * The {@link java.beans.PropertyChangeEvent#getOldValue()} will be the removed
155
     * last focused component and the {@link java.beans.PropertyChangeEvent#getNewValue()}
155
     * last focused component and the {@link java.beans.PropertyChangeEvent#getNewValue()}
156
     * will be the component that would currently be returned from {@link #lastFocusedComponent()}.
156
     * will be the component that would currently be returned from {@link #lastFocusedComponent()}.
157
     * <br/>
157
     * <br>
158
     * If {@link java.beans.PropertyChangeEvent#getNewValue()} returns <code>null</code>
158
     * If {@link java.beans.PropertyChangeEvent#getNewValue()} returns <code>null</code>
159
     * then there are no longer any registered components
159
     * then there are no longer any registered components
160
     * ({@link #componentList()} would return empty list). If the client
160
     * ({@link #componentList()} would return empty list). If the client
Lines 180-186 Link Here
180
180
181
    /**
181
    /**
182
     * Return last focused text component (from the ones included in the registry).
182
     * Return last focused text component (from the ones included in the registry).
183
     * <br/>
183
     * <br>
184
     * It may or may not currently have a focus.
184
     * It may or may not currently have a focus.
185
     * 
185
     * 
186
     * @return last focused text component or null if no text components
186
     * @return last focused text component or null if no text components
Lines 193-199 Link Here
193
    /**
193
    /**
194
     * Return the last focused component if it currently has a focus
194
     * Return the last focused component if it currently has a focus
195
     * or return null if none of the registered components currently have the focus.
195
     * or return null if none of the registered components currently have the focus.
196
     * <br/>
196
     * <br>
197
     * @return focused component or null if none of the registered components
197
     * @return focused component or null if none of the registered components
198
     *  is currently focused.
198
     *  is currently focused.
199
     */
199
     */
Lines 205-211 Link Here
205
    /**
205
    /**
206
     * Get list of all components present in the registry starting with the most active
206
     * Get list of all components present in the registry starting with the most active
207
     * and ending with least active component.
207
     * and ending with least active component.
208
     * <br/>
208
     * <br>
209
     * The list is a snapshot of the current state and it may be modified
209
     * The list is a snapshot of the current state and it may be modified
210
     * by the caller if desired.
210
     * by the caller if desired.
211
     * 
211
     * 
Lines 240-246 Link Here
240
     *   <li>{@link #FOCUS_LOST_PROPERTY}</li>
240
     *   <li>{@link #FOCUS_LOST_PROPERTY}</li>
241
     *   <li>{@link #FOCUSED_DOCUMENT_PROPERTY}</li>
241
     *   <li>{@link #FOCUSED_DOCUMENT_PROPERTY}</li>
242
     * </ul>.
242
     * </ul>.
243
     * <br/>
243
     * <br>
244
     * All the firing should occur in AWT thread only
244
     * All the firing should occur in AWT thread only
245
     * (assuming the JTextComponent.setDocument() is done properly in AWT).
245
     * (assuming the JTextComponent.setDocument() is done properly in AWT).
246
     * 
246
     * 
(-)a/editor.lib2/src/org/netbeans/api/editor/EditorUtilities.java (+9 lines)
Lines 57-62 Link Here
57
 */
57
 */
58
58
59
public final class EditorUtilities {
59
public final class EditorUtilities {
60
    
61
    /**
62
     * Client property of editor component which determines
63
     * whether caret is currently in overwrite mode (Boolean.TRUE) or insert mode (Boolean.FALSE or null).
64
     * <br>
65
     * It's modified by appropriate editor actions.
66
     * @since 2.6
67
     */
68
    public static final String CARET_OVERWRITE_MODE_PROPERTY = "caret-overwrite-mode";
60
69
61
    private EditorUtilities() {
70
    private EditorUtilities() {
62
        // No instances
71
        // No instances
(-)a/editor.lib2/src/org/netbeans/api/editor/caret/CaretInfo.java (+182 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2015 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2015 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.api.editor.caret;
43
44
import java.awt.Point;
45
import java.util.logging.Logger;
46
import javax.swing.text.Position;
47
import org.netbeans.api.annotations.common.CheckForNull;
48
49
/**
50
 * Immutable info about a single caret used in caret API - see {@link EditorCaret}.
51
 * <br>
52
 * There is one-to-one reference between immutable caret info and mutable {@link CaretItem}.
53
 * CaretItem is not accessible through the caret API and it's managed privately by EditorCaret.
54
 * Once the caret item gets mutated its corresponding caret info becomes obsolete
55
 * and new caret info instance gets created lazily.
56
 *
57
 * @author Miloslav Metelka
58
 * @author Ralph Ruijs
59
 * @see EditorCaret
60
 * @since 2.6
61
 */
62
public final class CaretInfo {
63
    
64
    // -J-Dorg.netbeans.api.editor.CaretInfo.level=FINEST
65
    private static final Logger LOG = Logger.getLogger(CaretInfo.class.getName());
66
    
67
    private final CaretItem caretItem;
68
69
    private final Position dotPos;
70
71
    private final Position markPos;
72
73
    private final Point magicCaretPosition;
74
75
    CaretInfo(CaretItem caretItem) {
76
        this.caretItem = caretItem;
77
        this.dotPos = caretItem.getDotPosition();
78
        this.markPos = caretItem.getMarkPosition();
79
        this.magicCaretPosition = caretItem.getMagicCaretPosition();
80
    }
81
82
    /**
83
     * Get position of the caret itself.
84
     * @return non-null position of the caret placement. The position may be virtual
85
     *  so methods in {@link org.netbeans.api.editor.document.ShiftPositions} may be used if necessary.
86
     */
87
    @CheckForNull
88
    public Position getDotPosition() {
89
        return dotPos;
90
    }
91
92
    /**
93
     * Return either the same object like {@link #getDotPosition()} if there's no selection
94
     * or return position denoting the other end of an existing selection (which is either before
95
     * or after the dot position depending of how the selection was created).
96
     * @return non-null position of the caret placement. The position may be virtual
97
     *  so methods in {@link org.netbeans.api.editor.document.ShiftPositions} may be used if necessary.
98
     */
99
    @CheckForNull
100
    public Position getMarkPosition() {
101
        return markPos;
102
    }
103
    
104
    /**
105
     * Fetches the current position of the caret.
106
     *
107
     * @return the position &gt;=0
108
     */
109
    public int getDot() {
110
        return (dotPos != null) ? dotPos.getOffset() : 0;
111
    }
112
113
    /**
114
     * Fetches the current position of the mark.  If there
115
     * is a selection, the mark will not be the same as
116
     * the dot.
117
     *
118
     * @return the position &gt;=0
119
     */
120
    public int getMark() {
121
        return (markPos != null) ? markPos.getOffset() : getDot();
122
    }
123
    
124
    /**
125
     * Determines if there currently is a selection.
126
     * 
127
     * @return true if there's a selection or false if there's no selection for this caret.
128
     */
129
    public boolean isSelection() {
130
        return (dotPos != null && markPos != null &&
131
                markPos != dotPos && dotPos.getOffset() != markPos.getOffset());
132
    }
133
    
134
    /**
135
     * Determines if the selection is currently visible.
136
     * 
137
     * @return true if both {@link #isSelection() } and {@link EditorCaret#isSelectionVisible() } are true.
138
     */
139
    public boolean isSelectionShowing() {
140
        return caretItem.editorCaret().isSelectionVisible() && isSelection();
141
    }
142
143
    /**
144
     * Returns the selected text's start position.  Return 0 for an
145
     * empty document, or the value of dot if no selection.
146
     *
147
     * @return the start position &ge; 0
148
     */
149
    public int getSelectionStart() {
150
        return Math.min(getDot(), getMark());
151
    }
152
153
    /**
154
     * Returns the selected text's end position.  Return 0 if the document
155
     * is empty, or the value of dot if there is no selection.
156
     *
157
     * @return the end position &ge; 0
158
     */
159
    public int getSelectionEnd() {
160
        return Math.max(getDot(), getMark());
161
    }
162
    
163
    /**
164
     * Gets the current caret visual location.
165
     *
166
     * @return the visual position.
167
     */
168
    public @CheckForNull Point getMagicCaretPosition() {
169
        return magicCaretPosition;
170
    }
171
    
172
    CaretItem getCaretItem() {
173
        return caretItem;
174
    }
175
    
176
    @Override
177
    public String toString() {
178
        return "dotPos=" + dotPos + ", markPos=" + markPos + ", magicCaretPosition=" + magicCaretPosition + // NOI18N
179
                "\n  caretItem=" + caretItem; // NOI18N
180
    }
181
    
182
}
(-)a/editor.lib2/src/org/netbeans/api/editor/caret/CaretItem.java (+254 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2016 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.api.editor.caret;
43
44
import java.awt.Point;
45
import java.awt.Rectangle;
46
import java.util.logging.Logger;
47
import javax.swing.text.Position;
48
import org.netbeans.api.annotations.common.CheckForNull;
49
50
/**
51
 * A single caret inside {@link EditorCaret} handled internally by EditorCaret.
52
 * <br>
53
 * For API client methods {@link CaretInfo} class is used. There is one-to-one reference
54
 * between caret item and caret info. But the info is immutable so once the caret item
55
 * gets mutated its corresponding caret info becomes obsolete and new info gets created
56
 * lazily.
57
 *
58
 * @author Miloslav Metelka
59
 */
60
final class CaretItem implements Comparable {
61
    
62
    // -J-Dorg.netbeans.modules.editor.lib2.CaretItem.level=FINEST
63
    private static final Logger LOG = Logger.getLogger(CaretItem.class.getName());
64
65
    private static final int TRANSACTION_MARK_REMOVED = 1;
66
    
67
    private static final int UPDATE_VISUAL_BOUNDS = 2;
68
69
    private final EditorCaret editorCaret;
70
    
71
    private Position dotPos;
72
73
    private Position markPos;
74
75
    private Point magicCaretPosition;
76
    
77
    /**
78
     * Last info or null if info became obsolete and should be recomputed.
79
     */
80
    private CaretInfo info;
81
    
82
    private Rectangle caretBounds;
83
    
84
    /**
85
     * Hint of index of this caret item in replaceCaretItems in transaction context.
86
     */
87
    private int transactionIndexHint;
88
    
89
    /**
90
     * Transaction uses this flag to mark this item for removal.
91
     */
92
    private int statusBits;
93
94
    CaretItem(EditorCaret editorCaret, Position dotPos, Position markPos) {
95
        this.editorCaret = editorCaret;
96
        this.dotPos = dotPos;
97
        this.markPos = markPos;
98
        this.statusBits = UPDATE_VISUAL_BOUNDS; // Request visual bounds updating automatically
99
    }
100
    
101
    EditorCaret editorCaret() {
102
        return editorCaret;
103
    }
104
    
105
    void ensureValidInfo() {
106
        // No explicit locking - locking managed by EditorCaret
107
        if (info == null) {
108
            info = new CaretInfo(this);
109
        }
110
    }
111
    
112
    void clearInfo() {
113
        // No explicit locking - locking managed by EditorCaret
114
        this.info = null;
115
    }
116
117
    CaretInfo getValidInfo() {
118
        ensureValidInfo();
119
        return info;
120
    }
121
122
//    void clearTransactionInfo() {
123
//        // No explicit locking - locking managed by EditorCaret
124
//        this.transactionInfo = null;
125
//    }
126
//
127
//    CaretInfo getValidTransactionInfo() {
128
//        if (transactionInfo == null) {
129
//            transactionInfo = new CaretInfo(this);
130
//        }
131
//        return transactionInfo;
132
//    }
133
    
134
    /**
135
     * Get position of the caret itself.
136
     *
137
     * @return non-null position of the caret placement. The position may be
138
     * virtual so methods in {@link org.netbeans.api.editor.document.ShiftPositions} may be used if necessary.
139
     */
140
    @CheckForNull
141
    Position getDotPosition() {
142
        return dotPos;
143
    }
144
145
    /**
146
     * Return either the same object like {@link #getDotPosition()} if there's
147
     * no selection or return position denoting the other end of an existing
148
     * selection (which is either before or after the dot position depending of
149
     * how the selection was created).
150
     *
151
     * @return non-null position of the caret placement. The position may be
152
     * virtual so methods in {@link org.netbeans.api.editor.document.ShiftPositions} may be used if necessary.
153
     */
154
    @CheckForNull
155
    Position getMarkPosition() {
156
        return markPos;
157
    }
158
159
    int getDot() {
160
        return (dotPos != null) ? dotPos.getOffset() : 0;
161
    }
162
163
    int getMark() {
164
        return (markPos != null) ? markPos.getOffset() : 0;
165
    }
166
167
    /**
168
     * @return true if there's a selection or false if there's no selection for
169
     * this caret.
170
     */
171
    boolean isSelection() {
172
        return (dotPos != null && markPos != null &&
173
                markPos != dotPos && dotPos.getOffset() != markPos.getOffset());
174
    }
175
    
176
    boolean isSelectionShowing() {
177
        return editorCaret.isSelectionVisible() && isSelection();
178
    }
179
180
    Position getSelectionStart() {
181
        return dotPos; // TBD - possibly inspect virtual columns etc.
182
    }
183
184
    Position getSelectionEnd() {
185
        return dotPos; // TBD - possibly inspect virtual columns etc.
186
    }
187
188
    Point getMagicCaretPosition() {
189
        return magicCaretPosition;
190
    }
191
192
    void setDotPos(Position dotPos) {
193
        this.dotPos = dotPos;
194
    }
195
196
    void setMarkPos(Position markPos) {
197
        this.markPos = markPos;
198
    }
199
200
    void setMagicCaretPosition(Point newMagicCaretPosition) { // [TODO] move to transaction context
201
        this.magicCaretPosition = newMagicCaretPosition;
202
    }
203
204
    void setCaretBounds(Rectangle newCaretBounds) {
205
        this.caretBounds = newCaretBounds;
206
    }
207
208
    Rectangle getCaretBounds() {
209
        return this.caretBounds;
210
    }
211
212
    int getTransactionIndexHint() {
213
        return transactionIndexHint;
214
    }
215
216
    void setTransactionIndexHint(int transactionIndexHint) {
217
        this.transactionIndexHint = transactionIndexHint;
218
    }
219
220
    void markTransactionMarkRemoved() {
221
        this.statusBits |= TRANSACTION_MARK_REMOVED;
222
    }
223
224
    boolean isTransactionMarkRemoved() {
225
        return (this.statusBits & TRANSACTION_MARK_REMOVED) != 0;
226
    }
227
228
    void clearTransactionMarkRemoved() {
229
        this.statusBits &= ~TRANSACTION_MARK_REMOVED;
230
    }
231
    
232
    void markUpdateVisualBounds() {
233
        this.statusBits |= UPDATE_VISUAL_BOUNDS;
234
    }
235
    
236
    boolean isUpdateVisualBounds() {
237
        return (this.statusBits & UPDATE_VISUAL_BOUNDS) != 0;
238
    }
239
    
240
    void clearUpdateVisualBounds() {
241
        this.statusBits &= ~UPDATE_VISUAL_BOUNDS;
242
    }
243
    
244
    @Override
245
    public int compareTo(Object o) {
246
        return getDot() - ((CaretItem)o).getDot();
247
    }
248
249
    @Override
250
    public String toString() {
251
        return "dotPos=" + dotPos + ", markPos=" + markPos + ", magicCaretPosition=" + magicCaretPosition; // NOI18N
252
    }
253
254
}
(-)a/editor.lib2/src/org/netbeans/api/editor/caret/CaretMoveContext.java (+172 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2016 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.api.editor.caret;
43
44
import java.awt.Point;
45
import java.util.List;
46
import javax.swing.text.Document;
47
import javax.swing.text.JTextComponent;
48
import javax.swing.text.Position;
49
import org.netbeans.api.annotations.common.NonNull;
50
51
/**
52
 * Context for carets moving within {@link CaretMoveHandler}.
53
 *
54
 * @author Miloslav Metelka
55
 * @since 2.6
56
 */
57
public final class CaretMoveContext {
58
    
59
    private CaretTransaction transaction;
60
    
61
    CaretMoveContext(CaretTransaction transactionContext) {
62
        this.transaction = transactionContext;
63
    }
64
65
    /**
66
     * Get list of carets at the time when transaction started.
67
     * <br>
68
     * <b>Note</b>: information contained in the returned list will not reflect changes
69
     * performed by the dot/selection modification methods contained in this class.
70
     * It will always return the original state from the beginning of the move transaction.
71
     * All the performed modifications will be incorporated at the end of the move transaction.
72
     *
73
     * @return list of carets at the time when moving transaction has started.
74
     */
75
    public @NonNull List<CaretInfo> getOriginalCarets() {
76
        return transaction.getOriginalCarets();
77
    }
78
    
79
    /**
80
     * Get last item from the list returned by {@link #getOriginalCarets()}.
81
     * @return last caret at the time when caret moving transaction has started.
82
     * @see #getOriginalCarets()
83
     */
84
    public @NonNull CaretInfo getOriginalLastCaret() {
85
        List<CaretInfo> origCarets = getOriginalCarets();
86
        return origCarets.get(origCarets.size() - 1);
87
    }
88
    
89
    /**
90
     * Get list of carets sorted ascending order at the time when transaction started.
91
     * <br>
92
     * <b>Note</b>: information contained in the returned list will not reflect changes
93
     * performed by the dot/selection modification methods contained in this class.
94
     * It will always return the original state from the beginning of the move transaction.
95
     * All the performed modifications will be incorporated at the end of the move transaction.
96
     *
97
     * @return list of carets at the time when caret moving transaction has started.
98
     */
99
    public @NonNull List<CaretInfo> getOriginalSortedCarets() {
100
        return transaction.getOriginalSortedCarets();
101
    }
102
    
103
    /**
104
     * Change dot of the given caret.
105
     *
106
     * @param caret non-null caret.
107
     * @param dotPos new dot position.
108
     * @return false if passed caret is obsolete or invalid (e.g. a member of another {@link EditorCaret})
109
     *  or true otherwise.
110
     */
111
    public boolean setDot(@NonNull CaretInfo caret, @NonNull Position dotPos) {
112
        return setDotAndMark(caret, dotPos, dotPos);
113
    }
114
    
115
    /**
116
     * Move dot of the given getCaret so getCaret selection gets created or changed.
117
     *
118
     * @param caret non-null getCaret.
119
     * @param dotPos new dot position.
120
     * @return false if passed caret is obsolete or invalid (e.g. a member of another {@link EditorCaret})
121
     *  or true otherwise.
122
     */
123
    public boolean moveDot(@NonNull CaretInfo caret, @NonNull Position dotPos) {
124
        return transaction.moveDot(caret.getCaretItem(), dotPos);
125
    }
126
    
127
    /**
128
     * Move dot of the given getCaret so getCaret selection gets created or changed.
129
     *
130
     * @param caret non-null getCaret.
131
     * @param dotPos new dot position.
132
     * @param markPos starting position of the selection or the same position like dotPos if there should be no selection.
133
     *  <br>
134
     *  The position may point to a lower offset than dotPos in case the selection
135
     *  should extend from a higher offset to a lower offset.
136
     * @return false if passed caret is obsolete or invalid (e.g. a member of another {@link EditorCaret})
137
     *  or true otherwise.
138
     */
139
    public boolean setDotAndMark(@NonNull CaretInfo caret, @NonNull Position dotPos, @NonNull Position markPos) {
140
        return transaction.setDotAndMark(caret.getCaretItem(), dotPos, markPos);
141
    }
142
143
    /**
144
     * Set magic caret position of the given caret.
145
     *
146
     * @param caret non-null caret.
147
     * @param p new magic caret position.
148
     * @return false if passed caret is obsolete or invalid (e.g. a member of another {@link EditorCaret})
149
     *  or true otherwise.
150
     */
151
    public boolean setMagicCaretPosition(@NonNull CaretInfo caret, Point p) {
152
        return transaction.setMagicCaretPosition(caret.getCaretItem(), p);
153
    }
154
155
    /**
156
     * Return component in which the caret is currently installed.
157
     * @return non-null component in which the caret is installed.
158
     */
159
    public @NonNull JTextComponent getComponent() {
160
        return transaction.getComponent();
161
    }
162
    
163
    /**
164
     * Get document associated with the text component.
165
     * @return document of the associated component.
166
     * @see #getComponent()
167
     */
168
    public Document getDocument() {
169
        return getComponent().getDocument();
170
    }
171
172
}
(-)a/editor.lib2/src/org/netbeans/api/editor/caret/CaretTransaction.java (+627 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2016 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2016 Sun Microsystems, Inc.
41
 */
42
package org.netbeans.api.editor.caret;
43
44
import org.netbeans.spi.editor.caret.CaretMoveHandler;
45
import java.awt.Point;
46
import java.util.List;
47
import javax.swing.text.BadLocationException;
48
import javax.swing.text.Document;
49
import javax.swing.text.JTextComponent;
50
import javax.swing.text.Position;
51
import org.netbeans.api.annotations.common.NonNull;
52
import org.netbeans.api.editor.document.ShiftPositions;
53
import org.netbeans.lib.editor.util.GapList;
54
55
/**
56
 * Context passed to getCaret transaction allowing to create/remove/modify the carets during the transaction.
57
 *
58
 * @author Miloslav Metelka
59
 */
60
final class CaretTransaction {
61
    
62
    private final EditorCaret editorCaret;
63
    
64
    private final JTextComponent component;
65
    
66
    private final Document doc;
67
    
68
    /**
69
     * Original items held here mainly due to latter repaints of the removed items.
70
     */
71
    private final GapList<CaretItem> origCaretItems;
72
    
73
    private int modIndex;
74
    
75
    /**
76
     * For MOVE_HANDLER and CARETS_REMOVE this is end index of modified area.
77
     * For CARETS_ADD and MIXED this is not filled.
78
     */
79
    private int modEndIndex;
80
    
81
    private CaretItem[] addCaretItems;
82
    
83
    private int affectedStartIndex = Integer.MAX_VALUE;
84
85
    private int affectedEndIndex;
86
87
    private int affectedStartOffset = Integer.MAX_VALUE;
88
    
89
    private int affectedEndOffset;
90
    
91
    private boolean addOrRemove;
92
    
93
    private boolean dotOrMarkChanged;
94
    
95
    private GapList<CaretItem> replaceItems;
96
    
97
    private GapList<CaretItem> replaceSortedItems;
98
    
99
    private GapList<CaretItem> extraRemovedItems;
100
    
101
    private int[] indexes;
102
    
103
    private int indexesLength;
104
    
105
    /**
106
     * End of area where index hints were updated.
107
     */
108
    private int indexHintEnd;
109
    
110
    private boolean fullResort;
111
    
112
    
113
    CaretTransaction(EditorCaret caret, JTextComponent component, Document doc) {
114
        this.editorCaret = caret;
115
        this.component = component;
116
        this.doc = doc;
117
        this.origCaretItems = editorCaret.getCaretItems();
118
    }
119
    
120
121
    @NonNull EditorCaret getCaret() {
122
        return editorCaret;
123
    }
124
    
125
    @NonNull JTextComponent getComponent() {
126
        return component;
127
    }
128
    
129
    @NonNull Document getDocument() {
130
        return doc;
131
    }
132
    
133
    boolean isAnyChange() {
134
        return addOrRemove || dotOrMarkChanged;
135
    }
136
    
137
    boolean moveDot(@NonNull CaretItem caret, @NonNull Position dotPos) {
138
        return setDotAndMark(caret, dotPos, caret.getMarkPosition());
139
    }
140
141
    boolean setDotAndMark(@NonNull CaretItem caretItem, @NonNull Position dotPos, @NonNull Position markPos) {
142
        assert (dotPos != null) : "dotPos must not be null";
143
        assert (markPos != null) : "markPos must not be null";
144
        int index = findCaretItemIndex(origCaretItems, caretItem);
145
        if (index != -1) {
146
            Position origDotPos = caretItem.getDotPosition();
147
            Position origMarkPos = caretItem.getMarkPosition();
148
            boolean changed = false;
149
            if (origDotPos == null || ShiftPositions.compare(dotPos, origDotPos) != 0) {
150
                caretItem.setDotPos(dotPos);
151
                changed = true;
152
            }
153
            if (origMarkPos == null || ShiftPositions.compare(markPos, origMarkPos) != 0) {
154
                caretItem.setMarkPos(markPos);
155
                changed = true;
156
            }
157
            if (changed) {
158
                updateAffectedIndexes(index, index + 1);
159
                caretItem.markUpdateVisualBounds();
160
                caretItem.clearInfo();
161
                dotOrMarkChanged = true;
162
            }
163
            return changed;
164
        }
165
        return false;
166
        //caret.setDotCaret(offset, this, true);
167
    }
168
    
169
    boolean setMagicCaretPosition(@NonNull CaretItem caretItem, Point p) {
170
        int index = findCaretItemIndex(origCaretItems, caretItem);
171
        if (index != -1) {
172
            caretItem.setMagicCaretPosition(p);
173
            caretItem.clearInfo();
174
            updateAffectedIndexes(index, index + 1);
175
            return true;
176
        }
177
        return false;
178
    }
179
    
180
    void documentInsertAtZeroOffset(int insertEndOffset) {
181
        // Nested insert inside active transaction for caret moving
182
        // Since carets may already be moved - do the operations with CaretItems directly
183
        Position insertEndPos = null;
184
        for (CaretItem caretItem : editorCaret.getSortedCaretItems()) {
185
            Position dotPos = caretItem.getDotPosition();
186
            boolean modifyDot = (dotPos == null || dotPos.getOffset() == 0);
187
            Position markPos = caretItem.getMarkPosition();
188
            boolean modifyMark = (markPos == null || markPos.getOffset() == 0);
189
            if (modifyDot || modifyMark) {
190
                if (insertEndPos == null) {
191
                    try {
192
                        insertEndPos = doc.createPosition(insertEndOffset);
193
                    } catch (BadLocationException ex) {
194
                        // Should never happen
195
                        return;
196
                    }
197
                }
198
                if (modifyDot) {
199
                    dotPos = insertEndPos;
200
                }
201
                if (modifyMark) {
202
                    markPos = insertEndPos;
203
                }
204
                setDotAndMark(caretItem, dotPos, markPos);
205
            }
206
            // Do not break the loop when caret's pos is above zero offset
207
            // since the carets may be already moved during the transaction
208
            // - possibly to offset zero. But there could be optimization
209
            // at least scan position of only the caret items that were modified and not others.
210
        }
211
        if (insertEndPos != null) {
212
            updateAffectedOffsets(0, insertEndOffset); // TODO isn't this extra work that setDotAndMark() already did??
213
            fullResort = true;
214
        }
215
    }
216
    
217
    void documentRemove(int offset) {
218
        fullResort = true; // TODO modify to more specific update
219
    }
220
221
    private void setSelectionStartEnd(@NonNull CaretItemInfo info, @NonNull Position pos, boolean start) {
222
        int index = findCaretItemIndex(origCaretItems, info.caretItem);
223
        assert (index != -1) : "Index=" + index + " should be valid";
224
        if (start == info.dotAtStart) {
225
            info.caretItem.setDotPos(pos);
226
        } else {
227
            info.caretItem.setMarkPos(pos);
228
        }
229
        updateAffectedIndexes(index, index + 1);
230
    }
231
    
232
    void handleCaretRemove(@NonNull CaretInfo caret) {
233
        
234
    }
235
236
    GapList<CaretItem> getReplaceItems() {
237
        return replaceItems;
238
    }
239
    
240
    GapList<CaretItem> getSortedCaretItems() {
241
        return replaceSortedItems;
242
    }
243
    
244
    List<CaretInfo> getOriginalCarets() {
245
        // Current impl ignores possible replaceItems content since client transactions only operate over
246
        // original infos from editorCaret. Internal transactions know the type of transaction
247
        // that was performed so they will skip carets possibly removed by the transaction.
248
        return editorCaret.getCarets();
249
    }
250
251
    List<CaretInfo> getOriginalSortedCarets() {
252
        // Current impl ignores possible replaceItems content - see getOriginalCarets()
253
        return editorCaret.getSortedCarets();
254
    }
255
256
    void replaceCarets(RemoveType removeType, int offset, CaretItem[] addCaretItems) {
257
        int size = origCaretItems.size();
258
        switch (removeType) {
259
            case NO_REMOVE:
260
                break;
261
            case REMOVE_LAST_CARET:
262
                if (size > 1) {
263
                    modIndex = size - 1;
264
                    modEndIndex = size;
265
                    addOrRemove = true;
266
                }
267
                break;
268
            case RETAIN_LAST_CARET:
269
                if (size > 1) {
270
                    modEndIndex = size - 1;
271
                    addOrRemove = true;
272
                }
273
                break;
274
            case REMOVE_ALL_CARETS:
275
                if (size > 0) {
276
                    modEndIndex = size;
277
                    addOrRemove = true;
278
                }
279
                break;
280
            case DOCUMENT_REMOVE:
281
                break;
282
            case DOCUMENT_INSERT_ZERO_OFFSET:
283
                documentInsertAtZeroOffset(offset);
284
                break;
285
286
            default:
287
                throw new AssertionError("Unhandled removeType=" + removeType); // NOI18N
288
        }
289
        if (addCaretItems != null) {
290
            this.addCaretItems = addCaretItems;
291
            addOrRemove = true;
292
        }
293
    }
294
    
295
    void runCaretMoveHandler(CaretMoveHandler handler) {
296
        CaretMoveContext context = new CaretMoveContext(this);
297
        handler.moveCarets(context);
298
    }
299
    
300
    private static int getOffset(Position pos) {
301
        return (pos != null) ? pos.getOffset() : 0;
302
    }
303
    
304
    void removeOverlappingRegions() {
305
        removeOverlappingRegions(0, Integer.MAX_VALUE);
306
    }
307
308
    void removeOverlappingRegions(int removeOffset) {
309
        removeOverlappingRegions(0, removeOffset); // TODO compute startIndex by binary search
310
    }
311
    
312
    void removeOverlappingRegions(int startIndex, int stopOffset) {
313
        if (addOrRemove) {
314
            initReplaceItems();
315
        } else if (dotOrMarkChanged) {
316
            initReplaceItems(); // TODO optimize for low number of changed items
317
        }
318
        GapList<CaretItem> origSortedItems = (replaceSortedItems != null)
319
                ? replaceSortedItems
320
                : editorCaret.getSortedCaretItems();
321
        int origSortedItemsSize = origSortedItems.size();
322
        GapList<CaretItem> nonOverlappingItems = null;
323
        int copyStartIndex = 0;
324
        int i = startIndex - 1;
325
        boolean itemsRemoved = false;
326
        CaretItemInfo lastInfo = new CaretItemInfo();
327
        if (i >= 0) {
328
            lastInfo.update(origSortedItems.get(i));
329
        } // Otherwise leave the default zeros in lastInfo
330
        
331
        CaretItemInfo itemInfo = new CaretItemInfo();
332
        while (++i < origSortedItemsSize) {
333
            itemInfo.update(origSortedItems.get(i));
334
            if (itemInfo.overlapsAtStart(lastInfo)) {
335
                if (nonOverlappingItems == null) {
336
                    nonOverlappingItems = new GapList<CaretItem>(origSortedItemsSize - 1); // At least one will be skipped
337
                }
338
                itemsRemoved = true;
339
                // Determine type of overlap
340
                if (!lastInfo.dotAtStart) { // Caret of lastInfo moved into next block
341
                    if (lastInfo.startsBelow(itemInfo)) {
342
                        // Extend selection of itemInfo to start of lastInfo
343
                        updateAffectedOffsets(lastInfo.startOffset, itemInfo.startOffset);
344
                        setSelectionStartEnd(itemInfo, lastInfo.startPos, true);
345
                    }
346
                    // Remove lastInfo's getCaret item
347
                    lastInfo.caretItem.markTransactionMarkRemoved();
348
                    origSortedItems.copyElements(copyStartIndex, i - 1, nonOverlappingItems);
349
                    copyStartIndex = i;
350
                    
351
                } else { // Remove itemInfo and set selection of lastInfo to end of itemInfo
352
                    if (itemInfo.endsAbove(lastInfo)) {
353
                        updateAffectedOffsets(lastInfo.endOffset, itemInfo.endOffset);
354
                        setSelectionStartEnd(lastInfo, itemInfo.endPos, false);
355
                    }
356
                    // Remove itemInfo's getCaret item
357
                    itemInfo.caretItem.markTransactionMarkRemoved();
358
                    origSortedItems.copyElements(copyStartIndex, i, nonOverlappingItems);
359
                    copyStartIndex = i + 1;
360
                }
361
            } else { // No overlapping
362
                // Swap the items to reuse original lastInfo
363
                CaretItemInfo tmp = lastInfo;
364
                lastInfo = itemInfo;
365
                itemInfo = tmp;
366
                if (lastInfo.endOffset > stopOffset) {
367
                    break;
368
                }
369
            }
370
        }
371
372
        if (itemsRemoved) { // At least one item removed
373
            if (copyStartIndex < origSortedItemsSize) {
374
                origSortedItems.copyElements(copyStartIndex, origSortedItemsSize, nonOverlappingItems);
375
            }
376
            GapList<CaretItem> origItems = resultItems();
377
            int origItemsSize = origItems.size();
378
            replaceItems = new GapList<>(origItemsSize);
379
            for (i = 0; i < origItemsSize; i++) {
380
                CaretItem caretItem = origItems.get(i);
381
                if (caretItem.isTransactionMarkRemoved()) {
382
                    caretItem.clearTransactionMarkRemoved();
383
                    if (extraRemovedItems == null) {
384
                        extraRemovedItems = new GapList<>();
385
                    }
386
                    extraRemovedItems.add(caretItem);
387
                } else {
388
                    replaceItems.add(caretItem);
389
                }
390
            }
391
            replaceSortedItems = nonOverlappingItems;
392
        }
393
    }
394
    
395
    GapList<CaretItem> addRemovedItems(GapList<CaretItem> toItems) {
396
        int removeSize = modEndIndex - modIndex;
397
        int extraRemovedSize = (extraRemovedItems != null) ? extraRemovedItems.size() : 0;
398
        if (removeSize + extraRemovedSize > 0) {
399
            if (toItems == null) {
400
                toItems = new GapList<>(removeSize + extraRemovedSize);
401
            }
402
            if (removeSize > 0) {
403
                toItems.addAll(origCaretItems, modIndex, removeSize);
404
            }
405
            if (extraRemovedSize > 0) {
406
                toItems.addAll(extraRemovedItems);
407
            }
408
        }
409
        return toItems;
410
    }
411
    
412
    GapList<CaretItem> addUpdateVisualBoundsItems(GapList<CaretItem> toItems) {
413
        GapList<CaretItem> items = resultItems();
414
        int size = items.size();
415
        for (int i = 0; i < size; i++) {
416
            CaretItem caretItem = items.get(i);
417
            if (caretItem.isUpdateVisualBounds()) {
418
                caretItem.clearUpdateVisualBounds();
419
                if (toItems == null) {
420
                    toItems = new GapList<>();
421
                }
422
                toItems.add(caretItem);
423
            }
424
        }
425
        return toItems;
426
    }
427
428
    private GapList<CaretItem> resultItems() {
429
        return (replaceItems != null) ? replaceItems : origCaretItems;
430
    }
431
432
    private void initReplaceItems() {
433
        assert (replaceItems == null) : "replaceItems already inited to " + replaceItems; // NOI18N
434
        int size = origCaretItems.size();
435
        int removeSize = modEndIndex - modIndex;
436
        int addSize = (addCaretItems != null) ? addCaretItems.length : 0;
437
        int newSize = size - removeSize + addSize;
438
        replaceItems = new GapList<>(newSize);
439
        if (removeSize > 0) {
440
            replaceItems.addAll(origCaretItems, 0, modIndex);
441
            replaceItems.addAll(origCaretItems, modEndIndex, size - modEndIndex);
442
        } else {
443
            replaceItems.addAll(origCaretItems);
444
        }
445
        if (addCaretItems != null) {
446
            replaceItems.addArray(replaceItems.size(), addCaretItems);
447
        }
448
449
        assert (replaceItems.size() == newSize);
450
        boolean updateIndividual = (removeSize + addSize) < (newSize >> 2); // Threshold 1/4 of total size for full resort
451
        if (fullResort || true) { // Force full resort
452
            replaceSortedItems = replaceItems.copy();
453
            if (newSize > 1) {
454
                
455
            }
456
        } else { // Partial resort TODO
457
            
458
        }
459
    }
460
461
    private void resetIndexes() {
462
        indexesLength = 0;
463
    }
464
    
465
    private void addToIndexes(int index) {
466
        if (indexes == null) {
467
            indexes = new int[8];
468
        } else if (indexesLength == indexes.length) {
469
            int[] orig = indexes;
470
            indexes = new int[indexesLength << 1];
471
            System.arraycopy(orig, 0, indexes, 0, indexesLength);
472
        }
473
        indexes[indexesLength++] = index;
474
    }
475
    
476
    
477
    private int findCaretItemIndex(GapList<CaretItem> caretItems, CaretItem caretItem) {
478
        // Method only resolves existing items not added items
479
        int i = caretItem.getTransactionIndexHint();
480
        int size = caretItems.size();
481
        if (i >= size || caretItems.get(i) != caretItem) {
482
            while (indexHintEnd < size) {
483
                CaretItem c = caretItems.get(indexHintEnd);
484
                c.setTransactionIndexHint(indexHintEnd++);
485
                if (c == caretItem) {
486
                    return indexHintEnd - 1;
487
                }
488
            }
489
            return -1;
490
        }
491
        return i;
492
    }
493
    
494
    private void updateAffectedIndexes(int startIndex, int endIndex) {
495
        if (affectedStartIndex == Integer.MAX_VALUE) {
496
            affectedStartIndex = startIndex;
497
            affectedEndIndex = endIndex;
498
        } else {
499
            affectedStartIndex = Math.min(affectedStartIndex, startIndex);
500
            affectedEndIndex = Math.max(affectedEndIndex, endIndex);
501
        }
502
    }
503
504
    private void updateAffectedOffsets(int startOffset, int endOffset) {
505
        if (affectedStartOffset == Integer.MAX_VALUE) { // Affected range not inited yet
506
            affectedStartOffset = startOffset;
507
            affectedEndOffset = endOffset;
508
        } else { // Affected range already inited
509
            if (startOffset < affectedStartOffset) {
510
                affectedStartOffset = startOffset;
511
            }
512
            if (endOffset > affectedEndOffset) {
513
                affectedEndOffset = endOffset;
514
            }
515
        }
516
    }
517
518
    static CaretItem[] asCaretItems(EditorCaret caret, @NonNull List<Position> dotAndSelectionStartPosPairs) {
519
        int size = dotAndSelectionStartPosPairs.size();
520
        if ((size & 1) != 0) {
521
            throw new IllegalStateException("Passed list has size=" + size + " which is not an even number.");
522
        }
523
        CaretItem[] addedCarets = new CaretItem[size >> 1];
524
        int listIndex = 0;
525
        for (int j = 0; j < addedCarets.length; j++) {
526
            Position dotPos = dotAndSelectionStartPosPairs.get(listIndex++);
527
            Position selectionStartPos = dotAndSelectionStartPosPairs.get(listIndex++);
528
            CaretItem caretItem = new CaretItem(caret, dotPos, selectionStartPos);
529
            addedCarets[j] = caretItem;
530
        }
531
        return addedCarets;
532
    }
533
534
    enum RemoveType {
535
        NO_REMOVE,
536
        REMOVE_LAST_CARET,
537
        RETAIN_LAST_CARET,
538
        REMOVE_ALL_CARETS,
539
        DOCUMENT_REMOVE,
540
        DOCUMENT_INSERT_ZERO_OFFSET
541
    }
542
    
543
    /**
544
     * Helper class for resolving overlapping getCaret selections.
545
     */
546
    private static final class CaretItemInfo {
547
        
548
        CaretItem caretItem;
549
        
550
        Position startPos;
551
        
552
        Position endPos;
553
554
        int startOffset;
555
556
        int startShift;
557
        
558
        int endOffset;
559
560
        int endShift;
561
        
562
        boolean dotAtStart;
563
        
564
        void update(CaretItem caret) {
565
            this.caretItem = caret;
566
            Position dotPos = caret.getDotPosition();
567
            if (dotPos != null) {
568
                int dotOffset = dotPos.getOffset();
569
                int dotShift = ShiftPositions.getShift(dotPos);
570
                Position markPos = caret.getMarkPosition();
571
                if (markPos != null && markPos != dotPos) { // Still they may be equal which means no selection
572
                    int markOffset = markPos.getOffset();
573
                    int markShift = ShiftPositions.getShift(markPos);
574
                    if (markOffset < dotOffset || (markOffset == dotOffset && markShift <= dotShift)) {
575
                        startPos = markPos;
576
                        endPos = dotPos;
577
                        startOffset = markOffset;
578
                        startShift = markShift;
579
                        endOffset = dotOffset;
580
                        endShift = dotShift;
581
                        dotAtStart = false;
582
                    } else {
583
                        startPos = dotPos;
584
                        endPos = markPos;
585
                        startOffset = dotOffset;
586
                        startShift = dotShift;
587
                        endOffset = markOffset;
588
                        endShift = markShift;
589
                        dotAtStart = true;
590
                    }
591
                } else {
592
                    startPos = endPos = dotPos;
593
                    startOffset = endOffset = dotOffset;
594
                    startShift = startShift = dotShift;
595
                    dotAtStart = false;
596
                }
597
            } else {
598
                clear();
599
            }
600
        }
601
602
        private void clear() {
603
            caretItem = null;
604
            startPos = endPos = null;
605
            startOffset = endOffset = 0;
606
            startShift = startShift = 0;
607
            dotAtStart = false;
608
        }
609
        
610
        private boolean overlapsAtStart(CaretItemInfo info) {
611
            return (ShiftPositions.compare(info.endOffset, info.endShift,
612
                    startOffset, startShift) > 0);
613
        }
614
        
615
        private boolean startsBelow(CaretItemInfo info) {
616
            return (ShiftPositions.compare(startOffset, startShift,
617
                    info.startOffset, info.startShift) < 0);
618
        }
619
        
620
        private boolean endsAbove(CaretItemInfo info) {
621
            return (ShiftPositions.compare(endOffset, endShift,
622
                    info.endOffset, info.endShift) > 0);
623
        }
624
        
625
    }
626
627
}
(-)a/editor.lib2/src/org/netbeans/api/editor/caret/EditorCaret.java (+2585 lines)
Line 0 Link Here
1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
4
 * Copyright 2015 Oracle and/or its affiliates. All rights reserved.
5
 *
6
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7
 * Other names may be trademarks of their respective owners.
8
 *
9
 * The contents of this file are subject to the terms of either the GNU
10
 * General Public License Version 2 only ("GPL") or the Common
11
 * Development and Distribution License("CDDL") (collectively, the
12
 * "License"). You may not use this file except in compliance with the
13
 * License. You can obtain a copy of the License at
14
 * http://www.netbeans.org/cddl-gplv2.html
15
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
16
 * specific language governing permissions and limitations under the
17
 * License.  When distributing the software, include this License Header
18
 * Notice in each file and include the License file at
19
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
20
 * particular file as subject to the "Classpath" exception as provided
21
 * by Oracle in the GPL Version 2 section of the License file that
22
 * accompanied this code. If applicable, add the following below the
23
 * License Header, with the fields enclosed by brackets [] replaced by
24
 * your own identifying information:
25
 * "Portions Copyrighted [year] [name of copyright owner]"
26
 *
27
 * If you wish your version of this file to be governed by only the CDDL
28
 * or only the GPL Version 2, indicate your decision by adding
29
 * "[Contributor] elects to include this software in this distribution
30
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
31
 * single choice of license, a recipient has the option to distribute
32
 * your version of this file under either the CDDL, the GPL Version 2 or
33
 * to extend the choice of license to its licensees as provided above.
34
 * However, if you add GPL Version 2 code and therefore, elected the GPL
35
 * Version 2 license, then the option applies only if the new code is
36
 * made subject to such option by the copyright holder.
37
 *
38
 * Contributor(s):
39
 *
40
 * Portions Copyrighted 2015 Sun Microsystems, Inc.
41
 */
42
    package org.netbeans.api.editor.caret;
43
44
import org.netbeans.spi.editor.caret.CaretMoveHandler;
45
import java.awt.AlphaComposite;
46
import java.awt.BasicStroke;
47
import java.awt.Color;
48
import java.awt.Component;
49
import java.awt.Composite;
50
import java.awt.Container;
51
import java.awt.Cursor;
52
import java.awt.Dimension;
53
import java.awt.Graphics;
54
import java.awt.Graphics2D;
55
import java.awt.Point;
56
import java.awt.Rectangle;
57
import java.awt.Stroke;
58
import java.awt.datatransfer.Clipboard;
59
import java.awt.datatransfer.DataFlavor;
60
import java.awt.datatransfer.Transferable;
61
import java.awt.datatransfer.UnsupportedFlavorException;
62
import java.awt.event.ActionEvent;
63
import java.awt.event.ActionListener;
64
import java.awt.event.ComponentAdapter;
65
import java.awt.event.ComponentEvent;
66
import java.awt.event.ComponentListener;
67
import java.awt.event.FocusEvent;
68
import java.awt.event.FocusListener;
69
import java.awt.event.InputEvent;
70
import java.awt.event.KeyEvent;
71
import java.awt.event.KeyListener;
72
import java.awt.event.MouseEvent;
73
import java.awt.event.MouseListener;
74
import java.awt.event.MouseMotionListener;
75
import java.beans.PropertyChangeEvent;
76
import java.beans.PropertyChangeListener;
77
import java.io.IOException;
78
import java.util.ArrayList;
79
import java.util.List;
80
import java.util.concurrent.Callable;
81
import java.util.logging.Level;
82
import java.util.logging.Logger;
83
import java.util.prefs.PreferenceChangeEvent;
84
import java.util.prefs.PreferenceChangeListener;
85
import java.util.prefs.Preferences;
86
import javax.swing.Action;
87
import javax.swing.JComponent;
88
import javax.swing.JScrollBar;
89
import javax.swing.JScrollPane;
90
import javax.swing.JViewport;
91
import javax.swing.SwingUtilities;
92
import javax.swing.Timer;
93
import javax.swing.TransferHandler;
94
import javax.swing.event.ChangeEvent;
95
import javax.swing.event.ChangeListener;
96
import javax.swing.event.DocumentEvent;
97
import javax.swing.event.DocumentListener;
98
import javax.swing.text.AbstractDocument;
99
import javax.swing.text.AttributeSet;
100
import javax.swing.text.BadLocationException;
101
import javax.swing.text.Caret;
102
import javax.swing.text.DefaultEditorKit;
103
import javax.swing.text.Document;
104
import javax.swing.text.Element;
105
import javax.swing.text.JTextComponent;
106
import javax.swing.text.Position;
107
import javax.swing.text.StyleConstants;
108
import org.netbeans.api.annotations.common.CheckForNull;
109
import org.netbeans.api.annotations.common.NonNull;
110
import org.netbeans.api.editor.EditorUtilities;
111
import org.netbeans.api.editor.document.AtomicLockDocument;
112
import org.netbeans.api.editor.document.AtomicLockEvent;
113
import org.netbeans.api.editor.document.AtomicLockListener;
114
import org.netbeans.api.editor.document.LineDocument;
115
import org.netbeans.api.editor.document.LineDocumentUtils;
116
import org.netbeans.api.editor.mimelookup.MimeLookup;
117
import org.netbeans.api.editor.settings.FontColorNames;
118
import org.netbeans.api.editor.settings.FontColorSettings;
119
import org.netbeans.api.editor.settings.SimpleValueNames;
120
import org.netbeans.lib.editor.util.ArrayUtilities;
121
import org.netbeans.lib.editor.util.GapList;
122
import org.netbeans.lib.editor.util.ListenerList;
123
import org.netbeans.lib.editor.util.swing.DocumentListenerPriority;
124
import org.netbeans.lib.editor.util.swing.DocumentUtilities;
125
import org.netbeans.modules.editor.lib2.EditorPreferencesDefaults;
126
import org.netbeans.modules.editor.lib2.RectangularSelectionCaretAccessor;
127
import org.netbeans.modules.editor.lib2.RectangularSelectionTransferHandler;
128
import org.netbeans.modules.editor.lib2.RectangularSelectionUtils;
129
import org.netbeans.modules.editor.lib2.actions.EditorActionUtilities;
130
import org.netbeans.modules.editor.lib2.highlighting.CaretOverwriteModeHighlighting;
131
import org.netbeans.modules.editor.lib2.view.DocumentView;
132
import org.netbeans.modules.editor.lib2.view.LockedViewHierarchy;
133
import org.netbeans.modules.editor.lib2.view.ViewHierarchy;
134
import org.netbeans.modules.editor.lib2.view.ViewHierarchyEvent;
135
import org.netbeans.modules.editor.lib2.view.ViewHierarchyListener;
136
import org.netbeans.modules.editor.lib2.view.ViewUtils;
137
import org.openide.util.Exceptions;
138
import org.openide.util.WeakListeners;
139
140
/**
141
 * Extension to standard Swing caret used by all NetBeans editors.
142
 * <br>
143
 * It supports multi-caret editing mode where an arbitrary number of carets
144
 * is placed at arbitrary positions throughout a document.
145
 * In this mode each caret is described by its <code>CaretInfo</code> object.
146
 * <br>
147
 * The caret works over text components having {@link AbstractDocument} based document.
148
 *
149
 * @author Miloslav Metelka
150
 * @author Ralph Ruijs
151
 * @since 2.6
152
 */
153
public final class EditorCaret implements Caret {
154
    
155
    // Temporary until rectangular selection gets ported to multi-caret support
156
    private static final String RECTANGULAR_SELECTION_PROPERTY = "rectangular-selection"; // NOI18N
157
    private static final String RECTANGULAR_SELECTION_REGIONS_PROPERTY = "rectangular-selection-regions"; // NOI18N
158
    
159
    // -J-Dorg.netbeans.editor.BaseCaret.level=FINEST
160
    private static final Logger LOG = Logger.getLogger(EditorCaret.class.getName());
161
    
162
    static {
163
        RectangularSelectionCaretAccessor.register(new RectangularSelectionCaretAccessor() {
164
            @Override
165
            public void setRectangularSelectionToDotAndMark(EditorCaret editorCaret) {
166
                editorCaret.setRectangularSelectionToDotAndMark();
167
            }
168
169
            @Override
170
            public void updateRectangularUpDownSelection(EditorCaret editorCaret) {
171
                editorCaret.updateRectangularUpDownSelection();
172
            }
173
174
            @Override
175
            public void extendRectangularSelection(EditorCaret editorCaret, boolean toRight, boolean ctrl) {
176
                editorCaret.extendRectangularSelection(toRight, ctrl);
177
            }
178
        });
179
    }
180
181
    static final long serialVersionUID = 0L;
182
183
    /**
184
     * Non-empty list of individual carets in the order they were created.
185
     * At least one item is always present.
186
     */
187
    private @NonNull GapList<CaretItem> caretItems;
188
    
189
    /**
190
     * Non-empty list of individual carets in the order they were created.
191
     * At least one item is always present.
192
     */
193
    private @NonNull GapList<CaretItem> sortedCaretItems;
194
    
195
    /**
196
     * Cached infos corresponding to caret items or null if any of the items were changed
197
     *  so another copy of the caret items- (translated into caret infos) will get created
198
     * upon query to {@link #getCarets() }.
199
     * <br>
200
     * Once the list gets created both the list content and information in each caret info are immutable.
201
     */
202
    private List<CaretInfo> caretInfos;
203
    
204
    /**
205
     * Cached infos corresponding to sorted caret items or null if any of the sorted items were changed
206
     * so another copy of the sorted caret items (translated into caret infos) will get created
207
     * upon query to {@link #getSortedCarets() }.
208
     * <br>
209
     * Once the list gets created both the list content and information in each caret info are immutable.
210
     */
211
    private List<CaretInfo> sortedCaretInfos;
212
    
213
    /** Component this caret is bound to */
214
    private JTextComponent component;
215
    
216
    /** List of individual carets */
217
    private boolean overwriteMode;
218
219
    private final ListenerList<EditorCaretListener> listenerList;
220
221
    private final ListenerList<ChangeListener> changeListenerList;
222
223
    /**
224
     * Implementor of various listeners.
225
     */
226
    private final ListenerImpl listenerImpl;
227
    
228
    /** Is the caret visible (after <code>setVisible(true)</code> call? */
229
    private boolean visible;
230
231
    /**
232
     * Whether blinking caret is currently visible on the screen.
233
     * <br>
234
     * This changes from true to false after each tick of a timer
235
     * (assuming <code>visible == true</code>).
236
     */
237
    private boolean blinkVisible;
238
239
    /**
240
     * Determine if a possible selection would be displayed or not.
241
     */
242
    private boolean selectionVisible;
243
244
    /** Type of the caret */
245
    private CaretType type = CaretType.THICK_LINE_CARET;
246
247
    /** Width of caret */
248
    private int thickCaretWidth = EditorPreferencesDefaults.defaultThickCaretWidth;
249
    
250
    private MouseState mouseState = MouseState.DEFAULT;
251
    
252
    /** Timer used for blinking the caret */
253
    private Timer flasher;
254
255
    private Action selectWordAction;
256
    private Action selectLineAction;
257
258
    private AbstractDocument activeDoc;
259
    
260
    private Thread lockThread;
261
    
262
    private int lockDepth;
263
    
264
    private CaretTransaction activeTransaction;
265
    
266
    /**
267
     * Items from previous transaction(s) that need their visual rectangle
268
     * to get repainted to clear the previous caret representation visually.
269
     */
270
    private GapList<CaretItem> pendingRepaintRemovedItemsList;
271
272
    /**
273
     * Items from previous transaction(s) that need their visual bounds
274
     * to be recomputed and caret to be repainted then.
275
     */
276
    private GapList<CaretItem> pendingUpdateVisualBoundsItemsList;
277
    
278
    /**
279
     * Caret item to which the view should scroll or null for no scrolling.
280
     */
281
    private CaretItem scrollToItem;
282
283
    /**
284
     * Whether the text is being modified under atomic lock.
285
     * If so just one caret change is fired at the end of all modifications.
286
     */
287
    private transient boolean inAtomicLock = false;
288
    private transient boolean inAtomicUnlock = false;
289
    
290
    /**
291
     * Helps to check whether there was modification performed
292
     * and so the caret change needs to be fired.
293
     */
294
    private transient boolean modified;
295
    
296
    /**
297
     * Set to true once the folds have changed. The caret should retain
298
     * its relative visual position on the screen.
299
     */
300
    private boolean updateAfterFoldHierarchyChange;
301
    
302
    /**
303
     * Whether at least one typing change occurred during possibly several atomic operations.
304
     */
305
    private boolean typingModificationOccurred;
306
307
    private Preferences prefs = null;
308
309
    private PreferenceChangeListener weakPrefsListener = null;
310
    
311
    private boolean caretUpdatePending;
312
    
313
    /**
314
     * Minimum selection start for word and line selections.
315
     * This helps to ensure that when extending word (or line) selections
316
     * the selection will always include at least the initially selected word (or line).
317
     */
318
    private int minSelectionStartOffset;
319
    
320
    private int minSelectionEndOffset;
321
    
322
    private boolean rectangularSelection;
323
    
324
    /**
325
     * Rectangle that corresponds to model2View of current point of selection.
326
     */
327
    private Rectangle rsDotRect;
328
329
    /**
330
     * Rectangle that corresponds to model2View of beginning of selection.
331
     */
332
    private Rectangle rsMarkRect;
333
    
334
    /**
335
     * Rectangle marking rectangular selection.
336
     */
337
    private Rectangle rsPaintRect;
338
    
339
    /**
340
     * List of start-pos and end-pos pairs that denote rectangular selection
341
     * on the selected lines.
342
     */
343
    private List<Position> rsRegions;
344
345
    /**
346
     * Used for showing the default cursor instead of the text cursor when the
347
     * mouse is over a block of selected text.
348
     * This field is used to prevent repeated calls to component.setCursor()
349
     * with the same cursor.
350
     */
351
    private boolean showingTextCursor = true;
352
    
353
    public EditorCaret() {
354
        caretItems = new GapList<>();
355
        sortedCaretItems = new GapList<>();
356
        CaretItem singleCaret = new CaretItem(this, null, null);
357
        caretItems.add(singleCaret);
358
        sortedCaretItems.add(singleCaret);
359
360
        listenerList = new ListenerList<>();
361
        changeListenerList = new ListenerList<>();
362
        listenerImpl = new ListenerImpl();
363
    }
364
365
    @Override
366
    public int getDot() {
367
        return getLastCaret().getDot();
368
    }
369
    
370
    @Override
371
    public int getMark() {
372
        return getLastCaret().getMark();
373
    }
374
    
375
    /**
376
     * Get information about all existing carets in the order they were created.
377
     * <br>
378
     * The list always has at least one item. The last caret (last item of the list)
379
     * is the most recent caret.
380
     * <br>
381
     * The list is a snapshot of the current state of the carets. The list content itself and its contained
382
     * caret infos are guaranteed not change after subsequent calls to caret API or document modifications.
383
     * <br>
384
     * The list is nonmodifiable.
385
     * <br>
386
     * This method should be called with document's read-lock acquired which will guarantee
387
     * stability of {@link CaretInfo#getDot() } and {@link CaretInfo#getMark() } and prevent
388
     * caret merging as a possible effect of document modifications.
389
     * 
390
     * @return copy of caret list with size &gt;= 1 containing information about all carets.
391
     */
392
    public @NonNull List<CaretInfo> getCarets() {
393
        synchronized (listenerList) {
394
            if (caretInfos == null) {
395
                int i = caretItems.size();
396
                CaretInfo[] infos = new CaretInfo[i--];
397
                for (; i >= 0; i--) {
398
                    infos[i] = caretItems.get(i).getValidInfo();
399
                }
400
                caretInfos = ArrayUtilities.unmodifiableList(infos);
401
            }
402
            return caretInfos;
403
        }
404
    }
405
    
406
    /**
407
     * Get information about all existing carets sorted by dot positions in ascending order.
408
     * <br>
409
     * The list is a snapshot of the current state of the carets. The list content itself and its contained
410
     * caret infos are guaranteed not change after subsequent calls to caret API or document modifications.
411
     * <br>
412
     * The list is nonmodifiable.
413
     * <br>
414
     * This method should be called with document's read-lock acquired which will guarantee
415
     * stability of {@link CaretInfo#getDot() } and {@link CaretInfo#getMark() } and prevent
416
     * caret merging as a possible effect of document modifications.
417
     * 
418
     * @return copy of caret list with size &gt;= 1 sorted by dot positions in ascending order.
419
     */
420
    public @NonNull List<CaretInfo> getSortedCarets() {
421
        synchronized (listenerList) {
422
            if (sortedCaretInfos == null) {
423
                int i = sortedCaretItems.size();
424
                CaretInfo[] sortedInfos = new CaretInfo[i--];
425
                for (; i >= 0; i--) {
426
                    sortedInfos[i] = sortedCaretItems.get(i).getValidInfo();
427
                }
428
                sortedCaretInfos = ArrayUtilities.unmodifiableList(sortedInfos);
429
            }
430
            return sortedCaretInfos;
431
        }
432
    }
433
    
434
    /**
435
     * Get info about the most recently created caret.
436
     * <br>
437
     * For normal mode this is the only caret returned by {@link #getCarets() }.
438
     * <br>
439
     * For multi-caret mode this is the last item in the list returned by {@link #getCarets() }.
440
     * 
441
     * @return last caret (the most recently added caret).
442
     */
443
    public @NonNull CaretInfo getLastCaret() {
444
        synchronized (listenerList) {
445
            return caretItems.get(caretItems.size() - 1).getValidInfo();
446
        }
447
    }
448
    
449
    /**
450
     * Get information about the caret at the specified offset.
451
     *
452
     * @param offset the offset of the caret
453
     * @return CaretInfo for the caret at offset, null if there is no caret or
454
     * the offset is invalid
455
     */
456
    public @CheckForNull CaretInfo getCaretAt(int offset) {
457
        return null; // TBD
458
    }
459
    
460
    /**
461
     * Assign a new offset to the caret in the underlying document.
462
     * <br>
463
     * This method implicitly sets the selection range to zero.
464
     * <br>
465
     * If multiple carets are present this method retains only last caret
466
     * which then moves to the given offset.
467
     * 
468
     * @param offset {@inheritDoc}
469
     * @see Caret#setDot(int) 
470
     */
471
    public @Override void setDot(final int offset) {
472
        if (LOG.isLoggable(Level.FINE)) {
473
            LOG.fine("setDot: offset=" + offset); //NOI18N
474
            if (LOG.isLoggable(Level.FINEST)) {
475
                LOG.log(Level.INFO, "setDot call stack", new Exception());
476
            }
477
        }
478
        runTransaction(CaretTransaction.RemoveType.RETAIN_LAST_CARET, 0, null, new CaretMoveHandler() {
479
            @Override
480
            public void moveCarets(CaretMoveContext context) {
481
                Document doc = context.getComponent().getDocument();
482
                if (doc != null) {
483
                    try {
484
                        Position pos = doc.createPosition(offset);
485
                        context.setDot(context.getOriginalLastCaret(), pos);
486
                    } catch (BadLocationException ex) {
487
                        // Ignore the setDot() request
488
                    }
489
                }
490
            }
491
        });
492
    }
493
494
    /**
495
     * Moves the caret position (dot) to some other position, leaving behind the
496
     * mark. This is useful for making selections.
497
     * <br>
498
     * If multiple carets are present this method retains all carets
499
     * and moves the dot of the last caret to the given offset.
500
     * 
501
     * @param offset {@inheritDoc}
502
     * @see Caret#moveDot(int) 
503
     */
504
    public @Override void moveDot(final int offset) {
505
        if (LOG.isLoggable(Level.FINE)) {
506
            LOG.fine("moveDot: offset=" + offset); //NOI18N
507
        }
508
        
509
        runTransaction(CaretTransaction.RemoveType.NO_REMOVE, 0, null, new CaretMoveHandler() {
510
            @Override
511
            public void moveCarets(CaretMoveContext context) {
512
                Document doc = context.getComponent().getDocument();
513
                if (doc != null) {
514
                    try {
515
                        Position pos = doc.createPosition(offset);
516
                        context.moveDot(context.getOriginalLastCaret(), pos);
517
                    } catch (BadLocationException ex) {
518
                        // Ignore the setDot() request
519
                    }
520
                }
521
            }
522
        });
523
    }
524
525
    /**
526
     * Move multiple carets or create/modify selections.
527
     * <br>
528
     * For performance reasons this is made as a single transaction over the caret
529
     * with only one change event being fired.
530
     * <br>
531
     * Note that the move handler does not permit to add or remove carets - this has to be performed
532
     * by other methods present in this class (as another transaction over the editor caret).
533
     * <br>
534
     * <pre>
535
     * <code>
536
     *   // Go one line up with all carets
537
     *   editorCaret.moveCarets(new CaretMoveHandler() {
538
     *     &#64;Override public void moveCarets(CaretMoveContext context) {
539
     *       for (CaretInfo caretInfo : context.getOriginalSortedCarets()) {
540
     *         try {
541
     *           int dot = caretInfo.getDot();
542
     *           dot = Utilities.getPositionAbove(target, dot, p.x);
543
     *           Position dotPos = doc.createPosition(dot);
544
     *           context.setDot(caretInfo, dotPos);
545
     *         } catch (BadLocationException e) {
546
     *           // the position stays the same
547
     *         }
548
     *       }
549
     *     }
550
     *   });
551
     * </code>
552
     * </pre>
553
     *
554
     * @param moveHandler non-null move handler to perform the changes. The handler's methods
555
     *  will be given a context to operate on.
556
     * @return difference between current count of carets and the number of carets when the operation started.
557
     *  Returns Integer.MIN_VALUE if the operation was cancelled due to the caret not being installed in any text component
558
     *  or no document installed in the text component.
559
     */
560
    public int moveCarets(@NonNull CaretMoveHandler moveHandler) {
561
        return runTransaction(CaretTransaction.RemoveType.NO_REMOVE, 0, null, moveHandler);
562
    }
563
564
    /**
565
     * Create a new caret at the given position with a possible selection.
566
     * <br>
567
     * The caret will become the last caret of the list returned by {@link #getCarets() }.
568
     * <br>
569
     * This method requires the caller to have either read lock or write lock acquired
570
     * over the underlying document.
571
     * <br>
572
     * <pre>
573
     * <code>
574
     *   editorCaret.addCaret(pos, pos); // Add a new caret at pos.getOffset()
575
     * 
576
     *   Position pos2 = doc.createPosition(pos.getOffset() + 2);
577
     *   // Add a new caret with selection starting at pos and extending to pos2 with caret located at pos2
578
     *   editorCaret.addCaret(pos2, pos);
579
     *   // Add a new caret with selection starting at pos and extending to pos2 with caret located at pos
580
     *   editorCaret.addCaret(pos, pos2);
581
     * </code>
582
     * </pre>
583
     * 
584
     * @param dotPos position of the newly created caret.
585
     * @param markPos beginning of the selection (the other end is dotPos) or the same position like dotPos for no selection.
586
     *  The markPos may have higher offset than dotPos to select in a backward direction.
587
     * @return difference between current count of carets and the number of carets when the operation started.
588
     *  Returns Integer.MIN_VALUE if the operation was cancelled due to the caret not being installed in any text component
589
     *  or no document installed in the text component.
590
     *  <br>
591
     *  Note that adding a new caret to offset where another caret is already located may lead
592
     *  to its immediate removal.
593
     */
594
    public int addCaret(@NonNull Position dotPos, @NonNull Position markPos) {
595
        return runTransaction(CaretTransaction.RemoveType.NO_REMOVE, 0,
596
                new CaretItem[] { new CaretItem(this, dotPos, markPos) }, null);
597
    }
598
    
599
    /**
600
     * Add multiple carets at once.
601
     * <br>
602
     * It is similar to calling {@link #addCaret(javax.swing.text.Position, javax.swing.text.Position) }
603
     * multiple times but this method is more efficient (it only fires caret change once).
604
     * <br>
605
     * This method requires the caller to have either read lock or write lock acquired
606
     * over the underlying document.
607
     * <br>
608
     * <pre>
609
     * <code>
610
     *   List&lt;Position&gt; pairs = new ArrayList&lt;&gt;();
611
     *   pairs.add(dotPos);
612
     *   pairs.add(dotPos);
613
     *   pairs.add(dot2Pos);
614
     *   pairs.add(mark2Pos);
615
     *   // Add caret located at dotPos.getOffset() and another one with selection
616
     *   // starting at mark2Pos and extending to dot2Pos with caret located at dot2Pos
617
     *   editorCaret.addCaret(pairs);
618
     * </code>
619
     * </pre>
620
     * 
621
     * @param dotAndMarkPosPairs list of position pairs consisting of dot position
622
     *  and mark position (selection start position) which may be the same position like the dot
623
     *  if the particular caret has no selection. The list must have even size.
624
     * @return difference between current count of carets and the number of carets when the operation started.
625
     *  Returns Integer.MIN_VALUE if the operation was cancelled due to the caret not being installed in any text component
626
     *  or no document installed in the text component.
627
     */
628
    public int addCarets(@NonNull List<Position> dotAndMarkPosPairs) {
629
        return runTransaction(CaretTransaction.RemoveType.NO_REMOVE, 0,
630
                CaretTransaction.asCaretItems(this, dotAndMarkPosPairs), null);
631
    }
632
633
    /**
634
     * Replace all current carets with the new ones.
635
     * <br>
636
     * This method requires the caller to have either read lock or write lock acquired
637
     * over the underlying document.
638
     * <br>
639
     * @param dotAndMarkPosPairs list of position pairs consisting of dot position
640
     *  and mark position (selection start position) which may be the same position like dot
641
     *  if the particular caret has no selection. The list must have even size.
642
     * @return difference between current count of carets and the number of carets when the operation started.
643
     *  Returns Integer.MIN_VALUE if the operation was cancelled due to the caret not being installed in any text component
644
     *  or no document installed in the text component.
645
     */
646
    public int replaceCarets(@NonNull List<Position> dotAndMarkPosPairs) {
647
        if (dotAndMarkPosPairs.isEmpty()) {
648
            throw new IllegalArgumentException("dotAndSelectionStartPosPairs list must not be empty");
649
        }
650
        CaretItem[] addedItems = CaretTransaction.asCaretItems(this, dotAndMarkPosPairs);
651
        return runTransaction(CaretTransaction.RemoveType.REMOVE_ALL_CARETS, 0, addedItems, null);
652
    }
653
654
    /**
655
     * Remove last added caret (determined by {@link #getLastCaret() }).
656
     * <br>
657
     * If there is just one caret the method has no effect.
658
     * 
659
     * @return difference between current count of carets and the number of carets when the operation started.
660
     *  Returns Integer.MIN_VALUE if the operation was cancelled due to the caret not being installed in any text component
661
     *  or no document installed in the text component.
662
     */
663
    public int removeLastCaret() {
664
        return runTransaction(CaretTransaction.RemoveType.REMOVE_LAST_CARET, 0, null, null);
665
    }
666
667
    /**
668
     * Switch to single caret mode by removing all carets except the last caret.
669
     * @return difference between current count of carets and the number of carets when the operation started.
670
     *  Returns Integer.MIN_VALUE if the operation was cancelled due to the caret not being installed in any text component
671
     *  or no document installed in the text component.
672
     */
673
    public int retainLastCaretOnly() {
674
        return runTransaction(CaretTransaction.RemoveType.RETAIN_LAST_CARET, 0, null, null);
675
    }
676
    
677
    /**
678
     * Adds listener to track caret changes in detail.
679
     * 
680
     * @param listener non-null listener.
681
     */
682
    public void addEditorCaretListener(@NonNull EditorCaretListener listener) {
683
        listenerList.add(listener);
684
    }
685
    
686
    /**
687
     * Adds listener to track caret position changes (to fulfil {@link Caret} interface).
688
     */
689
    @Override
690
    public void addChangeListener(@NonNull ChangeListener l) {
691
        changeListenerList.add(l);
692
    }
693
694
    public void removeEditorCaretListener(@NonNull EditorCaretListener listener) {
695
        listenerList.remove(listener);
696
    }
697
    
698
    /**
699
     * Removes listener to track caret position changes (to fulfil {@link Caret} interface).
700
     */
701
    @Override
702
    public void removeChangeListener(@NonNull ChangeListener l) {
703
        changeListenerList.remove(l);
704
    }
705
706
    /**
707
     * Determines if the caret is currently visible (it may be blinking depending on settings).
708
     * <p>
709
     * Caret becomes visible after <code>setVisible(true)</code> gets called on it.
710
     *
711
     * @return <code>true</code> if visible else <code>false</code>
712
     */
713
    @Override
714
    public boolean isVisible() {
715
        synchronized (listenerList) {
716
            return visible;
717
        }
718
    }
719
720
    /**
721
     * Sets the caret visibility, and repaints the caret.
722
     *
723
     * @param visible the visibility specifier
724
     * @see Caret#setVisible
725
     */
726
    @Override
727
    public void setVisible(boolean visible) {
728
        if (LOG.isLoggable(Level.FINER)) {
729
            LOG.finer("BaseCaret.setVisible(" + visible + ")\n");
730
            if (LOG.isLoggable(Level.FINEST)) {
731
                LOG.log(Level.INFO, "", new Exception());
732
            }
733
        }
734
        synchronized (listenerList) {
735
            if (flasher != null) {
736
                if (this.visible) {
737
                    flasher.stop();
738
                }
739
                if (LOG.isLoggable(Level.FINER)) {
740
                    LOG.finer((visible ? "Starting" : "Stopping") + // NOI18N
741
                            " the caret blinking timer: " + dumpVisibility() + '\n'); // NOI18N
742
                }
743
                this.visible = visible;
744
                if (visible) {
745
                    flasher.start();
746
                } else {
747
                    flasher.stop();
748
                }
749
            }
750
        }
751
        JTextComponent c = component;
752
        if (c != null) {
753
            // TODO only paint carets showing on screen
754
            List<CaretInfo> sortedCarets = getSortedCarets();
755
            for (CaretInfo caret : sortedCarets) {
756
                CaretItem caretItem = caret.getCaretItem();
757
                if (caretItem.getCaretBounds() != null) {
758
                    Rectangle repaintRect = caretItem.getCaretBounds();
759
                    c.repaint(repaintRect);
760
                }
761
            }
762
        }
763
    }
764
765
    @Override
766
    public boolean isSelectionVisible() {
767
        return selectionVisible;
768
    }
769
770
    @Override
771
    public void setSelectionVisible(boolean v) {
772
        if (selectionVisible == v) {
773
            return;
774
        }
775
        JTextComponent c = component;
776
        Document doc;
777
        if (c != null && (doc = c.getDocument()) != null) {
778
            selectionVisible = v;
779
            // [TODO] ensure to repaint 
780
        }
781
    }
782
783
    @Override
784
    public void install(JTextComponent c) {
785
        assert (SwingUtilities.isEventDispatchThread()); // must be done in AWT
786
        if (LOG.isLoggable(Level.FINE)) {
787
            LOG.fine("Installing to " + s2s(c)); //NOI18N
788
        }
789
        
790
        component = c;
791
        visible = true;
792
        modelChanged(null, c.getDocument());
793
794
        Boolean b = (Boolean) c.getClientProperty(EditorUtilities.CARET_OVERWRITE_MODE_PROPERTY);
795
        overwriteMode = (b != null) ? b : false;
796
        updateOverwriteModeLayer(true);
797
        setBlinkVisible(true);
798
        
799
        // Attempt to assign initial bounds - usually here the component
800
        // is not yet added to the component hierarchy.
801
        updateAllCaretsBounds();
802
        
803
        if(getLastCaretItem().getCaretBounds() == null) {
804
            // For null bounds wait for the component to get resized
805
            // and attempt to recompute bounds then
806
            component.addComponentListener(listenerImpl);
807
        }
808
809
        component.addPropertyChangeListener(listenerImpl);
810
        component.addFocusListener(listenerImpl);
811
        component.addMouseListener(listenerImpl);
812
        component.addMouseMotionListener(listenerImpl);
813
        component.addKeyListener(listenerImpl);
814
        ViewHierarchy.get(component).addViewHierarchyListener(listenerImpl);
815
816
        if (component.hasFocus()) {
817
            if (LOG.isLoggable(Level.FINE)) {
818
                LOG.fine("Component has focus, calling BaseCaret.focusGained(); doc=" // NOI18N
819
                    + component.getDocument().getProperty(Document.TitleProperty) + '\n');
820
            }
821
            listenerImpl.focusGained(null); // emulate focus gained
822
        }
823
824
        dispatchUpdate(false);
825
    }
826
827
    @Override
828
    public void deinstall(JTextComponent c) {
829
        if (LOG.isLoggable(Level.FINE)) {
830
            LOG.fine("Deinstalling from " + s2s(c)); //NOI18N
831
        }
832
        
833
        synchronized (listenerList) {
834
            if (flasher != null) {
835
                setBlinkRate(0);
836
            }
837
        }
838
        
839
        c.removeComponentListener(listenerImpl);
840
        c.removePropertyChangeListener(listenerImpl);
841
        c.removeFocusListener(listenerImpl);
842
        c.removeMouseListener(listenerImpl);
843
        c.removeMouseMotionListener(listenerImpl);
844
        ViewHierarchy.get(c).removeViewHierarchyListener(listenerImpl);
845
846
        
847
        modelChanged(activeDoc, null);
848
    }
849
850
    @Override
851
    public void paint(Graphics g) {
852
        JTextComponent c = component;
853
        if (c == null) return;
854
        
855
        // Check whether the caret was moved but the component was not
856
        // validated yet and therefore the caret bounds are still null
857
        // and if so compute the bounds and scroll the view if necessary.
858
        // TODO - could this be done by an extra flag rather than bounds checking??
859
        CaretItem lastCaret = getLastCaretItem();
860
        if (getDot() != 0 && lastCaret.getCaretBounds() == null) {
861
            update(true);
862
        }
863
        
864
        List<CaretInfo> carets = getSortedCarets();
865
        for (CaretInfo caretInfo : carets) { // TODO only paint the items in the clipped area - use binary search to located first item
866
            CaretItem caretItem = caretInfo.getCaretItem();
867
            if (LOG.isLoggable(Level.FINEST)) {
868
                LOG.finest("BaseCaret.paint(): caretBounds=" + caretItem.getCaretBounds() + dumpVisibility() + '\n');
869
            }
870
            if (caretItem.getCaretBounds() != null && isVisible() && blinkVisible) {
871
                paintCaret(g, caretItem);
872
            }
873
            if (rectangularSelection && rsPaintRect != null && g instanceof Graphics2D) {
874
                Graphics2D g2d = (Graphics2D) g;
875
                Stroke stroke = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] {4, 2}, 0);
876
                Stroke origStroke = g2d.getStroke();
877
                Color origColor = g2d.getColor();
878
                try {
879
                    // Render translucent rectangle
880
                    Color selColor = c.getSelectionColor();
881
                    g2d.setColor(selColor);
882
                    Composite origComposite = g2d.getComposite();
883
                    try {
884
                        g2d.setComposite(AlphaComposite.SrcOver.derive(0.2f));
885
                        g2d.fill(rsPaintRect);
886
                    } finally {
887
                        g2d.setComposite(origComposite);
888
                    }
889
                    // Paint stroked line around rectangular selection rectangle
890
                    g.setColor(c.getCaretColor());
891
                    g2d.setStroke(stroke);
892
                    Rectangle onePointSmallerRect = new Rectangle(rsPaintRect);
893
                    onePointSmallerRect.width--;
894
                    onePointSmallerRect.height--;
895
                    g2d.draw(onePointSmallerRect);
896
897
                } finally {
898
                    g2d.setStroke(origStroke);
899
                    g2d.setColor(origColor);
900
                }
901
            }
902
        }
903
    }
904
905
    public @Override void setMagicCaretPosition(Point p) {
906
        getLastCaretItem().setMagicCaretPosition(p);
907
    }
908
909
    public @Override final Point getMagicCaretPosition() {
910
        return getLastCaretItem().getMagicCaretPosition();
911
    }
912
913
    public @Override void setBlinkRate(int rate) {
914
        if (LOG.isLoggable(Level.FINER)) {
915
            LOG.finer("setBlinkRate(" + rate + ")" + dumpVisibility() + '\n'); // NOI18N
916
        }
917
        synchronized (listenerList) {
918
            if (flasher == null && rate > 0) {
919
                flasher = new Timer(rate, listenerImpl);
920
            }
921
            if (flasher != null) {
922
                if (rate > 0) {
923
                    if (flasher.getDelay() != rate) {
924
                        flasher.setDelay(rate);
925
                    }
926
                } else { // zero rate - don't blink
927
                    flasher.stop();
928
                    flasher.removeActionListener(listenerImpl);
929
                    flasher = null;
930
                    setBlinkVisible(true);
931
                    if (LOG.isLoggable(Level.FINER)){
932
                        LOG.finer("Zero blink rate - no blinking. flasher=null; blinkVisible=true"); // NOI18N
933
                    }
934
                }
935
            }
936
        }
937
    }
938
939
    @Override
940
    public int getBlinkRate() {
941
        synchronized (listenerList) {
942
            return (flasher != null) ? flasher.getDelay() : 0;
943
        }
944
    }
945
946
    /**
947
     * 
948
     */
949
    void setRectangularSelectionToDotAndMark() {
950
        int dotOffset = getDot();
951
        int markOffset = getMark();
952
        try {
953
            rsDotRect = component.modelToView(dotOffset);
954
            rsMarkRect = component.modelToView(markOffset);
955
        } catch (BadLocationException ex) {
956
            rsDotRect = rsMarkRect = null;
957
        }
958
        updateRectangularSelectionPaintRect();
959
    }
960
961
    /**
962
     * 
963
     */
964
    void updateRectangularUpDownSelection() {
965
        JTextComponent c = component;
966
        int dotOffset = getDot();
967
        try {
968
            Rectangle r = c.modelToView(dotOffset);
969
            rsDotRect.y = r.y;
970
            rsDotRect.height = r.height;
971
        } catch (BadLocationException ex) {
972
            // Leave rsDotRect unchanged
973
        }
974
    }
975
    
976
    /**
977
     * Extend rectangular selection either by char in a specified selection
978
     * or by word (if ctrl is pressed).
979
     *
980
     * @param toRight true for right or false for left.
981
     * @param ctrl true for ctrl pressed.
982
     */
983
    void extendRectangularSelection(boolean toRight, boolean ctrl) {
984
        JTextComponent c = component;
985
        Document doc = c.getDocument();
986
        int dotOffset = getDot();
987
        Element lineRoot = doc.getDefaultRootElement();
988
        int lineIndex = lineRoot.getElementIndex(dotOffset);
989
        Element lineElement = lineRoot.getElement(lineIndex);
990
        float charWidth;
991
        LockedViewHierarchy lvh = ViewHierarchy.get(c).lock();
992
        try {
993
            charWidth = lvh.getDefaultCharWidth();
994
        } finally {
995
            lvh.unlock();
996
        }
997
        int newDotOffset = -1;
998
        try {
999
            int newlineOffset = lineElement.getEndOffset() - 1;
1000
            Rectangle newlineRect = c.modelToView(newlineOffset);
1001
            if (!ctrl) {
1002
                if (toRight) {
1003
                    if (rsDotRect.x < newlineRect.x) {
1004
                        newDotOffset = dotOffset + 1;
1005
                    } else {
1006
                        rsDotRect.x += charWidth;
1007
                    }
1008
                } else { // toLeft
1009
                    if (rsDotRect.x > newlineRect.x) {
1010
                        rsDotRect.x -= charWidth;
1011
                        if (rsDotRect.x < newlineRect.x) { // Fix on rsDotRect
1012
                            newDotOffset = newlineOffset;
1013
                        }
1014
                    } else {
1015
                        newDotOffset = Math.max(dotOffset - 1, lineElement.getStartOffset());
1016
                    }
1017
                }
1018
1019
            } else { // With Ctrl
1020
                int numVirtualChars = 8; // Number of virtual characters per one Ctrl+Shift+Arrow press
1021
                if (toRight) {
1022
                    if (rsDotRect.x < newlineRect.x) {
1023
//[TODO] fix                        newDotOffset = Math.min(Utilities.getNextWord(c, dotOffset), lineElement.getEndOffset() - 1);
1024
                    } else { // Extend virtually
1025
                        rsDotRect.x += numVirtualChars * charWidth;
1026
                    }
1027
                } else { // toLeft
1028
                    if (rsDotRect.x > newlineRect.x) { // Virtually extended
1029
                        rsDotRect.x -= numVirtualChars * charWidth;
1030
                        if (rsDotRect.x < newlineRect.x) {
1031
                            newDotOffset = newlineOffset;
1032
                        }
1033
                    } else {
1034
//[TODO] fix                        newDotOffset = Math.max(Utilities.getPreviousWord(c, dotOffset), lineElement.getStartOffset());
1035
                    }
1036
                }
1037
            }
1038
1039
            if (newDotOffset != -1) {
1040
                rsDotRect = c.modelToView(newDotOffset);
1041
                moveDot(newDotOffset); // updates rs and fires state change
1042
            } else {
1043
                updateRectangularSelectionPaintRect();
1044
                fireStateChanged();
1045
            }
1046
        } catch (BadLocationException ex) {
1047
            // Leave selection as is
1048
        }
1049
    }
1050
    
1051
    
1052
    // Private implementation
1053
    
1054
    /** This method should only be accessed by transaction's methods */
1055
    GapList<CaretItem> getCaretItems() {
1056
        return caretItems; // No sync as this should only be accessed by transaction's methods
1057
    }
1058
    
1059
    /** This method should only be accessed by transaction's methods */
1060
    GapList<CaretItem> getSortedCaretItems() {
1061
        return sortedCaretItems; // No sync as this should only be accessed by transaction's methods
1062
    }
1063
1064
    /** This method may be accessed arbitrarily */
1065
    private CaretItem getLastCaretItem() {
1066
        synchronized (listenerList) {
1067
            return caretItems.get(caretItems.size() - 1);
1068
        }
1069
    }
1070
1071
    /**
1072
     * Run a transaction to modify number of carets or their dots or selections.
1073
     * @param removeType type of carets removal.
1074
     * @param offset offset of document text removal otherwise the value is ignored.
1075
     *  Internally this is also used for an end offset of the insertion at offset zero once it happens.
1076
     * @param addCarets carets to be added if any.
1077
     * @param moveHandler caret move handler or null if there's no caret moving. API client's move handlers
1078
     *  are only invoked without any extra removals or additions so the original caret infos are used.
1079
     *  Internal transactions may use caret additions or removals together with caret move handlers
1080
     *  but they are also passed with original caret infos so the handlers must be aware of the removal
1081
     *  and only pick the valid non-removed items.
1082
     * @return 
1083
     */
1084
    private int runTransaction(CaretTransaction.RemoveType removeType, int offset, CaretItem[] addCarets, CaretMoveHandler moveHandler) {
1085
        lock();
1086
        try {
1087
            if (activeTransaction == null) {
1088
                JTextComponent c = component;
1089
                Document d = activeDoc;
1090
                if (c != null && d != null) {
1091
                    activeTransaction = new CaretTransaction(this, c, d);
1092
                    try {
1093
                        activeTransaction.replaceCarets(removeType, offset, addCarets);
1094
                        if (moveHandler != null) {
1095
                            activeTransaction.runCaretMoveHandler(moveHandler);
1096
                        }
1097
                        activeTransaction.removeOverlappingRegions();
1098
                        int diffCount = 0;
1099
                        synchronized (listenerList) {
1100
                            GapList<CaretItem> replaceItems = activeTransaction.getReplaceItems();
1101
                            if (replaceItems != null) {
1102
                                diffCount = replaceItems.size() - caretItems.size();
1103
                                caretItems = replaceItems;
1104
                                sortedCaretItems = activeTransaction.getSortedCaretItems();
1105
                                assert (sortedCaretItems != null) : "Null sortedCaretItems! removeType=" + removeType; // NOI18N
1106
                            }
1107
                        }
1108
                        if (activeTransaction.isAnyChange()) {
1109
                            caretInfos = null;
1110
                            sortedCaretInfos = null;
1111
                        }
1112
                        pendingRepaintRemovedItemsList = activeTransaction.
1113
                                addRemovedItems(pendingRepaintRemovedItemsList);
1114
                        pendingUpdateVisualBoundsItemsList = activeTransaction.
1115
                                addUpdateVisualBoundsItems(pendingUpdateVisualBoundsItemsList);
1116
                        if (pendingUpdateVisualBoundsItemsList != null || pendingRepaintRemovedItemsList != null) {
1117
                            // For now clear the lists and use old way TODO update to selective updating and rendering
1118
                            fireStateChanged();
1119
                            dispatchUpdate(true);
1120
                            pendingRepaintRemovedItemsList = null;
1121
                            pendingUpdateVisualBoundsItemsList = null;
1122
                        }
1123
                        return diffCount;
1124
                    } finally {
1125
                        activeTransaction = null;
1126
                    }
1127
                }
1128
                return Integer.MIN_VALUE;
1129
                
1130
            } else { // Nested transaction - document insert/remove within transaction
1131
                switch (removeType) {
1132
                    case DOCUMENT_REMOVE:
1133
                        activeTransaction.documentRemove(offset);
1134
                        break;
1135
                    case DOCUMENT_INSERT_ZERO_OFFSET:
1136
                        activeTransaction.documentInsertAtZeroOffset(offset);
1137
                        break;
1138
                    default:
1139
                        throw new AssertionError("Unsupported removeType=" + removeType + " in nested transaction"); // NOI18N
1140
                }
1141
                return 0;
1142
            }
1143
        } finally {
1144
            unlock();
1145
        }
1146
    }
1147
    
1148
    private void moveDotCaret(int offset, CaretItem caret) throws IllegalStateException {
1149
        JTextComponent c = component;
1150
        AbstractDocument doc;
1151
        if (c != null && (doc = activeDoc) != null) {
1152
            if (offset >= 0 && offset <= doc.getLength()) {
1153
                doc.readLock();
1154
                try {
1155
                    int oldCaretPos = caret.getDot();
1156
                    if (offset == oldCaretPos) { // no change
1157
                        return;
1158
                    }
1159
                    caret.setDotPos(doc.createPosition(offset));
1160
                    // Selection highlighting should be handled automatically by highlighting layers
1161
                    if (rectangularSelection) {
1162
                        Rectangle r = c.modelToView(offset);
1163
                        if (rsDotRect != null) {
1164
                            rsDotRect.y = r.y;
1165
                            rsDotRect.height = r.height;
1166
                        } else {
1167
                            rsDotRect = r;
1168
                        }
1169
                        updateRectangularSelectionPaintRect();
1170
                    }
1171
                } catch (BadLocationException e) {
1172
                    throw new IllegalStateException(e.toString());
1173
                    // position is incorrect
1174
                } finally {
1175
                    doc.readUnlock();
1176
                }
1177
            }
1178
            fireStateChanged();
1179
            dispatchUpdate(true);
1180
        }
1181
    }
1182
    
1183
    private void fireEditorCaretChange(EditorCaretEvent evt) {
1184
        for (EditorCaretListener listener : listenerList.getListeners()) {
1185
            listener.caretChanged(evt);
1186
        }
1187
    }
1188
    
1189
    /**
1190
     * Notifies listeners that caret position has changed.
1191
     */
1192
    private void fireStateChanged() {
1193
        Runnable runnable = new Runnable() {
1194
            public @Override void run() {
1195
                JTextComponent c = component;
1196
                if (c == null || c.getCaret() != EditorCaret.this) {
1197
                    return;
1198
                }
1199
                fireEditorCaretChange(new EditorCaretEvent(EditorCaret.this, 0, Integer.MAX_VALUE)); // [TODO] temp firing without detailed info
1200
                ChangeEvent evt = new ChangeEvent(EditorCaret.this);
1201
                List<ChangeListener> listeners = changeListenerList.getListeners();
1202
                for (ChangeListener l : listeners) {
1203
                    l.stateChanged(evt);
1204
                }
1205
            }
1206
        };
1207
        
1208
        // Always fire in EDT
1209
        if (inAtomicUnlock) { // Cannot fire within atomic lock9
1210
            SwingUtilities.invokeLater(runnable);
1211
        } else {
1212
            ViewUtils.runInEDT(runnable);
1213
        }
1214
        updateSystemSelection();
1215
    }
1216
1217
    private void lock() {
1218
        Thread curThread = Thread.currentThread();
1219
        try {
1220
            synchronized (listenerList) {
1221
                while (lockThread != null) {
1222
                    if (curThread == lockThread) {
1223
                        lockDepth++;
1224
                        return;
1225
                    }
1226
                    listenerList.wait();
1227
                }
1228
                lockThread = curThread;
1229
                lockDepth = 1;
1230
1231
            }
1232
        } catch (InterruptedException e) {
1233
            throw new Error("Interrupted attempt to acquire lock"); // NOI18N
1234
        }
1235
    }
1236
    
1237
    private void unlock() {
1238
        Thread curThread = Thread.currentThread();
1239
        synchronized (listenerList) {
1240
            if (lockThread == curThread) {
1241
                lockDepth--;
1242
                if (lockDepth == 0) {
1243
                    lockThread = null;
1244
                    listenerList.notifyAll();
1245
                }
1246
            } else {
1247
                throw new IllegalStateException("Invalid thread called EditorCaret.unlock():  thread=" + // NOI18N
1248
                        curThread + ", lockThread=" + lockThread); // NOI18N
1249
            }
1250
        }
1251
    }
1252
1253
    private void updateType() {
1254
        final JTextComponent c = component;
1255
        if (c != null) {
1256
            Color caretColor = null;
1257
            CaretType newType;
1258
            boolean cIsTextField = Boolean.TRUE.equals(c.getClientProperty("AsTextField"));
1259
            
1260
            if (cIsTextField) {
1261
                newType = CaretType.THIN_LINE_CARET;
1262
            } else if (prefs != null) {
1263
                String newTypeStr;
1264
                if (overwriteMode) {
1265
                    newTypeStr = prefs.get(SimpleValueNames.CARET_TYPE_OVERWRITE_MODE, EditorPreferencesDefaults.defaultCaretTypeOverwriteMode);
1266
                } else { // insert mode
1267
                    newTypeStr = prefs.get(SimpleValueNames.CARET_TYPE_INSERT_MODE, EditorPreferencesDefaults.defaultCaretTypeInsertMode);
1268
                    this.thickCaretWidth = prefs.getInt(SimpleValueNames.THICK_CARET_WIDTH, EditorPreferencesDefaults.defaultThickCaretWidth);
1269
                }
1270
                newType = CaretType.decode(newTypeStr);
1271
            } else {
1272
                newType = CaretType.THICK_LINE_CARET;
1273
            }
1274
1275
            String mimeType = DocumentUtilities.getMimeType(c);
1276
            FontColorSettings fcs = (mimeType != null) ? MimeLookup.getLookup(mimeType).lookup(FontColorSettings.class) : null;
1277
            if (fcs != null) {
1278
                AttributeSet attribs = fcs.getFontColors(overwriteMode
1279
                        ? FontColorNames.CARET_COLOR_OVERWRITE_MODE
1280
                        : FontColorNames.CARET_COLOR_INSERT_MODE); //NOI18N
1281
                if (attribs != null) {
1282
                    caretColor = (Color) attribs.getAttribute(StyleConstants.Foreground);
1283
                }
1284
            }
1285
            
1286
            this.type = newType;
1287
            final Color caretColorFinal = caretColor;
1288
            ViewUtils.runInEDT(
1289
                new Runnable() {
1290
                    public @Override void run() {
1291
                        if (caretColorFinal != null) {
1292
                            c.setCaretColor(caretColorFinal);
1293
                            if (LOG.isLoggable(Level.FINER)) {
1294
                                LOG.finer("Updating caret color:" + caretColorFinal + '\n'); // NOI18N
1295
                            }
1296
                        }
1297
1298
                        resetBlink();
1299
                        dispatchUpdate(false);
1300
                    }
1301
                }
1302
            );
1303
        }
1304
    }
1305
1306
    /**
1307
     * Assign new caret bounds into <code>caretBounds</code> variable.
1308
     *
1309
     * @return true if the new caret bounds were successfully computed
1310
     *  and assigned or false otherwise.
1311
     */
1312
    private boolean updateAllCaretsBounds() {
1313
        JTextComponent c = component;
1314
        AbstractDocument doc;
1315
        boolean ret = false;
1316
        if (c != null && (doc = activeDoc) != null) {
1317
            doc.readLock();
1318
            try {
1319
                List<CaretInfo> sortedCarets = getSortedCarets();
1320
                for (CaretInfo caret : sortedCarets) {
1321
                    ret |= updateRealCaretBounds(caret.getCaretItem(), doc, c);
1322
                }
1323
            } finally {
1324
                doc.readUnlock();
1325
            }
1326
        }
1327
        return ret;
1328
    }
1329
    
1330
    private boolean updateCaretBounds(CaretItem caret) {
1331
        JTextComponent c = component;
1332
        boolean ret = false;
1333
        AbstractDocument doc;
1334
        if (c != null && (doc = activeDoc) != null) {
1335
            doc.readLock();
1336
            try {
1337
                ret = updateRealCaretBounds(caret, doc, c);
1338
            } finally {
1339
                doc.readUnlock();
1340
            }
1341
        }
1342
        return ret;
1343
    }
1344
    
1345
    private boolean updateRealCaretBounds(CaretItem caret, Document doc, JTextComponent c) {
1346
        Position dotPos = caret.getDotPosition();
1347
        int offset = dotPos == null? 0 : dotPos.getOffset();
1348
        if (offset > doc.getLength()) {
1349
            offset = doc.getLength();
1350
        }
1351
        Rectangle newCaretBounds;
1352
        try {
1353
            DocumentView docView = DocumentView.get(c);
1354
            if (docView != null) {
1355
                // docView.syncViewsRebuild(); // Make sure pending views changes are resolved
1356
            }
1357
            newCaretBounds = c.getUI().modelToView(
1358
                    c, offset, Position.Bias.Forward);
1359
            // [TODO] Temporary fix - impl should remember real bounds computed by paintCustomCaret()
1360
            if (newCaretBounds != null) {
1361
                newCaretBounds.width = Math.max(newCaretBounds.width, 2);
1362
            }
1363
1364
        } catch (BadLocationException e) {
1365
            
1366
            newCaretBounds = null;
1367
        }
1368
        if (newCaretBounds != null) {
1369
            if (LOG.isLoggable(Level.FINE)) {
1370
                LOG.log(Level.FINE, "updateCaretBounds: old={0}, new={1}, offset={2}",
1371
                        new Object[]{caret.getCaretBounds(), newCaretBounds, offset}); //NOI18N
1372
            }
1373
            caret.setCaretBounds(newCaretBounds);
1374
            return true;
1375
        } else {
1376
            return false;
1377
        }
1378
    }
1379
1380
    private void modelChanged(Document oldDoc, Document newDoc) {
1381
        if (oldDoc != null) {
1382
            // ideally the oldDoc param shouldn't exist and only listenDoc should be used
1383
            assert (oldDoc == activeDoc);
1384
1385
            DocumentUtilities.removeDocumentListener(
1386
                    oldDoc, listenerImpl, DocumentListenerPriority.CARET_UPDATE);
1387
            AtomicLockDocument oldAtomicDoc = LineDocumentUtils.as(oldDoc, AtomicLockDocument.class);
1388
            if (oldAtomicDoc != null) {
1389
                oldAtomicDoc.removeAtomicLockListener(listenerImpl);
1390
            }
1391
1392
            activeDoc = null;
1393
            if (prefs != null && weakPrefsListener != null) {
1394
                prefs.removePreferenceChangeListener(weakPrefsListener);
1395
            }
1396
        }
1397
1398
        // EditorCaret only installs successfully into AbstractDocument based documents that carry a mime-type
1399
        if (newDoc instanceof AbstractDocument) {
1400
            String mimeType = DocumentUtilities.getMimeType(newDoc);
1401
            activeDoc = (AbstractDocument) newDoc;
1402
            DocumentUtilities.addDocumentListener(
1403
                    newDoc, listenerImpl, DocumentListenerPriority.CARET_UPDATE);
1404
            AtomicLockDocument newAtomicDoc = LineDocumentUtils.as(oldDoc, AtomicLockDocument.class);
1405
            if (newAtomicDoc != null) {
1406
                newAtomicDoc.addAtomicLockListener(listenerImpl);
1407
            }
1408
1409
            // Set caret to zero position upon document change (DefaultCaret impl does this too)
1410
            runTransaction(CaretTransaction.RemoveType.REMOVE_ALL_CARETS, 0,
1411
                    new CaretItem[] { new CaretItem(this, newDoc.getStartPosition(), null) }, null);
1412
            
1413
            // Leave caretPos and markPos null => offset==0
1414
            prefs = (mimeType != null) ? MimeLookup.getLookup(mimeType).lookup(Preferences.class) : null;
1415
            if (prefs != null) {
1416
                weakPrefsListener = WeakListeners.create(PreferenceChangeListener.class, listenerImpl, prefs);
1417
                prefs.addPreferenceChangeListener(weakPrefsListener);
1418
            }
1419
            
1420
            updateType();
1421
        }
1422
    }
1423
    
1424
    private void paintCaret(Graphics g, CaretItem caret) {
1425
        JTextComponent c = component;
1426
        if (c != null) {
1427
            g.setColor(c.getCaretColor());
1428
            Rectangle caretBounds = caret.getCaretBounds();
1429
            switch (type) {
1430
                case THICK_LINE_CARET:
1431
                    g.fillRect(caretBounds.x, caretBounds.y, this.thickCaretWidth, caretBounds.height - 1);
1432
                    break;
1433
1434
                case THIN_LINE_CARET:
1435
                    int upperX = caret.getCaretBounds().x;
1436
                    g.drawLine((int) upperX, caret.getCaretBounds().y, caret.getCaretBounds().x,
1437
                            (caret.getCaretBounds().y + caret.getCaretBounds().height - 1));
1438
                    break;
1439
1440
                case BLOCK_CARET:
1441
                    // Use a CaretOverwriteModeHighlighting layer to paint the caret
1442
                    break;
1443
1444
                default:
1445
                    throw new IllegalStateException("Invalid caret type=" + type);
1446
            }
1447
        }
1448
    }
1449
1450
    void dispatchUpdate() {
1451
        JTextComponent c = component;
1452
        if (c != null) {
1453
            dispatchUpdate(c.hasFocus()); // Scroll to caret only for component with focus
1454
        }
1455
    }
1456
    /** Update visual position of caret(s) */
1457
    private void dispatchUpdate(final boolean scrollViewToCaret) {
1458
        /* Ensure that the caret's document listener will be added AFTER the views hierarchy's
1459
         * document listener so the code can run synchronously again
1460
         * which should eliminate the problem with caret lag.
1461
         * However the document can be modified from non-AWT thread
1462
         * which is the case in #57316 and in that case the code
1463
         * must run asynchronously in AWT thread.
1464
         */
1465
        ViewUtils.runInEDT(
1466
            new Runnable() {
1467
                public @Override void run() {
1468
                    AbstractDocument doc = activeDoc;
1469
                    if (doc != null) {
1470
                        doc.readLock();
1471
                        try {
1472
                            update(scrollViewToCaret);
1473
                        } finally {
1474
                            doc.readUnlock();
1475
                        }
1476
                    }
1477
                }
1478
            }
1479
        );
1480
    }
1481
1482
    /**
1483
     * Update the caret's visual position.
1484
     * <br>
1485
     * The document is read-locked while calling this method.
1486
     *
1487
     * @param scrollViewToCaret whether the view of the text component should be
1488
     *  scrolled to the position of the caret.
1489
     */
1490
    private void update(boolean scrollViewToCaret) {
1491
        caretUpdatePending = false;
1492
        JTextComponent c = component;
1493
        if (c != null) {
1494
            if (!c.isValid()) {
1495
                c.validate();
1496
            }
1497
            Document doc = c.getDocument();
1498
            if (doc != null) {
1499
                List<CaretInfo> sortedCarets = getSortedCarets();
1500
                for (CaretInfo caret : sortedCarets) {
1501
                    CaretItem caretItem = caret.getCaretItem();
1502
                    Rectangle oldCaretBounds = caretItem.getCaretBounds(); // no need to deep copy
1503
                    if (oldCaretBounds != null) {
1504
                        c.repaint(oldCaretBounds);
1505
                    }
1506
1507
                    // note - the order is important ! caret bounds must be updated even if the fold flag is true.
1508
                    if (updateCaretBounds(caretItem) || updateAfterFoldHierarchyChange) {
1509
                        Rectangle scrollBounds = new Rectangle(caretItem.getCaretBounds());
1510
1511
                        // Optimization to avoid extra repaint:
1512
                        // If the caret bounds were not yet assigned then attempt
1513
                        // to scroll the window so that there is an extra vertical space 
1514
                        // for the possible horizontal scrollbar that may appear
1515
                        // if the line-view creation process finds line-view that
1516
                        // is too wide and so the horizontal scrollbar will appear
1517
                        // consuming an extra vertical space at the bottom.
1518
                        if (oldCaretBounds == null) {
1519
                            Component viewport = c.getParent();
1520
                            if (viewport instanceof JViewport) {
1521
                                Component scrollPane = viewport.getParent();
1522
                                if (scrollPane instanceof JScrollPane) {
1523
                                    JScrollBar hScrollBar = ((JScrollPane) scrollPane).getHorizontalScrollBar();
1524
                                    if (hScrollBar != null) {
1525
                                        int hScrollBarHeight = hScrollBar.getPreferredSize().height;
1526
                                        Dimension extentSize = ((JViewport) viewport).getExtentSize();
1527
                                        // If the extent size is high enough then extend
1528
                                        // the scroll region by extra vertical space
1529
                                        if (extentSize.height >= caretItem.getCaretBounds().height + hScrollBarHeight) {
1530
                                            scrollBounds.height += hScrollBarHeight;
1531
                                        }
1532
                                    }
1533
                                }
1534
                            }
1535
                        }
1536
1537
                        Rectangle visibleBounds = c.getVisibleRect();
1538
1539
                        // If folds have changed attempt to scroll the view so that 
1540
                        // relative caret's visual position gets retained
1541
                        // (the absolute position will change because of collapsed/expanded folds).
1542
                        boolean doScroll = scrollViewToCaret;
1543
                        boolean explicit = false;
1544
                        if (oldCaretBounds != null && (!scrollViewToCaret || updateAfterFoldHierarchyChange)) {
1545
                            int oldRelY = oldCaretBounds.y - visibleBounds.y;
1546
                            // Only fix if the caret is within visible bounds and the new x or y coord differs from the old one
1547
                            if (LOG.isLoggable(Level.FINER)) {
1548
                                LOG.log(Level.FINER, "oldCaretBounds: {0}, visibleBounds: {1}, caretBounds: {2}",
1549
                                        new Object[]{oldCaretBounds, visibleBounds, caretItem.getCaretBounds()});
1550
                            }
1551
                            if (oldRelY >= 0 && oldRelY < visibleBounds.height
1552
                                    && (oldCaretBounds.y != caretItem.getCaretBounds().y || oldCaretBounds.x != caretItem.getCaretBounds().x)) {
1553
                                doScroll = true; // Perform explicit scrolling
1554
                                explicit = true;
1555
                                int oldRelX = oldCaretBounds.x - visibleBounds.x;
1556
                                // Do not retain the horizontal caret bounds by scrolling
1557
                                // since many modifications do not explicitly say that they are typing modifications
1558
                                // and this would cause problems like #176268
1559
//                            scrollBounds.x = Math.max(caretBounds.x - oldRelX, 0);
1560
                                scrollBounds.y = Math.max(caretItem.getCaretBounds().y - oldRelY, 0);
1561
//                            scrollBounds.width = visibleBounds.width;
1562
                                scrollBounds.height = visibleBounds.height;
1563
                            }
1564
                        }
1565
1566
                        // Historically the caret is expected to appear
1567
                        // in the middle of the window if setDot() gets called
1568
                        // e.g. by double-clicking in Navigator.
1569
                        // If the caret bounds are more than a caret height below the present
1570
                        // visible view bounds (or above the view bounds)
1571
                        // then scroll the window so that the caret is in the middle
1572
                        // of the visible window to see the context around the caret.
1573
                        // This should work fine with PgUp/Down because these
1574
                        // scroll the view explicitly.
1575
                        if (scrollViewToCaret
1576
                                && !explicit
1577
                                && // #219580: if the preceding if-block computed new scrollBounds, it cannot be offset yet more
1578
                                /* # 70915 !updateAfterFoldHierarchyChange && */ (caretItem.getCaretBounds().y > visibleBounds.y + visibleBounds.height + caretItem.getCaretBounds().height
1579
                                || caretItem.getCaretBounds().y + caretItem.getCaretBounds().height < visibleBounds.y - caretItem.getCaretBounds().height)) {
1580
                            // Scroll into the middle
1581
                            scrollBounds.y -= (visibleBounds.height - caretItem.getCaretBounds().height) / 2;
1582
                            scrollBounds.height = visibleBounds.height;
1583
                        }
1584
                        if (LOG.isLoggable(Level.FINER)) {
1585
                            LOG.finer("Resetting fold flag, current: " + updateAfterFoldHierarchyChange);
1586
                        }
1587
                        updateAfterFoldHierarchyChange = false;
1588
1589
                        // Ensure that the viewport will be scrolled either to make the caret visible
1590
                        // or to retain cart's relative visual position against the begining of the viewport's visible rectangle.
1591
                        if (doScroll) {
1592
                            if (LOG.isLoggable(Level.FINER)) {
1593
                                LOG.finer("Scrolling to: " + scrollBounds);
1594
                            }
1595
                            c.scrollRectToVisible(scrollBounds);
1596
                            if (!c.getVisibleRect().intersects(scrollBounds)) {
1597
                                // HACK: see #21958