Bug 11701 fixed: Under Windows a CR was added in SciNotes each time the file is saved
[scilab.git] / scilab / modules / scinotes / src / java / org / scilab / modules / scinotes / ScilabDocument.java
1 /*
2  * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3  * Copyright (C) 2010 - Calixte DENIZET
4  *
5  * This file must be used under the terms of the CeCILL.
6  * This source file is licensed as described in the file COPYING, which
7  * you should have received as part of this distribution.  The terms
8  * are also available at
9  * http://www.cecill.info/licences/Licence_CeCILL_V2-en.txt
10  *
11  */
12
13 package org.scilab.modules.scinotes;
14
15 import java.nio.charset.Charset;
16
17 import java.util.Vector;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.TreeSet;
21 import java.util.Set;
22 import java.util.HashSet;
23 import java.util.Comparator;
24 import java.util.Iterator;
25
26 import javax.swing.text.GapContent;
27 import javax.swing.text.PlainDocument;
28 import javax.swing.text.BadLocationException;
29 import javax.swing.text.Element;
30 import javax.swing.text.AttributeSet;
31 import javax.swing.text.View;
32
33 import javax.swing.tree.DefaultMutableTreeNode;
34
35 import javax.swing.event.DocumentEvent;
36 import javax.swing.event.DocumentListener;
37
38 import org.scilab.modules.scinotes.utils.ConfigSciNotesManager;
39 import org.scilab.modules.scinotes.utils.SciNotesMessages;
40 import org.scilab.modules.console.utils.ScilabLaTeXViewer;
41
42 /**
43  * The class ScilabDocument is used to render a document .sci or .sce
44  * @author Calixte DENIZET
45  */
46 public class ScilabDocument extends PlainDocument implements DocumentListener {
47
48     private static final long serialVersionUID = -1227880612912063687L;
49
50     /**
51      * The EOL in mac OS
52      */
53     public static final String EOLMAC = "\r";
54
55     /**
56      * The EOL in windows OS
57      */
58     public static final String EOLWIN = "\r\n";
59
60     /**
61      * The EOL in unix OS
62      */
63     public static final String EOLUNIX = "\n";
64
65     private static final int GAPBUFFERCAPACITY = 2;
66     private static final String LINE_SEPARATOR = "line.separator";
67     private static final int INITFUNCTIONSNUMBER = 128;
68
69     private View view;
70     private List<String> saved = new Vector<String>();
71     private FunctionScanner funScanner;
72
73     private Set<String> functions = new HashSet<String>(INITFUNCTIONSNUMBER);
74
75     private boolean contentModified;
76     private boolean contentModifiedSinceBackup;
77     private boolean alphaOrder;
78
79     // Editor's default encoding is UTF-8
80     private String encoding;
81     private boolean updater = true;
82     private boolean binary;
83     private boolean autoIndent;
84     private boolean shouldMergeEdits;
85     private boolean undoManagerEnabled;
86     private CompoundUndoManager undo;
87
88     private ScilabEditorPane pane;
89     private boolean focused;
90
91     private String eolStyle;
92
93     /**
94      * Constructor
95      */
96     public ScilabDocument() {
97         this(true);
98     }
99
100     /**
101      * Constructor
102      */
103     public ScilabDocument(boolean paned) {
104         super(new GapContent(GAPBUFFERCAPACITY));
105         contentModified = false;
106
107         if (paned) {
108             setAsynchronousLoadPriority(2);
109
110             autoIndent = SciNotesOptions.getSciNotesDisplay().automaticIndent;
111             encoding = Charset.forName(SciNotesOptions.getSciNotesPreferences().encoding).toString();
112             eolStyle = SciNotesOptions.getSciNotesPreferences().eol;
113
114             undo = new CompoundUndoManager(this);
115             addUndoableEditListener(undo);
116             undoManagerEnabled = true;
117
118             contentModifiedSinceBackup = false;
119         }
120     }
121
122     /**
123      * Set the ScilabEditorPane associated with this doc
124      * @param pane the ScilabEditorPane
125      */
126     public void setEditorPane(ScilabEditorPane pane) {
127         this.pane = pane;
128     }
129
130     /**
131      * Get the ScilabEditorPane associated with this doc
132      * @return pane the ScilabEditorPane
133      */
134     public ScilabEditorPane getEditorPane() {
135         return pane;
136     }
137
138     /**
139      * Set to true of the document is focused in the EditorPane
140      * @param b the boolean
141      */
142     public void setFocused(boolean b) {
143         focused = b;
144     }
145
146     /**
147      * Create a lexer used to colorize the text
148      * @return ScilabLexer the lexer
149      */
150     public ScilabLexer createLexer() {
151         return new ScilabLexer(this);
152     }
153
154     /**
155      * @return the Set containing the functions name
156      */
157     public Set<String> getFunctionsInDoc() {
158         return functions;
159     }
160
161     /**
162      * Set the current view to render the code
163      * @param view the used view
164      */
165     public void setView(View view) {
166         this.view = view;
167     }
168
169     /**
170      * @return the current used view
171      */
172     public View getView() {
173         return view;
174     }
175
176     /**
177      * Get encoding
178      * @return String encoding
179      */
180     public String getEncoding() {
181         return encoding;
182     }
183
184     /**
185      * set Encoding
186      * @param encoding encoding
187      */
188     public void setEncoding(String encoding) {
189         this.encoding = encoding;
190     }
191
192     /**
193      * Set to true if it's a binary doc
194      * @param binary boolean
195      */
196     public void setBinary(boolean binary) {
197         this.binary = binary;
198     }
199
200     /**
201      * @return true if it's a binary file
202      */
203     public boolean getBinary() {
204         return binary;
205     }
206
207     /**
208      * set end of line value
209      * @param eol String
210      */
211     public void setEOL(String eol) {
212         this.eolStyle = eol;
213     }
214
215     /**
216      * get end of line
217      * @return end of line
218      */
219     public String getEOL() {
220         return this.eolStyle;
221     }
222
223     /**
224      * get end of line
225      * @return end of line
226      */
227     public String getDefaultEOL() {
228         return System.getProperty(LINE_SEPARATOR);
229     }
230
231     /**
232      * isUpdater
233      * @return boolean
234      */
235     public boolean isUpdater() {
236         return updater;
237     }
238
239     /**
240      * getAutoIndent
241      * @return boolean
242      */
243     public boolean getAutoIndent() {
244         return autoIndent;
245     }
246
247     /**
248      * setAutoIndent
249      * @param b boolean
250      */
251     public void setAutoIndent(boolean b) {
252         autoIndent = b;
253     }
254
255     /**
256      * setUpdater
257      * @param updaterDisabled boolean
258      */
259     public void setUpdater(boolean updaterDisabled) {
260         this.updater = updaterDisabled;
261     }
262
263     /**
264      * Get document text
265      * @return String
266      */
267     public String getText() {
268         try {
269             return getText(0, getLength());
270         } catch (BadLocationException e) {
271             return "";
272         }
273     }
274
275     public void addEOL() {
276         if (SciNotesOptions.getSciNotesPreferences().addLineTermination) {
277             int len = getLength();
278             if (getLength() >= 1) {
279                 try {
280                     String end = getText(len - 1, 1);
281                     if (!end.equals("\n")) {
282                         insertString(len, "\n", null);
283                     }
284                 } catch (BadLocationException e) {
285                     System.err.println(e);
286                 }
287             } else {
288                 try {
289                     insertString(len, "\n", null);
290                 } catch (BadLocationException e) {
291                     System.err.println(e);
292                 }
293             }
294         }
295     }
296
297     /**
298      * Begins a compound edit (for the undo)
299      */
300     public void mergeEditsBegin() {
301         undo.endCompoundEdit();
302         undo.startCompoundEdit();
303     }
304
305     /**
306      * Ends a compound edit (for the undo)
307      */
308     public void mergeEditsEnd() {
309         undo.endCompoundEdit();
310     }
311
312     /**
313      * getUndoManager
314      * @return CompoundUndoManager
315      */
316     public CompoundUndoManager getUndoManager() {
317         return undo;
318     }
319
320     /**
321      * disableUndoManager
322      */
323     public void disableUndoManager() {
324         if (undoManagerEnabled) {
325             this.removeUndoableEditListener(undo);
326             undoManagerEnabled = false;
327         }
328     }
329
330     /**
331      * enableUndoManager
332      */
333     public void enableUndoManager() {
334         if (!undoManagerEnabled) {
335             this.addUndoableEditListener(undo);
336             undoManagerEnabled = true;
337         }
338     }
339
340     /**
341      * isContentModified
342      * @return boolean
343      */
344     public boolean isContentModified() {
345         return contentModified;
346     }
347
348     /**
349      * isContentModifiedSinceBackup
350      * @return boolean
351      */
352     public boolean isContentModifiedSinceBackup() {
353         return contentModifiedSinceBackup;
354     }
355
356     /**
357      * setContentModified
358      * @param contentModified boolean
359      */
360     public void setContentModifiedSinceBackup(boolean contentModified) {
361         this.contentModifiedSinceBackup = contentModified;
362     }
363
364     /**
365      * setContentModified
366      * @param contentModified boolean
367      */
368     public void setContentModified(boolean contentModified) {
369         this.contentModified = contentModified;
370         if (pane != null && !contentModified) {
371             undo.setReference();
372             pane.updateTitle();
373         } else {
374             this.contentModifiedSinceBackup = true;
375         }
376     }
377
378     /**
379      * dump document on stderr with line positions
380      */
381     public void dump() {
382         readLock();
383         try {
384             Element root = getDefaultRootElement();
385             for (int i = 0; i != root.getElementCount(); ++i) {
386                 Element e = root.getElement(i);
387                 int start = e.getStartOffset();
388                 int end = e.getEndOffset();
389                 System.err.println("line " + i + " from: " + start + " to: " + end + ":|" + getText(start, end - start) + "|");
390             }
391         } catch (BadLocationException e) {
392             System.err.println(e);
393         }
394         readUnlock();
395     }
396
397     /**
398      * Search the position of the function name in the Document
399      * @param name the name of the function
400      * @return the position where to go or -1 if not found
401      */
402     public int searchFunctionByName(String name) {
403         Element root = getDefaultRootElement();
404         for (int i = 0; i < root.getElementCount(); i++) {
405             Element e = root.getElement(i);
406             if (e instanceof ScilabLeafElement) {
407                 ScilabLeafElement se = (ScilabLeafElement) e;
408                 if (se.isFunction() && se.getFunctionInfo().functionName.equals(name)) {
409                     return e.getStartOffset();
410                 }
411             }
412         }
413         return -1;
414     }
415
416     /**
417      * @return a list containing all the infos about functions available in this document
418      */
419     public List<FunctionScanner.FunctionInfo> getFunctionInfo() {
420         List<FunctionScanner.FunctionInfo> list = new ArrayList<FunctionScanner.FunctionInfo>();
421         Element root = getDefaultRootElement();
422         for (int i = 0; i < root.getElementCount(); i++) {
423             Element e = root.getElement(i);
424             if (e instanceof ScilabLeafElement) {
425                 ScilabLeafElement se = (ScilabLeafElement) e;
426                 if (se.isFunction()) {
427                     list.add(se.getFunctionInfo());
428                 }
429             }
430         }
431         return list;
432     }
433
434     /**
435      * @param alphaOrder is true if names must be sorted with alphabetic order
436      */
437     public void setAlphaOrderInTree(boolean alphaOrder) {
438         this.alphaOrder = alphaOrder;
439     }
440
441     /**
442      * Fill a tree with function's name according to alphabetic order or not
443      * @param base to fill
444      */
445     public synchronized void fillTreeFuns(DefaultMutableTreeNode base) {
446         Element root = getDefaultRootElement();
447         int nlines = root.getElementCount();
448         if (!alphaOrder) {
449             for (int i = 0; i < nlines; i++) {
450                 Element elem = root.getElement(i);
451                 if (elem instanceof ScilabDocument.ScilabLeafElement) {
452                     int type = ((ScilabDocument.ScilabLeafElement) elem).getType();
453                     switch (type) {
454                         case ScilabDocument.ScilabLeafElement.NOTHING :
455                             break;
456                         case ScilabDocument.ScilabLeafElement.FUN :
457                             base.add(new DefaultMutableTreeNode(elem));
458                             break;
459                         case ScilabDocument.ScilabLeafElement.ENDFUN :
460                             break;
461                         default :
462                             break;
463                     }
464                 }
465             }
466         } else {
467             Set<DefaultMutableTreeNode> set = new TreeSet<DefaultMutableTreeNode>(new Comparator<DefaultMutableTreeNode>() {
468                 public int compare(DefaultMutableTreeNode o1, DefaultMutableTreeNode o2) {
469                     ScilabLeafElement l1 = (ScilabLeafElement) o1.getUserObject();
470                     ScilabLeafElement l2 = (ScilabLeafElement) o2.getUserObject();
471                     int n = l1.getFunctionName().compareTo(l2.getFunctionName());
472                     if (n != 0) {
473                         return n;
474                     }
475                     return l1.getStartOffset() - l2.getStartOffset();
476                 }
477
478                 public boolean equals(DefaultMutableTreeNode o1, DefaultMutableTreeNode o2) {
479                     return false;
480                 }
481             });
482             for (int i = 0; i < nlines; i++) {
483                 Element elem = root.getElement(i);
484                 if (elem instanceof ScilabDocument.ScilabLeafElement) {
485                     int type = ((ScilabDocument.ScilabLeafElement) elem).getType();
486                     switch (type) {
487                         case ScilabDocument.ScilabLeafElement.NOTHING :
488                             break;
489                         case ScilabDocument.ScilabLeafElement.FUN :
490                             set.add(new DefaultMutableTreeNode(elem));
491                             break;
492                         case ScilabDocument.ScilabLeafElement.ENDFUN :
493                             break;
494                         default :
495                             break;
496                     }
497                 }
498             }
499             Iterator<DefaultMutableTreeNode> iter = set.iterator();
500             while (iter.hasNext()) {
501                 base.add(iter.next());
502             }
503         }
504     }
505
506     /**
507      * Fill a tree with anchor's name according to alphabetic order or not
508      * @param base to fill
509      */
510     public synchronized void fillTreeAnchors(DefaultMutableTreeNode base) {
511         Element root = getDefaultRootElement();
512         int nlines = root.getElementCount();
513         if (!alphaOrder) {
514             for (int i = 0; i < nlines; i++) {
515                 ScilabLeafElement elem = (ScilabLeafElement) root.getElement(i);
516                 if (elem.isAnchor()) {
517                     base.add(new DefaultMutableTreeNode(elem));
518                 }
519             }
520         } else {
521             Set<DefaultMutableTreeNode> set = new TreeSet<DefaultMutableTreeNode>(new Comparator<DefaultMutableTreeNode>() {
522                 public int compare(DefaultMutableTreeNode o1, DefaultMutableTreeNode o2) {
523                     ScilabLeafElement l1 = (ScilabLeafElement) o1.getUserObject();
524                     ScilabLeafElement l2 = (ScilabLeafElement) o2.getUserObject();
525                     int n = l1.getAnchorName().compareTo(l2.getAnchorName());
526                     if (n != 0) {
527                         return n;
528                     }
529                     return l1.getStartOffset() - l2.getStartOffset();
530                 }
531
532                 public boolean equals(DefaultMutableTreeNode o1, DefaultMutableTreeNode o2) {
533                     return false;
534                 }
535             });
536             for (int i = 0; i < nlines; i++) {
537                 ScilabLeafElement elem = (ScilabLeafElement) root.getElement(i);
538                 if (elem.isAnchor()) {
539                     set.add(new DefaultMutableTreeNode(elem));
540                 }
541             }
542             Iterator<DefaultMutableTreeNode> iter = set.iterator();
543             while (iter.hasNext()) {
544                 base.add(iter.next());
545             }
546         }
547     }
548
549     /**
550      * @return the first function name which appears in this doc or null
551      */
552     public String getFirstFunctionName() {
553         Element root = getDefaultRootElement();
554         for (int i = 0; i < root.getElementCount(); i++) {
555             Element e = root.getElement(i);
556             if (e instanceof ScilabLeafElement) {
557                 ScilabLeafElement se = (ScilabLeafElement) e;
558                 if (se.isFunction()) {
559                     return se.getFunctionInfo().functionName;
560                 }
561             }
562         }
563         return null;
564     }
565
566     /**
567      * @param line the number of the line where to begin the search
568      * @return the next anchor
569      */
570     public int nextAnchorFrom(int line) {
571         Element root = getDefaultRootElement();
572         for (int i = line + 1; i < root.getElementCount(); i++) {
573             ScilabLeafElement se = (ScilabLeafElement) root.getElement(i);
574             if (se.isAnchor()) {
575                 return i;
576             }
577         }
578         for (int i = 0; i < line; i++) {
579             ScilabLeafElement se = (ScilabLeafElement) root.getElement(i);
580             if (se.isAnchor()) {
581                 return i;
582             }
583         }
584
585         return -1;
586     }
587
588     /**
589      * @param line the number of the line where to begin the search
590      * @return the previous anchor
591      */
592     public int previousAnchorFrom(int line) {
593         Element root = getDefaultRootElement();
594         for (int i = line - 1; i >= 0; i--) {
595             ScilabLeafElement se = (ScilabLeafElement) root.getElement(i);
596             if (se.isAnchor()) {
597                 return i;
598             }
599         }
600         for (int i = root.getElementCount() - 1; i > line; i--) {
601             ScilabLeafElement se = (ScilabLeafElement) root.getElement(i);
602             if (se.isAnchor()) {
603                 return i;
604             }
605         }
606
607         return -1;
608     }
609
610     /**
611      * Get the anchors between two positions
612      * @param start the beginning
613      * @param end the end
614      * @return a list of the anchors
615      */
616     public List<Anchor> getAnchorsBetween(int start, int end) {
617         Element root = getDefaultRootElement();
618         int lineS = root.getElementIndex(start);
619         int lineE = root.getElementIndex(end);
620         List<Anchor> list = new ArrayList<Anchor>();
621         for (int i = lineS; i <= lineE; i++) {
622             final ScilabLeafElement se = (ScilabLeafElement) root.getElement(i);
623             if (se.isAnchor()) {
624                 list.add(new Anchor(i, se.getAnchorName()));
625             }
626         }
627
628         return list;
629     }
630
631     /**
632      * Get the lhs/rhs args used in a function declaration
633      * @param pos the position in the document
634      * @return the two lists containing args and returned values or null if we are not
635      * in a function
636      */
637     public List<String>[] getInOutArgs(int pos) {
638         Element root = getDefaultRootElement();
639         int index = root.getElementIndex(pos);
640         int compt = 0;
641         while (index != -1) {
642             ScilabLeafElement e = (ScilabLeafElement) root.getElement(index--);
643             switch (e.getType()) {
644                 case ScilabLeafElement.NOTHING :
645                     break;
646                 case ScilabLeafElement.FUN :
647                     if (compt == 0) {
648                         FunctionScanner.FunctionInfo info = e.getFunctionInfo();
649                         return new List[] {info.returnValues, info.argsValues};
650                     } else {
651                         compt++;
652                     }
653                     break;
654                 case ScilabLeafElement.ENDFUN :
655                     compt--;
656                     break;
657                 default :
658             }
659         }
660         return null;
661     }
662
663     /**
664      * Get the function name where the caret is
665      * @param pos the position in the document
666      * @return the nearest function name
667      */
668     public String getCurrentFunction(int pos) {
669         Element root = getDefaultRootElement();
670         int index = root.getElementIndex(pos);
671         int line = index;
672         int compt = 0;
673         while (index != -1) {
674             ScilabLeafElement e = (ScilabLeafElement) root.getElement(index--);
675             switch (e.getType()) {
676                 case ScilabLeafElement.NOTHING :
677                     break;
678                 case ScilabLeafElement.FUN :
679                     if (compt == 0) {
680                         String str = e.getFunctionInfo().functionName;
681                         if (str == null) {
682                             str = SciNotesMessages.UNKNOWN_FUNCTION;
683                         }
684                         return String.format(SciNotesMessages.POSFUN_IN_DOC, line + 1, pos - root.getElement(line).getStartOffset(), str, line - index);
685                     } else {
686                         compt++;
687                     }
688                     break;
689                 case ScilabLeafElement.ENDFUN :
690                     compt--;
691                     break;
692                 default :
693             }
694         }
695         return String.format(SciNotesMessages.POS_IN_DOC, line + 1, pos - root.getElement(line).getStartOffset());
696     }
697
698     /**
699      * lock
700      */
701     public void lock() {
702         super.writeLock();
703     }
704
705     /**
706      * unlock
707      */
708     public void unlock() {
709         super.writeUnlock();
710     }
711
712     /**
713      * Nothing !
714      * @param e the event
715      */
716     public void changedUpdate(DocumentEvent e) { }
717
718     /**
719      * Called when an insertion is made in the doc
720      * @param e the event
721      */
722     public void insertUpdate(DocumentEvent e) {
723         handleEvent(e);
724     }
725
726     /**
727      * Called when a remove is made in the doc
728      * @param e the event
729      */
730     public void removeUpdate(DocumentEvent e) {
731         handleEvent(e);
732     }
733
734     /**
735      * {@inheritDoc}
736      */
737     protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
738         // Fix bug 8277 in putting attr=null
739         // Java, by default, highlights the chinese chars when entered on keyboard
740         super.insertUpdate(chng, null);
741     }
742
743     /**
744      * @param ev the DocumentEvent to handle
745      */
746     private void handleEvent(DocumentEvent ev) {
747         if (!contentModified && pane != null) {
748             contentModified = true;
749             pane.updateTitle();
750         }
751         contentModifiedSinceBackup = true;
752
753         DocumentEvent.ElementChange chg = ev.getChange(getDefaultRootElement());
754         if (chg != null) {
755             Element[] added = chg.getChildrenAdded();
756             Element[] removed = chg.getChildrenRemoved();
757             if ((added != null && added.length > 0) || (removed != null && removed.length > 0)) {
758                 for (int i = 0; i < removed.length; i++) {
759                     String name = ((ScilabLeafElement) removed[i]).getFunctionName();
760                     if (name != null && name.length() != 0) {
761                         functions.remove(name);
762                     }
763                 }
764                 for (int i = 0; i < added.length; i++) {
765                     ((ScilabLeafElement) added[i]).resetType();
766                     ((ScilabLeafElement) added[i]).resetTypeWhenBroken();
767                     String name = ((ScilabLeafElement) added[i]).getFunctionName();
768                     if (name != null && name.length() != 0) {
769                         functions.add(name);
770                     }
771                 }
772             }
773         } else {
774             // change occurred only on one line
775             Element root = getDefaultRootElement();
776             int index = root.getElementIndex(ev.getOffset());
777             ScilabLeafElement line = (ScilabLeafElement) root.getElement(index);
778             boolean broken = line.isBroken();
779             if (pane != null && (line.resetType() == ScilabLeafElement.FUN || broken != line.isBroken()
780                                  || (index > 0 && ((ScilabLeafElement) root.getElement(index - 1)).isBroken()))) {
781                 pane.repaint();
782             }
783         }
784
785         if (pane != null) {
786             KeywordEvent e = pane.getKeywordEvent();
787
788             if (ScilabLexerConstants.isLaTeX(e.getType())) {
789                 try {
790                     int start = e.getStart();
791                     int end = start + e.getLength();
792                     String exp = getText(start, e.getLength());
793                     int height = pane.getScrollPane().getHeight() + pane.getScrollPane().getVerticalScrollBar().getValue();
794                     ScilabLaTeXViewer.displayExpressionIfVisible(pane, height, exp, start, end);
795                 } catch (BadLocationException ex) { }
796             }
797         }
798     }
799
800     /**
801      * @overload #createDefaultRoot
802      * @return the element base
803      */
804     protected AbstractElement createDefaultRoot() {
805         funScanner = new FunctionScanner(this);
806         BranchElement map = (BranchElement) createBranchElement(null, null);
807         Element line = createLeafElement(map, null, 0, 1);
808         map.replace(0, 0, new Element[] {line});
809         return map;
810     }
811
812     /**
813      * @overload #createLeafElement
814      * @param parent the parent Element
815      * @param a an AttributeSet
816      * @param p0 start in the doc
817      * @param p1 end in the doc
818      * @return the created LeafElement
819      */
820     protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
821         return new ScilabLeafElement(parent, a, p0, p1);
822     }
823
824     /**
825      * Inner class to consider the type of a line :
826      *  - FUN : function y=foo(x)
827      *  - ENDFUN : endfunction
828      *  - NOTHING : bla bla bla
829      * This inner class is useful to make a line numbering compatible with the whereami macro.
830      */
831     public class ScilabLeafElement extends LeafElement {
832
833         private static final long serialVersionUID = 4389590345677765643L;
834
835         /**
836          * Nothing in this line
837          */
838         public static final int NOTHING = 0;
839
840         /**
841          * function ... in this line
842          */
843         public static final int FUN = 1;
844
845         /**
846          * endfunction in this line
847          */
848         public static final int ENDFUN = 2;
849
850         /**
851          * broken line
852          */
853         public static final int BROKEN = 4;
854
855         private boolean visible = true;
856         private int type;
857         private FunctionScanner.FunctionInfo info;
858         private boolean broken;
859         private boolean brokenString;
860
861         private boolean anchor;
862         private String anchorName;
863
864         /**
865          * The same constructor as in LeafElement.
866          * @param parent the parent Element
867          * @param a an AttributeSet
868          * @param p0 start in the doc
869          * @param p1 end in the doc
870          */
871         public ScilabLeafElement(Element parent, AttributeSet a, int p0, int p1) {
872             super(parent, a, p0, p1);
873             type = funScanner.getLineType(p0, p1);
874             if ((type & BROKEN) == BROKEN) {
875                 broken = true;
876                 type -= BROKEN;
877             }
878
879             if (type == FUN) {
880                 info = funScanner.getFunctionInfo();
881             }
882         }
883
884         /**
885          * Reset type (normally called on a change in the document)
886          * @return the new type
887          */
888         public int resetType() {
889             String oldName = "";
890             if (type == FUN) {
891                 oldName = info.functionName;
892             }
893
894             type = funScanner.getLineType(getStartOffset(), getEndOffset());
895
896             if ((type & BROKEN) == BROKEN) {
897                 broken = true;
898                 type -= BROKEN;
899             } else {
900                 broken = false;
901             }
902
903             if (type == FUN) {
904                 info = funScanner.getFunctionInfo();
905                 if (info.functionName != null) {
906                     if (!info.functionName.equals(oldName)) {
907                         functions.remove(oldName);
908                         functions.add(info.functionName);
909                     }
910                 } else {
911                     functions.remove(oldName);
912                 }
913             }
914
915             resetTypeWhenBroken();
916
917             return type;
918         }
919
920         /**
921          * If the previous line is broken, then this line is a part of it
922          * so we need to resetType of the previous.
923          */
924         public void resetTypeWhenBroken() {
925             int p0 = getStartOffset();
926             if (p0 != 0) {
927                 Element parent = getParentElement();
928                 ScilabLeafElement elem = (ScilabLeafElement) parent.getElement(parent.getElementIndex(p0 - 1));
929                 if (elem.broken) {
930                     elem.resetType();
931                 }
932             }
933         }
934
935         /**
936          * @return the type of this line (FUN,...)
937          */
938         public int getType() {
939             return type;
940         }
941
942         /**
943          * @return the info about this line containing a function def
944          */
945         public FunctionScanner.FunctionInfo getFunctionInfo() {
946             return info;
947         }
948
949         /**
950          * @return if this line begins with function
951          */
952         public boolean isFunction() {
953             return type == FUN;
954         }
955
956         /**
957          * @return if this line begins with endfunction
958          */
959         public boolean isEndfunction() {
960             return type == ENDFUN;
961         }
962
963         /**
964          * @return if this line is visible
965          */
966         public boolean isVisible() {
967             return visible;
968         }
969
970         /**
971          * @param b true if this line is visible
972          */
973         public void setVisible(boolean b) {
974             visible = b;
975         }
976
977         /**
978          * @return if this line is broken
979          */
980         public boolean isBroken() {
981             return broken;
982         }
983
984         /**
985          * @param b true if this line is broken
986          */
987         public void setBroken(boolean b) {
988             broken = b;
989         }
990
991         /**
992          * @return if this line is broken
993          */
994         public boolean isBrokenString() {
995             return brokenString;
996         }
997
998         /**
999          * @param b true if this line is broken in a string
1000          */
1001         public void setBrokenString(boolean b) {
1002             brokenString = b;
1003             if (b) {
1004                 broken = true;
1005             }
1006         }
1007
1008         /**
1009          * @return the function's name
1010          */
1011         public String getFunctionName() {
1012             if (type == FUN) {
1013                 return info.functionName;
1014             }
1015             return "";
1016         }
1017
1018         /**
1019          * @return if this line is an anchor
1020          */
1021         public boolean isAnchor() {
1022             return anchor;
1023         }
1024
1025         /**
1026          * @param name the name of the anchor, if null remove
1027          * the anchor.
1028          */
1029         public void setAnchor(String name) {
1030             if (name == null) {
1031                 anchor = false;
1032                 return;
1033             }
1034
1035             anchor = true;
1036             anchorName = name;
1037         }
1038
1039         /**
1040          * @return the name of the anchor if exists
1041          */
1042         public String getAnchorName() {
1043             if (anchor) {
1044                 return anchorName;
1045             } else {
1046                 return "";
1047             }
1048         }
1049
1050         /**
1051          * @return String representation
1052          */
1053         public String toString() {
1054             if (anchor) {
1055                 if (type == FUN) {
1056                     return "function: " + info.functionName + " & anchor: " + anchorName;
1057                 } else {
1058                     return anchorName;
1059                 }
1060             }
1061             return info.functionName;
1062         }
1063     }
1064
1065     /**
1066      * Inner class to get infos on anchor
1067      */
1068     public class Anchor {
1069
1070         private int line;
1071         private String name;
1072
1073         /**
1074          * Default constructor
1075          * @param line the line where the anchor is
1076          * @param name the anchor's name
1077          */
1078         public Anchor(int line, String name) {
1079             this.line = line;
1080             this.name = name;
1081         }
1082
1083         /**
1084          * {@inheritDoc}
1085          */
1086         public String toString() {
1087             return name;
1088         }
1089
1090         /**
1091          * @return the line number
1092          */
1093         public int getLine() {
1094             return line;
1095         }
1096     }
1097 }