/jEdit/tags/jedit-4-2-pre9/org/gjt/sp/jedit/gui/CompleteWord.java

# · Java · 600 lines · 447 code · 85 blank · 68 comment · 87 complexity · f418136816e544d4205545aea809e5c5 MD5 · raw file

  1. /*
  2. * CompleteWord.java - Complete word dialog
  3. * :tabSize=8:indentSize=8:noTabs=false:
  4. * :folding=explicit:collapseFolds=1:
  5. *
  6. * Copyright (C) 2000, 2001 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.gui;
  23. //{{{ Imports
  24. import javax.swing.*;
  25. import java.awt.*;
  26. import java.awt.event.*;
  27. import java.util.HashSet;
  28. import java.util.TreeSet;
  29. import java.util.Set;
  30. import org.gjt.sp.jedit.syntax.*;
  31. import org.gjt.sp.jedit.textarea.*;
  32. import org.gjt.sp.jedit.*;
  33. //}}}
  34. public class CompleteWord extends JWindow
  35. {
  36. //{{{ completeWord() method
  37. public static void completeWord(View view)
  38. {
  39. JEditTextArea textArea = view.getTextArea();
  40. Buffer buffer = view.getBuffer();
  41. int caretLine = textArea.getCaretLine();
  42. int caret = textArea.getCaretPosition();
  43. if(!buffer.isEditable())
  44. {
  45. textArea.getToolkit().beep();
  46. return;
  47. }
  48. KeywordMap keywordMap = buffer.getKeywordMapAtOffset(caret);
  49. String noWordSep = getNonAlphaNumericWordChars(
  50. buffer,keywordMap);
  51. String word = getWordToComplete(buffer,caretLine,
  52. caret,noWordSep);
  53. if(word == null)
  54. {
  55. textArea.getToolkit().beep();
  56. return;
  57. }
  58. Completion[] completions = getCompletions(buffer,word,caret);
  59. if(completions.length == 0)
  60. {
  61. textArea.getToolkit().beep();
  62. }
  63. //{{{ if there is only one competion, insert in buffer
  64. else if(completions.length == 1)
  65. {
  66. Completion c = completions[0];
  67. if(c.text.equals(word))
  68. {
  69. textArea.getToolkit().beep();
  70. }
  71. else
  72. {
  73. textArea.setSelectedText(c.text.substring(
  74. word.length()));
  75. }
  76. } //}}}
  77. //{{{ show popup if > 1
  78. else
  79. {
  80. String longestPrefix = MiscUtilities.getLongestPrefix(
  81. completions,
  82. keywordMap != null
  83. ? keywordMap.getIgnoreCase()
  84. : false);
  85. if (word.length() < longestPrefix.length())
  86. {
  87. buffer.insert(caret,longestPrefix.substring(
  88. word.length()));
  89. }
  90. textArea.scrollToCaret(false);
  91. Point location = textArea.offsetToXY(
  92. caret - word.length());
  93. location.y += textArea.getPainter().getFontMetrics()
  94. .getHeight();
  95. SwingUtilities.convertPointToScreen(location,
  96. textArea.getPainter());
  97. new CompleteWord(view,longestPrefix,
  98. completions,location,noWordSep);
  99. } //}}}
  100. } //}}}
  101. //{{{ CompleteWord constructor
  102. public CompleteWord(View view, String word, Completion[] completions,
  103. Point location, String noWordSep)
  104. {
  105. super(view);
  106. this.noWordSep = noWordSep;
  107. setContentPane(new JPanel(new BorderLayout())
  108. {
  109. /**
  110. * Returns if this component can be traversed by pressing the
  111. * Tab key. This returns false.
  112. */
  113. public boolean isManagingFocus()
  114. {
  115. return false;
  116. }
  117. /**
  118. * Makes the tab key work in Java 1.4.
  119. */
  120. public boolean getFocusTraversalKeysEnabled()
  121. {
  122. return false;
  123. }
  124. });
  125. this.view = view;
  126. this.textArea = view.getTextArea();
  127. this.buffer = view.getBuffer();
  128. this.word = word;
  129. words = new JList(completions);
  130. words.setVisibleRowCount(Math.min(completions.length,8));
  131. words.addMouseListener(new MouseHandler());
  132. words.setSelectedIndex(0);
  133. words.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  134. words.setCellRenderer(new Renderer());
  135. // stupid scrollbar policy is an attempt to work around
  136. // bugs people have been seeing with IBM's JDK -- 7 Sep 2000
  137. JScrollPane scroller = new JScrollPane(words,
  138. JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
  139. JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
  140. getContentPane().add(scroller, BorderLayout.CENTER);
  141. GUIUtilities.requestFocus(this,words);
  142. pack();
  143. setLocation(location);
  144. show();
  145. KeyHandler keyHandler = new KeyHandler();
  146. addKeyListener(keyHandler);
  147. words.addKeyListener(keyHandler);
  148. view.setKeyEventInterceptor(keyHandler);
  149. } //}}}
  150. //{{{ dispose() method
  151. public void dispose()
  152. {
  153. view.setKeyEventInterceptor(null);
  154. super.dispose();
  155. SwingUtilities.invokeLater(new Runnable()
  156. {
  157. public void run()
  158. {
  159. textArea.requestFocus();
  160. }
  161. });
  162. } //}}}
  163. //{{{ Private members
  164. //{{{ getNonAlphaNumericWordChars() method
  165. private static String getNonAlphaNumericWordChars(Buffer buffer,
  166. KeywordMap keywordMap)
  167. {
  168. // figure out what constitutes a word character and what
  169. // doesn't
  170. String noWordSep = buffer.getStringProperty("noWordSep");
  171. if(noWordSep == null)
  172. noWordSep = "";
  173. if(keywordMap != null)
  174. {
  175. String keywordNoWordSep = keywordMap.getNonAlphaNumericChars();
  176. if(keywordNoWordSep != null)
  177. noWordSep = noWordSep + keywordNoWordSep;
  178. }
  179. return noWordSep;
  180. } //}}}
  181. //{{{ getWordToComplete() method
  182. private static String getWordToComplete(Buffer buffer, int caretLine,
  183. int caret, String noWordSep)
  184. {
  185. String line = buffer.getLineText(caretLine);
  186. int dot = caret - buffer.getLineStartOffset(caretLine);
  187. if(dot == 0)
  188. return null;
  189. char ch = line.charAt(dot-1);
  190. if(!Character.isLetterOrDigit(ch)
  191. && noWordSep.indexOf(ch) == -1)
  192. {
  193. // attempting to expand non-word char
  194. return null;
  195. }
  196. int wordStart = TextUtilities.findWordStart(line,dot-1,noWordSep);
  197. String word = line.substring(wordStart,dot);
  198. if(word.length() == 0)
  199. return null;
  200. return word;
  201. } //}}}
  202. //{{{ getCompletions() method
  203. private static Completion[] getCompletions(Buffer buffer, String word,
  204. int caret)
  205. {
  206. // build a list of unique words in all visible buffers
  207. Set completions = new TreeSet(new MiscUtilities
  208. .StringICaseCompare());
  209. Set buffers = new HashSet();
  210. // only complete current buffer's keyword map
  211. KeywordMap keywordMap = buffer.getKeywordMapAtOffset(caret);
  212. String noWordSep = getNonAlphaNumericWordChars(
  213. buffer,keywordMap);
  214. View views = jEdit.getFirstView();
  215. while(views != null)
  216. {
  217. EditPane[] panes = views.getEditPanes();
  218. for(int i = 0; i < panes.length; i++)
  219. {
  220. Buffer b = panes[i].getBuffer();
  221. if(buffers.contains(b))
  222. continue;
  223. buffers.add(b);
  224. // only complete current buffer's keyword map
  225. KeywordMap _keywordMap;
  226. if(b == buffer)
  227. _keywordMap = keywordMap;
  228. else
  229. _keywordMap = null;
  230. int offset = (b == buffer ? caret : 0);
  231. getCompletions(b,word,keywordMap,noWordSep,
  232. offset,completions);
  233. }
  234. views = views.getNext();
  235. }
  236. Completion[] completionArray = (Completion[])completions
  237. .toArray(new Completion[completions.size()]);
  238. return completionArray;
  239. } //}}}
  240. //{{{ getCompletions() method
  241. private static void getCompletions(Buffer buffer, String word,
  242. KeywordMap keywordMap, String noWordSep, int caret,
  243. Set completions)
  244. {
  245. int wordLen = word.length();
  246. //{{{ try to find matching keywords
  247. if(keywordMap != null)
  248. {
  249. String[] keywords = keywordMap.getKeywords();
  250. for(int i = 0; i < keywords.length; i++)
  251. {
  252. String _keyword = keywords[i];
  253. if(_keyword.regionMatches(keywordMap.getIgnoreCase(),
  254. 0,word,0,wordLen))
  255. {
  256. Completion keyword = new Completion(_keyword,true);
  257. if(!completions.contains(keyword))
  258. {
  259. completions.add(keyword);
  260. }
  261. }
  262. }
  263. } //}}}
  264. //{{{ loop through all lines of current buffer
  265. for(int i = 0; i < buffer.getLineCount(); i++)
  266. {
  267. String line = buffer.getLineText(i);
  268. int start = buffer.getLineStartOffset(i);
  269. // check for match at start of line
  270. if(line.startsWith(word) && caret != start + word.length())
  271. {
  272. String _word = completeWord(line,0,noWordSep);
  273. Completion comp = new Completion(_word,false);
  274. // remove duplicates
  275. if(!completions.contains(comp))
  276. {
  277. completions.add(comp);
  278. }
  279. }
  280. // check for match inside line
  281. int len = line.length() - word.length();
  282. for(int j = 0; j < len; j++)
  283. {
  284. char c = line.charAt(j);
  285. if(!Character.isLetterOrDigit(c) && noWordSep.indexOf(c) == -1)
  286. {
  287. if(line.regionMatches(j + 1,word,0,wordLen)
  288. && caret != start + j + word.length() + 1)
  289. {
  290. String _word = completeWord(line,j + 1,noWordSep);
  291. Completion comp = new Completion(_word,false);
  292. // remove duplicates
  293. if(!completions.contains(comp))
  294. {
  295. completions.add(comp);
  296. }
  297. }
  298. }
  299. }
  300. } //}}}
  301. } //}}}
  302. //{{{ completeWord() method
  303. private static String completeWord(String line, int offset, String noWordSep)
  304. {
  305. // '+ 1' so that findWordEnd() doesn't pick up the space at the start
  306. int wordEnd = TextUtilities.findWordEnd(line,offset + 1,noWordSep);
  307. return line.substring(offset,wordEnd);
  308. } //}}}
  309. //{{{ Instance variables
  310. private View view;
  311. private JEditTextArea textArea;
  312. private Buffer buffer;
  313. private String word;
  314. private JList words;
  315. private String noWordSep;
  316. //}}}
  317. //{{{ insertSelected() method
  318. private void insertSelected()
  319. {
  320. textArea.setSelectedText(words.getSelectedValue().toString()
  321. .substring(word.length()));
  322. dispose();
  323. } //}}}
  324. //}}}
  325. //{{{ Completion class
  326. static class Completion
  327. {
  328. String text;
  329. boolean keyword;
  330. Completion(String text, boolean keyword)
  331. {
  332. this.text = text;
  333. this.keyword = keyword;
  334. }
  335. public String toString()
  336. {
  337. return text;
  338. }
  339. public int hashCode()
  340. {
  341. return text.hashCode();
  342. }
  343. public boolean equals(Object obj)
  344. {
  345. if(obj instanceof Completion)
  346. return ((Completion)obj).text.equals(text);
  347. else
  348. return false;
  349. }
  350. } //}}}
  351. //{{{ Renderer class
  352. static class Renderer extends DefaultListCellRenderer
  353. {
  354. public Component getListCellRendererComponent(JList list, Object value,
  355. int index, boolean isSelected, boolean cellHasFocus)
  356. {
  357. super.getListCellRendererComponent(list,null,index,
  358. isSelected,cellHasFocus);
  359. Completion comp = (Completion)value;
  360. if(index < 9)
  361. setText((index + 1) + ": " + comp.text);
  362. else if(index == 9)
  363. setText("0: " + comp.text);
  364. else
  365. setText(comp.text);
  366. if(comp.keyword)
  367. setFont(list.getFont().deriveFont(Font.BOLD));
  368. else
  369. setFont(list.getFont());
  370. return this;
  371. }
  372. } //}}}
  373. //{{{ KeyHandler class
  374. class KeyHandler extends KeyAdapter
  375. {
  376. //{{{ keyPressed() method
  377. public void keyPressed(KeyEvent evt)
  378. {
  379. switch(evt.getKeyCode())
  380. {
  381. case KeyEvent.VK_TAB:
  382. case KeyEvent.VK_ENTER:
  383. insertSelected();
  384. evt.consume();
  385. break;
  386. case KeyEvent.VK_ESCAPE:
  387. dispose();
  388. evt.consume();
  389. break;
  390. case KeyEvent.VK_UP:
  391. int selected = words.getSelectedIndex();
  392. if(selected == 0)
  393. selected = words.getModel().getSize() - 1;
  394. else if(getFocusOwner() == words)
  395. return;
  396. else
  397. selected = selected - 1;
  398. words.setSelectedIndex(selected);
  399. words.ensureIndexIsVisible(selected);
  400. evt.consume();
  401. break;
  402. case KeyEvent.VK_DOWN:
  403. /* int */ selected = words.getSelectedIndex();
  404. if(selected == words.getModel().getSize() - 1)
  405. selected = 0;
  406. else if(getFocusOwner() == words)
  407. return;
  408. else
  409. selected = selected + 1;
  410. words.setSelectedIndex(selected);
  411. words.ensureIndexIsVisible(selected);
  412. evt.consume();
  413. break;
  414. case KeyEvent.VK_BACK_SPACE:
  415. if(word.length() == 1)
  416. {
  417. textArea.backspace();
  418. evt.consume();
  419. dispose();
  420. }
  421. else
  422. {
  423. word = word.substring(0,word.length() - 1);
  424. textArea.backspace();
  425. int caret = textArea.getCaretPosition();
  426. Completion[] completions
  427. = getCompletions(buffer,word,
  428. caret);
  429. if(completions.length == 0)
  430. {
  431. dispose();
  432. return;
  433. }
  434. words.setListData(completions);
  435. words.setSelectedIndex(0);
  436. words.setVisibleRowCount(Math.min(completions.length,8));
  437. pack();
  438. evt.consume();
  439. }
  440. break;
  441. default:
  442. if(evt.isActionKey()
  443. || evt.isControlDown()
  444. || evt.isAltDown()
  445. || evt.isMetaDown())
  446. {
  447. dispose();
  448. view.processKeyEvent(evt);
  449. }
  450. break;
  451. }
  452. } //}}}
  453. //{{{ keyTyped() method
  454. public void keyTyped(KeyEvent evt)
  455. {
  456. char ch = evt.getKeyChar();
  457. evt = KeyEventWorkaround.processKeyEvent(evt);
  458. if(evt == null)
  459. return;
  460. if(Character.isDigit(ch))
  461. {
  462. int index = ch - '0';
  463. if(index == 0)
  464. index = 9;
  465. else
  466. index--;
  467. if(index < words.getModel().getSize())
  468. {
  469. words.setSelectedIndex(index);
  470. textArea.setSelectedText(words.getModel()
  471. .getElementAt(index).toString()
  472. .substring(word.length()));
  473. dispose();
  474. return;
  475. }
  476. else
  477. /* fall through */;
  478. }
  479. // \t handled above
  480. if(ch != '\b' && ch != '\t')
  481. {
  482. /* eg, foo<C+b>, will insert foobar, */
  483. if(!Character.isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1)
  484. {
  485. insertSelected();
  486. textArea.userInput(ch);
  487. dispose();
  488. return;
  489. }
  490. textArea.userInput(ch);
  491. word = word + ch;
  492. int caret = textArea.getCaretPosition();
  493. KeywordMap keywordMap = buffer.getKeywordMapAtOffset(caret);
  494. Completion[] completions = getCompletions(
  495. buffer,word,caret);
  496. if(completions.length == 0)
  497. {
  498. dispose();
  499. return;
  500. }
  501. words.setListData(completions);
  502. words.setSelectedIndex(0);
  503. words.setVisibleRowCount(Math.min(completions.length,8));
  504. }
  505. } //}}}
  506. } //}}}
  507. //{{{ MouseHandler class
  508. class MouseHandler extends MouseAdapter
  509. {
  510. public void mouseClicked(MouseEvent evt)
  511. {
  512. insertSelected();
  513. }
  514. } //}}}
  515. }