001: /*
002: * Mode.java - jEdit editing mode
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 1998, 1999, 2000 Slava Pestov
007: * Copyright (C) 1999 mike dillon
008: *
009: * This program is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; either version 2
012: * of the License, or any later version.
013: *
014: * This program is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017: * GNU General Public License for more details.
018: *
019: * You should have received a copy of the GNU General Public License
020: * along with this program; if not, write to the Free Software
021: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
022: */
023:
024: package org.gjt.sp.jedit;
025:
026: //{{{ Imports
027: import java.lang.reflect.Method;
028: import java.util.Hashtable;
029: import java.util.Collections;
030: import java.util.LinkedList;
031: import java.util.List;
032: import java.util.Map;
033: import java.util.regex.Pattern;
034: import java.util.regex.PatternSyntaxException;
035: import org.gjt.sp.jedit.indent.DeepIndentRule;
036: import org.gjt.sp.jedit.indent.IndentRule;
037: import org.gjt.sp.jedit.indent.IndentRuleFactory;
038: import org.gjt.sp.jedit.indent.WhitespaceRule;
039: import org.gjt.sp.jedit.syntax.TokenMarker;
040: import org.gjt.sp.jedit.syntax.ModeProvider;
041: import org.gjt.sp.util.Log;
042: import org.gjt.sp.util.StandardUtilities;
043:
044: //}}}
045:
046: /**
047: * An edit mode defines specific settings for editing some type of file.
048: * One instance of this class is created for each supported edit mode.
049: *
050: * @author Slava Pestov
051: * @version $Id: Mode.java 9883 2007-06-29 22:03:35Z kpouer $
052: */
053: public class Mode {
054: //{{{ Mode constructor
055: /**
056: * Creates a new edit mode.
057: *
058: * @param name The name used in mode listings and to query mode
059: * properties
060: * @see #getProperty(String)
061: */
062: public Mode(String name) {
063: this .name = name;
064: this .ignoreWhitespace = true;
065: props = new Hashtable<String, Object>();
066: } //}}}
067:
068: //{{{ init() method
069: /**
070: * Initializes the edit mode. Should be called after all properties
071: * are loaded and set.
072: */
073: public void init() {
074: try {
075: String filenameGlob = (String) getProperty("filenameGlob");
076: if (filenameGlob != null && filenameGlob.length() != 0) {
077: filenameRE = Pattern.compile(StandardUtilities
078: .globToRE(filenameGlob),
079: Pattern.CASE_INSENSITIVE);
080: }
081:
082: String firstlineGlob = (String) getProperty("firstlineGlob");
083: if (firstlineGlob != null && firstlineGlob.length() != 0) {
084: firstlineRE = Pattern.compile(StandardUtilities
085: .globToRE(firstlineGlob),
086: Pattern.CASE_INSENSITIVE);
087: }
088: } catch (PatternSyntaxException re) {
089: Log.log(Log.ERROR, this , "Invalid filename/firstline"
090: + " globs in mode " + name);
091: Log.log(Log.ERROR, this , re);
092: }
093:
094: // Fix for this bug:
095: // -- Put a mode into the user dir with the same name as one
096: // on the system dir.
097: // -- Reload edit modes.
098: // -- Old mode from system dir still used for highlighting
099: // until jEdit restart.
100: marker = null;
101: } //}}}
102:
103: //{{{ getTokenMarker() method
104: /**
105: * Returns the token marker for this mode.
106: */
107: public TokenMarker getTokenMarker() {
108: loadIfNecessary();
109: return marker;
110: } //}}}
111:
112: //{{{ setTokenMarker() method
113: /**
114: * Sets the token marker for this mode.
115: * @param marker The new token marker
116: */
117: public void setTokenMarker(TokenMarker marker) {
118: this .marker = marker;
119: } //}}}
120:
121: //{{{ loadIfNecessary() method
122: /**
123: * Loads the mode from disk if it hasn't been loaded already.
124: * @since jEdit 2.5pre3
125: */
126: public void loadIfNecessary() {
127: if (marker == null) {
128: ModeProvider.instance.loadMode(this );
129: if (marker == null)
130: Log
131: .log(Log.ERROR, this ,
132: "Mode not correctly loaded, token marker is still null");
133: }
134: } //}}}
135:
136: //{{{ getProperty() method
137: /**
138: * Returns a mode property.
139: * @param key The property name
140: *
141: * @since jEdit 2.2pre1
142: */
143: public Object getProperty(String key) {
144: Object value = props.get(key);
145: if (value != null)
146: return value;
147: return null;
148: } //}}}
149:
150: //{{{ getBooleanProperty() method
151: /**
152: * Returns the value of a boolean property.
153: * @param key The property name
154: *
155: * @since jEdit 2.5pre3
156: */
157: public boolean getBooleanProperty(String key) {
158: Object value = getProperty(key);
159: if ("true".equals(value) || "on".equals(value)
160: || "yes".equals(value))
161: return true;
162: else
163: return false;
164: } //}}}
165:
166: //{{{ setProperty() method
167: /**
168: * Sets a mode property.
169: * @param key The property name
170: * @param value The property value
171: */
172: public void setProperty(String key, Object value) {
173: props.put(key, value);
174: } //}}}
175:
176: //{{{ unsetProperty() method
177: /**
178: * Unsets a mode property.
179: * @param key The property name
180: * @since jEdit 3.2pre3
181: */
182: public void unsetProperty(String key) {
183: props.remove(key);
184: } //}}}
185:
186: //{{{ setProperties() method
187: /**
188: * Should only be called by <code>XModeHandler</code>.
189: * @since jEdit 4.0pre3
190: */
191: public void setProperties(Map props) {
192: if (props == null)
193: props = new Hashtable<String, Object>();
194:
195: ignoreWhitespace = !"false".equalsIgnoreCase((String) props
196: .get("ignoreWhitespace"));
197:
198: // need to carry over file name and first line globs because they are
199: // not given to us by the XMode handler, but instead are filled in by
200: // the catalog loader.
201: String filenameGlob = (String) this .props.get("filenameGlob");
202: String firstlineGlob = (String) this .props.get("firstlineGlob");
203: String filename = (String) this .props.get("file");
204: this .props = props;
205: if (filenameGlob != null)
206: props.put("filenameGlob", filenameGlob);
207: if (firstlineGlob != null)
208: props.put("firstlineGlob", firstlineGlob);
209: if (filename != null)
210: props.put("file", filename);
211: } //}}}
212:
213: //{{{ accept() method
214: /**
215: * Returns if the edit mode is suitable for editing the specified
216: * file. The buffer name and first line is checked against the
217: * file name and first line globs, respectively.
218: * @param fileName The buffer's name
219: * @param firstLine The first line of the buffer
220: *
221: * @since jEdit 3.2pre3
222: */
223: public boolean accept(String fileName, String firstLine) {
224: if (filenameRE != null
225: && filenameRE.matcher(fileName).matches())
226: return true;
227:
228: if (firstlineRE != null
229: && firstlineRE.matcher(firstLine).matches())
230: return true;
231:
232: return false;
233: } //}}}
234:
235: //{{{ getName() method
236: /**
237: * Returns the internal name of this edit mode.
238: */
239: public String getName() {
240: return name;
241: } //}}}
242:
243: //{{{ toString() method
244: /**
245: * Returns a string representation of this edit mode.
246: */
247: public String toString() {
248: return name;
249: } //}}}
250:
251: //{{{ getIgnoreWhitespace() method
252: public boolean getIgnoreWhitespace() {
253: return ignoreWhitespace;
254: } //}}}
255:
256: //{{{ Indent rules
257:
258: public synchronized List<IndentRule> getIndentRules() {
259: if (indentRules == null) {
260: initIndentRules();
261: }
262: return indentRules;
263: }
264:
265: public synchronized boolean isElectricKey(char ch) {
266: if (electricKeys == null) {
267: String[] props = { "indentOpenBrackets",
268: "indentCloseBrackets", "electricKeys" };
269:
270: StringBuilder buf = new StringBuilder();
271: for (int i = 0; i < props.length; i++) {
272: String prop = (String) getProperty(props[i]);
273: if (prop != null)
274: buf.append(prop);
275: }
276:
277: electricKeys = buf.toString();
278: }
279:
280: return (electricKeys.indexOf(ch) >= 0);
281: }
282:
283: private void initIndentRules() {
284: List<IndentRule> rules = new LinkedList<IndentRule>();
285:
286: String[] regexpProps = { "indentNextLine", "indentNextLines" };
287:
288: for (int i = 0; i < regexpProps.length; i++) {
289: IndentRule rule = createRegexpIndentRule(regexpProps[i]);
290: if (rule != null)
291: rules.add(rule);
292: }
293:
294: String[] bracketProps = { "indentOpenBracket",
295: "indentCloseBracket", "unalignedOpenBracket",
296: "unalignedCloseBracket", };
297:
298: for (int i = 0; i < bracketProps.length; i++) {
299: createBracketIndentRules(bracketProps[i], rules);
300: }
301:
302: String[] finalProps = { "unindentThisLine", "unindentNextLines" };
303:
304: for (int i = 0; i < finalProps.length; i++) {
305: IndentRule rule = createRegexpIndentRule(finalProps[i]);
306: if (rule != null)
307: rules.add(rule);
308: }
309:
310: if (getBooleanProperty("deepIndent")) {
311: String unalignedOpenBrackets = (String) getProperty("unalignedOpenBrackets");
312: if (unalignedOpenBrackets != null) {
313: for (int i = 0; i < unalignedOpenBrackets.length(); i++) {
314: char openChar = unalignedOpenBrackets.charAt(i);
315: char closeChar = TextUtilities
316: .getComplementaryBracket(openChar, null);
317: if (closeChar != '\0')
318: rules.add(new DeepIndentRule(openChar,
319: closeChar));
320: }
321: }
322: }
323:
324: if (!getIgnoreWhitespace())
325: rules.add(new WhitespaceRule());
326:
327: indentRules = Collections.unmodifiableList(rules);
328: }
329:
330: private IndentRule createRegexpIndentRule(String prop) {
331: String value = (String) getProperty(prop);
332:
333: try {
334: if (value != null) {
335: Method m = IndentRuleFactory.class.getMethod(prop,
336: new Class[] { String.class });
337: return (IndentRule) m.invoke(null, value);
338: }
339: } catch (Exception e) {
340: Log.log(Log.ERROR, this , "Bad indent rule " + prop + '='
341: + value + ':');
342: Log.log(Log.ERROR, this , e);
343: }
344:
345: return null;
346: }
347:
348: private void createBracketIndentRules(String prop,
349: List<IndentRule> rules) {
350: String value = (String) getProperty(prop + 's');
351:
352: try {
353: if (value != null) {
354: for (int i = 0; i < value.length(); i++) {
355: char ch = value.charAt(i);
356:
357: Method m = IndentRuleFactory.class.getMethod(prop,
358: new Class[] { char.class });
359: rules.add((IndentRule) m.invoke(null, ch));
360: }
361: }
362: } catch (Exception e) {
363: Log.log(Log.ERROR, this , "Bad indent rule " + prop + '='
364: + value + ':');
365: Log.log(Log.ERROR, this , e);
366: }
367: }
368:
369: //}}}
370:
371: //{{{ Private members
372: protected String name;
373: protected Map<String, Object> props;
374: private Pattern firstlineRE;
375: private Pattern filenameRE;
376: protected TokenMarker marker;
377: private List<IndentRule> indentRules;
378: private String electricKeys;
379: private boolean ignoreWhitespace;
380: //}}}
381: }
|