PageRenderTime 89ms CodeModel.GetById 63ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-3-pre5/org/gjt/sp/jedit/gui/CompleteWord.java

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