1 | /*
|
---|
2 | * 08/29/2004
|
---|
3 | *
|
---|
4 | * RSyntaxTextAreaEditorKit.java - The editor kit used by RSyntaxTextArea.
|
---|
5 | *
|
---|
6 | * This library is distributed under a modified BSD license. See the included
|
---|
7 | * RSyntaxTextArea.License.txt file for details.
|
---|
8 | */
|
---|
9 | package org.fife.ui.rsyntaxtextarea;
|
---|
10 |
|
---|
11 | import java.awt.*;
|
---|
12 | import java.awt.event.*;
|
---|
13 | import java.util.ResourceBundle;
|
---|
14 | import java.util.Stack;
|
---|
15 | import javax.swing.*;
|
---|
16 | import javax.swing.text.*;
|
---|
17 |
|
---|
18 | import org.fife.ui.rsyntaxtextarea.folding.Fold;
|
---|
19 | import org.fife.ui.rsyntaxtextarea.folding.FoldCollapser;
|
---|
20 | import org.fife.ui.rsyntaxtextarea.folding.FoldManager;
|
---|
21 | import org.fife.ui.rsyntaxtextarea.templates.CodeTemplate;
|
---|
22 | import org.fife.ui.rtextarea.Gutter;
|
---|
23 | import org.fife.ui.rtextarea.IconRowHeader;
|
---|
24 | import org.fife.ui.rtextarea.RecordableTextAction;
|
---|
25 | import org.fife.ui.rtextarea.RTextArea;
|
---|
26 | import org.fife.ui.rtextarea.RTextAreaEditorKit;
|
---|
27 |
|
---|
28 |
|
---|
29 | /**
|
---|
30 | * An extension of <code>RTextAreaEditorKit</code> that adds functionality for
|
---|
31 | * programming-specific stuff. There are currently subclasses to handle:
|
---|
32 | *
|
---|
33 | * <ul>
|
---|
34 | * <li>Toggling code folds.</li>
|
---|
35 | * <li>Aligning "closing" curly braces with their matches, if the current
|
---|
36 | * programming language uses curly braces to identify code blocks.</li>
|
---|
37 | * <li>Copying the current selection as RTF.</li>
|
---|
38 | * <li>Block indentation (increasing the indent of one or multiple lines)</li>
|
---|
39 | * <li>Block un-indentation (decreasing the indent of one or multiple lines)
|
---|
40 | * </li>
|
---|
41 | * <li>Inserting a "code template" when a configurable key (e.g. a space) is
|
---|
42 | * pressed</li>
|
---|
43 | * <li>Decreasing the point size of all fonts in the text area</li>
|
---|
44 | * <li>Increasing the point size of all fonts in the text area</li>
|
---|
45 | * <li>Moving the caret to the "matching bracket" of the one at the current
|
---|
46 | * caret position</li>
|
---|
47 | * <li>Toggling whether the currently selected lines are commented out.</li>
|
---|
48 | * <li>Better selection of "words" on mouse double-clicks for programming
|
---|
49 | * languages.</li>
|
---|
50 | * <li>Better keyboard navigation via Ctrl+arrow keys for programming
|
---|
51 | * languages.</li>
|
---|
52 | * </ul>
|
---|
53 | *
|
---|
54 | * @author Robert Futrell
|
---|
55 | * @version 0.5
|
---|
56 | */
|
---|
57 | public class RSyntaxTextAreaEditorKit extends RTextAreaEditorKit {
|
---|
58 |
|
---|
59 | private static final long serialVersionUID = 1L;
|
---|
60 |
|
---|
61 | public static final String rstaCloseCurlyBraceAction = "RSTA.CloseCurlyBraceAction";
|
---|
62 | public static final String rstaCloseMarkupTagAction = "RSTA.CloseMarkupTagAction";
|
---|
63 | public static final String rstaCollapseAllFoldsAction = "RSTA.CollapseAllFoldsAction";
|
---|
64 | public static final String rstaCollapseAllCommentFoldsAction = "RSTA.CollapseAllCommentFoldsAction";
|
---|
65 | public static final String rstaCollapseFoldAction = "RSTA.CollapseFoldAction";
|
---|
66 | public static final String rstaCopyAsRtfAction = "RSTA.CopyAsRtfAction";
|
---|
67 | public static final String rstaDecreaseIndentAction = "RSTA.DecreaseIndentAction";
|
---|
68 | public static final String rstaExpandAllFoldsAction = "RSTA.ExpandAllFoldsAction";
|
---|
69 | public static final String rstaExpandFoldAction = "RSTA.ExpandFoldAction";
|
---|
70 | public static final String rstaGoToMatchingBracketAction = "RSTA.GoToMatchingBracketAction";
|
---|
71 | public static final String rstaPossiblyInsertTemplateAction = "RSTA.TemplateAction";
|
---|
72 | public static final String rstaToggleCommentAction = "RSTA.ToggleCommentAction";
|
---|
73 | public static final String rstaToggleCurrentFoldAction = "RSTA.ToggleCurrentFoldAction";
|
---|
74 |
|
---|
75 | private static final String MSG = "org.fife.ui.rsyntaxtextarea.RSyntaxTextArea";
|
---|
76 | private static final ResourceBundle msg = ResourceBundle.getBundle(MSG);
|
---|
77 |
|
---|
78 |
|
---|
79 | /**
|
---|
80 | * The actions that <code>RSyntaxTextAreaEditorKit</code> adds to those of
|
---|
81 | * <code>RTextAreaEditorKit</code>.
|
---|
82 | */
|
---|
83 | private static final Action[] defaultActions = {
|
---|
84 | new CloseCurlyBraceAction(),
|
---|
85 | new CloseMarkupTagAction(),
|
---|
86 | new BeginWordAction(beginWordAction, false),
|
---|
87 | new BeginWordAction(selectionBeginWordAction, true),
|
---|
88 | new ChangeFoldStateAction(rstaCollapseFoldAction, true),
|
---|
89 | new ChangeFoldStateAction(rstaExpandFoldAction, false),
|
---|
90 | new CollapseAllFoldsAction(),
|
---|
91 | new CopyAsRtfAction(),
|
---|
92 | //new DecreaseFontSizeAction(),
|
---|
93 | new DecreaseIndentAction(),
|
---|
94 | new DeletePrevWordAction(),
|
---|
95 | new EndAction(endAction, false),
|
---|
96 | new EndAction(selectionEndAction, true),
|
---|
97 | new EndWordAction(endWordAction, false),
|
---|
98 | new EndWordAction(endWordAction, true),
|
---|
99 | new ExpandAllFoldsAction(),
|
---|
100 | new GoToMatchingBracketAction(),
|
---|
101 | new InsertBreakAction(),
|
---|
102 | //new IncreaseFontSizeAction(),
|
---|
103 | new InsertTabAction(),
|
---|
104 | new NextWordAction(nextWordAction, false),
|
---|
105 | new NextWordAction(selectionNextWordAction, true),
|
---|
106 | new PossiblyInsertTemplateAction(),
|
---|
107 | new PreviousWordAction(previousWordAction, false),
|
---|
108 | new PreviousWordAction(selectionPreviousWordAction, true),
|
---|
109 | new SelectWordAction(),
|
---|
110 | new ToggleCommentAction(),
|
---|
111 | };
|
---|
112 |
|
---|
113 |
|
---|
114 | /**
|
---|
115 | * Constructor.
|
---|
116 | */
|
---|
117 | public RSyntaxTextAreaEditorKit() {
|
---|
118 | }
|
---|
119 |
|
---|
120 |
|
---|
121 | /**
|
---|
122 | * Returns the default document used by <code>RSyntaxTextArea</code>s.
|
---|
123 | *
|
---|
124 | * @return The document.
|
---|
125 | */
|
---|
126 | public Document createDefaultDocument() {
|
---|
127 | return new RSyntaxDocument(SyntaxConstants.SYNTAX_STYLE_NONE);
|
---|
128 | }
|
---|
129 |
|
---|
130 |
|
---|
131 | /**
|
---|
132 | * Overridden to return a row header that is aware of folding.
|
---|
133 | *
|
---|
134 | * @param textArea The text area.
|
---|
135 | * @return The icon row header.
|
---|
136 | */
|
---|
137 | public IconRowHeader createIconRowHeader(RTextArea textArea) {
|
---|
138 | return new FoldingAwareIconRowHeader((RSyntaxTextArea)textArea);
|
---|
139 | }
|
---|
140 |
|
---|
141 |
|
---|
142 | /**
|
---|
143 | * Fetches the set of commands that can be used
|
---|
144 | * on a text component that is using a model and
|
---|
145 | * view produced by this kit.
|
---|
146 | *
|
---|
147 | * @return the command list
|
---|
148 | */
|
---|
149 | public Action[] getActions() {
|
---|
150 | return TextAction.augmentList(super.getActions(),
|
---|
151 | RSyntaxTextAreaEditorKit.defaultActions);
|
---|
152 | }
|
---|
153 |
|
---|
154 |
|
---|
155 | /**
|
---|
156 | * Returns localized text for an action. There's definitely a better place
|
---|
157 | * for this functionality.
|
---|
158 | *
|
---|
159 | * @param key The key into the action resource bundle.
|
---|
160 | * @return The localized text.
|
---|
161 | */
|
---|
162 | public static String getString(String key) {
|
---|
163 | return msg.getString(key);
|
---|
164 | }
|
---|
165 |
|
---|
166 |
|
---|
167 | /**
|
---|
168 | * Positions the caret at the beginning of the word. This class is here
|
---|
169 | * to better handle finding the "beginning of the word" for programming
|
---|
170 | * languages.
|
---|
171 | */
|
---|
172 | protected static class BeginWordAction
|
---|
173 | extends RTextAreaEditorKit.BeginWordAction {
|
---|
174 |
|
---|
175 | private Segment seg;
|
---|
176 |
|
---|
177 | protected BeginWordAction(String name, boolean select) {
|
---|
178 | super(name, select);
|
---|
179 | seg = new Segment();
|
---|
180 | }
|
---|
181 |
|
---|
182 | protected int getWordStart(RTextArea textArea, int offs)
|
---|
183 | throws BadLocationException {
|
---|
184 |
|
---|
185 | if (offs==0) {
|
---|
186 | return offs;
|
---|
187 | }
|
---|
188 |
|
---|
189 | RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
|
---|
190 | int line = textArea.getLineOfOffset(offs);
|
---|
191 | int start = textArea.getLineStartOffset(line);
|
---|
192 | if (offs==start) {
|
---|
193 | return start;
|
---|
194 | }
|
---|
195 | int end = textArea.getLineEndOffset(line);
|
---|
196 | if (line!=textArea.getLineCount()-1) {
|
---|
197 | end--;
|
---|
198 | }
|
---|
199 | doc.getText(start, end-start, seg);
|
---|
200 |
|
---|
201 | // Determine the "type" of char at offs - lower case, upper case,
|
---|
202 | // whitespace or other. We take special care here as we're starting
|
---|
203 | // in the middle of the Segment to check whether we're already at
|
---|
204 | // the "beginning" of a word.
|
---|
205 | int firstIndex = seg.getBeginIndex() + (offs-start) - 1;
|
---|
206 | seg.setIndex(firstIndex);
|
---|
207 | char ch = seg.current();
|
---|
208 | char nextCh = offs==end ? 0 : seg.array[seg.getIndex() + 1];
|
---|
209 |
|
---|
210 | // The "word" is a group of letters and/or digits
|
---|
211 | if (Character.isLetterOrDigit(ch)) {
|
---|
212 | if (offs!=end && !Character.isLetterOrDigit(nextCh)) {
|
---|
213 | return offs;
|
---|
214 | }
|
---|
215 | do {
|
---|
216 | ch = seg.previous();
|
---|
217 | } while (Character.isLetterOrDigit(ch));
|
---|
218 | }
|
---|
219 |
|
---|
220 | // The "word" is whitespace
|
---|
221 | else if (Character.isWhitespace(ch)) {
|
---|
222 | if (offs!=end && !Character.isWhitespace(nextCh)) {
|
---|
223 | return offs;
|
---|
224 | }
|
---|
225 | do {
|
---|
226 | ch = seg.previous();
|
---|
227 | } while (Character.isWhitespace(ch));
|
---|
228 | }
|
---|
229 |
|
---|
230 | // Otherwise, the "word" a single "something else" char (operator,
|
---|
231 | // etc.).
|
---|
232 |
|
---|
233 | offs -= firstIndex - seg.getIndex() + 1;//seg.getEndIndex() - seg.getIndex();
|
---|
234 | if (ch!=Segment.DONE && nextCh!='\n') {
|
---|
235 | offs++;
|
---|
236 | }
|
---|
237 |
|
---|
238 | return offs;
|
---|
239 |
|
---|
240 | }
|
---|
241 |
|
---|
242 | }
|
---|
243 |
|
---|
244 |
|
---|
245 | /**
|
---|
246 | * Expands or collapses the nearest fold.
|
---|
247 | */
|
---|
248 | public static class ChangeFoldStateAction extends FoldRelatedAction {
|
---|
249 |
|
---|
250 | private boolean collapse;
|
---|
251 |
|
---|
252 | public ChangeFoldStateAction(String name, boolean collapse) {
|
---|
253 | super(name);
|
---|
254 | this.collapse = collapse;
|
---|
255 | }
|
---|
256 |
|
---|
257 | public ChangeFoldStateAction(String name, Icon icon,
|
---|
258 | String desc, Integer mnemonic, KeyStroke accelerator) {
|
---|
259 | super(name, icon, desc, mnemonic, accelerator);
|
---|
260 | }
|
---|
261 |
|
---|
262 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
263 | RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
---|
264 | if (rsta.isCodeFoldingEnabled()) {
|
---|
265 | Fold fold = getClosestFold(rsta);
|
---|
266 | if (fold!=null) {
|
---|
267 | fold.setCollapsed(collapse);
|
---|
268 | }
|
---|
269 | possiblyRepaintGutter(textArea);
|
---|
270 | }
|
---|
271 | else {
|
---|
272 | UIManager.getLookAndFeel().provideErrorFeedback(rsta);
|
---|
273 | }
|
---|
274 | }
|
---|
275 |
|
---|
276 | public final String getMacroID() {
|
---|
277 | return getName();
|
---|
278 | }
|
---|
279 |
|
---|
280 | }
|
---|
281 |
|
---|
282 |
|
---|
283 | /**
|
---|
284 | * Action that (optionally) aligns a closing curly brace with the line
|
---|
285 | * containing its matching opening curly brace.
|
---|
286 | */
|
---|
287 | public static class CloseCurlyBraceAction extends RecordableTextAction {
|
---|
288 |
|
---|
289 | private static final long serialVersionUID = 1L;
|
---|
290 |
|
---|
291 | private Segment seg;
|
---|
292 |
|
---|
293 | public CloseCurlyBraceAction() {
|
---|
294 | super(rstaCloseCurlyBraceAction);
|
---|
295 | seg = new Segment();
|
---|
296 | }
|
---|
297 |
|
---|
298 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
299 |
|
---|
300 | RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
---|
301 | RSyntaxDocument doc = (RSyntaxDocument)rsta.getDocument();
|
---|
302 | boolean alignCurlyBraces = rsta.isAutoIndentEnabled() &&
|
---|
303 | doc.getCurlyBracesDenoteCodeBlocks();
|
---|
304 |
|
---|
305 | if (alignCurlyBraces) {
|
---|
306 | textArea.beginAtomicEdit();
|
---|
307 | }
|
---|
308 |
|
---|
309 | try {
|
---|
310 |
|
---|
311 | textArea.replaceSelection("}");
|
---|
312 |
|
---|
313 | // If the user wants to align curly braces...
|
---|
314 | if (alignCurlyBraces) {
|
---|
315 |
|
---|
316 | Element root = doc.getDefaultRootElement();
|
---|
317 | int dot = rsta.getCaretPosition() - 1; // Start before '{'
|
---|
318 | int line = root.getElementIndex(dot);
|
---|
319 | Element elem = root.getElement(line);
|
---|
320 | int start = elem.getStartOffset();
|
---|
321 |
|
---|
322 | // Get the current line's text up to the '}' entered.
|
---|
323 | try {
|
---|
324 | doc.getText(start, dot-start, seg);
|
---|
325 | } catch (BadLocationException ble) { // Never happens
|
---|
326 | ble.printStackTrace();
|
---|
327 | return;
|
---|
328 | }
|
---|
329 |
|
---|
330 | // Only attempt to align if there's only whitespace up to
|
---|
331 | // the '}' entered.
|
---|
332 | for (int i=0; i<seg.count; i++) {
|
---|
333 | char ch = seg.array[seg.offset+i];
|
---|
334 | if (!Character.isWhitespace(ch)) {
|
---|
335 | return;
|
---|
336 | }
|
---|
337 | }
|
---|
338 |
|
---|
339 | // Locate the matching '{' bracket, and replace the leading
|
---|
340 | // whitespace for the '}' to match that of the '{' char's line.
|
---|
341 | int match = RSyntaxUtilities.getMatchingBracketPosition(rsta);
|
---|
342 | if (match>-1) {
|
---|
343 | elem = root.getElement(root.getElementIndex(match));
|
---|
344 | int start2 = elem.getStartOffset();
|
---|
345 | int end = elem.getEndOffset() - 1;
|
---|
346 | String text = null;
|
---|
347 | try {
|
---|
348 | text = doc.getText(start2, end-start2);
|
---|
349 | } catch (BadLocationException ble) { // Never happens
|
---|
350 | ble.printStackTrace();
|
---|
351 | return;
|
---|
352 | }
|
---|
353 | String ws = RSyntaxUtilities.getLeadingWhitespace(text);
|
---|
354 | rsta.replaceRange(ws, start, dot);
|
---|
355 | }
|
---|
356 |
|
---|
357 | }
|
---|
358 |
|
---|
359 | } finally {
|
---|
360 | if (alignCurlyBraces) {
|
---|
361 | textArea.endAtomicEdit();
|
---|
362 | }
|
---|
363 | }
|
---|
364 |
|
---|
365 | }
|
---|
366 |
|
---|
367 | public final String getMacroID() {
|
---|
368 | return rstaCloseCurlyBraceAction;
|
---|
369 | }
|
---|
370 |
|
---|
371 | }
|
---|
372 |
|
---|
373 |
|
---|
374 | /**
|
---|
375 | * (Optionally) completes a closing markup tag.
|
---|
376 | */
|
---|
377 | public static class CloseMarkupTagAction extends RecordableTextAction {
|
---|
378 |
|
---|
379 | private static final long serialVersionUID = 1L;
|
---|
380 |
|
---|
381 | public CloseMarkupTagAction() {
|
---|
382 | super(rstaCloseMarkupTagAction);
|
---|
383 | }
|
---|
384 |
|
---|
385 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
386 |
|
---|
387 | if (!textArea.isEditable() || !textArea.isEnabled()) {
|
---|
388 | UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
---|
389 | return;
|
---|
390 | }
|
---|
391 |
|
---|
392 | RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
---|
393 | RSyntaxDocument doc = (RSyntaxDocument)rsta.getDocument();
|
---|
394 |
|
---|
395 | Caret c = rsta.getCaret();
|
---|
396 | boolean selection = c.getDot()!=c.getMark();
|
---|
397 | rsta.replaceSelection("/");
|
---|
398 |
|
---|
399 | // Don't automatically complete a tag if there was a selection
|
---|
400 | int dot = c.getDot();
|
---|
401 |
|
---|
402 | if (doc.getLanguageIsMarkup() &&
|
---|
403 | doc.getCompleteMarkupCloseTags() &&
|
---|
404 | !selection && rsta.getCloseMarkupTags() && dot>1) {
|
---|
405 |
|
---|
406 | try {
|
---|
407 |
|
---|
408 | // Check actual char before token type, since it's quicker
|
---|
409 | char ch = doc.charAt(dot-2);
|
---|
410 | if (ch=='<' || ch=='[') {
|
---|
411 |
|
---|
412 | Token t = doc.getTokenListForLine(
|
---|
413 | rsta.getCaretLineNumber());
|
---|
414 | t = RSyntaxUtilities.getTokenAtOffset(t, dot-1);
|
---|
415 | if (t!=null && t.type==Token.MARKUP_TAG_DELIMITER) {
|
---|
416 | //System.out.println("Huzzah - closing tag!");
|
---|
417 | String tagName = discoverTagName(doc, dot);
|
---|
418 | if (tagName!=null) {
|
---|
419 | rsta.replaceSelection(tagName + (char)(ch+2));
|
---|
420 | }
|
---|
421 | }
|
---|
422 |
|
---|
423 | }
|
---|
424 |
|
---|
425 | } catch (BadLocationException ble) { // Never happens
|
---|
426 | UIManager.getLookAndFeel().provideErrorFeedback(rsta);
|
---|
427 | ble.printStackTrace();
|
---|
428 | }
|
---|
429 |
|
---|
430 | }
|
---|
431 |
|
---|
432 | }
|
---|
433 |
|
---|
434 | /**
|
---|
435 | * Discovers the name of the tag being closed. Assumes standard
|
---|
436 | * SGML-style markup tags.
|
---|
437 | *
|
---|
438 | * @param doc The document to parse.
|
---|
439 | * @param dot The location of the caret. This should be right after
|
---|
440 | * the start of a closing tag token (e.g. "<code></</code>"
|
---|
441 | * or "<code>[</code>" in the case of BBCode).
|
---|
442 | * @return The name of the tag to close, or <code>null</code> if it
|
---|
443 | * could not be determined.
|
---|
444 | */
|
---|
445 | private String discoverTagName(RSyntaxDocument doc, int dot) {
|
---|
446 |
|
---|
447 | Stack stack = new Stack();
|
---|
448 |
|
---|
449 | Element root = doc.getDefaultRootElement();
|
---|
450 | int curLine = root.getElementIndex(dot);
|
---|
451 |
|
---|
452 | for (int i=0; i<=curLine; i++) {
|
---|
453 |
|
---|
454 | Token t = doc.getTokenListForLine(i);
|
---|
455 | while (t!=null && t.isPaintable()) {
|
---|
456 |
|
---|
457 | if (t.type==Token.MARKUP_TAG_DELIMITER) {
|
---|
458 | if (t.isSingleChar('<') || t.isSingleChar('[')) {
|
---|
459 | t = t.getNextToken();
|
---|
460 | while (t!=null && t.isPaintable()) {
|
---|
461 | if (t.type==Token.MARKUP_TAG_NAME ||
|
---|
462 | // Being lenient here and also checking
|
---|
463 | // for attributes, in case they
|
---|
464 | // (incorrectly) have whitespace between
|
---|
465 | // the '<' char and the element name.
|
---|
466 | t.type==Token.MARKUP_TAG_ATTRIBUTE) {
|
---|
467 | stack.push(t.getLexeme());
|
---|
468 | break;
|
---|
469 | }
|
---|
470 | t = t.getNextToken();
|
---|
471 | }
|
---|
472 | }
|
---|
473 | else if (t.textCount==2 && t.text[t.textOffset]=='/' &&
|
---|
474 | (t.text[t.textOffset+1]=='>' ||
|
---|
475 | t.text[t.textOffset+1]==']')) {
|
---|
476 | if (!stack.isEmpty()) { // Always true for valid XML
|
---|
477 | stack.pop();
|
---|
478 | }
|
---|
479 | }
|
---|
480 | else if (t.textCount==2 &&
|
---|
481 | (t.text[t.textOffset]=='<' || t.text[t.textOffset]=='[') &&
|
---|
482 | t.text[t.textOffset+1]=='/') {
|
---|
483 | String tagName = null;
|
---|
484 | if (!stack.isEmpty()) { // Always true for valid XML
|
---|
485 | tagName = (String)stack.pop();
|
---|
486 | }
|
---|
487 | if (t.offset+t.textCount>=dot) {
|
---|
488 | return tagName;
|
---|
489 | }
|
---|
490 | }
|
---|
491 | }
|
---|
492 |
|
---|
493 | t = t.getNextToken();
|
---|
494 |
|
---|
495 | }
|
---|
496 |
|
---|
497 | }
|
---|
498 |
|
---|
499 | return null; // Should never happen
|
---|
500 |
|
---|
501 | }
|
---|
502 |
|
---|
503 | public String getMacroID() {
|
---|
504 | return getName();
|
---|
505 | }
|
---|
506 |
|
---|
507 | }
|
---|
508 |
|
---|
509 |
|
---|
510 | /**
|
---|
511 | * Collapses all comment folds.
|
---|
512 | */
|
---|
513 | public static class CollapseAllCommentFoldsAction extends FoldRelatedAction{
|
---|
514 |
|
---|
515 | private static final long serialVersionUID = 1L;
|
---|
516 |
|
---|
517 | public CollapseAllCommentFoldsAction() {
|
---|
518 | super(rstaCollapseAllCommentFoldsAction);
|
---|
519 | setProperties(msg, "Action.CollapseCommentFolds");
|
---|
520 | }
|
---|
521 |
|
---|
522 | public CollapseAllCommentFoldsAction(String name, Icon icon,
|
---|
523 | String desc, Integer mnemonic, KeyStroke accelerator) {
|
---|
524 | super(name, icon, desc, mnemonic, accelerator);
|
---|
525 | }
|
---|
526 |
|
---|
527 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
528 | RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
---|
529 | if (rsta.isCodeFoldingEnabled()) {
|
---|
530 | FoldCollapser collapser = new FoldCollapser();
|
---|
531 | collapser.collapseFolds(rsta.getFoldManager());
|
---|
532 | possiblyRepaintGutter(textArea);
|
---|
533 | }
|
---|
534 | else {
|
---|
535 | UIManager.getLookAndFeel().provideErrorFeedback(rsta);
|
---|
536 | }
|
---|
537 | }
|
---|
538 |
|
---|
539 | public final String getMacroID() {
|
---|
540 | return rstaCollapseAllCommentFoldsAction;
|
---|
541 | }
|
---|
542 |
|
---|
543 | }
|
---|
544 |
|
---|
545 |
|
---|
546 | /**
|
---|
547 | * Collapses all folds.
|
---|
548 | */
|
---|
549 | public static class CollapseAllFoldsAction extends FoldRelatedAction {
|
---|
550 |
|
---|
551 | private static final long serialVersionUID = 1L;
|
---|
552 |
|
---|
553 | public CollapseAllFoldsAction() {
|
---|
554 | this(false);
|
---|
555 | }
|
---|
556 |
|
---|
557 | public CollapseAllFoldsAction(boolean localizedName) {
|
---|
558 | super(rstaCollapseAllFoldsAction);
|
---|
559 | if (localizedName) {
|
---|
560 | setProperties(msg, "Action.CollapseAllFolds");
|
---|
561 | }
|
---|
562 | }
|
---|
563 |
|
---|
564 | public CollapseAllFoldsAction(String name, Icon icon,
|
---|
565 | String desc, Integer mnemonic, KeyStroke accelerator) {
|
---|
566 | super(name, icon, desc, mnemonic, accelerator);
|
---|
567 | }
|
---|
568 |
|
---|
569 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
570 | RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
---|
571 | if (rsta.isCodeFoldingEnabled()) {
|
---|
572 | FoldCollapser collapser = new FoldCollapser() {
|
---|
573 | public boolean getShouldCollapse(Fold fold) {
|
---|
574 | return true;
|
---|
575 | }
|
---|
576 | };
|
---|
577 | collapser.collapseFolds(rsta.getFoldManager());
|
---|
578 | possiblyRepaintGutter(textArea);
|
---|
579 | }
|
---|
580 | else {
|
---|
581 | UIManager.getLookAndFeel().provideErrorFeedback(rsta);
|
---|
582 | }
|
---|
583 | }
|
---|
584 |
|
---|
585 | public final String getMacroID() {
|
---|
586 | return rstaCollapseAllFoldsAction;
|
---|
587 | }
|
---|
588 |
|
---|
589 | }
|
---|
590 |
|
---|
591 |
|
---|
592 | /**
|
---|
593 | * Action for copying text as RTF.
|
---|
594 | */
|
---|
595 | public static class CopyAsRtfAction extends RecordableTextAction {
|
---|
596 |
|
---|
597 | private static final long serialVersionUID = 1L;
|
---|
598 |
|
---|
599 | public CopyAsRtfAction() {
|
---|
600 | super(rstaCopyAsRtfAction);
|
---|
601 | }
|
---|
602 |
|
---|
603 | public CopyAsRtfAction(String name, Icon icon, String desc,
|
---|
604 | Integer mnemonic, KeyStroke accelerator) {
|
---|
605 | super(name, icon, desc, mnemonic, accelerator);
|
---|
606 | }
|
---|
607 |
|
---|
608 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
609 | ((RSyntaxTextArea)textArea).copyAsRtf();
|
---|
610 | textArea.requestFocusInWindow();
|
---|
611 | }
|
---|
612 |
|
---|
613 | public final String getMacroID() {
|
---|
614 | return getName();
|
---|
615 | }
|
---|
616 |
|
---|
617 | }
|
---|
618 |
|
---|
619 |
|
---|
620 | /**
|
---|
621 | * Action for decreasing the font size of all fonts in the text area.
|
---|
622 | */
|
---|
623 | public static class DecreaseFontSizeAction
|
---|
624 | extends RTextAreaEditorKit.DecreaseFontSizeAction {
|
---|
625 |
|
---|
626 | private static final long serialVersionUID = 1L;
|
---|
627 |
|
---|
628 | public DecreaseFontSizeAction() {
|
---|
629 | super();
|
---|
630 | }
|
---|
631 |
|
---|
632 | public DecreaseFontSizeAction(String name, Icon icon, String desc,
|
---|
633 | Integer mnemonic, KeyStroke accelerator) {
|
---|
634 | super(name, icon, desc, mnemonic, accelerator);
|
---|
635 | }
|
---|
636 |
|
---|
637 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
638 |
|
---|
639 | RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
---|
640 | SyntaxScheme scheme = rsta.getSyntaxScheme();
|
---|
641 |
|
---|
642 | // All we need to do is update all of the fonts in syntax
|
---|
643 | // schemes, then call setSyntaxHighlightingColorScheme with the
|
---|
644 | // same scheme already being used. This relies on the fact that
|
---|
645 | // that method does not check whether the new scheme is different
|
---|
646 | // from the old scheme before updating.
|
---|
647 |
|
---|
648 | boolean changed = false;
|
---|
649 | int count = scheme.getStyleCount();
|
---|
650 | for (int i=0; i<count; i++) {
|
---|
651 | Style ss = scheme.getStyle(i);
|
---|
652 | if (ss!=null) {
|
---|
653 | Font font = ss.font;
|
---|
654 | if (font!=null) {
|
---|
655 | float oldSize = font.getSize2D();
|
---|
656 | float newSize = oldSize - decreaseAmount;
|
---|
657 | if (newSize>=MINIMUM_SIZE) {
|
---|
658 | // Shrink by decreaseAmount.
|
---|
659 | ss.font = font.deriveFont(newSize);
|
---|
660 | changed = true;
|
---|
661 | }
|
---|
662 | else if (oldSize>MINIMUM_SIZE) {
|
---|
663 | // Can't shrink by full decreaseAmount, but
|
---|
664 | // can shrink a little bit.
|
---|
665 | ss.font = font.deriveFont(MINIMUM_SIZE);
|
---|
666 | changed = true;
|
---|
667 | }
|
---|
668 | }
|
---|
669 | }
|
---|
670 | }
|
---|
671 |
|
---|
672 | // Do the text area's font also.
|
---|
673 | Font font = rsta.getFont();
|
---|
674 | float oldSize = font.getSize2D();
|
---|
675 | float newSize = oldSize - decreaseAmount;
|
---|
676 | if (newSize>=MINIMUM_SIZE) {
|
---|
677 | // Shrink by decreaseAmount.
|
---|
678 | rsta.setFont(font.deriveFont(newSize));
|
---|
679 | changed = true;
|
---|
680 | }
|
---|
681 | else if (oldSize>MINIMUM_SIZE) {
|
---|
682 | // Can't shrink by full decreaseAmount, but
|
---|
683 | // can shrink a little bit.
|
---|
684 | rsta.setFont(font.deriveFont(MINIMUM_SIZE));
|
---|
685 | changed = true;
|
---|
686 | }
|
---|
687 |
|
---|
688 | // If we updated at least one font, update the screen. If
|
---|
689 | // all of the fonts were already the minimum size, beep.
|
---|
690 | if (changed) {
|
---|
691 | rsta.setSyntaxScheme(scheme);
|
---|
692 | // NOTE: This is a hack to get an encompassing
|
---|
693 | // RTextScrollPane to repaint its line numbers to account
|
---|
694 | // for a change in line height due to a font change. I'm
|
---|
695 | // not sure why we need to do this here but not when we
|
---|
696 | // change the syntax highlighting color scheme via the
|
---|
697 | // Options dialog... setSyntaxHighlightingColorScheme()
|
---|
698 | // calls revalidate() which won't repaint the scroll pane
|
---|
699 | // if scrollbars don't change, which is why we need this.
|
---|
700 | Component parent = rsta.getParent();
|
---|
701 | if (parent instanceof javax.swing.JViewport) {
|
---|
702 | parent = parent.getParent();
|
---|
703 | if (parent instanceof JScrollPane) {
|
---|
704 | parent.repaint();
|
---|
705 | }
|
---|
706 | }
|
---|
707 | }
|
---|
708 | else
|
---|
709 | UIManager.getLookAndFeel().provideErrorFeedback(rsta);
|
---|
710 |
|
---|
711 | }
|
---|
712 |
|
---|
713 | }
|
---|
714 |
|
---|
715 |
|
---|
716 | /**
|
---|
717 | * Action for when un-indenting lines (either the current line if there is
|
---|
718 | * selection, or all selected lines if there is one).
|
---|
719 | */
|
---|
720 | public static class DecreaseIndentAction extends RecordableTextAction {
|
---|
721 |
|
---|
722 | private static final long serialVersionUID = 1L;
|
---|
723 |
|
---|
724 | private Segment s;
|
---|
725 |
|
---|
726 | public DecreaseIndentAction() {
|
---|
727 | this(rstaDecreaseIndentAction);
|
---|
728 | }
|
---|
729 |
|
---|
730 | public DecreaseIndentAction(String name) {
|
---|
731 | super(name);
|
---|
732 | s = new Segment();
|
---|
733 | }
|
---|
734 |
|
---|
735 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
736 |
|
---|
737 | if (!textArea.isEditable() || !textArea.isEnabled()) {
|
---|
738 | UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
---|
739 | return;
|
---|
740 | }
|
---|
741 |
|
---|
742 | Document document = textArea.getDocument();
|
---|
743 | Element map = document.getDefaultRootElement();
|
---|
744 | Caret c = textArea.getCaret();
|
---|
745 | int dot = c.getDot();
|
---|
746 | int mark = c.getMark();
|
---|
747 | int line1 = map.getElementIndex(dot);
|
---|
748 | int tabSize = textArea.getTabSize();
|
---|
749 |
|
---|
750 | // If there is a selection, indent all lines in the selection.
|
---|
751 | // Otherwise, indent the line the caret is on.
|
---|
752 | if (dot!=mark) {
|
---|
753 | // Note that we cheaply reuse variables here, so don't
|
---|
754 | // take their names to mean what they are.
|
---|
755 | int line2 = map.getElementIndex(mark);
|
---|
756 | dot = Math.min(line1, line2);
|
---|
757 | mark = Math.max(line1, line2);
|
---|
758 | Element elem;
|
---|
759 | try {
|
---|
760 | for (line1=dot; line1<mark; line1++) {
|
---|
761 | elem = map.getElement(line1);
|
---|
762 | handleDecreaseIndent(elem, document, tabSize);
|
---|
763 | }
|
---|
764 | // Don't do the last line if the caret is at its
|
---|
765 | // beginning. We must call getDot() again and not just
|
---|
766 | // use 'dot' as the caret's position may have changed
|
---|
767 | // due to the insertion of the tabs above.
|
---|
768 | elem = map.getElement(mark);
|
---|
769 | int start = elem.getStartOffset();
|
---|
770 | if (Math.max(c.getDot(),c.getMark())!=start) {
|
---|
771 | handleDecreaseIndent(elem, document, tabSize);
|
---|
772 | }
|
---|
773 | } catch (BadLocationException ble) {
|
---|
774 | ble.printStackTrace();
|
---|
775 | UIManager.getLookAndFeel().
|
---|
776 | provideErrorFeedback(textArea);
|
---|
777 | }
|
---|
778 | }
|
---|
779 | else {
|
---|
780 | Element elem = map.getElement(line1);
|
---|
781 | try {
|
---|
782 | handleDecreaseIndent(elem, document, tabSize);
|
---|
783 | } catch (BadLocationException ble) {
|
---|
784 | ble.printStackTrace();
|
---|
785 | UIManager.getLookAndFeel().
|
---|
786 | provideErrorFeedback(textArea);
|
---|
787 | }
|
---|
788 | }
|
---|
789 |
|
---|
790 | }
|
---|
791 |
|
---|
792 | public final String getMacroID() {
|
---|
793 | return rstaDecreaseIndentAction;
|
---|
794 | }
|
---|
795 |
|
---|
796 | /**
|
---|
797 | * Actually does the "de-indentation." This method finds where the
|
---|
798 | * given element's leading whitespace ends, then, if there is indeed
|
---|
799 | * leading whitespace, removes either the last char in it (if it is a
|
---|
800 | * tab), or removes up to the number of spaces equal to a tab in the
|
---|
801 | * specified document (i.e., if the tab size was 5 and there were 3
|
---|
802 | * spaces at the end of the leading whitespace, the three will be
|
---|
803 | * removed; if there were 8 spaces, only the first 5 would be
|
---|
804 | * removed).
|
---|
805 | *
|
---|
806 | * @param elem The element to "de-indent."
|
---|
807 | * @param doc The document containing the specified element.
|
---|
808 | * @param tabSize The size of a tab, in spaces.
|
---|
809 | */
|
---|
810 | private final void handleDecreaseIndent(Element elem, Document doc,
|
---|
811 | int tabSize)
|
---|
812 | throws BadLocationException {
|
---|
813 | int start = elem.getStartOffset();
|
---|
814 | int end = elem.getEndOffset() - 1; // Why always true??
|
---|
815 | doc.getText(start,end-start, s);
|
---|
816 | int i = s.offset;
|
---|
817 | end = i+s.count;
|
---|
818 | if (end>i) {
|
---|
819 | // If the first character is a tab, remove it.
|
---|
820 | if (s.array[i]=='\t') {
|
---|
821 | doc.remove(start, 1);
|
---|
822 | }
|
---|
823 | // Otherwise, see if the first character is a space. If it
|
---|
824 | // is, remove all contiguous whitespaces at the beginning of
|
---|
825 | // this line, up to the tab size.
|
---|
826 | else if (s.array[i]==' ') {
|
---|
827 | i++;
|
---|
828 | int toRemove = 1;
|
---|
829 | while (i<end && s.array[i]==' ' && toRemove<tabSize) {
|
---|
830 | i++;
|
---|
831 | toRemove++;
|
---|
832 | }
|
---|
833 | doc.remove(start, toRemove);
|
---|
834 | }
|
---|
835 | }
|
---|
836 | }
|
---|
837 |
|
---|
838 | }
|
---|
839 |
|
---|
840 |
|
---|
841 | /**
|
---|
842 | * Deletes the previous word, but differentiates symbols from "words" to
|
---|
843 | * match the behavior of code editors.
|
---|
844 | */
|
---|
845 | public static class DeletePrevWordAction
|
---|
846 | extends RTextAreaEditorKit.DeletePrevWordAction {
|
---|
847 |
|
---|
848 | private Segment seg = new Segment();
|
---|
849 |
|
---|
850 | protected int getPreviousWordStart(RTextArea textArea, int offs)
|
---|
851 | throws BadLocationException {
|
---|
852 |
|
---|
853 | if (offs==0) {
|
---|
854 | return offs;
|
---|
855 | }
|
---|
856 |
|
---|
857 | RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
|
---|
858 | int line = textArea.getLineOfOffset(offs);
|
---|
859 | int start = textArea.getLineStartOffset(line);
|
---|
860 | if (offs==start) {
|
---|
861 | return start-1; // Just delete the newline
|
---|
862 | }
|
---|
863 | int end = textArea.getLineEndOffset(line);
|
---|
864 | if (line!=textArea.getLineCount()-1) {
|
---|
865 | end--;
|
---|
866 | }
|
---|
867 | doc.getText(start, end-start, seg);
|
---|
868 |
|
---|
869 | // Determine the "type" of char at offs - lower case, upper case,
|
---|
870 | // whitespace or other. We take special care here as we're starting
|
---|
871 | // in the middle of the Segment to check whether we're already at
|
---|
872 | // the "beginning" of a word.
|
---|
873 | int firstIndex = seg.getBeginIndex() + (offs-start) - 1;
|
---|
874 | seg.setIndex(firstIndex);
|
---|
875 | char ch = seg.current();
|
---|
876 |
|
---|
877 | // Always strip off whitespace first
|
---|
878 | if (Character.isWhitespace(ch)) {
|
---|
879 | do {
|
---|
880 | ch = seg.previous();
|
---|
881 | } while (Character.isWhitespace(ch));
|
---|
882 | }
|
---|
883 |
|
---|
884 | // The "word" is a group of letters and/or digits
|
---|
885 | if (Character.isLetterOrDigit(ch)) {
|
---|
886 | do {
|
---|
887 | ch = seg.previous();
|
---|
888 | } while (Character.isLetterOrDigit(ch));
|
---|
889 | }
|
---|
890 |
|
---|
891 | // The "word" is a series of symbols.
|
---|
892 | else {
|
---|
893 | while (!Character.isWhitespace(ch) &&
|
---|
894 | !Character.isLetterOrDigit(ch)
|
---|
895 | && ch!=Segment.DONE) {
|
---|
896 | ch = seg.previous();
|
---|
897 | }
|
---|
898 | }
|
---|
899 |
|
---|
900 | if (ch==Segment.DONE) {
|
---|
901 | return start; // Removed last "token" of the line
|
---|
902 | }
|
---|
903 | offs -= firstIndex - seg.getIndex();
|
---|
904 | return offs;
|
---|
905 |
|
---|
906 | }
|
---|
907 |
|
---|
908 | }
|
---|
909 |
|
---|
910 |
|
---|
911 | /**
|
---|
912 | * Moves the caret to the end of the document, taking into account code
|
---|
913 | * folding.
|
---|
914 | */
|
---|
915 | public static class EndAction extends RTextAreaEditorKit.EndAction {
|
---|
916 |
|
---|
917 | public EndAction(String name, boolean select) {
|
---|
918 | super(name, select);
|
---|
919 | }
|
---|
920 |
|
---|
921 | protected int getVisibleEnd(RTextArea textArea) {
|
---|
922 | RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
---|
923 | return rsta.getLastVisibleOffset();
|
---|
924 | }
|
---|
925 |
|
---|
926 | }
|
---|
927 |
|
---|
928 |
|
---|
929 | /**
|
---|
930 | * Positions the caret at the end of the word. This class is here to
|
---|
931 | * better handle finding the "end of the word" in programming languages.
|
---|
932 | */
|
---|
933 | protected static class EndWordAction
|
---|
934 | extends RTextAreaEditorKit.EndWordAction {
|
---|
935 |
|
---|
936 | private Segment seg;
|
---|
937 |
|
---|
938 | protected EndWordAction(String name, boolean select) {
|
---|
939 | super(name, select);
|
---|
940 | seg = new Segment();
|
---|
941 | }
|
---|
942 |
|
---|
943 | protected int getWordEnd(RTextArea textArea, int offs)
|
---|
944 | throws BadLocationException {
|
---|
945 |
|
---|
946 | RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
|
---|
947 | if (offs==doc.getLength()) {
|
---|
948 | return offs;
|
---|
949 | }
|
---|
950 |
|
---|
951 | int line = textArea.getLineOfOffset(offs);
|
---|
952 | int end = textArea.getLineEndOffset(line);
|
---|
953 | if (line!=textArea.getLineCount()-1) {
|
---|
954 | end--; // Hide newline
|
---|
955 | }
|
---|
956 | if (offs==end) {
|
---|
957 | return end;
|
---|
958 | }
|
---|
959 | doc.getText(offs, end-offs, seg);
|
---|
960 |
|
---|
961 | // Determine the "type" of char at offs - letter/digit,
|
---|
962 | // whitespace or other
|
---|
963 | char ch = seg.first();
|
---|
964 |
|
---|
965 | // The "word" is a group of letters and/or digits
|
---|
966 | if (Character.isLetterOrDigit(ch)) {
|
---|
967 | do {
|
---|
968 | ch = seg.next();
|
---|
969 | } while (Character.isLetterOrDigit(ch));
|
---|
970 | }
|
---|
971 |
|
---|
972 | // The "word" is whitespace.
|
---|
973 | else if (Character.isWhitespace(ch)) {
|
---|
974 |
|
---|
975 | do {
|
---|
976 | ch = seg.next();
|
---|
977 | } while (Character.isWhitespace(ch));
|
---|
978 | }
|
---|
979 |
|
---|
980 | // Otherwise, the "word" is a single character of some other type
|
---|
981 | // (operator, etc.).
|
---|
982 |
|
---|
983 | offs += seg.getIndex() - seg.getBeginIndex();
|
---|
984 | return offs;
|
---|
985 |
|
---|
986 | }
|
---|
987 |
|
---|
988 | }
|
---|
989 |
|
---|
990 |
|
---|
991 | /**
|
---|
992 | * Expands all folds.
|
---|
993 | */
|
---|
994 | public static class ExpandAllFoldsAction extends FoldRelatedAction {
|
---|
995 |
|
---|
996 | private static final long serialVersionUID = 1L;
|
---|
997 |
|
---|
998 | public ExpandAllFoldsAction() {
|
---|
999 | this(false);
|
---|
1000 | }
|
---|
1001 |
|
---|
1002 | public ExpandAllFoldsAction(boolean localizedName) {
|
---|
1003 | super(rstaExpandAllFoldsAction);
|
---|
1004 | if (localizedName) {
|
---|
1005 | setProperties(msg, "Action.ExpandAllFolds");
|
---|
1006 | }
|
---|
1007 | }
|
---|
1008 |
|
---|
1009 | public ExpandAllFoldsAction(String name, Icon icon,
|
---|
1010 | String desc, Integer mnemonic, KeyStroke accelerator) {
|
---|
1011 | super(name, icon, desc, mnemonic, accelerator);
|
---|
1012 | }
|
---|
1013 |
|
---|
1014 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
1015 | RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
---|
1016 | if (rsta.isCodeFoldingEnabled()) {
|
---|
1017 | FoldManager fm = rsta.getFoldManager();
|
---|
1018 | for (int i=0; i<fm.getFoldCount(); i++) {
|
---|
1019 | expand(fm.getFold(i));
|
---|
1020 | }
|
---|
1021 | possiblyRepaintGutter(rsta);
|
---|
1022 | }
|
---|
1023 | else {
|
---|
1024 | UIManager.getLookAndFeel().provideErrorFeedback(rsta);
|
---|
1025 | }
|
---|
1026 | }
|
---|
1027 |
|
---|
1028 | private void expand(Fold fold) {
|
---|
1029 | fold.setCollapsed(false);
|
---|
1030 | for (int i=0; i<fold.getChildCount(); i++) {
|
---|
1031 | expand(fold.getChild(i));
|
---|
1032 | }
|
---|
1033 | }
|
---|
1034 |
|
---|
1035 | public final String getMacroID() {
|
---|
1036 | return rstaExpandAllFoldsAction;
|
---|
1037 | }
|
---|
1038 |
|
---|
1039 | }
|
---|
1040 |
|
---|
1041 |
|
---|
1042 | /**
|
---|
1043 | * Base class for folding-related actions.
|
---|
1044 | */
|
---|
1045 | static abstract class FoldRelatedAction extends RecordableTextAction {
|
---|
1046 |
|
---|
1047 | public FoldRelatedAction(String name) {
|
---|
1048 | super(name);
|
---|
1049 | }
|
---|
1050 |
|
---|
1051 | public FoldRelatedAction(String name, Icon icon,
|
---|
1052 | String desc, Integer mnemonic, KeyStroke accelerator) {
|
---|
1053 | super(name, icon, desc, mnemonic, accelerator);
|
---|
1054 | }
|
---|
1055 |
|
---|
1056 | protected Fold getClosestFold(RSyntaxTextArea textArea) {
|
---|
1057 | int offs = textArea.getCaretPosition();
|
---|
1058 | int line = textArea.getCaretLineNumber();
|
---|
1059 | FoldManager fm = textArea.getFoldManager();
|
---|
1060 | Fold fold = fm.getFoldForLine(line);
|
---|
1061 | if (fold==null) {
|
---|
1062 | fold = fm.getDeepestOpenFoldContaining(offs);
|
---|
1063 | }
|
---|
1064 | return fold;
|
---|
1065 | }
|
---|
1066 |
|
---|
1067 | /**
|
---|
1068 | * Repaints the gutter in a text area's scroll pane, if necessary.
|
---|
1069 | *
|
---|
1070 | * @param textArea The text area.
|
---|
1071 | */
|
---|
1072 | protected void possiblyRepaintGutter(RTextArea textArea) {
|
---|
1073 | Gutter gutter = RSyntaxUtilities.getGutter(textArea);
|
---|
1074 | if (gutter!=null) {
|
---|
1075 | gutter.repaint();
|
---|
1076 | }
|
---|
1077 | }
|
---|
1078 |
|
---|
1079 | }
|
---|
1080 |
|
---|
1081 |
|
---|
1082 | /**
|
---|
1083 | * Action for moving the caret to the "matching bracket" of the bracket
|
---|
1084 | * at the caret position (either before or after).
|
---|
1085 | */
|
---|
1086 | public static class GoToMatchingBracketAction
|
---|
1087 | extends RecordableTextAction {
|
---|
1088 |
|
---|
1089 | private static final long serialVersionUID = 1L;
|
---|
1090 |
|
---|
1091 | public GoToMatchingBracketAction() {
|
---|
1092 | super(rstaGoToMatchingBracketAction);
|
---|
1093 | }
|
---|
1094 |
|
---|
1095 | public GoToMatchingBracketAction(String name, Icon icon, String desc,
|
---|
1096 | Integer mnemonic, KeyStroke accelerator) {
|
---|
1097 | super(name, icon, desc, mnemonic, accelerator);
|
---|
1098 | }
|
---|
1099 |
|
---|
1100 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
1101 | RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
---|
1102 | int pos = RSyntaxUtilities.getMatchingBracketPosition(rsta);
|
---|
1103 | if (pos>-1) {
|
---|
1104 | // Go to the position AFTER the bracket so the previous
|
---|
1105 | // bracket (which we were just on) is highlighted.
|
---|
1106 | rsta.setCaretPosition(pos+1);
|
---|
1107 | }
|
---|
1108 | else {
|
---|
1109 | UIManager.getLookAndFeel().provideErrorFeedback(rsta);
|
---|
1110 | }
|
---|
1111 | }
|
---|
1112 |
|
---|
1113 | public final String getMacroID() {
|
---|
1114 | return rstaGoToMatchingBracketAction;
|
---|
1115 | }
|
---|
1116 |
|
---|
1117 | }
|
---|
1118 |
|
---|
1119 |
|
---|
1120 | /**
|
---|
1121 | * Action for increasing the font size of all fonts in the text area.
|
---|
1122 | */
|
---|
1123 | public static class IncreaseFontSizeAction
|
---|
1124 | extends RTextAreaEditorKit.IncreaseFontSizeAction {
|
---|
1125 |
|
---|
1126 | private static final long serialVersionUID = 1L;
|
---|
1127 |
|
---|
1128 | public IncreaseFontSizeAction() {
|
---|
1129 | super();
|
---|
1130 | }
|
---|
1131 |
|
---|
1132 | public IncreaseFontSizeAction(String name, Icon icon, String desc,
|
---|
1133 | Integer mnemonic, KeyStroke accelerator) {
|
---|
1134 | super(name, icon, desc, mnemonic, accelerator);
|
---|
1135 | }
|
---|
1136 |
|
---|
1137 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
1138 |
|
---|
1139 | RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
---|
1140 | SyntaxScheme scheme = rsta.getSyntaxScheme();
|
---|
1141 |
|
---|
1142 | // All we need to do is update all of the fonts in syntax
|
---|
1143 | // schemes, then call setSyntaxHighlightingColorScheme with the
|
---|
1144 | // same scheme already being used. This relies on the fact that
|
---|
1145 | // that method does not check whether the new scheme is different
|
---|
1146 | // from the old scheme before updating.
|
---|
1147 |
|
---|
1148 | boolean changed = false;
|
---|
1149 | int count = scheme.getStyleCount();
|
---|
1150 | for (int i=0; i<count; i++) {
|
---|
1151 | Style ss = scheme.getStyle(i);
|
---|
1152 | if (ss!=null) {
|
---|
1153 | Font font = ss.font;
|
---|
1154 | if (font!=null) {
|
---|
1155 | float oldSize = font.getSize2D();
|
---|
1156 | float newSize = oldSize + increaseAmount;
|
---|
1157 | if (newSize<=MAXIMUM_SIZE) {
|
---|
1158 | // Grow by increaseAmount.
|
---|
1159 | ss.font = font.deriveFont(newSize);
|
---|
1160 | changed = true;
|
---|
1161 | }
|
---|
1162 | else if (oldSize<MAXIMUM_SIZE) {
|
---|
1163 | // Can't grow by full increaseAmount, but
|
---|
1164 | // can grow a little bit.
|
---|
1165 | ss.font = font.deriveFont(MAXIMUM_SIZE);
|
---|
1166 | changed = true;
|
---|
1167 | }
|
---|
1168 | }
|
---|
1169 | }
|
---|
1170 | }
|
---|
1171 |
|
---|
1172 | // Do the text area's font also.
|
---|
1173 | Font font = rsta.getFont();
|
---|
1174 | float oldSize = font.getSize2D();
|
---|
1175 | float newSize = oldSize + increaseAmount;
|
---|
1176 | if (newSize<=MAXIMUM_SIZE) {
|
---|
1177 | // Grow by increaseAmount.
|
---|
1178 | rsta.setFont(font.deriveFont(newSize));
|
---|
1179 | changed = true;
|
---|
1180 | }
|
---|
1181 | else if (oldSize<MAXIMUM_SIZE) {
|
---|
1182 | // Can't grow by full increaseAmount, but
|
---|
1183 | // can grow a little bit.
|
---|
1184 | rsta.setFont(font.deriveFont(MAXIMUM_SIZE));
|
---|
1185 | changed = true;
|
---|
1186 | }
|
---|
1187 |
|
---|
1188 | // If we updated at least one font, update the screen. If
|
---|
1189 | // all of the fonts were already the minimum size, beep.
|
---|
1190 | if (changed) {
|
---|
1191 | rsta.setSyntaxScheme(scheme);
|
---|
1192 | // NOTE: This is a hack to get an encompassing
|
---|
1193 | // RTextScrollPane to repaint its line numbers to account
|
---|
1194 | // for a change in line height due to a font change. I'm
|
---|
1195 | // not sure why we need to do this here but not when we
|
---|
1196 | // change the syntax highlighting color scheme via the
|
---|
1197 | // Options dialog... setSyntaxHighlightingColorScheme()
|
---|
1198 | // calls revalidate() which won't repaint the scroll pane
|
---|
1199 | // if scrollbars don't change, which is why we need this.
|
---|
1200 | Component parent = rsta.getParent();
|
---|
1201 | if (parent instanceof javax.swing.JViewport) {
|
---|
1202 | parent = parent.getParent();
|
---|
1203 | if (parent instanceof JScrollPane) {
|
---|
1204 | parent.repaint();
|
---|
1205 | }
|
---|
1206 | }
|
---|
1207 | }
|
---|
1208 | else
|
---|
1209 | UIManager.getLookAndFeel().provideErrorFeedback(rsta);
|
---|
1210 |
|
---|
1211 | }
|
---|
1212 |
|
---|
1213 | }
|
---|
1214 |
|
---|
1215 |
|
---|
1216 | /**
|
---|
1217 | * Action for when the user presses the Enter key. This is here so we can
|
---|
1218 | * be smart and "auto-indent" for programming languages.
|
---|
1219 | */
|
---|
1220 | public static class InsertBreakAction
|
---|
1221 | extends RTextAreaEditorKit.InsertBreakAction {
|
---|
1222 |
|
---|
1223 | private static final long serialVersionUID = 1L;
|
---|
1224 |
|
---|
1225 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
1226 |
|
---|
1227 | if (!textArea.isEditable() || !textArea.isEnabled()) {
|
---|
1228 | UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
---|
1229 | return;
|
---|
1230 | }
|
---|
1231 |
|
---|
1232 | RSyntaxTextArea sta = (RSyntaxTextArea)textArea;
|
---|
1233 | boolean noSelection= sta.getSelectionStart()==sta.getSelectionEnd();
|
---|
1234 |
|
---|
1235 | // First, see if this language wants to handle inserting newlines
|
---|
1236 | // itself.
|
---|
1237 | boolean handled = false;
|
---|
1238 | if (noSelection) {
|
---|
1239 | RSyntaxDocument doc = (RSyntaxDocument)sta.getDocument();
|
---|
1240 | handled = doc.insertBreakSpecialHandling(e);
|
---|
1241 | }
|
---|
1242 |
|
---|
1243 | // If not...
|
---|
1244 | if (!handled) {
|
---|
1245 | handleInsertBreak(sta, noSelection);
|
---|
1246 | }
|
---|
1247 |
|
---|
1248 | }
|
---|
1249 |
|
---|
1250 | /**
|
---|
1251 | * @return The first location in the string past <code>pos</code> that
|
---|
1252 | * is NOT a whitespace char, or <code>-1</code> if only
|
---|
1253 | * whitespace chars follow <code>pos</code> (or it is the end
|
---|
1254 | * position in the string).
|
---|
1255 | */
|
---|
1256 | private static final int atEndOfLine(int pos, String s, int sLen) {
|
---|
1257 | for (int i=pos; i<sLen; i++) {
|
---|
1258 | if (!RSyntaxUtilities.isWhitespace(s.charAt(i)))
|
---|
1259 | return i;
|
---|
1260 | }
|
---|
1261 | return -1;
|
---|
1262 | }
|
---|
1263 |
|
---|
1264 | private static final int getOpenBraceCount(RSyntaxDocument doc) {
|
---|
1265 | int openCount = 0;
|
---|
1266 | Element root = doc.getDefaultRootElement();
|
---|
1267 | int lineCount = root.getElementCount();
|
---|
1268 | for (int i=0; i<lineCount; i++) {
|
---|
1269 | Token t = doc.getTokenListForLine(i);
|
---|
1270 | while (t!=null && t.isPaintable()) {
|
---|
1271 | if (t.type==Token.SEPARATOR && t.textCount==1) {
|
---|
1272 | char ch = t.text[t.textOffset];
|
---|
1273 | if (ch=='{') {
|
---|
1274 | openCount++;
|
---|
1275 | }
|
---|
1276 | else if (ch=='}') {
|
---|
1277 | openCount--;
|
---|
1278 | }
|
---|
1279 | }
|
---|
1280 | t = t.getNextToken();
|
---|
1281 | }
|
---|
1282 | }
|
---|
1283 | return openCount;
|
---|
1284 | }
|
---|
1285 |
|
---|
1286 | /**
|
---|
1287 | * Actually inserts the newline into the document, and auto-indents
|
---|
1288 | * if appropriate. This method can be called by token makers who
|
---|
1289 | * implement a custom action for inserting newlines.
|
---|
1290 | *
|
---|
1291 | * @param textArea
|
---|
1292 | * @param noSelection Whether there is no selection.
|
---|
1293 | */
|
---|
1294 | protected void handleInsertBreak(RSyntaxTextArea textArea,
|
---|
1295 | boolean noSelection) {
|
---|
1296 | // If we're auto-indenting...
|
---|
1297 | if (noSelection && textArea.isAutoIndentEnabled()) {
|
---|
1298 | insertNewlineWithAutoIndent(textArea);
|
---|
1299 | }
|
---|
1300 | else {
|
---|
1301 | textArea.replaceSelection("\n");
|
---|
1302 | if (noSelection) {
|
---|
1303 | possiblyCloseCurlyBrace(textArea, null);
|
---|
1304 | }
|
---|
1305 | }
|
---|
1306 | }
|
---|
1307 |
|
---|
1308 | private void insertNewlineWithAutoIndent(RSyntaxTextArea sta) {
|
---|
1309 |
|
---|
1310 | try {
|
---|
1311 |
|
---|
1312 | int caretPos = sta.getCaretPosition();
|
---|
1313 | Document doc = sta.getDocument();
|
---|
1314 | Element map = doc.getDefaultRootElement();
|
---|
1315 | int lineNum = map.getElementIndex(caretPos);
|
---|
1316 | Element line = map.getElement(lineNum);
|
---|
1317 | int start = line.getStartOffset();
|
---|
1318 | int end = line.getEndOffset()-1; // Why always "-1"?
|
---|
1319 | int len = end-start;
|
---|
1320 | String s = doc.getText(start, len);
|
---|
1321 |
|
---|
1322 | // endWS is the end of the leading whitespace of the
|
---|
1323 | // current line.
|
---|
1324 | String leadingWS = RSyntaxUtilities.getLeadingWhitespace(s);
|
---|
1325 | StringBuffer sb = new StringBuffer("\n");
|
---|
1326 | sb.append(leadingWS);
|
---|
1327 |
|
---|
1328 | // If there is only whitespace between the caret and
|
---|
1329 | // the EOL, pressing Enter auto-indents the new line to
|
---|
1330 | // the same place as the previous line.
|
---|
1331 | int nonWhitespacePos = atEndOfLine(caretPos-start, s, len);
|
---|
1332 | if (nonWhitespacePos==-1) {
|
---|
1333 | if (leadingWS.length()==len &&
|
---|
1334 | sta.isClearWhitespaceLinesEnabled()) {
|
---|
1335 | // If the line was nothing but whitespace, select it
|
---|
1336 | // so its contents get removed.
|
---|
1337 | sta.setSelectionStart(start);
|
---|
1338 | sta.setSelectionEnd(end);
|
---|
1339 | }
|
---|
1340 | sta.replaceSelection(sb.toString());
|
---|
1341 | }
|
---|
1342 |
|
---|
1343 | // If there is non-whitespace between the caret and the
|
---|
1344 | // EOL, pressing Enter takes that text to the next line
|
---|
1345 | // and auto-indents it to the same place as the last
|
---|
1346 | // line.
|
---|
1347 | else {
|
---|
1348 | sb.append(s.substring(nonWhitespacePos));
|
---|
1349 | sta.replaceRange(sb.toString(), caretPos, end);
|
---|
1350 | sta.setCaretPosition(caretPos + leadingWS.length()+1);
|
---|
1351 | }
|
---|
1352 |
|
---|
1353 | // Must do it after everything else, as the "smart indent"
|
---|
1354 | // calculation depends on the previous line's state
|
---|
1355 | // AFTER the Enter press (stuff may have been moved down).
|
---|
1356 | if (sta.getShouldIndentNextLine(lineNum)) {
|
---|
1357 | sta.replaceSelection("\t");
|
---|
1358 | }
|
---|
1359 |
|
---|
1360 | possiblyCloseCurlyBrace(sta, leadingWS);
|
---|
1361 |
|
---|
1362 | } catch (BadLocationException ble) { // Never happens
|
---|
1363 | sta.replaceSelection("\n");
|
---|
1364 | ble.printStackTrace();
|
---|
1365 | }
|
---|
1366 |
|
---|
1367 | }
|
---|
1368 |
|
---|
1369 | private void possiblyCloseCurlyBrace(RSyntaxTextArea textArea,
|
---|
1370 | String leadingWS) {
|
---|
1371 |
|
---|
1372 | RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
|
---|
1373 |
|
---|
1374 | if (textArea.getCloseCurlyBraces() &&
|
---|
1375 | doc.getCurlyBracesDenoteCodeBlocks()) {
|
---|
1376 |
|
---|
1377 | int line = textArea.getCaretLineNumber();
|
---|
1378 | Token t = doc.getTokenListForLine(line-1);
|
---|
1379 | t = t.getLastNonCommentNonWhitespaceToken();
|
---|
1380 |
|
---|
1381 | if (t!=null && t.isLeftCurly()) {
|
---|
1382 |
|
---|
1383 | if (getOpenBraceCount(doc)>0) {
|
---|
1384 | StringBuffer sb = new StringBuffer();
|
---|
1385 | if (line==textArea.getLineCount()-1) {
|
---|
1386 | sb.append('\n');
|
---|
1387 | }
|
---|
1388 | if (leadingWS!=null) {
|
---|
1389 | sb.append(leadingWS);
|
---|
1390 | }
|
---|
1391 | sb.append("}\n");
|
---|
1392 | int dot = textArea.getCaretPosition();
|
---|
1393 | int end = textArea.getLineEndOffsetOfCurrentLine();
|
---|
1394 | // Insert at end of line, not at dot: they may have
|
---|
1395 | // pressed Enter in the middle of the line and brought
|
---|
1396 | // some text (though it must be whitespace and/or
|
---|
1397 | // comments) down onto the new line.
|
---|
1398 | textArea.insert(sb.toString(), end);
|
---|
1399 | textArea.setCaretPosition(dot); // Caret may have moved
|
---|
1400 | }
|
---|
1401 |
|
---|
1402 | }
|
---|
1403 |
|
---|
1404 | }
|
---|
1405 |
|
---|
1406 | }
|
---|
1407 |
|
---|
1408 | }
|
---|
1409 |
|
---|
1410 |
|
---|
1411 | /**
|
---|
1412 | * Action for inserting tabs. This is extended to "block indent" a
|
---|
1413 | * group of contiguous lines if they are selected.
|
---|
1414 | */
|
---|
1415 | public static class InsertTabAction extends RecordableTextAction {
|
---|
1416 |
|
---|
1417 | private static final long serialVersionUID = 1L;
|
---|
1418 |
|
---|
1419 | public InsertTabAction() {
|
---|
1420 | super(insertTabAction);
|
---|
1421 | }
|
---|
1422 |
|
---|
1423 | public InsertTabAction(String name) {
|
---|
1424 | super(name);
|
---|
1425 | }
|
---|
1426 |
|
---|
1427 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
1428 |
|
---|
1429 | if (!textArea.isEditable() || !textArea.isEnabled()) {
|
---|
1430 | UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
---|
1431 | return;
|
---|
1432 | }
|
---|
1433 |
|
---|
1434 | Document document = textArea.getDocument();
|
---|
1435 | Element map = document.getDefaultRootElement();
|
---|
1436 | Caret c = textArea.getCaret();
|
---|
1437 | int dot = c.getDot();
|
---|
1438 | int mark = c.getMark();
|
---|
1439 | int dotLine = map.getElementIndex(dot);
|
---|
1440 | int markLine = map.getElementIndex(mark);
|
---|
1441 |
|
---|
1442 | // If there is a multi-line selection, indent all lines in
|
---|
1443 | // the selection.
|
---|
1444 | if (dotLine!=markLine) {
|
---|
1445 | int first = Math.min(dotLine, markLine);
|
---|
1446 | int last = Math.max(dotLine, markLine);
|
---|
1447 | Element elem; int start;
|
---|
1448 |
|
---|
1449 | // Since we're using Document.insertString(), we must mimic the
|
---|
1450 | // soft tab behavior provided by RTextArea.replaceSelection().
|
---|
1451 | String replacement = "\t";
|
---|
1452 | if (textArea.getTabsEmulated()) {
|
---|
1453 | StringBuffer sb = new StringBuffer();
|
---|
1454 | int temp = textArea.getTabSize();
|
---|
1455 | for (int i=0; i<temp; i++) {
|
---|
1456 | sb.append(' ');
|
---|
1457 | }
|
---|
1458 | replacement = sb.toString();
|
---|
1459 | }
|
---|
1460 |
|
---|
1461 | textArea.beginAtomicEdit();
|
---|
1462 | try {
|
---|
1463 | for (int i=first; i<last; i++) {
|
---|
1464 | elem = map.getElement(i);
|
---|
1465 | start = elem.getStartOffset();
|
---|
1466 | document.insertString(start, replacement, null);
|
---|
1467 | }
|
---|
1468 | // Don't do the last line if the caret is at its
|
---|
1469 | // beginning. We must call getDot() again and not just
|
---|
1470 | // use 'dot' as the caret's position may have changed
|
---|
1471 | // due to the insertion of the tabs above.
|
---|
1472 | elem = map.getElement(last);
|
---|
1473 | start = elem.getStartOffset();
|
---|
1474 | if (Math.max(c.getDot(), c.getMark())!=start) {
|
---|
1475 | document.insertString(start, replacement, null);
|
---|
1476 | }
|
---|
1477 | } catch (BadLocationException ble) { // Never happens.
|
---|
1478 | ble.printStackTrace();
|
---|
1479 | UIManager.getLookAndFeel().
|
---|
1480 | provideErrorFeedback(textArea);
|
---|
1481 | } finally {
|
---|
1482 | textArea.endAtomicEdit();
|
---|
1483 | }
|
---|
1484 | }
|
---|
1485 | else {
|
---|
1486 | textArea.replaceSelection("\t");
|
---|
1487 | }
|
---|
1488 |
|
---|
1489 | }
|
---|
1490 |
|
---|
1491 | public final String getMacroID() {
|
---|
1492 | return insertTabAction;
|
---|
1493 | }
|
---|
1494 |
|
---|
1495 | }
|
---|
1496 |
|
---|
1497 |
|
---|
1498 | /**
|
---|
1499 | * Action to move the selection and/or caret. Constructor indicates
|
---|
1500 | * direction to use. This class overrides the behavior defined in
|
---|
1501 | * {@link RTextAreaEditorKit} to better skip "words" in source code.
|
---|
1502 | */
|
---|
1503 | public static class NextWordAction
|
---|
1504 | extends RTextAreaEditorKit.NextWordAction {
|
---|
1505 |
|
---|
1506 | private Segment seg;
|
---|
1507 |
|
---|
1508 | public NextWordAction(String nm, boolean select) {
|
---|
1509 | super(nm, select);
|
---|
1510 | seg = new Segment();
|
---|
1511 | }
|
---|
1512 |
|
---|
1513 | /**
|
---|
1514 | * Overridden to do better with skipping "words" in code.
|
---|
1515 | */
|
---|
1516 | protected int getNextWord(RTextArea textArea, int offs)
|
---|
1517 | throws BadLocationException {
|
---|
1518 |
|
---|
1519 | RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
|
---|
1520 | if (offs==doc.getLength()) {
|
---|
1521 | return offs;
|
---|
1522 | }
|
---|
1523 |
|
---|
1524 | Element root = doc.getDefaultRootElement();
|
---|
1525 | int line = root.getElementIndex(offs);
|
---|
1526 | int end = root.getElement(line).getEndOffset() - 1;
|
---|
1527 | if (offs==end) {// If we're already at the end of the line...
|
---|
1528 | RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
---|
1529 | if (rsta.isCodeFoldingEnabled()) { // Start of next visible line
|
---|
1530 | FoldManager fm = rsta.getFoldManager();
|
---|
1531 | int lineCount = root.getElementCount();
|
---|
1532 | while (++line<lineCount && fm.isLineHidden(line));
|
---|
1533 | if (line<lineCount) { // Found a lower visible line
|
---|
1534 | offs = root.getElement(line).getStartOffset();
|
---|
1535 | }
|
---|
1536 | // No lower visible line - we're already at last visible offset
|
---|
1537 | return offs;
|
---|
1538 | }
|
---|
1539 | else {
|
---|
1540 | return offs+1; // Start of next line.
|
---|
1541 | }
|
---|
1542 | }
|
---|
1543 | doc.getText(offs, end-offs, seg);
|
---|
1544 |
|
---|
1545 | // Determine the "type" of char at offs - letter/digit,
|
---|
1546 | // whitespace or other
|
---|
1547 | char ch = seg.first();
|
---|
1548 |
|
---|
1549 | // Skip the group of letters and/or digits
|
---|
1550 | if (Character.isLetterOrDigit(ch)) {
|
---|
1551 | do {
|
---|
1552 | ch = seg.next();
|
---|
1553 | } while (Character.isLetterOrDigit(ch));
|
---|
1554 | }
|
---|
1555 |
|
---|
1556 | // Skip groups of "anything else" (operators, etc.).
|
---|
1557 | else if (!Character.isWhitespace(ch)) {
|
---|
1558 | do {
|
---|
1559 | ch = seg.next();
|
---|
1560 | } while (ch!=Segment.DONE &&
|
---|
1561 | !(Character.isLetterOrDigit(ch) ||
|
---|
1562 | Character.isWhitespace(ch)));
|
---|
1563 | }
|
---|
1564 |
|
---|
1565 | // Skip any trailing whitespace
|
---|
1566 | while (Character.isWhitespace(ch)) {
|
---|
1567 | ch = seg.next();
|
---|
1568 | }
|
---|
1569 |
|
---|
1570 | offs += seg.getIndex() - seg.getBeginIndex();
|
---|
1571 | return offs;
|
---|
1572 |
|
---|
1573 | }
|
---|
1574 |
|
---|
1575 | }
|
---|
1576 |
|
---|
1577 |
|
---|
1578 | /**
|
---|
1579 | * Action for when the user tries to insert a template (that is,
|
---|
1580 | * they've typed a template ID and pressed the trigger character
|
---|
1581 | * (a space) in an attempt to do the substitution).
|
---|
1582 | */
|
---|
1583 | public static class PossiblyInsertTemplateAction extends RecordableTextAction {
|
---|
1584 |
|
---|
1585 | private static final long serialVersionUID = 1L;
|
---|
1586 |
|
---|
1587 | public PossiblyInsertTemplateAction() {
|
---|
1588 | super(rstaPossiblyInsertTemplateAction);
|
---|
1589 | }
|
---|
1590 |
|
---|
1591 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
1592 |
|
---|
1593 | if (!textArea.isEditable() || !textArea.isEnabled())
|
---|
1594 | return;
|
---|
1595 |
|
---|
1596 | RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
---|
1597 |
|
---|
1598 | if (RSyntaxTextArea.getTemplatesEnabled()) {
|
---|
1599 |
|
---|
1600 | Document doc = textArea.getDocument();
|
---|
1601 | if (doc != null) {
|
---|
1602 |
|
---|
1603 | try {
|
---|
1604 |
|
---|
1605 | CodeTemplateManager manager = RSyntaxTextArea.
|
---|
1606 | getCodeTemplateManager();
|
---|
1607 | CodeTemplate template = manager==null ? null :
|
---|
1608 | manager.getTemplate(rsta);
|
---|
1609 |
|
---|
1610 | // A non-null template means modify the text to insert!
|
---|
1611 | if (template!=null) {
|
---|
1612 | template.invoke(rsta);
|
---|
1613 | }
|
---|
1614 |
|
---|
1615 | // No template - insert default text. This is
|
---|
1616 | // exactly what DefaultKeyTypedAction does.
|
---|
1617 | else {
|
---|
1618 | doDefaultInsert(rsta);
|
---|
1619 | }
|
---|
1620 |
|
---|
1621 | } catch (BadLocationException ble) {
|
---|
1622 | UIManager.getLookAndFeel().
|
---|
1623 | provideErrorFeedback(textArea);
|
---|
1624 | }
|
---|
1625 |
|
---|
1626 |
|
---|
1627 | } // End of if (doc!=null).
|
---|
1628 |
|
---|
1629 | } // End of if (textArea.getTemplatesEnabled()).
|
---|
1630 |
|
---|
1631 | // If templates aren't enabled, just insert the text as usual.
|
---|
1632 | else {
|
---|
1633 | doDefaultInsert(rsta);
|
---|
1634 | }
|
---|
1635 |
|
---|
1636 | }
|
---|
1637 |
|
---|
1638 | private final void doDefaultInsert(RTextArea textArea) {
|
---|
1639 | // FIXME: We need a way to get the "trigger string" (i.e.,
|
---|
1640 | // the text that was just typed); however, the text area's
|
---|
1641 | // template manager might be null (if templates are disabled).
|
---|
1642 | // Also, the manager's trigger string doesn't yet match up with
|
---|
1643 | // that defined in RSyntaxTextAreaEditorKit.java (which is
|
---|
1644 | // hardcoded as a space)...
|
---|
1645 | //String str = manager.getInsertTriggerString();
|
---|
1646 | //int mod = manager.getInsertTrigger().getModifiers();
|
---|
1647 | //if (str!=null && str.length()>0 &&
|
---|
1648 | // ((mod&ActionEvent.ALT_MASK)==(mod&ActionEvent.CTRL_MASK))) {
|
---|
1649 | // char ch = str.charAt(0);
|
---|
1650 | // if (ch>=0x20 && ch!=0x7F)
|
---|
1651 | // textArea.replaceSelection(str);
|
---|
1652 | //}
|
---|
1653 | textArea.replaceSelection(" ");
|
---|
1654 | }
|
---|
1655 |
|
---|
1656 | public final String getMacroID() {
|
---|
1657 | return rstaPossiblyInsertTemplateAction;
|
---|
1658 | }
|
---|
1659 |
|
---|
1660 | }
|
---|
1661 |
|
---|
1662 |
|
---|
1663 | /**
|
---|
1664 | * Action to move the selection and/or caret. Constructor indicates
|
---|
1665 | * direction to use. This class overrides the behavior defined in
|
---|
1666 | * {@link RTextAreaEditorKit} to better skip "words" in source code.
|
---|
1667 | */
|
---|
1668 | public static class PreviousWordAction
|
---|
1669 | extends RTextAreaEditorKit.PreviousWordAction {
|
---|
1670 |
|
---|
1671 | private Segment seg;
|
---|
1672 |
|
---|
1673 | public PreviousWordAction(String nm, boolean select) {
|
---|
1674 | super(nm, select);
|
---|
1675 | seg = new Segment();
|
---|
1676 | }
|
---|
1677 |
|
---|
1678 | /**
|
---|
1679 | * Overridden to do better with skipping "words" in code.
|
---|
1680 | */
|
---|
1681 | protected int getPreviousWord(RTextArea textArea, int offs)
|
---|
1682 | throws BadLocationException {
|
---|
1683 |
|
---|
1684 | if (offs==0) {
|
---|
1685 | return offs;
|
---|
1686 | }
|
---|
1687 |
|
---|
1688 | RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
|
---|
1689 | Element root = doc.getDefaultRootElement();
|
---|
1690 | int line = root.getElementIndex(offs);
|
---|
1691 | int start = root.getElement(line).getStartOffset();
|
---|
1692 | if (offs==start) {// If we're already at the start of the line...
|
---|
1693 | RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
---|
1694 | if (rsta.isCodeFoldingEnabled()) { // End of next visible line
|
---|
1695 | FoldManager fm = rsta.getFoldManager();
|
---|
1696 | while (--line>=0 && fm.isLineHidden(line));
|
---|
1697 | if (line>=0) { // Found an earlier visible line
|
---|
1698 | offs = root.getElement(line).getEndOffset() - 1;
|
---|
1699 | }
|
---|
1700 | // No earlier visible line - we must be at offs==0...
|
---|
1701 | return offs;
|
---|
1702 | }
|
---|
1703 | else {
|
---|
1704 | return start-1; // End of previous line.
|
---|
1705 | }
|
---|
1706 | }
|
---|
1707 | doc.getText(start, offs-start, seg);
|
---|
1708 |
|
---|
1709 | // Determine the "type" of char at offs - lower case, upper case,
|
---|
1710 | // whitespace or other
|
---|
1711 | char ch = seg.last();
|
---|
1712 |
|
---|
1713 | // Skip any "leading" whitespace
|
---|
1714 | while (Character.isWhitespace(ch)) {
|
---|
1715 | ch = seg.previous();
|
---|
1716 | }
|
---|
1717 |
|
---|
1718 | // Skip the group of letters and/or digits
|
---|
1719 | if (Character.isLetterOrDigit(ch)) {
|
---|
1720 | do {
|
---|
1721 | ch = seg.previous();
|
---|
1722 | } while (Character.isLetterOrDigit(ch));
|
---|
1723 | }
|
---|
1724 |
|
---|
1725 | // Skip groups of "anything else" (operators, etc.).
|
---|
1726 | else if (!Character.isWhitespace(ch)) {
|
---|
1727 | do {
|
---|
1728 | ch = seg.previous();
|
---|
1729 | } while (ch!=Segment.DONE &&
|
---|
1730 | !(Character.isLetterOrDigit(ch) ||
|
---|
1731 | Character.isWhitespace(ch)));
|
---|
1732 | }
|
---|
1733 |
|
---|
1734 | offs -= seg.getEndIndex() - seg.getIndex();
|
---|
1735 | if (ch!=Segment.DONE) {
|
---|
1736 | offs++;
|
---|
1737 | }
|
---|
1738 |
|
---|
1739 | return offs;
|
---|
1740 |
|
---|
1741 | }
|
---|
1742 |
|
---|
1743 | }
|
---|
1744 |
|
---|
1745 |
|
---|
1746 | /**
|
---|
1747 | * Selects the word around the caret. This class is here to better
|
---|
1748 | * handle selecting "words" in programming languages.
|
---|
1749 | */
|
---|
1750 | public static class SelectWordAction
|
---|
1751 | extends RTextAreaEditorKit.SelectWordAction {
|
---|
1752 |
|
---|
1753 | protected void createActions() {
|
---|
1754 | start = new BeginWordAction("pigdog", false);
|
---|
1755 | end = new EndWordAction("pigdog", true);
|
---|
1756 | }
|
---|
1757 |
|
---|
1758 | }
|
---|
1759 |
|
---|
1760 |
|
---|
1761 | /**
|
---|
1762 | * Action that toggles whether the currently selected lines are
|
---|
1763 | * commented.
|
---|
1764 | */
|
---|
1765 | public static class ToggleCommentAction extends RecordableTextAction {
|
---|
1766 |
|
---|
1767 | public ToggleCommentAction() {
|
---|
1768 | super(rstaToggleCommentAction);
|
---|
1769 | }
|
---|
1770 |
|
---|
1771 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
1772 |
|
---|
1773 | if (!textArea.isEditable() || !textArea.isEnabled()) {
|
---|
1774 | UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
---|
1775 | return;
|
---|
1776 | }
|
---|
1777 |
|
---|
1778 | RSyntaxDocument doc = (RSyntaxDocument)textArea.getDocument();
|
---|
1779 | String[] startEnd = doc.getLineCommentStartAndEnd();
|
---|
1780 |
|
---|
1781 | if (startEnd==null) {
|
---|
1782 | UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
---|
1783 | return;
|
---|
1784 | }
|
---|
1785 |
|
---|
1786 | Element map = doc.getDefaultRootElement();
|
---|
1787 | Caret c = textArea.getCaret();
|
---|
1788 | int dot = c.getDot();
|
---|
1789 | int mark = c.getMark();
|
---|
1790 | int line1 = map.getElementIndex(dot);
|
---|
1791 | int line2 = map.getElementIndex(mark);
|
---|
1792 | int start = Math.min(line1, line2);
|
---|
1793 | int end = Math.max(line1, line2);
|
---|
1794 |
|
---|
1795 | // Don't toggle comment on last line if there is no
|
---|
1796 | // text selected on it.
|
---|
1797 | if (start!=end) {
|
---|
1798 | Element elem = map.getElement(end);
|
---|
1799 | if (Math.max(dot, mark)==elem.getStartOffset()) {
|
---|
1800 | end--;
|
---|
1801 | }
|
---|
1802 | }
|
---|
1803 |
|
---|
1804 | textArea.beginAtomicEdit();
|
---|
1805 | try {
|
---|
1806 | boolean add = getDoAdd(doc,map, start,end, startEnd);
|
---|
1807 | for (line1=start; line1<=end; line1++) {
|
---|
1808 | Element elem = map.getElement(line1);
|
---|
1809 | handleToggleComment(elem, doc, startEnd, add);
|
---|
1810 | }
|
---|
1811 | } catch (BadLocationException ble) {
|
---|
1812 | ble.printStackTrace();
|
---|
1813 | UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
---|
1814 | } finally {
|
---|
1815 | textArea.endAtomicEdit();
|
---|
1816 | }
|
---|
1817 |
|
---|
1818 | }
|
---|
1819 |
|
---|
1820 | private boolean getDoAdd(Document doc, Element map, int startLine,
|
---|
1821 | int endLine, String[] startEnd)
|
---|
1822 | throws BadLocationException {
|
---|
1823 | boolean doAdd = false;
|
---|
1824 | for (int i=startLine; i<=endLine; i++) {
|
---|
1825 | Element elem = map.getElement(i);
|
---|
1826 | int start = elem.getStartOffset();
|
---|
1827 | String t = doc.getText(start, elem.getEndOffset()-start-1);
|
---|
1828 | if (!t.startsWith(startEnd[0]) ||
|
---|
1829 | (startEnd[1]!=null && !t.endsWith(startEnd[1]))) {
|
---|
1830 | doAdd = true;
|
---|
1831 | break;
|
---|
1832 | }
|
---|
1833 | }
|
---|
1834 | return doAdd;
|
---|
1835 | }
|
---|
1836 |
|
---|
1837 | private void handleToggleComment(Element elem, Document doc,
|
---|
1838 | String[] startEnd, boolean add) throws BadLocationException {
|
---|
1839 | int start = elem.getStartOffset();
|
---|
1840 | int end = elem.getEndOffset() - 1;
|
---|
1841 | if (add) {
|
---|
1842 | doc.insertString(start, startEnd[0], null);
|
---|
1843 | if (startEnd[1]!=null) {
|
---|
1844 | doc.insertString(end+startEnd[0].length(), startEnd[1],
|
---|
1845 | null);
|
---|
1846 | }
|
---|
1847 | }
|
---|
1848 | else {
|
---|
1849 | doc.remove(start, startEnd[0].length());
|
---|
1850 | if (startEnd[1]!=null) {
|
---|
1851 | int temp = startEnd[1].length();
|
---|
1852 | doc.remove(end-startEnd[0].length()-temp, temp);
|
---|
1853 | }
|
---|
1854 | }
|
---|
1855 | }
|
---|
1856 |
|
---|
1857 | public final String getMacroID() {
|
---|
1858 | return rstaToggleCommentAction;
|
---|
1859 | }
|
---|
1860 |
|
---|
1861 | }
|
---|
1862 |
|
---|
1863 |
|
---|
1864 | /**
|
---|
1865 | * Toggles the fold at the current caret position or line.
|
---|
1866 | */
|
---|
1867 | public static class ToggleCurrentFoldAction extends FoldRelatedAction {
|
---|
1868 |
|
---|
1869 | private static final long serialVersionUID = 1L;
|
---|
1870 |
|
---|
1871 | public ToggleCurrentFoldAction() {
|
---|
1872 | super(rstaToggleCurrentFoldAction);
|
---|
1873 | setProperties(msg, "Action.ToggleCurrentFold");
|
---|
1874 | }
|
---|
1875 |
|
---|
1876 | public ToggleCurrentFoldAction(String name, Icon icon, String desc,
|
---|
1877 | Integer mnemonic, KeyStroke accelerator) {
|
---|
1878 | super(name, icon, desc, mnemonic, accelerator);
|
---|
1879 | }
|
---|
1880 |
|
---|
1881 | public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
|
---|
1882 | RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
|
---|
1883 | if (rsta.isCodeFoldingEnabled()) {
|
---|
1884 | Fold fold = getClosestFold(rsta);
|
---|
1885 | if (fold!=null) {
|
---|
1886 | fold.toggleCollapsedState();
|
---|
1887 | }
|
---|
1888 | possiblyRepaintGutter(textArea);
|
---|
1889 | }
|
---|
1890 | else {
|
---|
1891 | UIManager.getLookAndFeel().provideErrorFeedback(rsta);
|
---|
1892 | }
|
---|
1893 | }
|
---|
1894 |
|
---|
1895 | public final String getMacroID() {
|
---|
1896 | return rstaToggleCurrentFoldAction;
|
---|
1897 | }
|
---|
1898 |
|
---|
1899 | }
|
---|
1900 |
|
---|
1901 |
|
---|
1902 | } |
---|