add the function of auto-indent in the java-mode
@@ -156,11 +156,6 @@ | ||
156 | 156 | return parentPanel; |
157 | 157 | } |
158 | 158 | |
159 | - public BufferWindow renewWindow() { | |
160 | - parentPanel = new BufferWindow(this); | |
161 | - return parentPanel; | |
162 | - } | |
163 | - | |
164 | 159 | public boolean forceDefaultAction() { |
165 | 160 | return false; |
166 | 161 | } |
@@ -143,7 +143,6 @@ | ||
143 | 143 | |
144 | 144 | RootPanel rootPanel = (RootPanel) e.getSource(); |
145 | 145 | rootPanel.revalidate(); |
146 | -// rootPanel.repaint(); | |
147 | 146 | } |
148 | 147 | } |
149 | 148 |
@@ -40,7 +40,7 @@ | ||
40 | 40 | keymapRegistry = new HashMap<String, JmKeymap>(); |
41 | 41 | } |
42 | 42 | |
43 | - public abstract void deinstall(BasicTextPane textPane); | |
43 | + public abstract void uninstall(BasicTextPane textPane); | |
44 | 44 | |
45 | 45 | public abstract void install(BasicTextPane textPane); |
46 | 46 |
@@ -106,7 +106,7 @@ | ||
106 | 106 | } |
107 | 107 | |
108 | 108 | @Override |
109 | - public void deinstall(BasicTextPane textPane) { | |
109 | + public void uninstall(BasicTextPane textPane) { | |
110 | 110 | if (rightClickListener != null) { |
111 | 111 | textPane.removeMouseListener(rightClickListener); |
112 | 112 | } |
@@ -69,7 +69,7 @@ | ||
69 | 69 | } |
70 | 70 | |
71 | 71 | @Override |
72 | - public void deinstall(BasicTextPane textPane) { | |
72 | + public void uninstall(BasicTextPane textPane) { | |
73 | 73 | } |
74 | 74 | |
75 | 75 | @Override |
@@ -1,8 +1,19 @@ | ||
1 | 1 | /* |
2 | 2 | * Copyright (C) 2008-2009, mshio <mshio@users.sourceforge.jp> |
3 | - * You can redistribute it and/or modify it under GPLv2, | |
4 | - * as published by the Free Software Foundation. | |
5 | 3 | * |
4 | + * This program is free software: you can redistribute it and/or | |
5 | + * modify it under the terms of the GNU General Public License | |
6 | + * as published by the Free Software Foundation; either version 2 | |
7 | + * of the License, or any later version. | |
8 | + * | |
9 | + * This program is distributed in the hope that it will be useful, | |
10 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | + * GNU General Public License for more details. | |
13 | + * | |
14 | + * You should have received a copy of the GNU General Public License | |
15 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | + * --- | |
6 | 17 | * Require JDK 1.5 (or later) |
7 | 18 | */ |
8 | 19 | package jp.sourceforge.nine; |
@@ -22,13 +33,6 @@ | ||
22 | 33 | public interface MainBufferPane { |
23 | 34 | |
24 | 35 | /** |
25 | - * Renews the {@code BufferPanel} associated with this component | |
26 | - * and returns it. | |
27 | - * @return new {@code BufferPanel} | |
28 | - */ | |
29 | - public abstract BufferWindow renewWindow(); | |
30 | - | |
31 | - /** | |
32 | 36 | * Returns the {@code BufferPanel} associated with this component. |
33 | 37 | * @return {@code BufferPanel} associated with this component |
34 | 38 | */ |
@@ -167,8 +167,8 @@ | ||
167 | 167 | NineFrame f = new NineFrame(APPLICATION_NAME); |
168 | 168 | frames.add(f); |
169 | 169 | |
170 | + f.setRootPanel(new RootPanel(bufferPane)); | |
170 | 171 | f.setSize(600, 600); |
171 | - f.setRootPanel(new RootPanel(bufferPane)); | |
172 | 172 | f.setVisible(true); |
173 | 173 | } |
174 | 174 |
@@ -144,7 +144,7 @@ | ||
144 | 144 | */ |
145 | 145 | protected void setEditorKit(BasicEditorKit kit) { |
146 | 146 | BasicEditorKit old = this.kit; |
147 | - if (old != null) { old.deinstall(this); } | |
147 | + if (old != null) { old.uninstall(this); } | |
148 | 148 | this.kit = kit; |
149 | 149 | if (this.kit != null) { |
150 | 150 | this.kit.install(this); |
@@ -324,17 +324,6 @@ | ||
324 | 324 | return k; |
325 | 325 | } |
326 | 326 | |
327 | - /* | |
328 | - * (non-Javadoc) | |
329 | - * @see jp.sourceforge.nine.MainBufferPane#renewWindow() | |
330 | - */ | |
331 | - public BufferWindow renewWindow() { | |
332 | - int row = bufferWindow.getModeLine().getRowPosition(); | |
333 | - bufferWindow = new BufferWindow(this); | |
334 | - bufferWindow.getModeLine().setRowPosition(row); | |
335 | - return bufferWindow; | |
336 | - } | |
337 | - | |
338 | 327 | public BufferWindow getWindow() { |
339 | 328 | return bufferWindow; |
340 | 329 | } |
@@ -58,4 +58,5 @@ | ||
58 | 58 | public void suggestionKeyPressed(ActionEvent e) { |
59 | 59 | super.suggestionKeyPressed(e); |
60 | 60 | } |
61 | + | |
61 | 62 | } |
@@ -18,6 +18,7 @@ | ||
18 | 18 | */ |
19 | 19 | package jp.sourceforge.nine.action; |
20 | 20 | |
21 | +import javax.swing.JFrame; | |
21 | 22 | import javax.swing.SwingUtilities; |
22 | 23 | |
23 | 24 | import jp.sourceforge.nine.Main; |
@@ -24,6 +25,7 @@ | ||
24 | 25 | import jp.sourceforge.nine.MainBufferPane; |
25 | 26 | import jp.sourceforge.nine.action.util.Command; |
26 | 27 | import jp.sourceforge.nine.buffer.BufferManager; |
28 | +import jp.sourceforge.nine.util.NineUtilities; | |
27 | 29 | |
28 | 30 | public class MakeFrameCommand implements Command<Boolean> { |
29 | 31 | private final MainBufferPane bufferPane; |
@@ -41,6 +43,8 @@ | ||
41 | 43 | SwingUtilities.invokeLater(new Runnable() { |
42 | 44 | public void run() { |
43 | 45 | Main.application.createFrame(bufferPane); |
46 | + JFrame[] frames = Main.application.getFrames(); | |
47 | + changeNewFrameLocation(frames); | |
44 | 48 | } |
45 | 49 | }); |
46 | 50 |
@@ -47,4 +51,35 @@ | ||
47 | 51 | return true; |
48 | 52 | } |
49 | 53 | |
54 | + // TODO | |
55 | + private void changeNewFrameLocation(JFrame[] frames) { | |
56 | + int[] desktop = NineUtilities.getDesktopSize(); | |
57 | + int x0 = desktop[0]; | |
58 | + int x1 = 0; | |
59 | + JFrame current = null; | |
60 | + for (int i = 0; i < frames.length - 1; i++) { | |
61 | + if ((frames[i].getExtendedState() & JFrame.ICONIFIED) != 0) { continue; } | |
62 | + int x = frames[i].getX(); | |
63 | + if (x < x0) { x0 = x; } | |
64 | + int w = x + frames[i].getWidth(); | |
65 | + if (w > x1) { x1 = w; } | |
66 | + if (frames[i].isFocused()) { current = frames[i]; } | |
67 | + } | |
68 | + JFrame target = frames[frames.length - 1]; | |
69 | + int x; | |
70 | + if ((x0 + x1) / 2 < desktop[0] / 2) { | |
71 | + x = x1 + 10; | |
72 | + if (x + target.getWidth() > desktop[0]) { | |
73 | + if (current != null && | |
74 | + current.getX() + current.getWidth() / 2 > desktop[0] / 2) { | |
75 | + x = Math.max(x0 - target.getWidth() - 10, 0); | |
76 | + } else { | |
77 | + x = desktop[0] - target.getWidth(); | |
78 | + } | |
79 | + } | |
80 | + } else { | |
81 | + x = Math.max(x0 - target.getWidth() - 10, 0); | |
82 | + } | |
83 | + target.setLocation(x, target.getY()); | |
84 | + } | |
50 | 85 | } |
@@ -0,0 +1,40 @@ | ||
1 | +/* | |
2 | + * Copyright (C) 2008-2009, mshio <mshio@users.sourceforge.jp> | |
3 | + * | |
4 | + * This program is free software: you can redistribute it and/or | |
5 | + * modify it under the terms of the GNU General Public License | |
6 | + * as published by the Free Software Foundation; either version 2 | |
7 | + * of the License, or any later version. | |
8 | + * | |
9 | + * This program is distributed in the hope that it will be useful, | |
10 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | + * GNU General Public License for more details. | |
13 | + * | |
14 | + * You should have received a copy of the GNU General Public License | |
15 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | + */ | |
17 | +automodeSet("text/java", | |
18 | + "java", | |
19 | + "jp.sourceforge.nine.javamode.JavaEditorKit" | |
20 | +); | |
21 | + | |
22 | +with (defineKeymap("java-mode-keymap")) { | |
23 | +// defineKey("TAB", "javamode-tabulate"); | |
24 | +// defineKey("}", "closing-curly-brace"); | |
25 | + defineKey("TAB", "java-indent"); | |
26 | +}; | |
27 | + | |
28 | +actionDefinitions.TEXT['java-indent'] = { | |
29 | + name: 'jp.sourceforge.nine.action.javamode.JavaTabAction', | |
30 | + plugin: true, | |
31 | +}; | |
32 | +actionDefinitions.TEXT['java-mode'] = { | |
33 | + name: 'jp.sourceforge.nine.action.ScriptTextAction', | |
34 | + types: ['String'], | |
35 | + args: ['java-mode'], | |
36 | +}; | |
37 | + | |
38 | +interactives['java-mode'] = function() { | |
39 | + ChangeMode(textComponent, 'text/java'); | |
40 | +}; |
@@ -63,7 +63,7 @@ | ||
63 | 63 | } |
64 | 64 | |
65 | 65 | @Override |
66 | - public void deinstall(BasicTextPane textPane) { | |
66 | + public void uninstall(BasicTextPane textPane) { | |
67 | 67 | CaretListener[] ls = textPane.getCaretListeners(); |
68 | 68 | for (CaretListener l : ls) { |
69 | 69 | if (l instanceof HighlightCaretListener) { |
@@ -70,7 +70,7 @@ | ||
70 | 70 | textPane.removeCaretListener(l); |
71 | 71 | } |
72 | 72 | } |
73 | - super.deinstall(textPane); | |
73 | + super.uninstall(textPane); | |
74 | 74 | } |
75 | 75 | |
76 | 76 | @Override |
@@ -19,9 +19,11 @@ | ||
19 | 19 | package jp.sourceforge.nine.javamode; |
20 | 20 | |
21 | 21 | import javax.swing.Action; |
22 | +import javax.swing.event.CaretListener; | |
22 | 23 | import javax.swing.text.Document; |
23 | 24 | import javax.swing.text.TextAction; |
24 | 25 | |
26 | +import jp.sourceforge.nine.BasicTextPane; | |
25 | 27 | import jp.sourceforge.nine.MainTextPane; |
26 | 28 | import jp.sourceforge.nine.document.DocumentAttachment; |
27 | 29 | import jp.sourceforge.nine.editorkit.TextEditorKit; |
@@ -58,6 +60,23 @@ | ||
58 | 60 | } |
59 | 61 | |
60 | 62 | @Override |
63 | + public void install(BasicTextPane textPane) { | |
64 | + textPane.addCaretListener(new HighlightCaretListener()); | |
65 | + super.install(textPane); | |
66 | + } | |
67 | + | |
68 | + @Override | |
69 | + public void uninstall(BasicTextPane textPane) { | |
70 | + CaretListener[] ls = textPane.getCaretListeners(); | |
71 | + for (CaretListener l : ls) { | |
72 | + if (l instanceof HighlightCaretListener) { | |
73 | + textPane.removeCaretListener(l); | |
74 | + } | |
75 | + } | |
76 | + super.uninstall(textPane); | |
77 | + } | |
78 | + | |
79 | + @Override | |
61 | 80 | protected ViewPainter createViewPainter(Document document) { |
62 | 81 | PlainStringPainterMaker<JavaType, JavaStatus> m = |
63 | 82 | new PlainStringPainterMaker<JavaType, JavaStatus>(); |
@@ -33,9 +33,6 @@ | ||
33 | 33 | switch (status) { |
34 | 34 | case NORMAL: |
35 | 35 | switch (type) { |
36 | - case KEYWORD: | |
37 | - ret = DefaultBrush.COLOR_KEYWORD; | |
38 | - break; | |
39 | 36 | case DOUBLE_QUOTE: |
40 | 37 | case SINGLE_QUOTE: |
41 | 38 | if (! escaping) { ret = DefaultBrush.COLOR_STRING; } |
@@ -44,6 +41,11 @@ | ||
44 | 41 | case LINE_COMMENT_BEGIN: |
45 | 42 | ret = DefaultBrush.COLOR_COMMENT; |
46 | 43 | break; |
44 | + default: | |
45 | + if (type.isKeyword()) { | |
46 | + ret = DefaultBrush.COLOR_KEYWORD; | |
47 | + } | |
48 | + break; | |
47 | 49 | } |
48 | 50 | break; |
49 | 51 | case DOUBLE_QUOTE: |
@@ -0,0 +1,218 @@ | ||
1 | +/* | |
2 | + * Copyright (C) 2009, mshio <mshio@users.sourceforge.jp> | |
3 | + * | |
4 | + * This program is free software: you can redistribute it and/or | |
5 | + * modify it under the terms of the GNU General Public License | |
6 | + * as published by the Free Software Foundation; either version 2 | |
7 | + * of the License, or any later version. | |
8 | + * | |
9 | + * This program is distributed in the hope that it will be useful, | |
10 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | + * GNU General Public License for more details. | |
13 | + * | |
14 | + * You should have received a copy of the GNU General Public License | |
15 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | + * | |
17 | + * Require JDK 1.5 (or later) | |
18 | + */ | |
19 | +package jp.sourceforge.nine.javamode; | |
20 | + | |
21 | +import java.awt.Color; | |
22 | +import java.util.ArrayList; | |
23 | + | |
24 | +import javax.swing.SwingUtilities; | |
25 | +import javax.swing.event.CaretEvent; | |
26 | +import javax.swing.event.CaretListener; | |
27 | +import javax.swing.text.BadLocationException; | |
28 | +import javax.swing.text.DefaultHighlighter; | |
29 | +import javax.swing.text.Element; | |
30 | +import javax.swing.text.Highlighter; | |
31 | + | |
32 | +import jp.sourceforge.nine.MainTextPane; | |
33 | +import jp.sourceforge.nine.document.DocumentAttachment; | |
34 | +import jp.sourceforge.nine.document.MarkableDocument; | |
35 | +import jp.sourceforge.nine.parser.Token; | |
36 | +import jp.sourceforge.nine.parser.TokenLine; | |
37 | +import jp.sourceforge.nine.parser.javamode.JavaStatus; | |
38 | +import jp.sourceforge.nine.parser.javamode.JavaStatusManager; | |
39 | +import jp.sourceforge.nine.parser.javamode.JavaType; | |
40 | + | |
41 | +public class HighlightCaretListener implements CaretListener { | |
42 | + protected final static Color HIGHLIGHT = new Color(0xccccff); | |
43 | + | |
44 | + protected MainTextPane textPane = null; | |
45 | + protected MarkableDocument document = null; | |
46 | + ArrayList<TokenLine<JavaType, JavaStatus>> tokenlist; | |
47 | + JavaStatusManager statusManager = new JavaStatusManager(); | |
48 | + | |
49 | + public void caretUpdate(final CaretEvent e) { | |
50 | + textPane = getMainTextPane(e); | |
51 | + MarkableDocument doc = (MarkableDocument) textPane.getDocument(); | |
52 | + if (document != doc) { | |
53 | + document = doc; | |
54 | + tokenlist = getTokenList(doc); | |
55 | + } | |
56 | + SwingUtilities.invokeLater(new Runnable() { | |
57 | + public void run() { action(e); } | |
58 | + }); | |
59 | + } | |
60 | + | |
61 | + private void action(CaretEvent e) { | |
62 | + int pos = e.getDot(); | |
63 | + int row = getCurrentRowIndex(pos); | |
64 | + int col = getCurrentColumnIndex(pos, row); | |
65 | + dehighlight(); | |
66 | + TokenLine<JavaType, JavaStatus> line = tokenlist.get(row); | |
67 | + JavaStatus status = row == 0 ? | |
68 | + JavaStatus.NORMAL : tokenlist.get(row - 1).getEndStatus(); | |
69 | + statusManager.setCurrentStatus(status); | |
70 | + for (Token<JavaType> token : line) { | |
71 | + JavaType type = token.getType(); | |
72 | + statusManager.switchStatus(type); | |
73 | + if (statusManager.getStatus() == JavaStatus.NORMAL && | |
74 | + token.getOffset() + token.getLength() == col) { | |
75 | + boolean searchForward = false; | |
76 | + JavaType searchType = null; | |
77 | + switch (type) { | |
78 | + case BRACE_BEGIN: | |
79 | + searchForward = true; | |
80 | + searchType = JavaType.BRACE_END; | |
81 | + break; | |
82 | + case BRACE_END: | |
83 | + searchType = JavaType.BRACE_BEGIN; | |
84 | + break; | |
85 | + case BRACKET_BEGIN: | |
86 | + searchForward = true; | |
87 | + searchType = JavaType.BRACKET_END; | |
88 | + break; | |
89 | + case BRACKET_END: | |
90 | + searchType = JavaType.BRACKET_BEGIN; | |
91 | + break; | |
92 | + case PARENTHESIS_BEGIN: | |
93 | + searchForward = true; | |
94 | + searchType = JavaType.PARENTHESIS_END; | |
95 | + break; | |
96 | + case PARENTHESIS_END: | |
97 | + searchType = JavaType.PARENTHESIS_BEGIN; | |
98 | + break; | |
99 | + } | |
100 | + if (searchType != null) { | |
101 | + search(searchForward, row, type, searchType, col); | |
102 | + } | |
103 | + } | |
104 | + } | |
105 | + } | |
106 | + | |
107 | + private void search(boolean forward, int row, JavaType type, JavaType search, int col) { | |
108 | + if (forward) { | |
109 | + searchForward(row, type, search, col); | |
110 | + } else { | |
111 | + searchBackward(row, type, search, col); | |
112 | + } | |
113 | + } | |
114 | + | |
115 | + private void searchForward(int row, JavaType type, JavaType search, int col) { | |
116 | + int count = 0; | |
117 | + for (int i = row; i < tokenlist.size(); i++) { | |
118 | + TokenLine<JavaType, JavaStatus> line = tokenlist.get(i); | |
119 | + for (Token<JavaType> t : line) { | |
120 | + statusManager.switchStatus(t.getType()); | |
121 | + if (statusManager.getStatus() == JavaStatus.NORMAL && | |
122 | + ! (i == row && t.getOffset() < col)) { | |
123 | + if (t.getType() == search) { | |
124 | + if (count-- == 0) { | |
125 | + int offs = getRowStartOffset(i) + t.getOffset(); | |
126 | + highlight(offs); | |
127 | + return; | |
128 | + } | |
129 | + } else if (t.getType() == type) { | |
130 | + count++; | |
131 | + } | |
132 | + } | |
133 | + } | |
134 | + statusManager.sendLineEnd(); | |
135 | + } | |
136 | + } | |
137 | + | |
138 | + private void searchBackward(int row, JavaType type, JavaType search, int col) { | |
139 | + int count = 0; | |
140 | + for (int i = row; i >= 0; i--) { | |
141 | + TokenLine<JavaType, JavaStatus> line = tokenlist.get(i); | |
142 | + for (int j = line.size() - 1; j >= 0; j--) { | |
143 | + Token<JavaType> token = line.get(j); | |
144 | + if (i == row && token.getOffset() + token.getLength() >= col) { continue; } | |
145 | + if (token.getType() == search) { | |
146 | + if (statusIsNormal(i, j)) { | |
147 | + if (count-- == 0) { | |
148 | + int offs = getRowStartOffset(i) + token.getOffset(); | |
149 | + highlight(offs); | |
150 | + return; | |
151 | + } | |
152 | + } | |
153 | + } else if (token.getType() == type) { | |
154 | + if (statusIsNormal(i, j)) { count++; } | |
155 | + } | |
156 | + } | |
157 | + } | |
158 | + } | |
159 | + | |
160 | + private boolean statusIsNormal(int row, int tokenOffset) { | |
161 | + JavaStatus prev = row == 0 ? | |
162 | + JavaStatus.NORMAL : tokenlist.get(row - 1).getEndStatus(); | |
163 | + statusManager.setCurrentStatus(prev); | |
164 | + TokenLine<JavaType, JavaStatus> line = tokenlist.get(row); | |
165 | + for (int i = 0; i < line.size(); i++) { | |
166 | + Token<JavaType> token = line.get(i); | |
167 | + statusManager.switchStatus(token.getType()); | |
168 | + if (i == tokenOffset) { | |
169 | + return statusManager.getStatus() == JavaStatus.NORMAL; | |
170 | + } | |
171 | + } | |
172 | + return false; | |
173 | + } | |
174 | + | |
175 | + protected int getCurrentRowIndex(int pos) { | |
176 | + Element root = document.getDefaultRootElement(); | |
177 | + return root.getElementIndex(pos); | |
178 | + } | |
179 | + | |
180 | + protected int getRowStartOffset(int index) { | |
181 | + Element e = document.getDefaultRootElement().getElement(index); | |
182 | + return e.getStartOffset(); | |
183 | + } | |
184 | + | |
185 | + protected int getCurrentColumnIndex(int pos, int index) { | |
186 | + int startOffs = getRowStartOffset(index); | |
187 | + return pos - startOffs; | |
188 | + } | |
189 | + | |
190 | + protected void highlight(int pos) { | |
191 | + Highlighter.HighlightPainter p = | |
192 | + new DefaultHighlighter.DefaultHighlightPainter(HIGHLIGHT); | |
193 | + try { | |
194 | + textPane.getHighlighter().addHighlight(pos, pos + 1, p); | |
195 | + } catch (BadLocationException e) { | |
196 | + e.printStackTrace(); | |
197 | + } | |
198 | + } | |
199 | + | |
200 | + protected void dehighlight() { | |
201 | + textPane.getHighlighter().removeAllHighlights(); | |
202 | + } | |
203 | + | |
204 | + protected MainTextPane getMainTextPane(CaretEvent e) { | |
205 | + Object o = e.getSource(); | |
206 | + return (o instanceof MainTextPane) ? (MainTextPane) o : null; | |
207 | + } | |
208 | + | |
209 | + private ArrayList<TokenLine<JavaType, JavaStatus>> | |
210 | + getTokenList(MarkableDocument document) { | |
211 | + DocumentAttachment da = document.getDocumentAttachment(); | |
212 | + if (da instanceof JavaDocumentAttachment) { | |
213 | + JavaDocumentAttachment jda = (JavaDocumentAttachment) da; | |
214 | + return jda.getTokenList(); | |
215 | + } | |
216 | + return null; | |
217 | + } | |
218 | +} |
@@ -18,6 +18,8 @@ | ||
18 | 18 | */ |
19 | 19 | package jp.sourceforge.nine.parser.javamode; |
20 | 20 | |
21 | +import jp.sourceforge.nine.util.NineLogger; | |
22 | + | |
21 | 23 | public enum JavaType { |
22 | 24 | BLOCK_COMMENT_BEGIN, |
23 | 25 | BLOCK_COMMENT_END, |
@@ -26,14 +28,80 @@ | ||
26 | 28 | DOUBLE_QUOTE, |
27 | 29 | SINGLE_QUOTE, |
28 | 30 | BACK_SLASH, |
31 | + GREATER_THAN, | |
32 | + LESSER_THAN, | |
33 | + AT_MARK, | |
29 | 34 | KEYWORD, |
30 | 35 | WHITE_SPACE, |
36 | + MARK, | |
37 | + SEMICOLON, | |
38 | + NORMAL, | |
39 | + | |
40 | + // BRACKETS | |
31 | 41 | BRACE_BEGIN, |
32 | 42 | BRACE_END, |
33 | - MARK, | |
34 | - NORMAL, | |
43 | + PARENTHESIS_BEGIN, | |
44 | + PARENTHESIS_END, | |
45 | + BRACKET_BEGIN, | |
46 | + BRACKET_END, | |
47 | + | |
48 | + // KEYWORDS | |
49 | + KW_ABSTRACT, | |
50 | + KW_BREAK, | |
51 | + KW_CASE, | |
52 | + KW_CATCH, | |
53 | + KW_CLASS, | |
54 | + KW_CONST, | |
55 | + KW_CONTINUE, | |
56 | + KW_DEFAULT, | |
57 | + KW_DO, | |
58 | + KW_ELSE, | |
59 | + KW_EXTENDS, | |
60 | + KW_FINAL, | |
61 | + KW_FINALLY, | |
62 | + KW_FOR, | |
63 | + KW_IF, | |
64 | + KW_IMPLEMENTS, | |
65 | + KW_IMPORT, | |
66 | + KW_INSTANCEOF, | |
67 | + KW_INTERFACE, | |
68 | + KW_NATIVE, | |
69 | + KW_NEW, | |
70 | + KW_PACKAGE, | |
71 | + KW_PRIVATE, | |
72 | + KW_PROTECTED, | |
73 | + KW_PUBLIC, | |
74 | + KW_RETURN, | |
75 | + KW_STATIC, | |
76 | + KW_STRICTFP, | |
77 | + KW_SUPER, | |
78 | + KW_SWITCH, | |
79 | + KW_SYNCHRONIZED, | |
80 | + KW_THIS, | |
81 | + KW_THROW, | |
82 | + KW_THROWS, | |
83 | + KW_TRANSIENT, | |
84 | + KW_TRY, | |
85 | + KW_VOLATILE, | |
86 | + KW_WHILE, | |
35 | 87 | ; |
36 | 88 | |
89 | + public boolean isKeyword() { | |
90 | + String s = name(); | |
91 | + return (s.startsWith("KW_")); | |
92 | + } | |
93 | + | |
94 | + public static JavaType getKeywordType(String keyword) { | |
95 | + JavaType ret = null; | |
96 | + String n = "KW_" + keyword.toUpperCase(); | |
97 | + try { | |
98 | + ret = JavaType.valueOf(n); | |
99 | + } catch (Exception e) { | |
100 | + NineLogger.instance.severe(e); | |
101 | + } | |
102 | + return ret; | |
103 | + } | |
104 | + | |
37 | 105 | public JavaStatus getStatus() { |
38 | 106 | JavaStatus ret; |
39 | 107 | switch (this) { |
@@ -18,7 +18,6 @@ | ||
18 | 18 | */ |
19 | 19 | package jp.sourceforge.nine.parser.javamode; |
20 | 20 | |
21 | -import jp.sourceforge.nine.js.ModeProperties; | |
22 | 21 | import jp.sourceforge.nine.parser.DefaultMarkScanner; |
23 | 22 | import jp.sourceforge.nine.parser.DefaultTokenizer; |
24 | 23 | import jp.sourceforge.nine.parser.StatusManager; |
@@ -27,12 +26,44 @@ | ||
27 | 26 | |
28 | 27 | |
29 | 28 | public class JavaTokenizer extends DefaultTokenizer<JavaType, JavaStatus> { |
30 | - private final String[] keywords; | |
29 | + private static final Keyword[] keywords; | |
31 | 30 | |
31 | + static { | |
32 | + keywords = createKeywords(); | |
33 | + } | |
34 | + | |
35 | + private static Keyword[] createKeywords() { | |
36 | + Keyword[] ret = null; | |
37 | + String[] words = { | |
38 | + "abstract", "break", | |
39 | + "case", "catch", | |
40 | + "class", "const", | |
41 | + "continue", "default", | |
42 | + "do", "else", | |
43 | + "extends", "final", | |
44 | + "finally", "for", | |
45 | + "if", "implements", | |
46 | + "import", "instanceof", | |
47 | + "interface", "native", | |
48 | + "new", "package", | |
49 | + "private", "protected", | |
50 | + "public", "return", | |
51 | + "static", "strictfp", | |
52 | + "super", "switch", | |
53 | + "synchronized", "this", | |
54 | + "throw", "throws", | |
55 | + "transient", "try", | |
56 | + "volatile", "while" | |
57 | + }; | |
58 | + ret = new Keyword[words.length]; | |
59 | + for (int i = 0; i < words.length; i++) { | |
60 | + ret[i] = new Keyword(words[i], JavaType.getKeywordType(words[i])); | |
61 | + } | |
62 | + return ret; | |
63 | + } | |
64 | + | |
32 | 65 | public JavaTokenizer(StatusManager<JavaStatus, JavaType> statusManager) { |
33 | 66 | super(statusManager); |
34 | - ModeProperties m = ModeProperties.getInstance(); | |
35 | - keywords = m.getKeywords("text/java"); | |
36 | 67 | } |
37 | 68 | |
38 | 69 | @Override |
@@ -47,8 +78,8 @@ | ||
47 | 78 | |
48 | 79 | static class JavaWordScanner |
49 | 80 | extends WordScanner<JavaType, JavaStatus> { |
50 | - private String[] keywords = null; | |
51 | - boolean[] keywordNotMatch; | |
81 | + private static Keyword[] keywords = JavaTokenizer.keywords; | |
82 | + boolean[] keywordNotMatch = new boolean[keywords.length]; | |
52 | 83 | |
53 | 84 | public JavaWordScanner(JavaTokenizer tokenizer) { |
54 | 85 | super(tokenizer); |
@@ -56,12 +87,11 @@ | ||
56 | 87 | |
57 | 88 | @Override |
58 | 89 | protected void characterMatched(char ch, int offset, int length) { |
59 | - if (keywords == null) { setupKeywordList(); } | |
60 | 90 | // check keywords |
61 | 91 | for (int i = 0; i < keywords.length; i++) { |
62 | 92 | if (keywordNotMatch[i]) { continue; } |
63 | 93 | |
64 | - String w = keywords[i]; | |
94 | + String w = keywords[i].keyword; | |
65 | 95 | if (w.length() <= length) { |
66 | 96 | keywordNotMatch[i] = true; |
67 | 97 | } else if (w.charAt(length) != ch) { |
@@ -74,33 +104,28 @@ | ||
74 | 104 | protected void characterUnmatched(char ch, int offset, int length) { |
75 | 105 | int start = offset - length; |
76 | 106 | checkKeywordsLength(length); |
77 | - JavaType type = currentIsKeyword() ? JavaType.KEYWORD : JavaType.NORMAL; | |
107 | + Integer x = currentIsKeyword(); | |
108 | + JavaType type = x != null ? keywords[x].type : JavaType.NORMAL; | |
78 | 109 | tokenizer.setTokenIntoLine(type, start, length); |
79 | - if (keywords != null) { | |
80 | - keywordNotMatch = new boolean[keywords.length]; | |
81 | - } | |
110 | + keywordNotMatch = new boolean[keywords.length]; | |
82 | 111 | } |
83 | 112 | |
84 | 113 | private void checkKeywordsLength(int length) { |
85 | 114 | for (int i = 0; i < keywords.length; i++) { |
86 | 115 | if (! keywordNotMatch[i] && |
87 | - keywords[i].length() != length) { | |
116 | + keywords[i].keyword.length() != length) { | |
88 | 117 | keywordNotMatch[i] = true; |
89 | 118 | } |
90 | 119 | } |
91 | 120 | } |
92 | 121 | |
93 | - private boolean currentIsKeyword() { | |
122 | + private Integer currentIsKeyword() { | |
94 | 123 | for (int i = 0; i < keywordNotMatch.length; i++) { |
95 | - if (! keywordNotMatch[i]) { return true; } | |
124 | + if (! keywordNotMatch[i]) { return i; } | |
96 | 125 | } |
97 | - return false; | |
126 | + return null; | |
98 | 127 | } |
99 | 128 | |
100 | - private void setupKeywordList() { | |
101 | - keywords = ((JavaTokenizer) tokenizer).keywords; | |
102 | - keywordNotMatch = new boolean[keywords.length]; | |
103 | - } | |
104 | 129 | } |
105 | 130 | |
106 | 131 |
@@ -160,6 +185,30 @@ | ||
160 | 185 | case '}': |
161 | 186 | type = JavaType.BRACE_END; |
162 | 187 | break; |
188 | + case '(': | |
189 | + type = JavaType.PARENTHESIS_BEGIN; | |
190 | + break; | |
191 | + case ')': | |
192 | + type = JavaType.PARENTHESIS_END; | |
193 | + break; | |
194 | + case '[': | |
195 | + type = JavaType.BRACKET_BEGIN; | |
196 | + break; | |
197 | + case ']': | |
198 | + type = JavaType.BRACKET_END; | |
199 | + break; | |
200 | + case '<': | |
201 | + type = JavaType.LESSER_THAN; | |
202 | + break; | |
203 | + case '>': | |
204 | + type = JavaType.GREATER_THAN; | |
205 | + break; | |
206 | + case '@': | |
207 | + type = JavaType.AT_MARK; | |
208 | + break; | |
209 | + case ';': | |
210 | + type = JavaType.SEMICOLON; | |
211 | + break; | |
163 | 212 | default: |
164 | 213 | type = JavaType.MARK; |
165 | 214 | break; |
@@ -186,4 +235,15 @@ | ||
186 | 235 | tokenizer.setTokenIntoLine(type, offset - 1, 1); |
187 | 236 | } |
188 | 237 | } |
238 | + | |
239 | + | |
240 | + static class Keyword { | |
241 | + final String keyword; | |
242 | + final JavaType type; | |
243 | + | |
244 | + Keyword(String keyword, JavaType type) { | |
245 | + this.keyword = keyword; | |
246 | + this.type = type; | |
247 | + } | |
248 | + } | |
189 | 249 | } |
@@ -0,0 +1,291 @@ | ||
1 | +/* | |
2 | + * Copyright (C) 2010, mshio <mshio@users.sourceforge.jp> | |
3 | + * | |
4 | + * This program is free software: you can redistribute it and/or | |
5 | + * modify it under the terms of the GNU General Public License | |
6 | + * as published by the Free Software Foundation; either version 2 | |
7 | + * of the License, or any later version. | |
8 | + * | |
9 | + * This program is distributed in the hope that it will be useful, | |
10 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | + * GNU General Public License for more details. | |
13 | + * | |
14 | + * You should have received a copy of the GNU General Public License | |
15 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | + * | |
17 | + * Require JDK 1.5 (or later) | |
18 | + */ | |
19 | +package jp.sourceforge.nine.action.javamode; | |
20 | + | |
21 | +import java.awt.event.ActionEvent; | |
22 | +import java.util.ArrayList; | |
23 | +import java.util.Stack; | |
24 | + | |
25 | +import javax.swing.text.Document; | |
26 | +import javax.swing.text.Element; | |
27 | +import javax.swing.text.JTextComponent; | |
28 | + | |
29 | +import jp.sourceforge.nine.MainTextPane; | |
30 | +import jp.sourceforge.nine.action.ScriptableTextAction; | |
31 | +import jp.sourceforge.nine.document.DocumentAttachment; | |
32 | +import jp.sourceforge.nine.document.MarkableDocument; | |
33 | +import jp.sourceforge.nine.javamode.JavaDocumentAttachment; | |
34 | +import jp.sourceforge.nine.parser.Token; | |
35 | +import jp.sourceforge.nine.parser.TokenLine; | |
36 | +import jp.sourceforge.nine.parser.javamode.JavaStatus; | |
37 | +import jp.sourceforge.nine.parser.javamode.JavaStatusManager; | |
38 | +import jp.sourceforge.nine.parser.javamode.JavaType; | |
39 | + | |
40 | +/** | |
41 | + * カレント行の文字列に対して、自動インデントを行うクラスです。 | |
42 | + * 基本的には、直前のカッコ類を検出し、その行のインデント情報を元に、カレント行のインデントを自動的に変更します。 | |
43 | + * インデントは、GNU に近い様式で行われる予定ですが、少なくとも現状ではまったく厳密ではありません。 | |
44 | + * また、このバージョンでは実装もまったく不完全です。 | |
45 | + * たとえば、通常の文を途中で改行しても、インデントが増えません。 | |
46 | + * switch case 文の case が適切にインデントされません。 | |
47 | + * | |
48 | + * @author mshio | |
49 | + */ | |
50 | +public class JavaTabAction extends ScriptableTextAction { | |
51 | + private static final long serialVersionUID = -8283585316654470522L; | |
52 | + public static final String ACTION_NAME = "java-indent"; | |
53 | + private final JavaStatusManager statusManager = new JavaStatusManager(); | |
54 | + | |
55 | + public JavaTabAction() { | |
56 | + super(ACTION_NAME); | |
57 | + } | |
58 | + | |
59 | + /** | |
60 | + * 自動インデントを実行します。 | |
61 | + */ | |
62 | + @Override | |
63 | + public void perform(ActionEvent e) { | |
64 | + JTextComponent text = getTextComponent(e); | |
65 | + MarkableDocument doc = getMarkableDocument(text); | |
66 | + if (doc == null) return; | |
67 | + Element root = doc.getDefaultRootElement(); | |
68 | + int currentRow = root.getElementIndex(text.getCaretPosition()); | |
69 | + KeywordInfo lastBracket = getLastBracket(text, doc, currentRow); | |
70 | + if (lastBracket == null) return; | |
71 | + | |
72 | + if (lastBracket != null) { | |
73 | + String indent = getIndentText(text, doc, currentRow, lastBracket); | |
74 | + String line = getCurrentText(text, doc); | |
75 | + int s = root.getElement(currentRow).getStartOffset(); | |
76 | + int pos = getNextCaretPosition(text, line, indent, s); | |
77 | + editText(text, root, currentRow, line, indent, pos); | |
78 | + } | |
79 | + } | |
80 | + | |
81 | + private void editText(JTextComponent text, Element root, int currentRow, | |
82 | + String line, String indent, int caretPosition) { | |
83 | + Element r = root.getElement(currentRow); | |
84 | + if (text instanceof MainTextPane) { | |
85 | + MainTextPane pane = (MainTextPane) text; | |
86 | + pane.beginUndoTransaction(); | |
87 | + text.setSelectionStart(r.getStartOffset()); | |
88 | + text.setSelectionEnd(r.getEndOffset()); | |
89 | + text.replaceSelection(indent + getLeftTrimedText(line)); | |
90 | + text.setCaretPosition(caretPosition); | |
91 | + pane.endUndoTransaction(); | |
92 | + } | |
93 | + } | |
94 | + | |
95 | + /** | |
96 | + * Converts the text component into {@code MarkableDocument} object. | |
97 | + * | |
98 | + * @param textComponent the text component | |
99 | + * @return {@code MarkableDocument} object, or null if the text component | |
100 | + * is not an instance of {@code MarkableDocument}. | |
101 | + */ | |
102 | + private MarkableDocument getMarkableDocument(JTextComponent textComponent) { | |
103 | + Document d = textComponent.getDocument(); | |
104 | + if (d instanceof MarkableDocument) { | |
105 | + return (MarkableDocument) d; | |
106 | + } | |
107 | + return null; | |
108 | + } | |
109 | + | |
110 | + private JavaDocumentAttachment getJavaDocumentAttachment(MarkableDocument doc) { | |
111 | + DocumentAttachment a = doc.getDocumentAttachment(); | |
112 | + if (a instanceof JavaDocumentAttachment) { | |
113 | + return (JavaDocumentAttachment) a; | |
114 | + } | |
115 | + return null; | |
116 | + } | |
117 | + | |
118 | + private String getIndentText(JTextComponent text, Document doc, | |
119 | + int currentRow, KeywordInfo bracket) { | |
120 | + Element root = doc.getDefaultRootElement(); | |
121 | + String bracketLine = getRowText(text, doc, root, bracket.row); | |
122 | + String currentLine = getRowText(text, doc, root, currentRow); | |
123 | + boolean closed = isClosedBracket(currentLine); | |
124 | + return getIndentText(bracketLine, bracket.token, closed); | |
125 | + } | |
126 | + | |
127 | + private KeywordInfo getLastBracket(JTextComponent t, MarkableDocument doc, int currentRow) { | |
128 | + JavaDocumentAttachment a = getJavaDocumentAttachment(doc); | |
129 | + if (a == null) return null; | |
130 | + ArrayList<TokenLine<JavaType, JavaStatus>> list = a.getTokenList(); | |
131 | + return getLastBracket(list, currentRow); | |
132 | + } | |
133 | + | |
134 | + private KeywordInfo getLastBracket(ArrayList<TokenLine<JavaType, JavaStatus>> list, | |
135 | + int row) { | |
136 | + int i = 0; | |
137 | + Stack<KeywordInfo> s = new Stack<KeywordInfo>(); | |
138 | + statusManager.setCurrentStatus(JavaStatus.NORMAL); | |
139 | + | |
140 | + for (TokenLine<JavaType, JavaStatus> l : list) { | |
141 | + for (Token<JavaType> t : l) { | |
142 | + JavaType type = t.getType(); | |
143 | + statusManager.switchStatus(type); | |
144 | + if (statusManager.getStatus() != JavaStatus.NORMAL) { continue; } | |
145 | + switch (type) { | |
146 | + case KW_IF: | |
147 | + case KW_WHILE: | |
148 | + case KW_FOR: | |
149 | + case BRACE_BEGIN: | |
150 | + case PARENTHESIS_BEGIN: | |
151 | + case BRACKET_BEGIN: | |
152 | + s.add(new KeywordInfo(t, i)); | |
153 | + break; | |
154 | + case PARENTHESIS_END: | |
155 | + case BRACKET_END: | |
156 | + s.pop(); | |
157 | + break; | |
158 | + case BRACE_END: | |
159 | + s.pop(); | |
160 | + modifyIfStatement(s); | |
161 | + break; | |
162 | + case SEMICOLON: | |
163 | + modifyIfStatement(s); | |
164 | + break; | |
165 | + } | |
166 | + } | |
167 | + if (++i >= row) break; | |
168 | + statusManager.sendLineEnd(); | |
169 | + } | |
170 | + return s.size() > 0 ? s.pop() : null; | |
171 | + } | |
172 | + | |
173 | + private void modifyIfStatement(Stack<KeywordInfo> stack) { | |
174 | + if (stack.isEmpty()) { return; } | |
175 | + KeywordInfo b = stack.peek(); | |
176 | + if (b != null) { | |
177 | + JavaType t = b.token.getType(); | |
178 | + if (t == JavaType.KW_IF || t == JavaType.KW_FOR || t == JavaType.KW_WHILE) { | |
179 | + stack.pop(); | |
180 | + } | |
181 | + } | |
182 | + } | |
183 | + | |
184 | + private String getRowText(JTextComponent t, Document doc, Element root, int row) { | |
185 | + Element r = root.getElement(row); | |
186 | + int s = r.getStartOffset(); | |
187 | + int e = r.getEndOffset(); | |
188 | + int len = t.getText().length(); | |
189 | + e = len < e ? len : e; | |
190 | + return s == e ? "" : t.getText().substring(s, e); | |
191 | + } | |
192 | + | |
193 | + private String getIndentText(String text, Token<JavaType> token, boolean closed) { | |
194 | + int left = 0; | |
195 | + for (char ch : text.toCharArray()) { | |
196 | + if (ch != ' ' && ch != '\t') break; | |
197 | + left++; | |
198 | + } | |
199 | + String indent = text.substring(0, left); | |
200 | + JavaType t = token.getType(); | |
201 | + if (t == JavaType.BRACE_BEGIN) { | |
202 | + return closed ? indent : indent + "\t"; | |
203 | + } else if (t == JavaType.BRACKET_BEGIN || t == JavaType.PARENTHESIS_BEGIN) { | |
204 | + int l = token.getOffset() - left; | |
205 | + StringBuilder sb = new StringBuilder(indent.length() + l); | |
206 | + sb.append(indent); | |
207 | + for (int i = 0; i < l; i++) { | |
208 | + sb.append(' '); | |
209 | + } | |
210 | + if (! closed) sb.append(' '); | |
211 | + return sb.toString(); | |
212 | + } | |
213 | + return closed ? indent : indent + "\t"; | |
214 | + } | |
215 | + | |
216 | + private String getCurrentText(JTextComponent t, Document doc) { | |
217 | + Element root = doc.getDefaultRootElement(); | |
218 | + int row = root.getElementIndex(t.getCaretPosition()); | |
219 | + return getRowText(t, doc, root, row); | |
220 | + } | |
221 | + | |
222 | + private boolean isClosedBracket(String text) { | |
223 | + char ch = 0; | |
224 | + for (int i = 0; i < text.length(); i++) { | |
225 | + ch = text.charAt(i); | |
226 | + if (ch != ' ' && ch != '\t') { break; } | |
227 | + } | |
228 | + return ch == '}' || ch == ']' || ch == ')'; | |
229 | + } | |
230 | + | |
231 | + /** | |
232 | + * インデント調整後のキャレット位置を算出します。 | |
233 | + * 現在のキャレットの位置(インデックス値)から、取り除かれるインデントの文字数を引き、 | |
234 | + * 追加されるインデントの文字数を足して算出しています。 | |
235 | + * | |
236 | + * @param text テキストコンポーネント オブジェクト | |
237 | + * @param line 行全体の文字列 | |
238 | + * @param indent インデントを構成する空白文字(スペースとタブ)による文字列 | |
239 | + * @param leftPos 行の左端のインデックス(これより小さな値になると、前の行に移動してしまう) | |
240 | + * @return インデント調整後のあるべきキャレット位置(インデックス値) | |
241 | + */ | |
242 | + private int getNextCaretPosition(JTextComponent text, String line, | |
243 | + String indent, int leftPos) { | |
244 | + int d = getLeftBrank(line); | |
245 | + int pos = text.getCaretPosition(); | |
246 | + int ret = pos - d + indent.length(); | |
247 | + return ret < leftPos ? leftPos : ret; | |
248 | + } | |
249 | + | |
250 | + /** | |
251 | + * 渡された文字列の、左側(先頭側)の空白文字(スペースとタブ)の文字数を算出します。 | |
252 | + * | |
253 | + * @param line 対象となる文字列 | |
254 | + * @return 左端に検出された空白文字の数 | |
255 | + */ | |
256 | + private int getLeftBrank(String line) { | |
257 | + int i; | |
258 | + for (i = 0; i < line.length(); i++) { | |
259 | + char c = line.charAt(i); | |
260 | + if (c != ' ' && c != '\t') { break; } | |
261 | + } | |
262 | + return i; | |
263 | + } | |
264 | + | |
265 | + private String getLeftTrimedText(String line) { | |
266 | + int p0 = getLeftBrank(line); | |
267 | + return line.substring(p0); | |
268 | + } | |
269 | + | |
270 | + | |
271 | + /** | |
272 | + * カッコ類などキーワードの種類や位置情報を保持するためのオブジェクト。 | |
273 | + * 位置情報は、行と列の値で保持する。 | |
274 | + * | |
275 | + * @author mshio | |
276 | + */ | |
277 | + static class KeywordInfo { | |
278 | + final int row; | |
279 | + final Token<JavaType> token; | |
280 | + | |
281 | + KeywordInfo(Token<JavaType> token, int row) { | |
282 | + this.token = token; | |
283 | + this.row = row; | |
284 | + } | |
285 | + | |
286 | + @Override | |
287 | + public String toString() { | |
288 | + return String.format("[token: %s, row: %d]", token, row); | |
289 | + } | |
290 | + } | |
291 | +} |