PageRenderTime 234ms CodeModel.GetById 208ms app.highlight 22ms RepoModel.GetById 0ms app.codeStats 1ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/Mode.java

#
Java | 524 lines | 294 code | 50 blank | 180 comment | 73 complexity | 8c868e8cdc89804334e8c8d6fcc18d8b MD5 | raw file
  1/*
  2 * Mode.java - jEdit editing mode
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 1998, 1999, 2000 Slava Pestov
  7 * Copyright (C) 1999 mike dillon
  8 *
  9 * This program is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU General Public License
 11 * as published by the Free Software Foundation; either version 2
 12 * of the License, or any later version.
 13 *
 14 * This program is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17 * GNU General Public License for more details.
 18 *
 19 * You should have received a copy of the GNU General Public License
 20 * along with this program; if not, write to the Free Software
 21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 22 */
 23
 24package org.gjt.sp.jedit;
 25
 26//{{{ Imports
 27import java.lang.reflect.Method;
 28import java.util.Hashtable;
 29import java.util.Collections;
 30import java.util.LinkedList;
 31import java.util.List;
 32import java.util.Map;
 33import java.util.regex.Pattern;
 34import java.util.regex.PatternSyntaxException;
 35import org.gjt.sp.jedit.indent.DeepIndentRule;
 36import org.gjt.sp.jedit.indent.IndentRule;
 37import org.gjt.sp.jedit.indent.IndentRuleFactory;
 38import org.gjt.sp.jedit.indent.WhitespaceRule;
 39import org.gjt.sp.jedit.syntax.TokenMarker;
 40import org.gjt.sp.jedit.syntax.ModeProvider;
 41import org.gjt.sp.util.Log;
 42import org.gjt.sp.util.StandardUtilities;
 43//}}}
 44
 45/**
 46 * An edit mode defines specific settings for editing some type of file.
 47 * One instance of this class is created for each supported edit mode.
 48 *
 49 * @author Slava Pestov
 50 * @version $Id: Mode.java 20007 2011-09-24 00:49:35Z Vampire0 $
 51 */
 52public class Mode
 53{
 54	//{{{ Mode constructor
 55	/**
 56	 * Creates a new edit mode.
 57	 *
 58	 * @param name The name used in mode listings and to query mode
 59	 * properties
 60	 * @see #getProperty(String)
 61	 */
 62	public Mode(String name)
 63	{
 64		this.name = name;
 65		this.ignoreWhitespace = true;
 66		props = new Hashtable<String, Object>();
 67	} //}}}
 68
 69	//{{{ init() method
 70	/**
 71	 * Initializes the edit mode. Should be called after all properties
 72	 * are loaded and set.
 73	 */
 74	public void init()
 75	{
 76		try
 77		{
 78			filepathRE = null;
 79			String filenameGlob = (String)getProperty("filenameGlob");
 80			if(filenameGlob != null && filenameGlob.length() != 0)
 81			{
 82				// translate glob to regex
 83				String filepathRE = StandardUtilities.globToRE(filenameGlob);
 84				// if glob includes a path separator (both are supported as
 85				// users can supply them in the GUI and thus will copy
 86				// Windows paths in there)
 87				if (filepathRE.contains("/") || filepathRE.contains("\\\\"))
 88				{
 89					// replace path separators by both separator possibilities in the regex
 90					filepathRE = filepathRE.replaceAll("/|\\\\\\\\", "[/\\\\\\\\]");
 91				} else {
 92					// glob is for a filename without path, prepend the regex with
 93					// an optional path prefix to be able to match against full paths
 94					filepathRE = String.format("(?:.*[/\\\\])?%s", filepathRE);
 95				}
 96				this.filepathRE = Pattern.compile(filepathRE, Pattern.CASE_INSENSITIVE);
 97			}
 98
 99			firstlineRE = null;
100			String firstlineGlob = (String)getProperty("firstlineGlob");
101			if(firstlineGlob != null && firstlineGlob.length() != 0)
102			{
103				firstlineRE = Pattern.compile(StandardUtilities.globToRE(firstlineGlob),
104							      Pattern.CASE_INSENSITIVE);
105			}
106		}
107		catch(PatternSyntaxException re)
108		{
109			Log.log(Log.ERROR,this,"Invalid filename/firstline"
110				+ " globs in mode " + name);
111			Log.log(Log.ERROR,this,re);
112		}
113
114		// Fix for this bug:
115		// -- Put a mode into the user dir with the same name as one
116		//    on the system dir.
117		// -- Reload edit modes.
118		// -- Old mode from system dir still used for highlighting
119		//    until jEdit restart.
120		marker = null;
121	} //}}}
122
123	//{{{ getTokenMarker() method
124	/**
125	 * Returns the token marker for this mode.
126	 */
127	public TokenMarker getTokenMarker()
128	{
129		loadIfNecessary();
130		return marker;
131	} //}}}
132
133	//{{{ setTokenMarker() method
134	/**
135	 * Sets the token marker for this mode.
136	 * @param marker The new token marker
137	 */
138	public void setTokenMarker(TokenMarker marker)
139	{
140		this.marker = marker;
141	} //}}}
142
143	//{{{ loadIfNecessary() method
144	/**
145	 * Loads the mode from disk if it hasn't been loaded already.
146	 * @since jEdit 2.5pre3
147	 */
148	public void loadIfNecessary()
149	{
150		if(marker == null)
151		{
152			ModeProvider.instance.loadMode(this);
153			if (marker == null)
154				Log.log(Log.ERROR, this, "Mode not correctly loaded, token marker is still null");
155		}
156	} //}}}
157
158	//{{{ getProperty() method
159	/**
160	 * Returns a mode property.
161	 * @param key The property name
162	 *
163	 * @since jEdit 2.2pre1
164	 */
165	public Object getProperty(String key)
166	{
167		Object value = props.get(key);
168		if(value != null)
169			return value;
170		return null;
171	} //}}}
172
173	//{{{ getBooleanProperty() method
174	/**
175	 * Returns the value of a boolean property.
176	 * @param key The property name
177	 *
178	 * @since jEdit 2.5pre3
179	 */
180	public boolean getBooleanProperty(String key)
181	{
182		Object value = getProperty(key);
183		return StandardUtilities.getBoolean(value, false);
184	} //}}}
185
186	//{{{ setProperty() method
187	/**
188	 * Sets a mode property.
189	 * @param key The property name
190	 * @param value The property value
191	 */
192	public void setProperty(String key, Object value)
193	{
194		props.put(key,value);
195	} //}}}
196
197	//{{{ unsetProperty() method
198	/**
199	 * Unsets a mode property.
200	 * @param key The property name
201	 * @since jEdit 3.2pre3
202	 */
203	public void unsetProperty(String key)
204	{
205		props.remove(key);
206	} //}}}
207
208	//{{{ setProperties() method
209	/**
210	 * Should only be called by <code>XModeHandler</code>.
211	 * @since jEdit 4.0pre3
212	 */
213	public void setProperties(Map props)
214	{
215		if(props == null)
216			props = new Hashtable<String, Object>();
217
218		ignoreWhitespace = !"false".equalsIgnoreCase(
219					(String)props.get("ignoreWhitespace"));
220
221		// need to carry over file name and first line globs because they are
222		// not given to us by the XMode handler, but instead are filled in by
223		// the catalog loader.
224		String filenameGlob = (String)this.props.get("filenameGlob");
225		String firstlineGlob = (String)this.props.get("firstlineGlob");
226		String filename = (String)this.props.get("file");
227		this.props = props;
228		if(filenameGlob != null)
229			props.put("filenameGlob",filenameGlob);
230		if(firstlineGlob != null)
231			props.put("firstlineGlob",firstlineGlob);
232		if(filename != null)
233			props.put("file",filename);
234	} //}}}
235
236	//{{{ accept() method
237	/**
238	 * Returns true if the edit mode is suitable for editing the specified
239	 * file. The buffer name and first line is checked against the
240	 * file name and first line globs, respectively.
241	 * @param fileName The buffer's name, can be {@code null}
242	 * @param firstLine The first line of the buffer
243	 *
244	 * @since jEdit 3.2pre3
245	 */
246	public boolean accept(String fileName, String firstLine)
247	{
248		return accept(null, fileName, firstLine);
249	} //}}}
250
251	//{{{ accept() method
252	/**
253	 * Returns true if the edit mode is suitable for editing the specified
254	 * file. The buffer name and first line is checked against the
255	 * file name and first line globs, respectively.
256	 * @param filePath The buffer's path, can be {@code null}
257	 * @param fileName The buffer's name, can be {@code null}
258	 * @param firstLine The first line of the buffer
259	 *
260	 * @since jEdit 4.5pre1
261	 */
262	public boolean accept(String filePath, String fileName, String firstLine)
263	{
264		return acceptIdentical(filePath, fileName)
265		       || acceptFile(filePath, fileName)
266		       || acceptFirstLine(firstLine);
267	} //}}}
268
269	//{{{ acceptFilename() method
270	/**
271	 * Returns true if the buffer name matches the file name glob.
272	 * @param fileName The buffer's name, can be {@code null}
273	 * @return true if the file name matches the file name glob.
274	 * @since jEdit 4.3pre18
275	 * @deprecated use {@link #acceptFile(String, String)} instead
276	 */
277	@Deprecated
278	public boolean acceptFilename(String fileName)
279	{
280		return acceptFile(null, fileName);
281	} //}}}
282
283	//{{{ acceptFile() method
284	/**
285	 * Returns true if the buffer's name or path matches the file name glob.
286	 * @param filePath The buffer's path, can be {@code null}
287	 * @param fileName The buffer's name, can be {@code null}
288	 * @return true if the file path or name matches the file name glob.
289	 * @since jEdit 4.5pre1
290	 */
291	public boolean acceptFile(String filePath, String fileName)
292	{
293		return filepathRE != null
294		       && (((filePath != null) && filepathRE.matcher(filePath).matches())
295			   || ((fileName != null) && filepathRE.matcher(fileName).matches()));
296	} //}}}
297
298	//{{{ acceptFilenameIdentical() method
299	/**
300	 * Returns true if the buffer name is identical to the file name glob.
301	 * This works only for regular expressions that only represent themselves,
302	 * i.e. without any meta-characters.
303	 * @param fileName The buffer's name, can be {@code null}
304	 * @return true if the file name matches the file name glob.
305	 * @since jEdit 4.4pre1
306	 */
307	public boolean acceptFilenameIdentical(String fileName)
308	{
309		return acceptIdentical(null, fileName);
310	} //}}}
311
312	//{{{ acceptIdentical() method
313	/**
314	 * Returns true if the buffer path or name is identical to the file name glob.
315	 * This works only for regular expressions that only represent themselves,
316	 * i.e. without any meta-characters.
317	 * @param filePath The buffer's path, can be {@code null}
318	 * @param fileName The buffer's name, can be {@code null}
319	 * @return true if the file name matches the file name glob.
320	 * @since jEdit 4.5pre1
321	 */
322	public boolean acceptIdentical(String filePath, String fileName)
323	{
324		return ((filePath != null) && filePath.equals(getProperty("filenameGlob"))
325		        && (filepathRE == null || filepathRE.matcher(filePath).matches()))
326		       || ((fileName != null) && fileName.equals(getProperty("filenameGlob"))
327		           && (filepathRE == null || filepathRE.matcher(fileName).matches()));
328	} //}}}
329
330	//{{{ acceptFirstLine() method
331	/**
332	 * Returns true if the first line matches the first line glob.
333	 * @param firstLine The first line of the buffer
334	 * @return true if the first line matches the first line glob.
335	 * @since jEdit 4.3pre18
336	 */
337	public boolean acceptFirstLine(String firstLine)
338	{
339		return firstlineRE != null && firstlineRE.matcher(firstLine).matches();
340	} //}}}
341
342	//{{{ getName() method
343	/**
344	 * Returns the internal name of this edit mode.
345	 */
346	public String getName()
347	{
348		return name;
349	} //}}}
350
351	//{{{ toString() method
352	/**
353	 * Returns a string representation of this edit mode.
354	 */
355	public String toString()
356	{
357		return name;
358	} //}}}
359
360	//{{{ getIgnoreWhitespace() method
361	public boolean getIgnoreWhitespace()
362	{
363		return ignoreWhitespace;
364	} //}}}
365
366	//{{{ Indent rules
367
368	public synchronized List<IndentRule> getIndentRules()
369	{
370		if (indentRules == null)
371		{
372			initIndentRules();
373		}
374		return indentRules;
375	}
376
377	public synchronized boolean isElectricKey(char ch)
378	{
379		if (electricKeys == null)
380		{
381			String[] props = {
382				"indentOpenBrackets",
383				"indentCloseBrackets",
384				"electricKeys"
385			};
386
387			StringBuilder buf = new StringBuilder();
388			for(int i = 0; i < props.length; i++)
389			{
390				String prop = (String) getProperty(props[i]);
391				if (prop != null)
392					buf.append(prop);
393			}
394
395			electricKeys = buf.toString();
396		}
397
398		return (electricKeys.indexOf(ch) >= 0);
399	}
400
401	private void initIndentRules()
402	{
403		List<IndentRule> rules = new LinkedList<IndentRule>();
404
405		String[] regexpProps = {
406			"indentNextLine",
407			"indentNextLines"
408		};
409
410		for(int i = 0; i < regexpProps.length; i++)
411		{
412			IndentRule rule = createRegexpIndentRule(regexpProps[i]);
413			if(rule != null)
414				rules.add(rule);
415		}
416
417		String[] bracketProps = {
418			"indentOpenBracket",
419			"indentCloseBracket",
420			"unalignedOpenBracket",
421			"unalignedCloseBracket",
422		};
423
424		for(int i = 0; i < bracketProps.length; i++)
425		{
426			createBracketIndentRules(bracketProps[i], rules);
427		}
428
429		String[] finalProps = {
430			"unindentThisLine",
431			"unindentNextLines"
432		};
433
434		for(int i = 0; i < finalProps.length; i++)
435		{
436			IndentRule rule = createRegexpIndentRule(finalProps[i]);
437			if(rule != null)
438				rules.add(rule);
439		}
440
441		if (getBooleanProperty("deepIndent"))
442		{
443			String unalignedOpenBrackets = (String) getProperty("unalignedOpenBrackets");
444			if (unalignedOpenBrackets != null)
445			{
446				for (int i = 0 ; i < unalignedOpenBrackets.length();i++)
447				{
448					char openChar = unalignedOpenBrackets.charAt(i);
449					char closeChar = TextUtilities.getComplementaryBracket(openChar, null);
450					if (closeChar != '\0')
451						rules.add(new DeepIndentRule(openChar, closeChar));
452				}
453			}
454		}
455
456		if (!getIgnoreWhitespace())
457			rules.add(new WhitespaceRule());
458
459		indentRules = Collections.unmodifiableList(rules);
460	}
461
462	private IndentRule createRegexpIndentRule(String prop)
463	{
464		String value = (String) getProperty(prop);
465
466		try
467		{
468			if(value != null)
469			{
470				Method m = IndentRuleFactory.class.getMethod(
471					prop,new Class[] { String.class });
472				return (IndentRule)m.invoke(null, value);
473			}
474		}
475		catch(Exception e)
476		{
477			Log.log(Log.ERROR,this,"Bad indent rule " + prop
478				+ '=' + value + ':');
479			Log.log(Log.ERROR,this,e);
480		}
481
482		return null;
483	}
484
485	private void createBracketIndentRules(String prop,
486						List<IndentRule> rules)
487	{
488		String value = (String) getProperty(prop + 's');
489
490		try
491		{
492			if(value != null)
493			{
494				for(int i = 0; i < value.length(); i++)
495				{
496					char ch = value.charAt(i);
497
498					Method m = IndentRuleFactory.class.getMethod(
499						prop,new Class[] { char.class });
500					rules.add((IndentRule) m.invoke(null, ch));
501				}
502			}
503		}
504		catch(Exception e)
505		{
506			Log.log(Log.ERROR,this,"Bad indent rule " + prop
507				+ '=' + value + ':');
508			Log.log(Log.ERROR,this,e);
509		}
510	}
511
512	//}}}
513
514	//{{{ Private members
515	protected String name;
516	protected Map<String, Object> props;
517	private Pattern firstlineRE;
518	private Pattern filepathRE;
519	protected TokenMarker marker;
520	private List<IndentRule> indentRules;
521	private String electricKeys;
522	private boolean ignoreWhitespace;
523	//}}}
524}