PageRenderTime 44ms CodeModel.GetById 15ms RepoModel.GetById 0ms 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
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  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. package org.gjt.sp.jedit.buffer;
  23. import javax.swing.event.ListDataListener;
  24. import java.io.*;
  25. import java.util.*;
  26. import org.xml.sax.Attributes;
  27. import org.xml.sax.InputSource;
  28. import org.xml.sax.helpers.DefaultHandler;
  29. import org.gjt.sp.jedit.*;
  30. import org.gjt.sp.jedit.gui.MutableListModel;
  31. import org.gjt.sp.util.Log;
  32. /**
  33. * The kill ring retains deleted text. This class is a singleton -- only one
  34. * kill ring is used for all of jEdit. Nothing prevents plugins from making their
  35. * own kill rings for whatever reason, though.
  36. */
  37. public class KillRing implements MutableListModel
  38. {
  39. //{{{ getInstance() method
  40. public static KillRing getInstance()
  41. {
  42. if(killRing == null)
  43. killRing = new KillRing();
  44. return killRing;
  45. } //}}}
  46. //{{{ propertiesChanged() method
  47. public void propertiesChanged()
  48. {
  49. int newSize = Math.max(1,jEdit.getIntegerProperty("history",25));
  50. if(ring == null)
  51. ring = new UndoManager.Remove[newSize];
  52. else if(newSize != ring.length)
  53. {
  54. UndoManager.Remove[] newRing = new UndoManager.Remove[
  55. newSize];
  56. int newCount = Math.min(getSize(),newSize);
  57. for(int i = 0; i < newCount; i++)
  58. {
  59. newRing[i] = (UndoManager.Remove)getElementAt(i);
  60. }
  61. ring = newRing;
  62. count = newCount;
  63. wrap = false;
  64. }
  65. if(count == ring.length)
  66. {
  67. count = 0;
  68. wrap = true;
  69. }
  70. } //}}}
  71. //{{{ load() method
  72. public void load()
  73. {
  74. String settingsDirectory = jEdit.getSettingsDirectory();
  75. if(settingsDirectory == null)
  76. return;
  77. File killRing = new File(MiscUtilities.constructPath(
  78. settingsDirectory,"killring.xml"));
  79. if(!killRing.exists())
  80. return;
  81. killRingModTime = killRing.lastModified();
  82. Log.log(Log.MESSAGE,KillRing.class,"Loading killring.xml");
  83. KillRingHandler handler = new KillRingHandler();
  84. try
  85. {
  86. MiscUtilities.parseXML(new FileInputStream(killRing), handler);
  87. }
  88. catch (IOException ioe)
  89. {
  90. Log.log(Log.ERROR, this, ioe);
  91. }
  92. ring = (UndoManager.Remove[])handler.list.toArray(
  93. new UndoManager.Remove[handler.list.size()]);
  94. count = 0;
  95. wrap = true;
  96. } //}}}
  97. //{{{ save() method
  98. public void save()
  99. {
  100. String settingsDirectory = jEdit.getSettingsDirectory();
  101. if(settingsDirectory == null)
  102. return;
  103. File file1 = new File(MiscUtilities.constructPath(
  104. settingsDirectory, "#killring.xml#save#"));
  105. File file2 = new File(MiscUtilities.constructPath(
  106. settingsDirectory, "killring.xml"));
  107. if(file2.exists() && file2.lastModified() != killRingModTime)
  108. {
  109. Log.log(Log.WARNING,KillRing.class,file2
  110. + " changed on disk; will not save recent"
  111. + " files");
  112. return;
  113. }
  114. jEdit.backupSettingsFile(file2);
  115. Log.log(Log.MESSAGE,KillRing.class,"Saving killring.xml");
  116. String lineSep = System.getProperty("line.separator");
  117. BufferedWriter out = null;
  118. try
  119. {
  120. out = new BufferedWriter(new FileWriter(file1));
  121. out.write("<?xml version=\"1.0\"?>");
  122. out.write(lineSep);
  123. out.write("<!DOCTYPE KILLRING SYSTEM \"killring.dtd\">");
  124. out.write(lineSep);
  125. out.write("<KILLRING>");
  126. out.write(lineSep);
  127. int size = getSize();
  128. for(int i = size - 1; i >=0; i--)
  129. {
  130. out.write("<ENTRY>");
  131. out.write(MiscUtilities.charsToEntities(
  132. getElementAt(i).toString()));
  133. out.write("</ENTRY>");
  134. out.write(lineSep);
  135. }
  136. out.write("</KILLRING>");
  137. out.write(lineSep);
  138. out.close();
  139. /* to avoid data loss, only do this if the above
  140. * completed successfully */
  141. file2.delete();
  142. file1.renameTo(file2);
  143. }
  144. catch(Exception e)
  145. {
  146. Log.log(Log.ERROR,KillRing.class,e);
  147. }
  148. finally
  149. {
  150. try
  151. {
  152. if(out != null)
  153. out.close();
  154. }
  155. catch(IOException e)
  156. {
  157. }
  158. }
  159. killRingModTime = file2.lastModified();
  160. } //}}}
  161. //{{{ MutableListModel implementation
  162. public void addListDataListener(ListDataListener listener) {}
  163. public void removeListDataListener(ListDataListener listener) {}
  164. //{{{ getElementAt() method
  165. public Object getElementAt(int index)
  166. {
  167. return ring[virtualToPhysicalIndex(index)];
  168. } //}}}
  169. //{{{ getSize() method
  170. public int getSize()
  171. {
  172. if(wrap)
  173. return ring.length;
  174. else
  175. return count;
  176. } //}}}
  177. //{{{ removeElement() method
  178. public boolean removeElement(Object value)
  179. {
  180. for(int i = 0; i < getSize(); i++)
  181. {
  182. if(ring[i].equals(value))
  183. {
  184. remove(i);
  185. return true;
  186. }
  187. }
  188. return false;
  189. } //}}}
  190. //{{{ insertElementAt() method
  191. public void insertElementAt(Object value, int index)
  192. {
  193. /* This is not terribly efficient, but this method is only
  194. called by the 'Paste Deleted' dialog where the performance
  195. is not exactly vital */
  196. remove(index);
  197. add((UndoManager.Remove)value);
  198. } //}}}
  199. //}}}
  200. //{{{ Package-private members
  201. UndoManager.Remove[] ring;
  202. int count;
  203. boolean wrap;
  204. //{{{ changed() method
  205. void changed(UndoManager.Remove rem)
  206. {
  207. if(rem.inKillRing)
  208. {
  209. // compare existing entries' hashcode with this
  210. int length = (wrap ? ring.length : count);
  211. int kill = -1;
  212. for(int i = 0; i < length; i++)
  213. {
  214. if(ring[i] != rem
  215. && ring[i].hashcode == rem.hashcode
  216. && ring[i].str.equals(rem.str))
  217. {
  218. // we don't want duplicate
  219. // entries in the kill ring
  220. kill = i;
  221. break;
  222. }
  223. }
  224. if(kill != -1)
  225. remove(kill);
  226. }
  227. else
  228. add(rem);
  229. } //}}}
  230. //{{{ add() method
  231. void add(UndoManager.Remove rem)
  232. {
  233. // compare existing entries' hashcode with this
  234. int length = (wrap ? ring.length : count);
  235. for(int i = 0; i < length; i++)
  236. {
  237. if(ring[i].hashcode == rem.hashcode)
  238. {
  239. // strings might be equal!
  240. if(ring[i].str.equals(rem.str))
  241. {
  242. // we don't want duplicate entries
  243. // in the kill ring
  244. return;
  245. }
  246. }
  247. }
  248. // no duplicates, check for all-whitespace string
  249. boolean allWhitespace = true;
  250. for(int i = 0; i < rem.str.length(); i++)
  251. {
  252. if(!Character.isWhitespace(rem.str.charAt(i)))
  253. {
  254. allWhitespace = false;
  255. break;
  256. }
  257. }
  258. if(allWhitespace)
  259. return;
  260. rem.inKillRing = true;
  261. if(ring[count] != null)
  262. ring[count].inKillRing = false;
  263. ring[count] = rem;
  264. if(++count >= ring.length)
  265. {
  266. wrap = true;
  267. count = 0;
  268. }
  269. } //}}}
  270. //{{{ remove() method
  271. void remove(int i)
  272. {
  273. if(wrap)
  274. {
  275. UndoManager.Remove[] newRing = new UndoManager.Remove[
  276. ring.length];
  277. int newCount = 0;
  278. for(int j = 0; j < ring.length; j++)
  279. {
  280. int index = virtualToPhysicalIndex(j);
  281. if(i == index)
  282. {
  283. ring[index].inKillRing = false;
  284. continue;
  285. }
  286. newRing[newCount++] = ring[index];
  287. }
  288. ring = newRing;
  289. count = newCount;
  290. wrap = false;
  291. }
  292. else
  293. {
  294. System.arraycopy(ring,i + 1,ring,i,count - i - 1);
  295. count--;
  296. }
  297. } //}}}
  298. //}}}
  299. //{{{ Private members
  300. private long killRingModTime;
  301. private static KillRing killRing;
  302. //{{{ virtualToPhysicalIndex() method
  303. /**
  304. * Since the kill ring has a wrap-around representation, we need to
  305. * convert user-visible indices to actual indices in the array.
  306. */
  307. private int virtualToPhysicalIndex(int index)
  308. {
  309. if(wrap)
  310. {
  311. if(index < count)
  312. return count - index - 1;
  313. else
  314. return count + ring.length - index - 1;
  315. }
  316. else
  317. return count - index - 1;
  318. } //}}}
  319. //}}}
  320. //{{{ KillRingHandler class
  321. class KillRingHandler extends DefaultHandler
  322. {
  323. List list = new LinkedList();
  324. public InputSource resolveEntity(String publicId, String systemId)
  325. {
  326. return MiscUtilities.findEntity(systemId, "killring.dtd", getClass());
  327. }
  328. public void startElement(String uri, String localName,
  329. String qName, Attributes attrs)
  330. {
  331. inEntry = qName.equals("ENTRY");
  332. }
  333. public void endElement(String uri, String localName, String name)
  334. {
  335. if(name.equals("ENTRY"))
  336. {
  337. list.add(new UndoManager.Remove(null,0,0,charData.toString()));
  338. inEntry = false;
  339. charData.setLength(0);
  340. }
  341. }
  342. public void characters(char[] ch, int start, int length)
  343. {
  344. if (inEntry)
  345. charData.append(ch, start, length);
  346. }
  347. private StringBuffer charData = new StringBuffer();
  348. private boolean inEntry;
  349. } //}}}
  350. }