001: package tide.editor.styler;
002:
003: import tide.utils.SyntaxUtils;
004: import snow.texteditor.*;
005: import tide.editor.*;
006: import snow.concurrent.*;
007: import javax.swing.text.*;
008: import java.util.*;
009: import java.util.concurrent.*;
010:
011: /** Exists only once.
012: * Associated to the EditorPanel.
013: * normally triggered from the EditorDocumentFilter when text is inserted.
014: * Also notified of scroll view changes.
015: * manages the simple parsing and styling of the document as user types,
016: * trying as possible to be discrete and lazy.
017: * TODO: don't flicker (double buffer ?)
018: */
019: public final class JavaCodeStyler implements Runnable {
020: final private EditorDocument doc;
021:
022: // stops forever, no restart possible
023: private volatile boolean shouldStylerTerminate = false;
024: // private volatile boolean restartStyling = false;
025:
026: // used to keep user typing reactant: allow
027: // to detect when the system is "idle".
028: private volatile long lastEdit = 0; // must be volatile
029:
030: private volatile int visibleStart = -1;
031: private volatile int visibleEnd = -1;
032:
033: // actually not used.
034: private volatile int actualyValidStyledFrom = -1;
035: private volatile int actualyValidStyledTo = -1;
036:
037: // store some potential restart positions.
038: //
039: private final Set<Integer> potentialRestartPositions = new TreeSet<Integer>();
040: private final Thread stylerThread;
041:
042: // can be used to monitor the "active" time
043: private long lastActiveEditTime = 0;
044: private long totalActiveEditTime = 0;
045:
046: public JavaCodeStyler(EditorDocument doc) {
047: this .doc = doc;
048:
049: stylerThread = new Thread(this );
050: stylerThread.setPriority(Thread.NORM_PRIORITY - 1);
051: stylerThread.setDaemon(true);
052: stylerThread.setName("JavaCodeStyler");
053: stylerThread.start();
054: }
055:
056: /** Don't call this, this is the run method called from the inner styler thread.
057: */
058: public void run() {
059: while (!shouldStylerTerminate) {
060: // wait if last edit < 1sec
061: long dt = (System.currentTimeMillis() - lastEdit);
062: if (dt < 500) {
063: try {
064: /*synchronized(stylerThread)
065: {
066: stylerThread.wait(500-dt); // wait 0.2 sec at least (Better than Sleep)
067: }*/
068: Thread.sleep(500 - dt);
069: } catch (InterruptedException ignored) {
070: throw new RuntimeException(ignored);
071: }
072: }
073:
074: if ((visibleEnd - visibleStart) > 0
075: && (this .actualyValidStyledTo < visibleEnd
076: || this .actualyValidStyledFrom > visibleStart
077: || actualyValidStyledTo == -1 || actualyValidStyledFrom == -1)) {
078: if (visibleEnd > doc.getLength()) {
079: visibleEnd = doc.getLength();
080: }
081:
082: // System.out.println("\nvalidity: "+actualyValidStyledFrom+" - "+actualyValidStyledTo+"");
083: // System.out.println("required: "+visibleStart+" - "+visibleEnd);
084:
085: this .shouldStop = false;
086: try {
087: // do style a little bit more than requested, scrolls are then nicer !
088: _internal_style(Math.max(visibleStart - 300, 0),
089: Math.min(visibleEnd + 400, doc.getLength()));
090: } catch (Exception e) {
091: e.printStackTrace(); // [Feb2008]: just catch them, to avoid the styler thread to die.
092: }
093: }
094:
095: // After having styled, wait...
096: synchronized (stylerThread) // if missing => java.lang.IllegalMonitorStateException: current thread not owner
097: {
098: try {
099: stylerThread.wait(); // [June2007]: 10 secs ?
100: } catch (InterruptedException ie) {
101: throw new RuntimeException(ie);
102: } // propagate... cause this thread to die. (stop, terminate)
103: }
104: }
105: }
106:
107: /** Called from the document filter just before the text is inserted
108: */
109: public void userInsertWillOccur(int offset,
110: int charsinsertedordeleted) {
111: updateActiveTimeTrackerEditOccured();
112: //System.out.println("insert will occur");
113: stopStyling();
114: }
115:
116: /** Called from document filter when some text was inserted
117: * @param charsinsertedordeleted is the number of chars to be inserted
118: */
119: public synchronized void userInsertOccured(int offset,
120: int charsinsertedordeleted) {
121: updateActiveTimeTrackerEditOccured();
122: //MainEditorFrame.debugOut("insert occured");
123:
124: if (!stylerThread.isAlive()) {
125: System.out.println("Styler thread is DEAD !!");
126: }
127:
128: lastEdit = System.currentTimeMillis(); // used for temporization...
129:
130: stopStyling();
131:
132: // limit validity.
133: if (actualyValidStyledTo > offset)
134: actualyValidStyledTo = offset;
135:
136: // take last "window", but at least 3000 chars.
137: /*int len = visibleEnd - visibleStart;
138: if(len<=2000) len=3000;*/
139:
140: synchronized (stylerThread) {
141: stylerThread.notifyAll();
142: }
143: }
144:
145: /** Called when the document content has been changed
146: */
147: public synchronized void documentContentFullyChanged(int newLength) {
148: stopStyling();
149:
150: // no quick restart possible.
151: potentialRestartPositions.clear();
152: actualyValidStyledTo = -1;
153: actualyValidStyledFrom = -1;
154: lastEdit = 0;
155:
156: if (visibleEnd > newLength)
157: visibleEnd = newLength;
158:
159: synchronized (stylerThread) {
160: stylerThread.notifyAll();
161: }
162: }
163:
164: /** Called when the view changed.
165: */
166: public synchronized void viewBoundsChanged(int start, int end) {
167: updateActiveTimeTrackerEditOccured();
168:
169: if (visibleStart == start && visibleEnd == end) {
170: //[May2007] was missing
171: synchronized (stylerThread) {
172: stylerThread.notifyAll();
173: }
174: return;
175: }
176:
177: lastEdit = System.currentTimeMillis(); // used for temporization...
178:
179: stopStyling();
180:
181: visibleStart = start;
182: visibleEnd = end;
183:
184: if (visibleEnd > doc.getLength())
185: visibleEnd = doc.getLength();
186:
187: synchronized (stylerThread) {
188: stylerThread.notifyAll();
189: }
190: }
191:
192: /** Pauses greater than 5 min are treated as not being working time.
193: */
194: private void updateActiveTimeTrackerEditOccured() {
195: long dt = System.currentTimeMillis() - lastActiveEditTime;
196: if (dt < 1000L * 60 * 5) {
197: // active
198: totalActiveEditTime += dt;
199: } else {
200: // inactive
201: }
202: lastActiveEditTime = System.currentTimeMillis();
203: }
204:
205: public void resetTotalActiveTime() {
206: lastActiveEditTime = 0;
207: totalActiveEditTime = 0;
208: }
209:
210: public long getTotalActiveTime() {
211: return totalActiveEditTime;
212: }
213:
214: // semaphor used to notify the thread stop
215: volatile private boolean shouldStop = false;
216:
217: /** stops actual styling (not waiting)
218: */
219: private synchronized void stopStyling() {
220: shouldStop = true;
221: }
222:
223: /** usually takes less than 100ms
224: */
225: private void _internal_style(final int from, final int to) {
226: try {
227:
228: long t0 = System.currentTimeMillis();
229:
230: // boosted: start from a known good position
231: int parseStartOffset = getGoodRestartPosition(from);
232: //System.out.println("Styling started from "+parseStartOffset+" for ("+from+"-"+to+")");
233:
234: int maxL = Math.min(to - parseStartOffset + 500, doc
235: .getLength()
236: - parseStartOffset);
237: String txt = doc.getText(parseStartOffset, maxL); // offset, length
238: SimpleCodeParser dtt = new SimpleCodeParser(txt, 0, txt
239: .length(), parseStartOffset);
240:
241: //t0 = System.currentTimeMillis();
242:
243: // reset styles for the given portion (QUICK but flickers!)
244: // NO ! doc.setCharacterAttributes(from, to-from, doc.getStyle("default"), true);
245:
246: SimpleCodeParser.Item w = null;
247: int n = 0;
248: int styledWords = 0;
249:
250: int lastStyledItemEnd = from;
251:
252: wl: while ((w = dtt.getNextItem()) != null) {
253: n++;
254: if (n > 1e7) {
255: System.out
256: .println("styler stopped : too much elements: "
257: + n);
258: return;
259: }
260:
261: if (shouldStop) {
262: //System.out.println("styler stopped through semaphor, n=" +n+", styledWords="+styledWords+", t="+(System.currentTimeMillis()-t0));
263: return;
264: }
265:
266: // start to format only from wanted position
267: if (w.getPositionEnd() < from - 20) // [May2006] Changed to end
268: {
269: continue wl;
270: }
271:
272: if (w.positionInDocument > to) {
273: //System.out.println("styler end reached at "+w.positionInDocument+" > "+to);
274: break wl;
275: }
276:
277: if (w.type == SimpleCodeParser.ItemType.Word) {
278: // BOST, jump over the non keywords !
279: // they will be styled below on next entry, using lastStyledItemEnd
280: if (!SyntaxUtils.javaKeywords.contains(w.word))
281: continue wl;
282: }
283:
284: styledWords++;
285:
286: // Slower than if resetting the whole region, but less flicker !
287: if (w.positionInDocument - lastStyledItemEnd > 0) {
288: // DOC: This method is thread safe, although most Swing methods are not !
289: try {
290: doc.setCharacterAttributes(lastStyledItemEnd,
291: w.positionInDocument
292: - lastStyledItemEnd, doc
293: .getStyle("default"), true);
294: } catch (Exception e2) // [Feb2008]: HAS some formatting errors that cause the styler to DIE !
295: {
296: System.out.println("styler formatting error: "
297: + e2.getMessage());
298: // e2.printStackTrace();
299: if (e2.getMessage() == null) {
300: MainEditorFrame.debugOut(e2);
301: }
302: }
303: }
304:
305: if (w.type == SimpleCodeParser.ItemType.Comment) {
306: doc.setCharacterAttributes(w.positionInDocument,
307: w.word.length(), doc.getCommentStyle(),
308: false);
309: } else if (w.type == SimpleCodeParser.ItemType.Litteral) {
310: doc.setCharacterAttributes(w.positionInDocument,
311: w.word.length(), doc.getLitteralStyle(),
312: false);
313: } else if (w.type == SimpleCodeParser.ItemType.Word) {
314:
315: if (SyntaxUtils.javaKeywords.contains(w.word)) {
316: if (n % 20 == 0 && from == 0) // only when parsed from start !, so we are sure not to start in a comment.
317: {
318: potentialRestartPositions
319: .add(w.positionInDocument);
320: }
321:
322: try {
323: doc.setCharacterAttributes(
324: w.positionInDocument, w.word
325: .length(), doc
326: .getKeywordStyle(), false);
327: } catch (Exception e2) {
328: System.out
329: .println("styler formatting error: "
330: + e2.getMessage());
331: // e2.printStackTrace();
332: if (e2.getMessage() == null) {
333: MainEditorFrame.debugOut(e2);
334: }
335: }
336: } else {
337: // [Aug2006]: the rest must be "cleared", otherwise, commenting and decommenting will reveal only keywords
338: // and the rest (variables stay commented)
339: //doc.setCharacterAttributes(w.positionInDocument, w.word.length(), doc.getStyle("default"), true);
340: continue wl; // BOOST ???
341: }
342: }
343:
344: lastStyledItemEnd = w.positionInDocument
345: + w.word.length();
346:
347: } // while wl
348:
349: // TODO: better !
350:
351: /* if(from<actualyValidStyledFrom || actualyValidStyledFrom==-1)
352: {
353: actualyValidStyledFrom = from;
354: }
355:
356: if(to>actualyValidStyledTo)
357: {
358: actualyValidStyledTo = to;
359: } */
360: actualyValidStyledFrom = from;
361: actualyValidStyledTo = lastStyledItemEnd; // was "to"
362:
363: long dt = System.currentTimeMillis() - t0;
364: if (dt > 1000) {
365: System.out.println("Doc styling took " + dt + " ms, n="
366: + n + ", styledWords=" + styledWords);
367: }
368: } catch (BadLocationException e) {
369: System.out.println("styler error: " + e.getMessage());
370: e.printStackTrace();
371: }
372: }
373:
374: private int getGoodRestartPosition(int from) {
375: int goodFrom = 0;
376: if (potentialRestartPositions.isEmpty())
377: return 0;
378: Iterator<Integer> it = potentialRestartPositions.iterator();
379: while (it.hasNext()) {
380: int f = it.next();
381:
382: if (f > from || f > actualyValidStyledTo) // no chance above
383: {
384: //System.out.println("restart from "+goodFrom);
385: return goodFrom;
386: }
387: goodFrom = f;
388: }
389: return goodFrom;
390:
391: }
392:
393: }
|