PageRenderTime 74ms CodeModel.GetById 30ms app.highlight 27ms RepoModel.GetById 13ms app.codeStats 0ms

/jEdit/tags/jedit-4-3-pre5/org/gjt/sp/jedit/buffer/KillRing.java

#
Java | 405 lines | 288 code | 55 blank | 62 comment | 51 complexity | f8d17831dbccdcabf75bc712f125f902 MD5 | raw file
  1/*
  2 * KillRing.java - Stores deleted text
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 2003, 2005 Slava Pestov
  7 *
  8 * This program is free software; you can redistribute it and/or
  9 * modify it under the terms of the GNU General Public License
 10 * as published by the Free Software Foundation; either version 2
 11 * of the License, or any later version.
 12 *
 13 * This program is distributed in the hope that it will be useful,
 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16 * GNU General Public License for more details.
 17 *
 18 * You should have received a copy of the GNU General Public License
 19 * along with this program; if not, write to the Free Software
 20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 21 */
 22
 23package org.gjt.sp.jedit.buffer;
 24
 25import javax.swing.event.ListDataListener;
 26import java.io.*;
 27import java.util.*;
 28
 29import org.xml.sax.Attributes;
 30import org.xml.sax.InputSource;
 31import org.xml.sax.helpers.DefaultHandler;
 32
 33import org.gjt.sp.jedit.*;
 34import org.gjt.sp.jedit.gui.MutableListModel;
 35import org.gjt.sp.util.Log;
 36
 37/**
 38 * The kill ring retains deleted text. This class is a singleton -- only one
 39 * kill ring is used for all of jEdit. Nothing prevents plugins from making their
 40 * own kill rings for whatever reason, though.
 41 */
 42public class KillRing implements MutableListModel
 43{
 44	//{{{ getInstance() method
 45	public static KillRing getInstance()
 46	{
 47		if(killRing == null)
 48			killRing = new KillRing();
 49		return killRing;
 50	} //}}}
 51
 52	//{{{ propertiesChanged() method
 53	public void propertiesChanged()
 54	{
 55		int newSize = Math.max(1,jEdit.getIntegerProperty("history",25));
 56		if(ring == null)
 57			ring = new UndoManager.Remove[newSize];
 58		else if(newSize != ring.length)
 59		{
 60			UndoManager.Remove[] newRing = new UndoManager.Remove[
 61				newSize];
 62			int newCount = Math.min(getSize(),newSize);
 63			for(int i = 0; i < newCount; i++)
 64			{
 65				newRing[i] = (UndoManager.Remove)getElementAt(i);
 66			}
 67			ring = newRing;
 68			count = newCount;
 69			wrap = false;
 70		}
 71
 72		if(count == ring.length)
 73		{
 74			count = 0;
 75			wrap = true;
 76		}
 77	} //}}}
 78
 79	//{{{ load() method
 80	public void load()
 81	{
 82		String settingsDirectory = jEdit.getSettingsDirectory();
 83		if(settingsDirectory == null)
 84			return;
 85
 86		File killRing = new File(MiscUtilities.constructPath(
 87			settingsDirectory,"killring.xml"));
 88		if(!killRing.exists())
 89			return;
 90
 91		killRingModTime = killRing.lastModified();
 92		Log.log(Log.MESSAGE,KillRing.class,"Loading killring.xml");
 93
 94		KillRingHandler handler = new KillRingHandler();
 95		try
 96		{
 97			MiscUtilities.parseXML(new FileInputStream(killRing), handler);
 98		}
 99		catch (IOException ioe)
100		{
101			Log.log(Log.ERROR, this, ioe);
102		}
103
104		ring = (UndoManager.Remove[])handler.list.toArray(
105			new UndoManager.Remove[handler.list.size()]);
106		count = 0;
107		wrap = true;
108	} //}}}
109
110	//{{{ save() method
111	public void save()
112	{
113		String settingsDirectory = jEdit.getSettingsDirectory();
114		if(settingsDirectory == null)
115			return;
116
117		File file1 = new File(MiscUtilities.constructPath(
118			settingsDirectory, "#killring.xml#save#"));
119		File file2 = new File(MiscUtilities.constructPath(
120			settingsDirectory, "killring.xml"));
121		if(file2.exists() && file2.lastModified() != killRingModTime)
122		{
123			Log.log(Log.WARNING,KillRing.class,file2
124				+ " changed on disk; will not save recent"
125				+ " files");
126			return;
127		}
128
129		jEdit.backupSettingsFile(file2);
130
131		Log.log(Log.MESSAGE,KillRing.class,"Saving killring.xml");
132
133		String lineSep = System.getProperty("line.separator");
134
135		BufferedWriter out = null;
136
137		try
138		{
139			out = new BufferedWriter(new FileWriter(file1));
140
141			out.write("<?xml version=\"1.0\"?>");
142			out.write(lineSep);
143			out.write("<!DOCTYPE KILLRING SYSTEM \"killring.dtd\">");
144			out.write(lineSep);
145			out.write("<KILLRING>");
146			out.write(lineSep);
147
148			int size = getSize();
149			for(int i = size - 1; i >=0; i--)
150			{
151				out.write("<ENTRY>");
152				out.write(MiscUtilities.charsToEntities(
153					getElementAt(i).toString()));
154				out.write("</ENTRY>");
155				out.write(lineSep);
156			}
157
158			out.write("</KILLRING>");
159			out.write(lineSep);
160
161			out.close();
162
163			/* to avoid data loss, only do this if the above
164			 * completed successfully */
165			file2.delete();
166			file1.renameTo(file2);
167		}
168		catch(Exception e)
169		{
170			Log.log(Log.ERROR,KillRing.class,e);
171		}
172		finally
173		{
174			try
175			{
176				if(out != null)
177					out.close();
178			}
179			catch(IOException e)
180			{
181			}
182		}
183
184		killRingModTime = file2.lastModified();
185	} //}}}
186
187	//{{{ MutableListModel implementation
188	public void addListDataListener(ListDataListener listener) {}
189
190	public void removeListDataListener(ListDataListener listener) {}
191
192	//{{{ getElementAt() method
193	public Object getElementAt(int index)
194	{
195		return ring[virtualToPhysicalIndex(index)];
196	} //}}}
197
198	//{{{ getSize() method
199	public int getSize()
200	{
201		if(wrap)
202			return ring.length;
203		else
204			return count;
205	} //}}}
206
207	//{{{ removeElement() method
208	public boolean removeElement(Object value)
209	{
210		for(int i = 0; i < getSize(); i++)
211		{
212			if(ring[i].equals(value))
213			{
214				remove(i);
215				return true;
216			}
217		}
218		return false;
219	} //}}}
220
221	//{{{ insertElementAt() method
222	public void insertElementAt(Object value, int index)
223	{
224		/* This is not terribly efficient, but this method is only
225		called by the 'Paste Deleted' dialog where the performance
226		is not exactly vital */
227		remove(index);
228		add((UndoManager.Remove)value);
229	} //}}}
230
231	//}}}
232
233	//{{{ Package-private members
234	UndoManager.Remove[] ring;
235	int count;
236	boolean wrap;
237
238	//{{{ changed() method
239	void changed(UndoManager.Remove rem)
240	{
241		if(rem.inKillRing)
242		{
243			// compare existing entries' hashcode with this
244			int length = (wrap ? ring.length : count);
245			int kill = -1;
246
247			for(int i = 0; i < length; i++)
248			{
249				if(ring[i] != rem
250					&& ring[i].hashcode == rem.hashcode
251					&& ring[i].str.equals(rem.str))
252				{
253					// we don't want duplicate
254					// entries in the kill ring
255					kill = i;
256					break;
257				}
258			}
259
260			if(kill != -1)
261				remove(kill);
262		}
263		else
264			add(rem);
265	} //}}}
266
267	//{{{ add() method
268	void add(UndoManager.Remove rem)
269	{
270		// compare existing entries' hashcode with this
271		int length = (wrap ? ring.length : count);
272		for(int i = 0; i < length; i++)
273		{
274			if(ring[i].hashcode == rem.hashcode)
275			{
276				// strings might be equal!
277				if(ring[i].str.equals(rem.str))
278				{
279					// we don't want duplicate entries
280					// in the kill ring
281					return;
282				}
283			}
284		}
285
286		// no duplicates, check for all-whitespace string
287		boolean allWhitespace = true;
288		for(int i = 0; i < rem.str.length(); i++)
289		{
290			if(!Character.isWhitespace(rem.str.charAt(i)))
291			{
292				allWhitespace = false;
293				break;
294			}
295		}
296
297		if(allWhitespace)
298			return;
299
300		rem.inKillRing = true;
301
302		if(ring[count] != null)
303			ring[count].inKillRing = false;
304
305		ring[count] = rem;
306		if(++count >= ring.length)
307		{
308			wrap = true;
309			count = 0;
310		}
311	} //}}}
312
313	//{{{ remove() method
314	void remove(int i)
315	{
316		if(wrap)
317		{
318			UndoManager.Remove[] newRing = new UndoManager.Remove[
319				ring.length];
320			int newCount = 0;
321			for(int j = 0; j < ring.length; j++)
322			{
323				int index = virtualToPhysicalIndex(j);
324
325				if(i == index)
326				{
327					ring[index].inKillRing = false;
328					continue;
329				}
330
331				newRing[newCount++] = ring[index];
332			}
333			ring = newRing;
334			count = newCount;
335			wrap = false;
336		}
337		else
338		{
339			System.arraycopy(ring,i + 1,ring,i,count - i - 1);
340			count--;
341		}
342	} //}}}
343
344	//}}}
345
346	//{{{ Private members
347	private long killRingModTime;
348	private static KillRing killRing;
349
350	//{{{ virtualToPhysicalIndex() method
351	/**
352	 * Since the kill ring has a wrap-around representation, we need to
353	 * convert user-visible indices to actual indices in the array.
354	 */
355	private int virtualToPhysicalIndex(int index)
356	{
357		if(wrap)
358		{
359			if(index < count)
360				return count - index - 1;
361			else
362				return count + ring.length - index - 1;
363		}
364		else
365			return count - index - 1;
366	} //}}}
367
368	//}}}
369
370	//{{{ KillRingHandler class
371	class KillRingHandler extends DefaultHandler
372	{
373		List list = new LinkedList();
374
375		public InputSource resolveEntity(String publicId, String systemId)
376		{
377			return MiscUtilities.findEntity(systemId, "killring.dtd", getClass());
378		}
379
380		public void startElement(String uri, String localName,
381					 String qName, Attributes attrs)
382		{
383			inEntry = qName.equals("ENTRY");
384		}
385
386		public void endElement(String uri, String localName, String name)
387		{
388			if(name.equals("ENTRY"))
389			{
390				list.add(new UndoManager.Remove(null,0,0,charData.toString()));
391				inEntry = false;
392				charData.setLength(0);
393			}
394		}
395
396		public void characters(char[] ch, int start, int length)
397		{
398			if (inEntry)
399				charData.append(ch, start, length);
400		}
401
402		private StringBuffer charData = new StringBuffer();
403		private boolean inEntry;
404	} //}}}
405}