/plugins/TextAutocomplete/tags/RELEASE-0_9_5/net/jakubholy/jedit/autocomplete/WordTypedListener.java

# · Java · 341 lines · 169 code · 29 blank · 143 comment · 29 complexity · 9e7bc7bb4e45a75a98343bf574cf1153 MD5 · raw file

  1. /*
  2. * KeyTypedListener.java
  3. * $id$
  4. * author Jakub (Kuba) Holy, 2005
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, write to the Free Software
  18. * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  19. */
  20. package net.jakubholy.jedit.autocomplete;
  21. import org.gjt.sp.jedit.buffer.BufferAdapter;
  22. import org.gjt.sp.jedit.buffer.JEditBuffer;
  23. import org.gjt.sp.util.Log;
  24. // WordTypedListener {{{
  25. //
  26. /**
  27. * Listens for word being inserted or removed into/from a buffer to know when a
  28. * whole word has been typed & notifies its observers.
  29. *
  30. * What is a word is detemined by the method accept of the call-back object
  31. * checkIsWord. Replace it by another one to change what characters are treated
  32. * as a part of a word (e.g. accept '_' too as a part of a word). By default,
  33. * only letters are considered to belong to a word.
  34. *
  35. * It is observable and fires the events AT_START, INSIDE, AT_END, RESET and
  36. * TRUNCATED when a word is inserted/ removed, see
  37. * {@link net.jakubholy.jedit.autocomplete.WordTypedEvent}. Notice that it
  38. * fires an event not only when a word has been finished but whenever the word
  39. * of the buffer changes.
  40. *
  41. * @see net.jakubholy.jedit.autocomplete.WordTypedEvent
  42. * @see org.gjt.sp.jedit.Buffer#addBufferChangeListener
  43. * @see org.gjt.sp.jedit.buffer.BufferChangeAdapter
  44. * @see org.gjt.sp.jedit.buffer.BufferChangeListener
  45. *
  46. * Note: according to the documentation of jEdit it's prefered to use
  47. * BufferChangeAdapter since BufferChangeListener might change in the future.
  48. *
  49. */
  50. public class WordTypedListener extends BufferAdapter
  51. {
  52. /** Value of the lastCaret when it is not set. */
  53. private static final int CARET_UNSET = -1;
  54. /** How much logging shall be printed to jEdit's log. */
  55. public int logLevel =
  56. PreferencesManager.getPreferencesManager().getLogLevel();
  57. /** Offset of the previously inserted word. */
  58. int lastCaret = CARET_UNSET;
  59. /** The word being currently typed. */
  60. StringBuffer word = new StringBuffer(15);
  61. /** Empty strign buffer. */
  62. final StringBuffer emptyWord = new StringBuffer(0);
  63. /** The text to be appended to the end of the word. */
  64. String insertion;
  65. // //////////////////////////////////////////////////////////////////////////////////////
  66. // BUFFER CHANGE STUFF
  67. // //////////////////////////////////////////////////////////////////////////////////////
  68. // //////////////////////////////////////////////////////////////////////////////////////
  69. // {{{ contentInserted
  70. /*
  71. * Called when a text is inserted into the buffer (e.g. a letter is typed).
  72. * @param offset offset (the number of chracters) from the stoart of the
  73. * buffer; it's offset of the place where the word was inserted @param
  74. * length the number of characters that have been inserted
  75. *
  76. * @see org.gjt.sp.jedit.buffer.BufferAdapter
  77. */
  78. public void contentInserted(JEditBuffer buffer, int startLine, int offset,
  79. int numLines, int length)
  80. {
  81. if (logLevel == PreferencesManager.LOG_ALL)
  82. {
  83. Log.log(Log.DEBUG, TextAutocompletePlugin.class,
  84. "WordTypedListener.contentInserted: lastCaret: " + lastCaret
  85. + ", offset: " + offset + ", length: " + length);
  86. }
  87. //
  88. // Set the caret after a reset
  89. //
  90. if (lastCaret == CARET_UNSET)
  91. {
  92. // If not at word beginning (pref. char. non-word), ignore the
  93. // insertion
  94. // == don't start recording a word from the middle
  95. boolean isWordStart = true;
  96. if (offset > 0)
  97. {
  98. char precedingChar = buffer.getText(offset - 1, 1).charAt(0);
  99. // Is the preceding character a non-word character or not?
  100. isWordStart = ! checkIsWord.accept(emptyWord, precedingChar);
  101. }
  102. if (! isWordStart)
  103. {
  104. if (logLevel == PreferencesManager.LOG_ALL)
  105. {
  106. Log.log(Log.DEBUG, TextAutocompletePlugin.class,
  107. "WordTypedListener: IGNORING an insert in the middle "
  108. + "of a word after a reset. Offset: "
  109. + offset);
  110. }
  111. return;
  112. }
  113. else
  114. {
  115. lastCaret = offset;
  116. } // set the current position
  117. } // if after reset
  118. // HANDLE INSERTION
  119. // If word inserted behind the last insertion and it's only 1 character
  120. //
  121. if ((lastCaret == offset) && (length == 1)) // TODO: support insertion
  122. // of longer texts
  123. {
  124. lastCaret = offset + length; // move to the end of the insertion
  125. insertion = buffer.getText(offset, length);
  126. if (checkIsWord.accept(word, insertion.charAt(0)))
  127. {
  128. //
  129. // INSERT
  130. //
  131. if (logLevel == PreferencesManager.LOG_ALL)
  132. {
  133. Log.log(Log.DEBUG, TextAutocompletePlugin.class,
  134. "WordTypedListener: Char appended: " + insertion);
  135. }
  136. int eventType = (word.length() == 0) ? WordTypedEvent.AT_START
  137. : WordTypedEvent.INSIDE;
  138. // Append the insertion before notifying observers
  139. word.append(insertion);
  140. notifier.notifyObservers(new WordTypedEvent(eventType,
  141. (new StringBuffer(15)).append(word),
  142. insertion));
  143. }
  144. else
  145. {
  146. // word ended or between non-word characters (then
  147. // word.length()==0)
  148. if (logLevel >= PreferencesManager.LOG_WORD) Log.log(Log.DEBUG,
  149. TextAutocompletePlugin.class,
  150. "WordTypedListener: WORD ENDED: \"" + word + "\"");
  151. notifier.notifyObservers(new WordTypedEvent(
  152. WordTypedEvent.AT_END, (new StringBuffer(15)).append(word), insertion));
  153. reset();
  154. } // if-else is a word constituent
  155. }
  156. else
  157. // lastCaret != offset => reset: backspace, cared moved, undo/redo etc.
  158. {
  159. if (logLevel == PreferencesManager.LOG_ALL) Log.log(Log.DEBUG,
  160. TextAutocompletePlugin.class,
  161. "WordTypedListener:firing JUMP RESET: lastCaret: "
  162. + lastCaret + ", offset: " + offset + ", length: " + length);
  163. notifier.notifyObservers(new WordTypedEvent(WordTypedEvent.RESET,
  164. (new StringBuffer(15)).append(word), null));
  165. reset();
  166. }
  167. } // contentInserted }}}
  168. // //////////////////////////////////////////////////////////////////////////////////////
  169. /* (non-Javadoc)
  170. * Called when text is removed from the buffer.
  171. * If the whole word is removed we issue RESET.
  172. * If only a part of the word being typed is removed, we issue TRUNCATED.
  173. *
  174. * @param buffer The buffer in question
  175. * @param startLine The first line
  176. * @param offset The start offset, from the beginning of the buffer
  177. * We remove the text between offest and (offset+length)
  178. * @param numLines The number of lines removed
  179. * @param length The number of characters removed
  180. * @see org.gjt.sp.jedit.buffer.BufferAdapter#contentRemoved(org.gjt.sp.jedit.buffer.JEditBuffer, int, int, int, int)
  181. */
  182. public void contentRemoved(JEditBuffer buffer, int startLine, int offset, int numLines, int length)
  183. {
  184. if (logLevel == PreferencesManager.LOG_ALL)
  185. {
  186. Log.log(Log.DEBUG, TextAutocompletePlugin.class,
  187. "WordTypedListener.contentRemoved(offset:"+offset
  188. + ",length:"+length + "), lastCaret:"+ lastCaret);
  189. }
  190. // Check that the text has been removed from the end of the word being typed
  191. // and that it's <= word.length
  192. // Check that we remove from the end of the word being typed
  193. if (lastCaret != (offset+length))
  194. {
  195. if (logLevel == PreferencesManager.LOG_ALL)
  196. {
  197. Log.log(Log.DEBUG, TextAutocompletePlugin.class,
  198. "WordTypedListener.contentRemoved: IGNORING - not "
  199. + "removing from the end of the last word. LastCaret:"
  200. + lastCaret + ", Offset+length: " + (offset + length));
  201. }
  202. return;
  203. }
  204. else // Removing from the end of the word being typed...
  205. {
  206. // All the word (or even more) removed?
  207. if(length >= word.length())
  208. {
  209. if (logLevel == PreferencesManager.LOG_ALL)
  210. {
  211. Log.log(Log.DEBUG, TextAutocompletePlugin.class,
  212. "WordTypedListener.contentRemoved: more than the "
  213. + "word removed: word.length=" + word.length()
  214. + ", removed:" + length);
  215. }
  216. notifier.notifyObservers(new WordTypedEvent(WordTypedEvent.RESET,
  217. (new StringBuffer(15)).append(word), null));
  218. reset();
  219. }
  220. else
  221. {
  222. // The last word has been shortened
  223. word.delete(word.length() - length, word.length());
  224. lastCaret -= length;
  225. notifier.notifyObservers(new WordTypedEvent(WordTypedEvent.TRUNCATED,
  226. (new StringBuffer(15)).append(word), new Integer(length)));
  227. } // if-else removing more than the length of the last word
  228. } // if-else not shortening the last word
  229. }
  230. // //////////////////////////////////////////////////////////////////////////////////////
  231. // {{{ reset
  232. /** The word currently typed is not a word => discard it and forget about it. */
  233. void reset()
  234. {
  235. lastCaret = CARET_UNSET;
  236. word.setLength(0);
  237. } // reset }}}
  238. // //////////////////////////////////////////////////////////////////////////////////////
  239. // OBSERVABLE STUFF
  240. // //////////////////////////////////////////////////////////////////////////////////////
  241. /** The object that to make us observable. */
  242. java.util.Observable notifier = new java.util.Observable()
  243. {
  244. public void notifyObservers(Object keyTypedEvent)
  245. {
  246. super.setChanged();
  247. super.notifyObservers(keyTypedEvent);
  248. }// letObserversKnow
  249. };
  250. public void addObserver(java.util.Observer o)
  251. {
  252. notifier.addObserver(o);
  253. }
  254. public void deleteObserver(java.util.Observer o)
  255. {
  256. notifier.deleteObserver(o);
  257. }
  258. // //////////////////////////////////////////////////////////////////////////////////////
  259. // / RULES FOR WHAT IS A WORD x WORD SEPARATOR
  260. // //////////////////////////////////////////////////////////////////////////////////////
  261. // IMPORTANT: this filter doesn't apply for it's replaced by a filter
  262. // defined in the PreferencesManager - done in AutoComplete's constructor
  263. /** Checker that determines what is a word separator and what is not. */
  264. Filter checkIsWord = new Filter()
  265. {
  266. public boolean accept(StringBuffer word, char insertion)
  267. {
  268. return (Character.isLetter(insertion));
  269. }
  270. };
  271. // /////////////////////////////////////////////////////////////////////////////
  272. // {{{ Filter
  273. /**
  274. * Decides what belongs to a word and what doesn't, i.e. it distinguishes
  275. * word separators and word elements. Modify it to change what is considered
  276. * to be a part of a word.
  277. */
  278. public interface Filter
  279. {
  280. /**
  281. * Decide whether the character can be appended to the word or whether
  282. * it ends it.
  283. *
  284. * @param word
  285. * The word typed so far, may be empty but not null.
  286. * @param insertion
  287. * The character typed at the end of the word
  288. * @return Return true if the insertion does not end the word i.e. if it
  289. * is not the first non-word character.
  290. */
  291. public boolean accept(StringBuffer word, char insertion);
  292. } // Filter }}}
  293. // ////////////////////////////////////////////////////////////////////////////////////
  294. /**
  295. * @return Returns the checkIsWord Filter
  296. */
  297. public Filter getCheckIsWord()
  298. {
  299. return checkIsWord;
  300. }
  301. // //////////////////////////////////////////////////////////////////////////////////////
  302. /**
  303. * @param checkIsWord
  304. * The checkIsWord Filer to set.
  305. * @see WordTypedListener.Filter
  306. */
  307. public void setCheckIsWord(Filter checkIsWord)
  308. {
  309. this.checkIsWord = checkIsWord;
  310. }
  311. } // //WordTypedListener }}}
  312. // ----------------------------------------------------------------