001: /*
002: * Copyright (c) 2001, Jacob Smullyan.
003: *
004: * This is part of SkunkDAV, a WebDAV client. See http://skunkdav.sourceforge.net/
005: * for the latest version.
006: *
007: * SkunkDAV is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License as published
009: * by the Free Software Foundation; either version 2, or (at your option)
010: * any later version.
011: *
012: * SkunkDAV is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with SkunkDAV; see the file COPYING. If not, write to the Free
019: * Software Foundation, 59 Temple Place - Suite 330, Boston, MA
020: * 02111-1307, USA.
021: */
022:
023: package org.skunk.swing.text.syntax;
024:
025: import java.io.CharArrayReader;
026: import java.io.IOException;
027: import java.lang.reflect.Constructor;
028: import java.util.Enumeration;
029: import java.util.HashMap;
030: import javax.swing.text.BadLocationException;
031: import javax.swing.text.Document;
032: import javax.swing.text.Element;
033: import javax.swing.text.Segment;
034: import javax.swing.text.Style;
035: import javax.swing.text.StyleContext;
036: import org.skunk.trace.Debug;
037: import org.skunk.util.GappedIntArray;
038:
039: //for test class
040: import java.awt.BorderLayout;
041: import java.awt.Color;
042: import java.awt.Dimension;
043: import java.awt.Font;
044: import java.awt.Insets;
045: import java.awt.event.KeyAdapter;
046: import java.awt.event.KeyEvent;
047: import java.awt.event.WindowEvent;
048: import java.awt.event.WindowAdapter;
049: import java.io.BufferedReader;
050: import java.io.FileReader;
051: import java.io.Reader;
052: import javax.swing.JFrame;
053: import javax.swing.JPanel;
054: import javax.swing.JScrollPane;
055: import javax.swing.text.AttributeSet;
056: import javax.swing.text.SimpleAttributeSet;
057: import javax.swing.text.StyleConstants;
058: import org.skunk.swing.text.TextEditorPane;
059:
060: public class Flexicizer implements SyntaxTokenizer {
061: private Segment segment;
062: private static HashMap scannerMap;
063:
064: static final String THIS_PACKAGE_PREFIX = "org.skunk.swing.text.syntax.";
065: public static final int DEFAULT_REPARSE_DISTANCE = 50;
066: private static int reparseDistance;
067:
068: static {
069: scannerMap = new HashMap();
070: try {
071: reparseDistance = Integer.parseInt(System
072: .getProperty("syntaxReparseDistance"));
073: if (Debug.DEBUG)
074: Debug.trace(Flexicizer.class, Debug.DP2,
075: "syntax reparse distance set to "
076: + reparseDistance);
077: } catch (Exception e) {
078: reparseDistance = DEFAULT_REPARSE_DISTANCE;
079: }
080: }
081:
082: public Flexicizer() {
083:
084: segment = new Segment();
085:
086: }
087:
088: private static FlexScanner _createScanner(FileMode fileMode,
089: Reader r) throws ModeNotSupportedException {
090: /*
091: I use the convention that the name of the scanner
092: class will be the name of the mode, capitalized,
093: + "FlexScanner". Does this suck? Probably.
094: */
095: int firstClassIndex = THIS_PACKAGE_PREFIX.length();
096: StringBuffer sb = new StringBuffer(THIS_PACKAGE_PREFIX).append(
097: fileMode.getName()).append("FlexScanner");
098: sb.setCharAt(firstClassIndex, Character.toUpperCase(sb
099: .charAt(firstClassIndex)));
100: try {
101: Class c = Class.forName(sb.toString());
102: Constructor struction = c
103: .getConstructor(new Class[] { Reader.class });
104: return (FlexScanner) struction
105: .newInstance(new Object[] { r });
106: } catch (Exception couldBeLots) {
107: throw new ModeNotSupportedException(fileMode, couldBeLots);
108: }
109: }
110:
111: /**
112: * obtain a scanner from the pool which matches the given file mode, installing the given reader.
113: * @param fileMode the FileMode of the scanner
114: * @param r the Reader which gives access to the text to be lexed
115: * @return the scanner, or null if no scanner can be found for the file mode.
116: */
117: protected static final FlexScanner getScanner(FileMode fileMode,
118: Reader r) {
119: if (fileMode == null)
120: return null;
121: FlexScanner scanner = null;
122: if (scannerMap.containsKey(fileMode))
123: scanner = (FlexScanner) scannerMap.get(fileMode);
124: if (scanner == null) {
125: try {
126: scanner = _createScanner(fileMode, r);
127: scannerMap.put(fileMode, scanner);
128: } catch (ModeNotSupportedException monster) {
129: if (Debug.DEBUG)
130: Debug.trace(Flexicizer.class, Debug.DP2, monster);
131: }
132: } else {
133: try {
134: scanner.yyreset(r);
135: } catch (IOException oyVeh) {
136: if (Debug.DEBUG)
137: Debug.trace(Flexicizer.class, Debug.DP2, oyVeh);
138: scanner = null;
139: }
140: }
141: return scanner;
142: }
143:
144: /**
145: * callback to tokenizer, which then tokenizes the necessary area around the indicated change.
146: * @param document the document
147: * @param offset the offset of the change to the document
148: * @param nInserted the numbers of characters inserted
149: * @param nRemoved the number of characters removed
150: */
151: public void tokenize(SyntaxDocument document, int offset,
152: int nInserted, int nRemoved) {
153: if (Debug.DEBUG)
154: Debug.trace(this , Debug.DP6, "offset: " + offset
155: + ", nInserted: " + nInserted + ", nRemoved"
156: + nRemoved);
157: int lastMod = offset + nInserted;
158: if (nInserted == 0 && nRemoved == 0)
159: return;
160: GappedIntArray styleBuffer = document.getStyleBuffer();
161: int beginParse = backwardContext(styleBuffer, document
162: .getParagraphElement(offset).getStartOffset());
163: int max = document.getLength() - 1;
164: if (max <= 0)
165: return;
166: int endParse = forwardContext(styleBuffer, document
167: .getParagraphElement(lastMod).getEndOffset(), max);
168: if (Debug.DEBUG)
169: Debug.trace(this , Debug.DP6, "beginParse: " + beginParse
170: + ", endParse: " + endParse);
171:
172: int lastStyle;
173: Element rootElement = document.getDefaultRootElement();
174: int lineNo = rootElement.getElementIndex(lastMod);
175:
176: while (!parseRange(document, beginParse, endParse)) {
177: if (Debug.DEBUG)
178: Debug
179: .trace(this , Debug.DP4,
180: "null returned from parseRange, will continue to parse");
181: Element elem = rootElement.getElement(++lineNo);
182: if (elem == null)
183: break;
184: beginParse = elem.getStartOffset();
185: endParse = Math.min(max, elem.getEndOffset());
186: }
187: }
188:
189: /**
190: * how many characters around the insertion point should be reparsed.
191: */
192: public int getReparseDistance() {
193: return reparseDistance;
194: }
195:
196: private int forwardContext(GappedIntArray styleBuffer, int forward,
197: int max) {
198: //sanity check
199: forward = Math.min(forward + reparseDistance, max);
200: int tmp1 = styleBuffer.get(forward);
201: int tmp2 = tmp1;
202: boolean reachedBarrier = false;
203: while (forward < max) {
204: tmp1 = styleBuffer.get(forward);
205: if (reachedBarrier && tmp1 != tmp2) {
206: forward--;
207: break;
208: }
209: if (tmp1 == SyntaxStyle.DEFAULT)
210: reachedBarrier = true;
211: tmp2 = tmp1;
212: forward++;
213: }
214: return forward;
215: }
216:
217: private int backwardContext(GappedIntArray styleBuffer, int back) {
218: back = Math.max(0, Math.min(back - reparseDistance, styleBuffer
219: .length() - 1));
220: if (back == 0)
221: return 0;
222: int tmp1 = styleBuffer.get(back);
223: int tmp2 = tmp1;
224: boolean reachedBarrier = false;
225: while (back > 0) {
226: tmp1 = styleBuffer.get(back);
227: if (reachedBarrier && tmp1 != tmp2) {
228: back++;
229: break;
230: }
231: if (tmp1 == SyntaxStyle.DEFAULT)
232: reachedBarrier = true;
233: tmp2 = tmp1;
234: back--;
235: }
236: return back;
237: }
238:
239: private boolean parseRange(SyntaxDocument doc, int beginParse,
240: int endParse) {
241: if (Debug.DEBUG) {
242: Debug.trace(this , Debug.DP6,
243: "in parseRange from {0} to {1}", new Object[] {
244: new Integer(beginParse),
245: new Integer(endParse) });
246: // commented out to prevent a dependency which would break the makefile. for testing only
247: // if (Debug.isDebug(this, Debug.DP8))
248: // {
249: // //this highlights the region being reparsed. For debugging only!
250: // Object o=doc.getProperty("editor");
251: // if (o!=null)
252: // {
253: // ((org.skunk.dav.client.gui.editor.SimpleTextEditor)o).select(beginParse, endParse);
254: // }
255: // }
256: }
257: try {
258: doc.getText(beginParse, endParse - beginParse, segment);
259: } catch (BadLocationException bellyache) {
260: if (Debug.DEBUG)
261: Debug.trace(this , Debug.DP2, bellyache);
262: return true;
263: }
264: if (Debug.DEBUG)
265: Debug.trace(this , Debug.DP8, segment);
266: FlexScanner scanner = getScanner(doc.getFileMode(),
267: new CharArrayReader(segment.array, segment.offset,
268: segment.count));
269:
270: GappedIntArray styleBuffer = doc.getStyleBuffer();
271: if (scanner == null) {
272: if (Debug.DEBUG)
273: Debug.trace(this , Debug.DP2, "scanner is null");
274: return true;
275: }
276:
277: scanner.setStyleBuffer(styleBuffer);
278: scanner.setOffset(beginParse);
279: int state = StyleBufferUtilities.getState(styleBuffer
280: .get(beginParse));
281: //int state=scanner.getStateCorrespondingToStyle(styleBuffer.get(beginParse));
282: if (Debug.DEBUG)
283: Debug.trace(this , Debug.DP6, "state at beginParse is "
284: + state);
285: scanner.yybegin(state);
286:
287: try {
288: if (Debug.DEBUG) {
289: Debug
290: .trace(this , Debug.DP8, "BEFORE: {0}",
291: styleBuffer);
292: Debug.trace(this , Debug.DP8, "gapOffset: "
293: + styleBuffer.getGapOffset());
294: Debug.trace(this , Debug.DP8, "currentGapSize: "
295: + styleBuffer.getCurrentGapSize());
296: }
297: scanner.scan();
298: if (Debug.DEBUG) {
299: Debug.trace(this , Debug.DP8, "AFTER: {0}", styleBuffer);
300: Debug.trace(this , Debug.DP8, "gapOffset: "
301: + styleBuffer.getGapOffset());
302: Debug.trace(this , Debug.DP8, "currentGapSize: "
303: + styleBuffer.getCurrentGapSize());
304: }
305:
306: } catch (IOException oyVeh) {
307: if (Debug.DEBUG)
308: Debug.trace(this , Debug.DP2, oyVeh);
309: return true;
310: }
311:
312: if (scanner.yystate() > 0)
313: return false;
314: else
315: return true;
316: }
317:
318: public static class Test extends JPanel {
319: private SyntaxDocument doc;
320: private TextEditorPane texter;
321:
322: public Test(String s) {
323: initComponents();
324:
325: doc.setFileMode(FileMode.getModeForFilename(s));
326: doc.setTokenizing(false);
327: texter.setText(slurp(s));
328: doc.setTokenizing(true);
329: doc.retokenizeAll();
330: }
331:
332: private void initComponents() {
333: texter = new TextEditorPane();
334: texter.setEditorKit(new SyntaxEditorKit());
335: doc = (SyntaxDocument) texter.getDocument();
336: texter.setWordWrap(false);
337: texter.setMargin(new Insets(15, 15, 15, 30));
338: texter.setBackground(new Color(0, 51, 51));
339: texter.setForeground(new Color(255, 255, 255));
340: texter.setFont(new Font("Monospaced", Font.PLAIN, 14));
341: texter.addKeyListener(new KeyAdapter() {
342: public void keyPressed(KeyEvent ke) {
343: if (ke.getKeyCode() == KeyEvent.VK_X) {
344: System.out.println(doc.getStyleBuffer());
345: }
346: }
347: });
348:
349: setLayout(new BorderLayout());
350: JScrollPane pane = new JScrollPane(texter);
351: add(pane, BorderLayout.CENTER);
352: setPreferredSize(new Dimension(500, 500));
353: }
354:
355: public static void main(String[] args) {
356: String filename = args[0];
357: //slurp file
358:
359: Test t = new Test(filename);
360: JFrame frame = new JFrame();
361: frame.addWindowListener(new WindowAdapter() {
362: public void windowClosing(WindowEvent woo) {
363: System.exit(0);
364: }
365: });
366: frame.setContentPane(t);
367: frame.pack();
368: frame.setBounds(20, 20, 850, 675);
369: frame.setVisible(true);
370: }
371:
372: private static String slurp(String filename) {
373: try {
374: Reader rdr = new BufferedReader(
375: new FileReader(filename));
376: char[] buffer = new char[1 << 12];
377: StringBuffer sb = new StringBuffer();
378: int read;
379: int offset = 0;
380: while (-1 != (read = rdr.read(buffer, 0, buffer.length))) {
381: if (read == buffer.length)
382: sb.append(buffer);
383: else
384: for (int i = 0; i < read; i++)
385: sb.append(buffer[i]);
386:
387: }
388: System.out.println("TOTAL LENGTH OF FILE: "
389: + sb.length());
390: return sb.toString();
391: } catch (IOException oyVeh) {
392: oyVeh.printStackTrace();
393: return "";
394: }
395: }
396: }
397: }
398:
399: /* $Log: Flexicizer.java,v $
400: /* Revision 1.12 2001/02/14 19:15:37 smulloni
401: /* modified usage of the style buffer to pack state information into its ints.
402: /* Previously, I had stipulated that a style could only be used in one state
403: /* per mode; that restriction may now be lifted.
404: /*
405: /* Revision 1.11 2001/02/13 22:53:50 smulloni
406: /* adding a syntax highlighting mode for STML (Skunk Template Markup Language);
407: /* fixed a bug in SyntaxStyle in reading default.styles.
408: /*
409: /* Revision 1.10 2001/02/13 01:37:37 smulloni
410: /* additional tweaks to html mode. Flexicizer bug (possible
411: /* ArrayIndexOutOfBoundsException on reparse) fixed.
412: /*
413: /* Revision 1.9 2001/02/11 22:55:39 smulloni
414: /* in order to capture undo and redo events, moving the style buffer and the
415: /* tokenizer into the SyntaxContent class, a subclass of GapContent.
416: /*
417: /* Revision 1.8 2001/02/09 21:02:37 smulloni
418: /* added very primitive html mode.
419: /*
420: /* Revision 1.7 2001/02/09 20:00:00 smulloni
421: /* fixed particularly nasty bug in GappedIntArray.set(int, int[]), and other
422: /* bugs in the syntax highlighting system.
423: /*
424: /* Revision 1.6 2001/02/08 18:57:23 smulloni
425: /* fixed several syntax highlighting bugs.
426: /*
427: /* Revision 1.5 2001/02/02 23:30:33 smulloni
428: /* adding customization features to the text editor.
429: /*
430: /* Revision 1.4 2001/01/30 23:03:19 smulloni
431: /* beginning of integration of syntax highlighting into SimpleTextEditor.
432: /*
433: /* Revision 1.3 2001/01/30 18:01:27 smulloni
434: /* first working beta of syntax highlighting. Nasty bug in
435: /* GappedIntArray.remove() fixed.
436: /*
437: /* Revision 1.2 2001/01/29 22:28:47 smulloni
438: /* syntax highlighting package now uses a custom view for painting the
439: /* highlights. Fixed bug in get(int, int[]) in GappedIntArray.
440: /*
441: /* Revision 1.1 2001/01/25 22:50:48 smulloni
442: /* integrating JFlex into still-broken syntax highlighting package.
443: /* */
|