/*
* CompleteWord.java - Complete word dialog
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 2000, 2001 Slava Pestov
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gjt.sp.jedit.gui;
//{{{ Imports
import java.awt.Component;
import java.awt.Font;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
import java.util.Arrays;
import java.util.Collection;
import javax.swing.DefaultListCellRenderer;
import javax.swing.JList;
import javax.swing.SwingUtilities;
import org.gjt.sp.jedit.Buffer;
import org.gjt.sp.jedit.EditPane;
import org.gjt.sp.jedit.visitors.JEditVisitorAdapter;
import org.gjt.sp.jedit.jEdit;
import org.gjt.sp.jedit.MiscUtilities;
import org.gjt.sp.jedit.TextUtilities;
import org.gjt.sp.jedit.View;
import org.gjt.sp.jedit.syntax.KeywordMap;
import org.gjt.sp.jedit.textarea.JEditTextArea;
import org.gjt.sp.util.StandardUtilities;
//}}}
/** A word completion popup.
*/
public class CompleteWord extends CompletionPopup
{
//{{{ completeWord() method
public static void completeWord(View view)
{
JEditTextArea textArea = view.getTextArea();
Buffer buffer = view.getBuffer();
int caretLine = textArea.getCaretLine();
int caret = textArea.getCaretPosition();
if(!buffer.isEditable())
{
textArea.getToolkit().beep();
return;
}
KeywordMap keywordMap = buffer.getKeywordMapAtOffset(caret);
String noWordSep = getNonAlphaNumericWordChars(
buffer,keywordMap);
String word = getWordToComplete(buffer,caretLine,
caret,noWordSep);
if(word == null)
{
textArea.getToolkit().beep();
return;
}
Completion[] completions = getCompletions(buffer,word,caret);
if(completions.length == 0)
{
textArea.getToolkit().beep();
}
//{{{ if there is only one competion, insert in buffer
else if(completions.length == 1)
{
Completion c = completions[0];
if(c.text.equals(word))
{
textArea.getToolkit().beep();
}
else
{
textArea.replaceSelection(c.text.substring(
word.length()));
}
} //}}}
//{{{ show popup if > 1
else
{
String longestPrefix = MiscUtilities.getLongestPrefix(
completions, keywordMap != null && keywordMap.getIgnoreCase());
if (word.length() < longestPrefix.length())
{
buffer.insert(caret,longestPrefix.substring(
word.length()));
}
textArea.scrollToCaret(false);
Point location = textArea.offsetToXY(
caret - word.length());
location.y += textArea.getPainter().getLineHeight();
SwingUtilities.convertPointToScreen(location,
textArea.getPainter());
new CompleteWord(view,longestPrefix,
completions,location,noWordSep);
} //}}}
} //}}}
//{{{ CompleteWord constructor
public CompleteWord(View view, String word, Completion[] completions,
Point location, String noWordSep)
{
super(view, location);
this.noWordSep = noWordSep;
this.view = view;
this.textArea = view.getTextArea();
this.buffer = view.getBuffer();
this.word = word;
reset(new Words(completions), true);
} //}}}
//{{{ Private members
//{{{ getNonAlphaNumericWordChars() method
private static String getNonAlphaNumericWordChars(Buffer buffer,
KeywordMap keywordMap)
{
// figure out what constitutes a word character and what
// doesn't
String noWordSep = buffer.getStringProperty("noWordSep");
if(noWordSep == null)
noWordSep = "";
if(keywordMap != null)
{
String keywordNoWordSep = keywordMap.getNonAlphaNumericChars();
if(keywordNoWordSep != null)
noWordSep += keywordNoWordSep;
}
return noWordSep;
} //}}}
//{{{ getWordToComplete() method
private static String getWordToComplete(Buffer buffer, int caretLine,
int caret, String noWordSep)
{
CharSequence line = buffer.getLineSegment(caretLine);
int dot = caret - buffer.getLineStartOffset(caretLine);
if(dot == 0)
return null;
char ch = line.charAt(dot-1);
if(!Character.isLetterOrDigit(ch)
&& noWordSep.indexOf(ch) == -1)
{
// attempting to expand non-word char
return null;
}
int wordStart = TextUtilities.findWordStart(line,dot-1,noWordSep);
CharSequence word = line.subSequence(wordStart,dot);
if(word.length() == 0)
return null;
return word.toString();
} //}}}
//{{{ getVisibleBuffers() method
private static Collection<Buffer> getVisibleBuffers()
{
final Set<Buffer> buffers = new HashSet<Buffer>();
jEdit.visit(new JEditVisitorAdapter()
{
@Override
public void visit(EditPane editPane)
{
buffers.add(editPane.getBuffer());
}
});
return buffers;
} //}}}
//{{{ getCompletions() method
private static Completion[] getCompletions(final Buffer buffer, final String word,
final int caret)
{
// build a list of unique words in all buffers, or visible buffers,
// depending on completeFromAllBuffers
final Set<Completion> completions = new TreeSet<Completion>(new StandardUtilities
.StringCompare<Completion>());
// only complete current buffer's keyword map
final KeywordMap keywordMap = buffer.getKeywordMapAtOffset(caret);
final String noWordSep = getNonAlphaNumericWordChars(
buffer,keywordMap);
final Collection<Buffer> sourceBuffers =
jEdit.getBooleanProperty("completeFromAllBuffers") ?
Arrays.asList(jEdit.getBuffers()) :
getVisibleBuffers();
for (Buffer b : sourceBuffers)
{
// only complete current buffer's keyword map
KeywordMap _keywordMap;
if(b == buffer)
_keywordMap = keywordMap;
else
_keywordMap = null;
int offset = (b == buffer ? caret : 0);
getCompletions(b,word,keywordMap,noWordSep,
offset,completions);
}
Completion[] completionArray = completions
.toArray(new Completion[completions.size()]);
return completionArray;
} //}}}
//{{{ getCompletions() method
private static void getCompletions(Buffer buffer, String word,
KeywordMap keywordMap, String noWordSep, int caret,
Set<Completion> completions)
{
int wordLen = word.length();
//{{{ try to find matching keywords
if(keywordMap != null)
{
String[] keywords = keywordMap.getKeywords();
for (String _keyword : keywords)
{
if (_keyword.regionMatches(keywordMap.getIgnoreCase(),
0, word, 0, wordLen))
{
Completion keyword = new Completion(_keyword, true);
if (!completions.contains(keyword))
completions.add(keyword);
}
}
} //}}}
//{{{ loop through all lines of current buffer
for(int i = 0; i < buffer.getLineCount(); i++)
{
CharSequence line = buffer.getLineSegment(i);
int start = buffer.getLineStartOffset(i);
// check for match at start of line
if (StandardUtilities.startsWith(line, word) &&
caret != start + word.length())
{
String _word = completeWord(line,0,noWordSep);
Completion comp = new Completion(_word,false);
// remove duplicates
if(!completions.contains(comp))
{
completions.add(comp);
}
}
// check for match inside line
int len = line.length() - word.length();
for(int j = 0; j < len; j++)
{
char c = line.charAt(j);
if(!Character.isLetterOrDigit(c) && noWordSep.indexOf(c) == -1)
{
if (StandardUtilities.regionMatches(line,j + 1,word,0,wordLen)
&& caret != start + j + word.length() + 1)
{
String _word = completeWord(line,j + 1,noWordSep);
Completion comp = new Completion(_word,false);
// remove duplicates
if(!completions.contains(comp))
{
completions.add(comp);
}
}
}
}
} //}}}
} //}}}
//{{{ completeWord() method
private static String completeWord(CharSequence line, int offset, String noWordSep)
{
// '+ 1' so that findWordEnd() doesn't pick up the space at the start
int wordEnd = TextUtilities.findWordEnd(line,offset + 1,noWordSep);
return line.subSequence(offset,wordEnd).toString();
} //}}}
//{{{ Instance variables
private View view;
private JEditTextArea textArea;
private Buffer buffer;
private String word;
private String noWordSep;
//}}}
//{{{ Completion class
private static class Completion
{
final String text;
final boolean keyword;
Completion(String text, boolean keyword)
{
this.text = text;
this.keyword = keyword;
}
public String toString()
{
return text;
}
public int hashCode()
{
return text.hashCode();
}
public boolean equals(Object obj)
{
if(obj instanceof Completion)
return ((Completion)obj).text.equals(text);
else
return false;
}
} //}}}
//{{{ Words class
private class Words implements Candidates
{
private final DefaultListCellRenderer renderer;
private final Completion[] completions;
public Words(Completion[] completions)
{
this.renderer = new DefaultListCellRenderer();
this.completions = completions;
}
public int getSize()
{
return completions.length;
}
public boolean isValid()
{
return true;
}
public void complete(int index)
{
String insertion = completions[index].toString().substring(word.length());
textArea.replaceSelection(insertion);
}
public Component getCellRenderer(JList list, int index,
boolean isSelected, boolean cellHasFocus)
{
renderer.getListCellRendererComponent(list,
null, index, isSelected, cellHasFocus);
Completion comp = completions[index];
String text = comp.text;
Font font = list.getFont();
if(index < 9)
text = (index + 1) + ": " + text;
else if(index == 9)
text = "0: " + text;
if(comp.keyword)
font = font.deriveFont(Font.BOLD);
renderer.setText(text);
renderer.setFont(font);
return renderer;
}
public String getDescription(int index)
{
return null;
}
} //}}}
//{{{ resetWords() method
private void resetWords(String newWord)
{
int caret = textArea.getCaretPosition();
Completion[] completions = getCompletions(
buffer,newWord,caret);
if(completions.length > 0)
{
word = newWord;
reset(new Words(completions), true);
}
else
{
dispose();
}
} //}}}
//}}}
//{{{ keyPressed() medhod
protected void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_BACK_SPACE)
{
textArea.backspace();
e.consume();
if(word.length() == 1)
{
dispose();
}
else
{
resetWords(word.substring(0,word.length() - 1));
}
}
} //}}}
//{{{ keyTyped() medhod
protected void keyTyped(KeyEvent e)
{
char ch = e.getKeyChar();
if(jEdit.getBooleanProperty("insertCompletionWithDigit") && Character.isDigit(ch))
{
int index = ch - '0';
if(index == 0)
index = 9;
else
index--;
if(index < getCandidates().getSize())
{
setSelectedIndex(index);
if(doSelectedCompletion())
{
e.consume();
dispose();
}
return;
}
else
/* fall through */;
}
// \t handled above
if(ch != '\b' && ch != '\t')
{
/* eg, foo<C+b>, will insert foobar, */
if(!Character.isLetterOrDigit(ch) && noWordSep.indexOf(ch) == -1)
{
doSelectedCompletion();
textArea.userInput(ch);
e.consume();
dispose();
return;
}
textArea.userInput(ch);
e.consume();
resetWords(word + ch);
}
} //}}}
}