001: /*
002: * Abbrevs.java - Abbreviation manager
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 1999, 2004 Slava Pestov
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022:
023: package org.gjt.sp.jedit;
024:
025: //{{{ Imports
026: import java.io.*;
027: import java.util.*;
028: import org.gjt.sp.jedit.gui.AddAbbrevDialog;
029: import org.gjt.sp.jedit.textarea.*;
030: import org.gjt.sp.util.Log;
031:
032: //}}}
033:
034: /**
035: * Abbreviation manager.
036: * @author Slava Pestov
037: * @version $Id: Abbrevs.java 5337 2006-01-23 23:04:25Z ezust $
038: */
039: public class Abbrevs {
040: public static final String ENCODING = "UTF8";
041:
042: //{{{ getExpandOnInput() method
043: /**
044: * Returns if abbreviations should be expanded after the
045: * user finishes typing a word.
046: */
047: public static boolean getExpandOnInput() {
048: return expandOnInput;
049: } //}}}
050:
051: //{{{ setExpandOnInput() method
052: /**
053: * Sets if abbreviations should be expanded after the
054: * user finishes typing a word.
055: * @param expandOnInput If true, typing a non-alphanumeric character
056: * will automatically attempt to expand the current abbrev
057: */
058: public static void setExpandOnInput(boolean expandOnInput) {
059: Abbrevs.expandOnInput = expandOnInput;
060: } //}}}
061:
062: //{{{ expandAbbrev() method
063: /**
064: * Expands the abbrev at the caret position in the specified
065: * view.
066: * @param view The view
067: * @param add If true and abbrev not found, will ask user if
068: * it should be added
069: * @since jEdit 2.6pre4
070: */
071: public static boolean expandAbbrev(View view, boolean add) {
072: //{{{ Figure out some minor things
073: Buffer buffer = view.getBuffer();
074: JEditTextArea textArea = view.getTextArea();
075: if (!buffer.isEditable()) {
076: view.getToolkit().beep();
077: return false;
078: }
079:
080: int line = textArea.getCaretLine();
081: int lineStart = buffer.getLineStartOffset(line);
082: int caret = textArea.getCaretPosition();
083:
084: String lineText = buffer.getLineText(line);
085: if (lineText.length() == 0) {
086: if (add)
087: view.getToolkit().beep();
088: return false;
089: }
090:
091: int pos = caret - lineStart;
092: if (pos == 0) {
093: if (add)
094: view.getToolkit().beep();
095: return false;
096: } //}}}
097:
098: // we reuse the 'pp' vector to save time
099: m_pp.removeAllElements();
100:
101: int wordStart;
102: String abbrev;
103:
104: //{{{ Handle abbrevs of the form abbrev#pos1#pos2#pos3#...
105: if (lineText.charAt(pos - 1) == '#') {
106: wordStart = lineText.indexOf('#');
107: wordStart = TextUtilities.findWordStart(lineText,
108: wordStart,
109: buffer.getStringProperty("noWordSep") + '#');
110:
111: abbrev = lineText.substring(wordStart, pos - 1);
112:
113: // positional parameters will be inserted where $1, $2, $3, ...
114: // occurs in the expansion
115:
116: int lastIndex = 0;
117: for (int i = 0; i < abbrev.length(); i++) {
118: if (abbrev.charAt(i) == '#') {
119: m_pp.addElement(abbrev.substring(lastIndex, i));
120: lastIndex = i + 1;
121: }
122: }
123:
124: m_pp.addElement(abbrev.substring(lastIndex));
125:
126: // the first element of pp is the abbrev itself
127: abbrev = (String) m_pp.elementAt(0);
128: m_pp.removeElementAt(0);
129: } //}}}
130: //{{{ Handle ordinary abbrevs
131: else {
132: wordStart = TextUtilities.findWordStart(lineText, pos - 1,
133: buffer.getStringProperty("noWordSep"));
134:
135: abbrev = lineText.substring(wordStart, pos);
136: } //}}}
137:
138: Expansion expand = expandAbbrev(buffer.getMode().getName(),
139: abbrev, (buffer.getBooleanProperty("noTabs") ? buffer
140: .getTabSize() : 0), m_pp);
141:
142: //{{{ Maybe show add abbrev dialog
143: if (expand == null) {
144: if (add)
145: new AddAbbrevDialog(view, abbrev);
146:
147: return false;
148: } //}}}
149: //{{{ Insert the expansion
150: else {
151: buffer.remove(lineStart + wordStart, pos - wordStart);
152:
153: int whitespace = buffer.insertIndented(lineStart
154: + wordStart, expand.text);
155:
156: int newlines = countNewlines(expand.text,
157: expand.caretPosition);
158:
159: if (expand.caretPosition != -1) {
160: textArea.setCaretPosition(lineStart + wordStart
161: + expand.caretPosition + newlines * whitespace);
162: }
163: if (expand.posParamCount != m_pp.size()) {
164: view
165: .getStatus()
166: .setMessageAndClear(
167: jEdit
168: .getProperty(
169: "view.status.incomplete-abbrev",
170: new Integer[] {
171: new Integer(
172: m_pp
173: .size()),
174: new Integer(
175: expand.posParamCount) }));
176: }
177:
178: return true;
179: } //}}}
180: } //}}}
181:
182: //{{{ getGlobalAbbrevs() method
183: /**
184: * Returns the global abbreviation set.
185: * @since jEdit 2.3pre1
186: */
187: public static Hashtable getGlobalAbbrevs() {
188: if (!loaded)
189: load();
190:
191: return globalAbbrevs;
192: } //}}}
193:
194: //{{{ setGlobalAbbrevs() method
195: /**
196: * Sets the global abbreviation set.
197: * @param globalAbbrevs The new global abbrev set
198: * @since jEdit 2.3pre1
199: */
200: public static void setGlobalAbbrevs(Hashtable globalAbbrevs) {
201: abbrevsChanged = true;
202: Abbrevs.globalAbbrevs = globalAbbrevs;
203: } //}}}
204:
205: //{{{ getModeAbbrevs() method
206: /**
207: * Returns the mode-specific abbreviation set.
208: * @since jEdit 2.3pre1
209: */
210: public static Hashtable getModeAbbrevs() {
211: if (!loaded)
212: load();
213:
214: return modes;
215: } //}}}
216:
217: //{{{ setModeAbbrevs() method
218: /**
219: * Sets the mode-specific abbreviation set.
220: * @param modes The new mode abbrev set
221: * @since jEdit 2.3pre1
222: */
223: public static void setModeAbbrevs(Hashtable modes) {
224: abbrevsChanged = true;
225: Abbrevs.modes = modes;
226: } //}}}
227:
228: //{{{ addGlobalAbbrev() method
229: /**
230: * Adds an abbreviation to the global abbreviation list.
231: * @param abbrev The abbreviation
232: * @param expansion The expansion
233: * @since jEdit 3.1pre1
234: */
235: public static void addGlobalAbbrev(String abbrev, String expansion) {
236: if (!loaded)
237: load();
238:
239: globalAbbrevs.put(abbrev, expansion);
240: abbrevsChanged = true;
241: } //}}}
242:
243: //{{{ addModeAbbrev() method
244: /**
245: * Adds a mode-specific abbrev.
246: * @param mode The edit mode
247: * @param abbrev The abbrev
248: * @param expansion The expansion
249: * @since jEdit 3.1pre1
250: */
251: public static void addModeAbbrev(String mode, String abbrev,
252: String expansion) {
253: if (!loaded)
254: load();
255:
256: Hashtable modeAbbrevs = (Hashtable) modes.get(mode);
257: if (modeAbbrevs == null) {
258: modeAbbrevs = new Hashtable();
259: modes.put(mode, modeAbbrevs);
260: }
261: modeAbbrevs.put(abbrev, expansion);
262: abbrevsChanged = true;
263: } //}}}
264:
265: //{{{ save() method
266: static void save() {
267: jEdit.setBooleanProperty("view.expandOnInput", expandOnInput);
268:
269: String settings = jEdit.getSettingsDirectory();
270: if (abbrevsChanged && settings != null) {
271: File file1 = new File(MiscUtilities.constructPath(settings,
272: "#abbrevs#save#"));
273: File file2 = new File(MiscUtilities.constructPath(settings,
274: "abbrevs"));
275: if (file2.exists()
276: && file2.lastModified() != abbrevsModTime) {
277: Log.log(Log.WARNING, Abbrevs.class, file2
278: + " changed on disk;"
279: + " will not save abbrevs");
280: } else {
281: jEdit.backupSettingsFile(file2);
282:
283: try {
284: saveAbbrevs(new OutputStreamWriter(
285: new FileOutputStream(file1), ENCODING));
286: file2.delete();
287: file1.renameTo(file2);
288: } catch (Exception e) {
289: Log.log(Log.ERROR, Abbrevs.class,
290: "Error while saving " + file1);
291: Log.log(Log.ERROR, Abbrevs.class, e);
292: }
293: abbrevsModTime = file2.lastModified();
294: }
295: }
296: } //}}}
297:
298: //{{{ Private members
299:
300: //{{{ Instance variables
301: private static boolean loaded;
302: private static boolean abbrevsChanged;
303: private static long abbrevsModTime;
304: private static boolean expandOnInput;
305: private static Hashtable globalAbbrevs;
306: private static Hashtable modes;
307:
308: /** Vector of Positional Parameters */
309: private static Vector m_pp = new Vector();
310:
311: //}}}
312:
313: private Abbrevs() {
314: }
315:
316: static {
317: expandOnInput = jEdit.getBooleanProperty("view.expandOnInput");
318: }
319:
320: //{{{ load() method
321: private static void load() {
322: globalAbbrevs = new Hashtable();
323: modes = new Hashtable();
324:
325: String settings = jEdit.getSettingsDirectory();
326: if (settings != null) {
327: File file = new File(MiscUtilities.constructPath(settings,
328: "abbrevs"));
329: abbrevsModTime = file.lastModified();
330:
331: try {
332: loadAbbrevs(new InputStreamReader(new FileInputStream(
333: file), ENCODING));
334: loaded = true;
335: } catch (FileNotFoundException fnf) {
336: } catch (Exception e) {
337: Log.log(Log.ERROR, Abbrevs.class,
338: "Error while loading " + file);
339: Log.log(Log.ERROR, Abbrevs.class, e);
340: }
341: }
342:
343: // only load global abbrevs if user abbrevs file could not be loaded
344: if (!loaded) {
345: try {
346: loadAbbrevs(new InputStreamReader(Abbrevs.class
347: .getResourceAsStream("default.abbrevs"),
348: ENCODING));
349: } catch (Exception e) {
350: Log.log(Log.ERROR, Abbrevs.class,
351: "Error while loading default.abbrevs");
352: Log.log(Log.ERROR, Abbrevs.class, e);
353: }
354: loaded = true;
355: }
356: } //}}}
357:
358: //{{{ countNewlines() method
359: private static int countNewlines(String s, int end) {
360: int counter = 0;
361:
362: for (int i = 0; i < end; i++) {
363: if (s.charAt(i) == '\n')
364: counter++;
365: }
366:
367: return counter;
368: } //}}}
369:
370: //{{{ expandAbbrev() method
371: private static Expansion expandAbbrev(String mode, String abbrev,
372: int softTabSize, Vector pp) {
373: m_pp = pp;
374: if (!loaded)
375: load();
376:
377: // try mode-specific abbrevs first
378: String expand = null;
379: Hashtable modeAbbrevs = (Hashtable) modes.get(mode);
380: if (modeAbbrevs != null)
381: expand = (String) modeAbbrevs.get(abbrev);
382:
383: if (expand == null)
384: expand = (String) globalAbbrevs.get(abbrev);
385:
386: if (expand == null)
387: return null;
388: else
389: return new Expansion(expand, softTabSize, m_pp);
390: } //}}}
391:
392: //{{{ loadAbbrevs() method
393: private static void loadAbbrevs(Reader _in) throws Exception {
394: BufferedReader in = new BufferedReader(_in);
395:
396: try {
397: Hashtable currentAbbrevs = globalAbbrevs;
398:
399: String line;
400: while ((line = in.readLine()) != null) {
401: int index = line.indexOf('|');
402:
403: if (line.length() == 0)
404: continue;
405: else if (line.startsWith("[") && index == -1) {
406: if (line.equals("[global]"))
407: currentAbbrevs = globalAbbrevs;
408: else {
409: String mode = line.substring(1,
410: line.length() - 1);
411: currentAbbrevs = (Hashtable) modes.get(mode);
412: if (currentAbbrevs == null) {
413: currentAbbrevs = new Hashtable();
414: modes.put(mode, currentAbbrevs);
415: }
416: }
417: } else if (index != -1) {
418: currentAbbrevs.put(line.substring(0, index), line
419: .substring(index + 1));
420: }
421: }
422: } finally {
423: in.close();
424: }
425: } //}}}
426:
427: //{{{ saveAbbrevs() method
428: private static void saveAbbrevs(Writer _out) throws Exception {
429: BufferedWriter out = new BufferedWriter(_out);
430: String lineSep = System.getProperty("line.separator");
431:
432: // write global abbrevs
433: out.write("[global]");
434: out.write(lineSep);
435:
436: saveAbbrevs(out, globalAbbrevs);
437:
438: // write mode abbrevs
439: Enumeration keys = modes.keys();
440: Enumeration values = modes.elements();
441: while (keys.hasMoreElements()) {
442: out.write('[');
443: out.write((String) keys.nextElement());
444: out.write(']');
445: out.write(lineSep);
446: saveAbbrevs(out, (Hashtable) values.nextElement());
447: }
448:
449: out.close();
450: } //}}}
451:
452: //{{{ saveAbbrevs() method
453: private static void saveAbbrevs(Writer out, Hashtable abbrevs)
454: throws Exception {
455: String lineSep = System.getProperty("line.separator");
456:
457: Enumeration keys = abbrevs.keys();
458: Enumeration values = abbrevs.elements();
459: while (keys.hasMoreElements()) {
460: String abbrev = (String) keys.nextElement();
461: out.write(abbrev);
462: out.write('|');
463: out.write(values.nextElement().toString());
464: out.write(lineSep);
465: }
466: } //}}}
467:
468: //}}}
469:
470: //{{{ Expansion class
471: static class Expansion {
472: String text;
473: int caretPosition = -1;
474: int lineCount;
475:
476: // number of positional parameters in abbreviation expansion
477: int posParamCount;
478:
479: //{{{ Expansion constructor
480: Expansion(String text, int softTabSize, Vector pp) {
481: StringBuffer buf = new StringBuffer();
482: boolean backslash = false;
483:
484: for (int i = 0; i < text.length(); i++) {
485: char ch = text.charAt(i);
486: //{{{ Handle backslash
487: if (backslash) {
488: backslash = false;
489:
490: if (ch == '|')
491: caretPosition = buf.length();
492: else if (ch == 'n') {
493: buf.append('\n');
494: lineCount++;
495: } else if (ch == 't') {
496: if (softTabSize == 0)
497: buf.append('\t');
498: else {
499: for (int j = 0; j < softTabSize; j++)
500: buf.append(' ');
501: }
502: } else
503: buf.append(ch);
504: } else if (ch == '\\')
505: backslash = true;
506: //}}}
507: //{{{ Handle $
508: else if (ch == '$') {
509: if (i != text.length() - 1) {
510: ch = text.charAt(i + 1);
511: if (Character.isDigit(ch) && ch != '0') {
512: i++;
513:
514: int pos = ch - '0';
515: posParamCount = Math
516: .max(pos, posParamCount);
517: // $n is 1-indexed, but vector
518: // contents is zero indexed
519: if (pos <= pp.size())
520: buf.append(pp.elementAt(pos - 1));
521: } else {
522: // $key will be $key, for
523: // example
524: buf.append('$');
525: }
526: } else
527: buf.append('$'); // $ at end is literal
528: } //}}}
529: else
530: buf.append(ch);
531: }
532:
533: this .text = buf.toString();
534: } //}}}
535: } //}}}
536: }
|