001: package tide.editor;
002:
003: import java.awt.EventQueue;
004: import tide.outputtabs.search.SearchResultsManager;
005: import tide.editor.completions.*;
006: import javax.swing.text.*;
007: import snow.texteditor.*;
008: import snow.utils.StringUtils;
009: import tide.editor.linemessages.*;
010:
011: /** Main purposes:
012: 1) simplify the editing : indent the {, } and line begins
013: 2) call the completion dialogs after (but only if a single char is inserted in document)
014: a) "import " and "import static "
015: b) "new "
016: c) "x." method and attributes completion
017: d) "new x(" constructor completion
018: e) "x(" method args completion
019: * f) "<" class parameter completion
020: g) "@"
021: h) CTRL+space
022: i) & in comments => &snsp; & > ...
023:
024: 3) shift LineMessage line numbers
025: 4) make the autoreplacements (sysop => System.out.println("");
026:
027: this is disabled (removed from textpane) when the whole document is set and reacts only on user manually entered data.
028: */
029: public class EditorDocumentFilter extends DocumentFilter {
030: final EditorPanel editorPanel;
031: final CompletionManager completionManager;
032:
033: public String indent = " ";
034:
035: public EditorDocumentFilter(CompletionManager completionManager,
036: EditorPanel editorPanel) {
037: super ();
038:
039: this .editorPanel = editorPanel;
040: this .completionManager = completionManager;
041: }
042:
043: /** from DocumentFilter
044: */
045: @Override
046: public void insertString(DocumentFilter.FilterBypass fb,
047: int offset, String string, AttributeSet attr) {
048: //System.out.println("ins "+offset+": "+string);
049: editorPanel.codeStyler.userInsertWillOccur(offset, string
050: .length());
051: try {
052: fb.insertString(offset, string, attr);
053: editorPanel.codeStyler.userInsertOccured(offset, string
054: .length());
055: editorPanel.setHasBeenEdited();
056:
057: // reorganize the LineMessage line numbers
058:
059: int oldCRCount = 0;
060: int newCRCount = StringUtils.count(string, '\n');
061:
062: adaptLineNumbers(newCRCount, oldCRCount, offset);
063: } catch (Exception e) {
064: e.printStackTrace();
065: }
066:
067: }
068:
069: /** from DocumentFilter
070: */
071: @Override
072: public void remove(DocumentFilter.FilterBypass fb, int offset,
073: int length) {
074: //System.out.println("remove "+offset+": "+length);
075: // System.out.println("rem");
076: editorPanel.codeStyler.userInsertWillOccur(offset, -length);
077: try {
078: int[] lineColStart = DocumentUtils.getLineColumnNumbers(
079: editorPanel.doc, offset);
080: int[] lineColEnd = DocumentUtils.getLineColumnNumbers(
081: editorPanel.doc, offset + length);
082:
083: // reorganize the LineMessage line numbers
084: notifyReplacedOrRemovedLines(lineColStart, lineColEnd); // first call this !!
085:
086: int oldCRCount = StringUtils.count(editorPanel.doc.getText(
087: offset, length), '\n');
088: int newCRCount = 0;
089: adaptLineNumbers(newCRCount, oldCRCount, offset);
090:
091: // perform remove now
092: fb.remove(offset, length);
093: editorPanel.codeStyler.userInsertOccured(offset, -length);
094: editorPanel.setHasBeenEdited();
095:
096: } catch (Exception e) {
097: e.printStackTrace();
098: }
099: }
100:
101: /** from DocumentFilter
102: * when typing some code in the editor, this is called for each char separately.
103: * Code to just replace: fb.replace(offset, length, text, attrs);
104: */
105: @Override
106: public void replace(DocumentFilter.FilterBypass fb, int offset,
107: int length, final String text, AttributeSet attrs) {
108: //System.out.println("replace "+offset+": "+length+": "+text);
109:
110: String replacedText = null;
111: int replacedLength = length;
112: int replacedOffset = offset;
113:
114: editorPanel.codeStyler.userInsertWillOccur(offset, text
115: .length()
116: - length);
117: try {
118: // reorganize the LineMessage line numbers
119: int[] lineColStart = DocumentUtils.getLineColumnNumbers(
120: editorPanel.doc, offset);
121: int[] lineColEnd = DocumentUtils.getLineColumnNumbers(
122: editorPanel.doc, offset + length);
123:
124: notifyReplacedOrRemovedLines(lineColStart, lineColEnd);
125:
126: int oldCRCount = StringUtils.count(editorPanel.doc.getText(
127: offset, length), '\n');
128: int newCRCount = StringUtils.count(text, '\n');
129:
130: // TODO: take autoreplacement in account !!
131: adaptLineNumbers(newCRCount, oldCRCount, offset);
132:
133: // auto replace
134: String[] ar = getAutoReplacement(offset, text, length); // { word, replacement, raw chars}
135: if (ar != null) {
136: // perform replacement
137: String line = DocumentUtils.getTextOfLineAtPosition(
138: editorPanel.doc, offset);
139: String spaces = DocumentUtils
140: .getSpacesAtBeginning(line);
141:
142: replacedText = StringUtils.indent(ar[1], spaces, true); // keep the spaces where we insert !
143: replacedOffset = offset - ar[0].length()
144: + text.length(); // consider the typed char (lengh=1)
145: replacedLength = length + ar[0].length()
146: - text.length();
147: fb.replace(replacedOffset, replacedLength,
148: replacedText, attrs);
149:
150: int rewInRepl = Integer.parseInt(ar[2]);
151: if (rewInRepl > 0) {
152: final int dotPosInRepl = ar[1].length() - rewInRepl;
153: final int lpr = StringUtils
154: .getLineNumberForPosition(ar[1],
155: dotPosInRepl); // first is 0
156: final int cpr = StringUtils
157: .getColumnNumberForPosition(ar[1],
158: dotPosInRepl); // first is 1
159:
160: if (lpr >= 0 && cpr >= 0) {
161: final int[] lineColStartDoc = DocumentUtils
162: .getLineColumnNumbers(editorPanel.doc,
163: replacedOffset);
164:
165: // must be delayed...
166: EventQueue.invokeLater(new Runnable() {
167: public void run() {
168: System.out.println("EDF:lpr=" + lpr
169: + ", cpr=" + cpr);
170: final int docPosCaret = DocumentUtils
171: .getDocPositionFor(
172: editorPanel.doc,
173: lineColStartDoc[0]
174: + lpr,
175: lineColStartDoc[1]
176: + cpr - 1);
177: editorPanel.textPane
178: .setCaretPosition(docPosCaret);
179: }
180: });
181: }
182: }
183: } else if (text.equals("\n") && length == 0) // look if it is an "enter"
184: {
185: // look at beginning of last line (=actual in document)
186: //int linePos = DocumentUtils.getLineNumber(editorPanel.doc, offset);
187: // todo: if line is empty,take preceding?
188:
189: String line = DocumentUtils.getTextOfLineAtPosition(
190: editorPanel.doc, offset);
191: int lineStartOffset = DocumentUtils
192: .getLineStartOffsetForPos(editorPanel.doc,
193: offset);
194: int col = offset - lineStartOffset;
195: String spaces = DocumentUtils
196: .getSpacesAtBeginning(line);
197: String lt = line.trim();
198: if (lt.endsWith("{") && col > line.lastIndexOf('{')) {
199: replacedText = text + spaces + indent;
200: fb.replace(offset, length, replacedText, attrs);
201:
202: } else if (lt.equals("(")) {
203: replacedText = text + spaces + indent;
204: fb.replace(offset, length, replacedText, attrs);
205: } else if (lt.endsWith("*/")) {
206: replacedText = text;
207: fb.replace(offset, length, replacedText, attrs);
208: } else if (lt.startsWith("*") || lt.startsWith("/*")) {
209: // todo: only if not after "*/" on the same line !
210: String actualLine = DocumentUtils
211: .getTextOfLineAtPosition_onlyUpToPos(
212: editorPanel.doc, offset); // without line feed and return
213:
214: boolean comment = true;
215: if (actualLine.indexOf("*/") >= 0) {
216: comment = false;
217: } else {
218: if (line.indexOf('*') >= col)
219: comment = false;
220: }
221: // and
222:
223: if (comment) {
224: replacedText = text + spaces + "*";
225: fb.replace(offset, length, replacedText, attrs); // a space at end is boring: it avoids typing directly the end tag */
226: } else {
227: replacedText = text + spaces;
228: fb.replace(offset, length, replacedText, attrs);
229: }
230: } else {
231: // append the same spaces
232: replacedText = text + spaces;
233: fb.replace(offset, length, replacedText, attrs);
234: }
235: } else if (text.equals("}") && length == 0) {
236: String actualLine = DocumentUtils
237: .getTextOfLineAtPosition_onlyUpToPos(
238: editorPanel.doc, offset); // without line feed and return
239: int posOpen = DocumentUtils
240: .getPositionOfCorrespondingOpeningBrace('{',
241: '}', editorPanel.doc, offset);
242: if (actualLine.trim().equals("") && posOpen >= 0) {
243: // the closing } is alone on his line => align with last opening {
244: //
245: String lineOpen = DocumentUtils
246: .getTextOfLineAtPosition(editorPanel.doc,
247: posOpen);
248: String openSpaces = DocumentUtils
249: .getSpacesAtBeginning(lineOpen);
250: // remove the spaces at actual line to replace with these openSpaces ...
251: // => the actual line is made only of spaces+CR => remove them !
252: //if(actualLine.le
253: //System.out.println("actual = "+actualLine);
254: //System.out.println("openSpaces+text = "+openSpaces+text);
255: replacedText = openSpaces + text;
256: replacedLength = length + actualLine.length();
257: replacedOffset = offset - actualLine.length();
258: fb.replace(offset - actualLine.length(), length
259: + actualLine.length(), replacedText, attrs);
260: } else {
261: // nothing
262: replacedText = text;
263: fb.replace(offset, length, text, attrs);
264: }
265: } else if (text.equals("@") && length == 0) {
266: replacedText = completionManager.ampersandPressed(fb,
267: offset, length, attrs);
268: } else if (text.equals(".") && length == 0) {
269: if (MainEditorFrame.instance.enableDotCompletion
270: .isSelected()) {
271: replacedText = completionManager.pointPressed(
272: editorPanel.getActualDisplayedFile(), fb,
273: offset, length, attrs);
274: } else {
275: // nothing
276: replacedText = text;
277: fb.replace(offset, length, text, attrs);
278: }
279: } else if (text.equals("(") && length == 0) {
280: replacedText = completionManager
281: .openingParenthesisPressed(editorPanel
282: .getActualDisplayedFile(), fb, offset,
283: length, attrs);
284: } else if (text.equals(" ") && length == 0) {
285: replacedText = completionManager.spacePressed(fb,
286: offset, length, attrs);
287: } else if (text.equals("{") && length == 0) {
288: //TODO: only if last on line.
289: replacedText = text;// +" }"; // automatically appends the end curly brace (make an option to deactivate this ??
290: fb.replace(offset, length, replacedText, attrs);
291:
292: // rewind in the editor
293: /* EventQueue.invokeLater(new Runnable() { public void run() {
294: editorPanel.textPane.setCaretPosition( editorPanel.textPane.getCaretPosition() - 2 );
295: }});*/
296: } else {
297: // no special action, just pass through the filter
298: replacedText = text;
299: fb.replace(offset, length, text, attrs);
300: }
301:
302: // todo: not exact, some replaces have other lengths !
303: if (replacedText != null) {
304: editorPanel.codeStyler.userInsertOccured(
305: replacedOffset, replacedText.length()
306: - replacedLength);
307: }
308: } catch (Exception e) {
309: e.printStackTrace();
310: } finally {
311: editorPanel.setHasBeenEdited();
312: }
313: }
314:
315: /** Called from all filter methods (insert, remove, replace).
316: * (after insert and before remove) ???
317: *
318: * this notifies the LineMessagesManager to shift the line numbers
319: * and SearchResultsManager (TODO: also columns)
320: */
321: private void adaptLineNumbers(int newCRCount, int oldCRCount,
322: int offset) {
323: int d = newCRCount - oldCRCount;
324: if (d == 0)
325: return;
326:
327: try {
328: //System.out.println("new cr="+newCRCount+" old="+oldCRCount);
329: int lineOfOffset = DocumentUtils.getLineNumber(
330: editorPanel.doc, offset);
331: LineMessagesManager.getInstance().lineInsertOccured(
332: editorPanel.getActualDisplayedFile().getJavaName(),
333: lineOfOffset, d);
334: SearchResultsManager.getInstance().lineInsertOccured(
335: editorPanel.getActualDisplayedFile().getJavaName(),
336: lineOfOffset, d);
337: } catch (Exception e) {
338: e.printStackTrace();
339: }
340: }
341:
342: /** Called from remove and replace filter methods (at the beginning).
343: */
344: private void notifyReplacedOrRemovedLines(int[] lineColStart,
345: int[] lineColEnd) {
346: try {
347: if (lineColEnd[0] > lineColStart[0]) {
348: LineMessagesManager.getInstance().lineRemoved(
349: editorPanel.getActualDisplayedFile()
350: .getJavaName(), lineColStart[0],
351: lineColEnd[0]);
352: SearchResultsManager.getInstance().lineRemoved(
353: editorPanel.getActualDisplayedFile()
354: .getJavaName(), lineColStart[0],
355: lineColEnd[0]);
356: }
357: } catch (Exception e) {
358: e.printStackTrace();
359: }
360: }
361:
362: /** null if no replacement found. Globally deactivated if so defined in the debug menu.
363: */
364: private String[] getAutoReplacement(int offset,
365: String insertedText, int length) {
366: // globally deactivated
367: if (!UserAutoReplacements.getInstance().enableAutoReplacements
368: .isSelected())
369: return null;
370:
371: // only consider single tped characters
372: if (length != 0)
373: return null; // not replacing a portion
374: if (insertedText.length() != 1)
375: return null; // single chars
376:
377: String w = DocumentUtils.getWordAt(editorPanel.doc, Math.max(0,
378: offset - 1))
379: + insertedText;
380: if (!UserAutoReplacements.getInstance().hasReplacement(w)) {
381: return null;
382: }
383:
384: UserAutoReplacements.AutoReplacement repl = UserAutoReplacements
385: .getInstance().getReplacement(w);
386:
387: return new String[] { w, repl.getReplacement(),
388: "" + repl.rewindAfterReplace };
389: }
390:
391: }
|