2 * Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
3 * Copyright (C) 2010 - Calixte DENIZET
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
13 package org.scilab.modules.scinotes;
15 import java.nio.charset.Charset;
17 import java.util.Vector;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.TreeSet;
22 import java.util.HashSet;
23 import java.util.Comparator;
24 import java.util.Iterator;
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;
33 import javax.swing.tree.DefaultMutableTreeNode;
35 import javax.swing.event.DocumentEvent;
36 import javax.swing.event.DocumentListener;
38 import org.scilab.modules.scinotes.utils.ConfigSciNotesManager;
39 import org.scilab.modules.scinotes.utils.SciNotesMessages;
40 import org.scilab.modules.console.utils.ScilabLaTeXViewer;
43 * The class ScilabDocument is used to render a document .sci or .sce
44 * @author Calixte DENIZET
46 public class ScilabDocument extends PlainDocument implements DocumentListener {
48 private static final long serialVersionUID = -1227880612912063687L;
53 public static final String EOLMAC = "\r";
56 * The EOL in windows OS
58 public static final String EOLWIN = "\r\n";
63 public static final String EOLUNIX = "\n";
65 private static final int GAPBUFFERCAPACITY = 2;
66 private static final String LINE_SEPARATOR = "line.separator";
67 private static final int INITFUNCTIONSNUMBER = 128;
70 private List<String> saved = new Vector<String>();
71 private FunctionScanner funScanner;
73 private Set<String> functions = new HashSet<String>(INITFUNCTIONSNUMBER);
75 private boolean contentModified;
76 private boolean contentModifiedSinceBackup;
77 private boolean alphaOrder;
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;
88 private ScilabEditorPane pane;
89 private boolean focused;
91 private String eolStyle;
96 public ScilabDocument() {
103 public ScilabDocument(boolean paned) {
104 super(new GapContent(GAPBUFFERCAPACITY));
105 contentModified = false;
108 setAsynchronousLoadPriority(2);
110 autoIndent = SciNotesOptions.getSciNotesDisplay().automaticIndent;
111 encoding = Charset.forName(SciNotesOptions.getSciNotesPreferences().encoding).toString();
112 eolStyle = SciNotesOptions.getSciNotesPreferences().eol;
114 undo = new CompoundUndoManager(this);
115 addUndoableEditListener(undo);
116 undoManagerEnabled = true;
118 contentModifiedSinceBackup = false;
123 * Set the ScilabEditorPane associated with this doc
124 * @param pane the ScilabEditorPane
126 public void setEditorPane(ScilabEditorPane pane) {
131 * Get the ScilabEditorPane associated with this doc
132 * @return pane the ScilabEditorPane
134 public ScilabEditorPane getEditorPane() {
139 * Set to true of the document is focused in the EditorPane
140 * @param b the boolean
142 public void setFocused(boolean b) {
147 * Create a lexer used to colorize the text
148 * @return ScilabLexer the lexer
150 public ScilabLexer createLexer() {
151 return new ScilabLexer(this);
155 * @return the Set containing the functions name
157 public Set<String> getFunctionsInDoc() {
162 * Set the current view to render the code
163 * @param view the used view
165 public void setView(View view) {
170 * @return the current used view
172 public View getView() {
178 * @return String encoding
180 public String getEncoding() {
186 * @param encoding encoding
188 public void setEncoding(String encoding) {
189 this.encoding = encoding;
193 * Set to true if it's a binary doc
194 * @param binary boolean
196 public void setBinary(boolean binary) {
197 this.binary = binary;
201 * @return true if it's a binary file
203 public boolean getBinary() {
208 * set end of line value
211 public void setEOL(String eol) {
217 * @return end of line
219 public String getEOL() {
220 return this.eolStyle;
225 * @return end of line
227 public String getDefaultEOL() {
228 return System.getProperty(LINE_SEPARATOR);
235 public boolean isUpdater() {
243 public boolean getAutoIndent() {
251 public void setAutoIndent(boolean b) {
257 * @param updaterDisabled boolean
259 public void setUpdater(boolean updaterDisabled) {
260 this.updater = updaterDisabled;
267 public String getText() {
269 return getText(0, getLength());
270 } catch (BadLocationException e) {
275 public void addEOL() {
276 if (SciNotesOptions.getSciNotesPreferences().addLineTermination) {
277 int len = getLength();
278 int lenEOL = getEOL().length();
279 if (getLength() >= lenEOL) {
281 String end = getText(len - lenEOL, lenEOL);
282 if (!end.equals(getEOL())) {
283 insertString(len, getEOL(), null);
285 } catch (BadLocationException e) {
286 System.err.println(e);
290 insertString(len, getEOL(), null);
291 } catch (BadLocationException e) {
292 System.err.println(e);
299 * Begins a compound edit (for the undo)
301 public void mergeEditsBegin() {
302 undo.endCompoundEdit();
303 undo.startCompoundEdit();
307 * Ends a compound edit (for the undo)
309 public void mergeEditsEnd() {
310 undo.endCompoundEdit();
315 * @return CompoundUndoManager
317 public CompoundUndoManager getUndoManager() {
324 public void disableUndoManager() {
325 if (undoManagerEnabled) {
326 this.removeUndoableEditListener(undo);
327 undoManagerEnabled = false;
334 public void enableUndoManager() {
335 if (!undoManagerEnabled) {
336 this.addUndoableEditListener(undo);
337 undoManagerEnabled = true;
345 public boolean isContentModified() {
346 return contentModified;
350 * isContentModifiedSinceBackup
353 public boolean isContentModifiedSinceBackup() {
354 return contentModifiedSinceBackup;
359 * @param contentModified boolean
361 public void setContentModifiedSinceBackup(boolean contentModified) {
362 this.contentModifiedSinceBackup = contentModified;
367 * @param contentModified boolean
369 public void setContentModified(boolean contentModified) {
370 this.contentModified = contentModified;
371 if (pane != null && !contentModified) {
375 this.contentModifiedSinceBackup = true;
380 * dump document on stderr with line positions
385 Element root = getDefaultRootElement();
386 for (int i = 0; i != root.getElementCount(); ++i) {
387 Element e = root.getElement(i);
388 int start = e.getStartOffset();
389 int end = e.getEndOffset();
390 System.err.println("line " + i + " from: " + start + " to: " + end + ":|" + getText(start, end - start) + "|");
392 } catch (BadLocationException e) {
393 System.err.println(e);
399 * Search the position of the function name in the Document
400 * @param name the name of the function
401 * @return the position where to go or -1 if not found
403 public int searchFunctionByName(String name) {
404 Element root = getDefaultRootElement();
405 for (int i = 0; i < root.getElementCount(); i++) {
406 Element e = root.getElement(i);
407 if (e instanceof ScilabLeafElement) {
408 ScilabLeafElement se = (ScilabLeafElement) e;
409 if (se.isFunction() && se.getFunctionInfo().functionName.equals(name)) {
410 return e.getStartOffset();
418 * @return a list containing all the infos about functions available in this document
420 public List<FunctionScanner.FunctionInfo> getFunctionInfo() {
421 List<FunctionScanner.FunctionInfo> list = new ArrayList<FunctionScanner.FunctionInfo>();
422 Element root = getDefaultRootElement();
423 for (int i = 0; i < root.getElementCount(); i++) {
424 Element e = root.getElement(i);
425 if (e instanceof ScilabLeafElement) {
426 ScilabLeafElement se = (ScilabLeafElement) e;
427 if (se.isFunction()) {
428 list.add(se.getFunctionInfo());
436 * @param alphaOrder is true if names must be sorted with alphabetic order
438 public void setAlphaOrderInTree(boolean alphaOrder) {
439 this.alphaOrder = alphaOrder;
443 * Fill a tree with function's name according to alphabetic order or not
444 * @param base to fill
446 public synchronized void fillTreeFuns(DefaultMutableTreeNode base) {
447 Element root = getDefaultRootElement();
448 int nlines = root.getElementCount();
450 for (int i = 0; i < nlines; i++) {
451 Element elem = root.getElement(i);
452 if (elem instanceof ScilabDocument.ScilabLeafElement) {
453 int type = ((ScilabDocument.ScilabLeafElement) elem).getType();
455 case ScilabDocument.ScilabLeafElement.NOTHING :
457 case ScilabDocument.ScilabLeafElement.FUN :
458 base.add(new DefaultMutableTreeNode(elem));
460 case ScilabDocument.ScilabLeafElement.ENDFUN :
468 Set<DefaultMutableTreeNode> set = new TreeSet<DefaultMutableTreeNode>(new Comparator<DefaultMutableTreeNode>() {
469 public int compare(DefaultMutableTreeNode o1, DefaultMutableTreeNode o2) {
470 ScilabLeafElement l1 = (ScilabLeafElement) o1.getUserObject();
471 ScilabLeafElement l2 = (ScilabLeafElement) o2.getUserObject();
472 int n = l1.getFunctionName().compareTo(l2.getFunctionName());
476 return l1.getStartOffset() - l2.getStartOffset();
479 public boolean equals(DefaultMutableTreeNode o1, DefaultMutableTreeNode o2) {
483 for (int i = 0; i < nlines; i++) {
484 Element elem = root.getElement(i);
485 if (elem instanceof ScilabDocument.ScilabLeafElement) {
486 int type = ((ScilabDocument.ScilabLeafElement) elem).getType();
488 case ScilabDocument.ScilabLeafElement.NOTHING :
490 case ScilabDocument.ScilabLeafElement.FUN :
491 set.add(new DefaultMutableTreeNode(elem));
493 case ScilabDocument.ScilabLeafElement.ENDFUN :
500 Iterator<DefaultMutableTreeNode> iter = set.iterator();
501 while (iter.hasNext()) {
502 base.add(iter.next());
508 * Fill a tree with anchor's name according to alphabetic order or not
509 * @param base to fill
511 public synchronized void fillTreeAnchors(DefaultMutableTreeNode base) {
512 Element root = getDefaultRootElement();
513 int nlines = root.getElementCount();
515 for (int i = 0; i < nlines; i++) {
516 ScilabLeafElement elem = (ScilabLeafElement) root.getElement(i);
517 if (elem.isAnchor()) {
518 base.add(new DefaultMutableTreeNode(elem));
522 Set<DefaultMutableTreeNode> set = new TreeSet<DefaultMutableTreeNode>(new Comparator<DefaultMutableTreeNode>() {
523 public int compare(DefaultMutableTreeNode o1, DefaultMutableTreeNode o2) {
524 ScilabLeafElement l1 = (ScilabLeafElement) o1.getUserObject();
525 ScilabLeafElement l2 = (ScilabLeafElement) o2.getUserObject();
526 int n = l1.getAnchorName().compareTo(l2.getAnchorName());
530 return l1.getStartOffset() - l2.getStartOffset();
533 public boolean equals(DefaultMutableTreeNode o1, DefaultMutableTreeNode o2) {
537 for (int i = 0; i < nlines; i++) {
538 ScilabLeafElement elem = (ScilabLeafElement) root.getElement(i);
539 if (elem.isAnchor()) {
540 set.add(new DefaultMutableTreeNode(elem));
543 Iterator<DefaultMutableTreeNode> iter = set.iterator();
544 while (iter.hasNext()) {
545 base.add(iter.next());
551 * @return the first function name which appears in this doc or null
553 public String getFirstFunctionName() {
554 Element root = getDefaultRootElement();
555 for (int i = 0; i < root.getElementCount(); i++) {
556 Element e = root.getElement(i);
557 if (e instanceof ScilabLeafElement) {
558 ScilabLeafElement se = (ScilabLeafElement) e;
559 if (se.isFunction()) {
560 return se.getFunctionInfo().functionName;
568 * @param line the number of the line where to begin the search
569 * @return the next anchor
571 public int nextAnchorFrom(int line) {
572 Element root = getDefaultRootElement();
573 for (int i = line + 1; i < root.getElementCount(); i++) {
574 ScilabLeafElement se = (ScilabLeafElement) root.getElement(i);
579 for (int i = 0; i < line; i++) {
580 ScilabLeafElement se = (ScilabLeafElement) root.getElement(i);
590 * @param line the number of the line where to begin the search
591 * @return the previous anchor
593 public int previousAnchorFrom(int line) {
594 Element root = getDefaultRootElement();
595 for (int i = line - 1; i >= 0; i--) {
596 ScilabLeafElement se = (ScilabLeafElement) root.getElement(i);
601 for (int i = root.getElementCount() - 1; i > line; i--) {
602 ScilabLeafElement se = (ScilabLeafElement) root.getElement(i);
612 * Get the anchors between two positions
613 * @param start the beginning
615 * @return a list of the anchors
617 public List<Anchor> getAnchorsBetween(int start, int end) {
618 Element root = getDefaultRootElement();
619 int lineS = root.getElementIndex(start);
620 int lineE = root.getElementIndex(end);
621 List<Anchor> list = new ArrayList<Anchor>();
622 for (int i = lineS; i <= lineE; i++) {
623 final ScilabLeafElement se = (ScilabLeafElement) root.getElement(i);
625 list.add(new Anchor(i, se.getAnchorName()));
633 * Get the lhs/rhs args used in a function declaration
634 * @param pos the position in the document
635 * @return the two lists containing args and returned values or null if we are not
638 public List<String>[] getInOutArgs(int pos) {
639 Element root = getDefaultRootElement();
640 int index = root.getElementIndex(pos);
642 while (index != -1) {
643 ScilabLeafElement e = (ScilabLeafElement) root.getElement(index--);
644 switch (e.getType()) {
645 case ScilabLeafElement.NOTHING :
647 case ScilabLeafElement.FUN :
649 FunctionScanner.FunctionInfo info = e.getFunctionInfo();
650 return new List[] {info.returnValues, info.argsValues};
655 case ScilabLeafElement.ENDFUN :
665 * Get the function name where the caret is
666 * @param pos the position in the document
667 * @return the nearest function name
669 public String getCurrentFunction(int pos) {
670 Element root = getDefaultRootElement();
671 int index = root.getElementIndex(pos);
674 while (index != -1) {
675 ScilabLeafElement e = (ScilabLeafElement) root.getElement(index--);
676 switch (e.getType()) {
677 case ScilabLeafElement.NOTHING :
679 case ScilabLeafElement.FUN :
681 String str = e.getFunctionInfo().functionName;
683 str = SciNotesMessages.UNKNOWN_FUNCTION;
685 return String.format(SciNotesMessages.POSFUN_IN_DOC, line + 1, pos - root.getElement(line).getStartOffset(), str, line - index);
690 case ScilabLeafElement.ENDFUN :
696 return String.format(SciNotesMessages.POS_IN_DOC, line + 1, pos - root.getElement(line).getStartOffset());
709 public void unlock() {
717 public void changedUpdate(DocumentEvent e) { }
720 * Called when an insertion is made in the doc
723 public void insertUpdate(DocumentEvent e) {
728 * Called when a remove is made in the doc
731 public void removeUpdate(DocumentEvent e) {
738 protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) {
739 // Fix bug 8277 in putting attr=null
740 // Java, by default, highlights the chinese chars when entered on keyboard
741 super.insertUpdate(chng, null);
745 * @param ev the DocumentEvent to handle
747 private void handleEvent(DocumentEvent ev) {
748 if (!contentModified && pane != null) {
749 contentModified = true;
752 contentModifiedSinceBackup = true;
754 DocumentEvent.ElementChange chg = ev.getChange(getDefaultRootElement());
756 Element[] added = chg.getChildrenAdded();
757 Element[] removed = chg.getChildrenRemoved();
758 if ((added != null && added.length > 0) || (removed != null && removed.length > 0)) {
759 for (int i = 0; i < removed.length; i++) {
760 String name = ((ScilabLeafElement) removed[i]).getFunctionName();
761 if (name != null && name.length() != 0) {
762 functions.remove(name);
765 for (int i = 0; i < added.length; i++) {
766 ((ScilabLeafElement) added[i]).resetType();
767 ((ScilabLeafElement) added[i]).resetTypeWhenBroken();
768 String name = ((ScilabLeafElement) added[i]).getFunctionName();
769 if (name != null && name.length() != 0) {
775 // change occurred only on one line
776 Element root = getDefaultRootElement();
777 int index = root.getElementIndex(ev.getOffset());
778 ScilabLeafElement line = (ScilabLeafElement) root.getElement(index);
779 boolean broken = line.isBroken();
780 if (pane != null && (line.resetType() == ScilabLeafElement.FUN || broken != line.isBroken()
781 || (index > 0 && ((ScilabLeafElement) root.getElement(index - 1)).isBroken()))) {
787 KeywordEvent e = pane.getKeywordEvent();
789 if (ScilabLexerConstants.isLaTeX(e.getType())) {
791 int start = e.getStart();
792 int end = start + e.getLength();
793 String exp = getText(start, e.getLength());
794 int height = pane.getScrollPane().getHeight() + pane.getScrollPane().getVerticalScrollBar().getValue();
795 ScilabLaTeXViewer.displayExpressionIfVisible(pane, height, exp, start, end);
796 } catch (BadLocationException ex) { }
802 * @overload #createDefaultRoot
803 * @return the element base
805 protected AbstractElement createDefaultRoot() {
806 funScanner = new FunctionScanner(this);
807 BranchElement map = (BranchElement) createBranchElement(null, null);
808 Element line = createLeafElement(map, null, 0, 1);
809 map.replace(0, 0, new Element[] {line});
814 * @overload #createLeafElement
815 * @param parent the parent Element
816 * @param a an AttributeSet
817 * @param p0 start in the doc
818 * @param p1 end in the doc
819 * @return the created LeafElement
821 protected Element createLeafElement(Element parent, AttributeSet a, int p0, int p1) {
822 return new ScilabLeafElement(parent, a, p0, p1);
826 * Inner class to consider the type of a line :
827 * - FUN : function y=foo(x)
828 * - ENDFUN : endfunction
829 * - NOTHING : bla bla bla
830 * This inner class is useful to make a line numbering compatible with the whereami macro.
832 public class ScilabLeafElement extends LeafElement {
834 private static final long serialVersionUID = 4389590345677765643L;
837 * Nothing in this line
839 public static final int NOTHING = 0;
842 * function ... in this line
844 public static final int FUN = 1;
847 * endfunction in this line
849 public static final int ENDFUN = 2;
854 public static final int BROKEN = 4;
856 private boolean visible = true;
858 private FunctionScanner.FunctionInfo info;
859 private boolean broken;
860 private boolean brokenString;
862 private boolean anchor;
863 private String anchorName;
866 * The same constructor as in LeafElement.
867 * @param parent the parent Element
868 * @param a an AttributeSet
869 * @param p0 start in the doc
870 * @param p1 end in the doc
872 public ScilabLeafElement(Element parent, AttributeSet a, int p0, int p1) {
873 super(parent, a, p0, p1);
874 type = funScanner.getLineType(p0, p1);
875 if ((type & BROKEN) == BROKEN) {
881 info = funScanner.getFunctionInfo();
886 * Reset type (normally called on a change in the document)
887 * @return the new type
889 public int resetType() {
892 oldName = info.functionName;
895 type = funScanner.getLineType(getStartOffset(), getEndOffset());
897 if ((type & BROKEN) == BROKEN) {
905 info = funScanner.getFunctionInfo();
906 if (info.functionName != null) {
907 if (!info.functionName.equals(oldName)) {
908 functions.remove(oldName);
909 functions.add(info.functionName);
912 functions.remove(oldName);
916 resetTypeWhenBroken();
922 * If the previous line is broken, then this line is a part of it
923 * so we need to resetType of the previous.
925 public void resetTypeWhenBroken() {
926 int p0 = getStartOffset();
928 Element parent = getParentElement();
929 ScilabLeafElement elem = (ScilabLeafElement) parent.getElement(parent.getElementIndex(p0 - 1));
937 * @return the type of this line (FUN,...)
939 public int getType() {
944 * @return the info about this line containing a function def
946 public FunctionScanner.FunctionInfo getFunctionInfo() {
951 * @return if this line begins with function
953 public boolean isFunction() {
958 * @return if this line begins with endfunction
960 public boolean isEndfunction() {
961 return type == ENDFUN;
965 * @return if this line is visible
967 public boolean isVisible() {
972 * @param b true if this line is visible
974 public void setVisible(boolean b) {
979 * @return if this line is broken
981 public boolean isBroken() {
986 * @param b true if this line is broken
988 public void setBroken(boolean b) {
993 * @return if this line is broken
995 public boolean isBrokenString() {
1000 * @param b true if this line is broken in a string
1002 public void setBrokenString(boolean b) {
1010 * @return the function's name
1012 public String getFunctionName() {
1014 return info.functionName;
1020 * @return if this line is an anchor
1022 public boolean isAnchor() {
1027 * @param name the name of the anchor, if null remove
1030 public void setAnchor(String name) {
1041 * @return the name of the anchor if exists
1043 public String getAnchorName() {
1052 * @return String representation
1054 public String toString() {
1057 return "function: " + info.functionName + " & anchor: " + anchorName;
1062 return info.functionName;
1067 * Inner class to get infos on anchor
1069 public class Anchor {
1072 private String name;
1075 * Default constructor
1076 * @param line the line where the anchor is
1077 * @param name the anchor's name
1079 public Anchor(int line, String name) {
1087 public String toString() {
1092 * @return the line number
1094 public int getLine() {