@ -2,7 +2,7 @@
* 12 / 21 / 2008
*
* DefaultCompletionProvider . java - A basic completion provider implementation .
*
*
* This library is distributed under a modified BSD license . See the included
* RSyntaxTextArea . License . txt file for details .
* /
@ -24,7 +24,11 @@ import javax.swing.text.Segment;
import javax.xml.parsers.ParserConfigurationException ;
import javax.xml.parsers.SAXParser ;
import javax.xml.parsers.SAXParserFactory ;
import com.fr.log.FineLoggerFactory ;
import org.xml.sax.SAXException ;
import org.xml.sax.SAXNotRecognizedException ;
import org.xml.sax.SAXNotSupportedException ;
/ * *
@ -38,337 +42,345 @@ import org.xml.sax.SAXException;
* /
public class DefaultCompletionProvider extends AbstractCompletionProvider {
protected Segment seg ;
/ * *
* Used to speed up { @link # getCompletionsAt ( JTextComponent , Point ) } .
* /
private String lastCompletionsAtText ;
/ * *
* Used to speed up { @link # getCompletionsAt ( JTextComponent , Point ) } ,
* since this may be called multiple times in succession ( this is usually
* called by < tt > JTextComponent . getToolTipText ( ) < / tt > , and if the user
* wiggles the mouse while a tool tip is displayed , this method gets
* repeatedly called . It can be costly so we try to speed it up a tad ) .
* /
private List < Completion > lastParameterizedCompletionsAt ;
/ * *
* Constructor . The returned provider will not be aware of any completions .
*
* @see # addCompletion ( Completion )
* /
public DefaultCompletionProvider ( ) {
init ( ) ;
}
/ * *
* Creates a completion provider that provides completion for a simple
* list of words .
*
* @param words The words to offer as completion suggestions . If this is
* < code > null < / code > , no completions will be known .
* @see BasicCompletion
* /
public DefaultCompletionProvider ( String [ ] words ) {
init ( ) ;
addWordCompletions ( words ) ;
}
/ * *
* Returns the text just before the current caret position that could be
* the start of something auto - completable . < p >
*
* This method returns all characters before the caret that are matched
* by { @link # isValidChar ( char ) } .
*
* { @inheritDoc }
* /
public String getAlreadyEnteredText ( JTextComponent comp ) {
Document doc = comp . getDocument ( ) ;
int dot = comp . getCaretPosition ( ) ;
Element root = doc . getDefaultRootElement ( ) ;
int index = root . getElementIndex ( dot ) ;
Element elem = root . getElement ( index ) ;
int start = elem . getStartOffset ( ) ;
int len = dot - start ;
try {
doc . getText ( start , len , seg ) ;
} catch ( BadLocationException ble ) {
return EMPTY_STRING ;
}
int segEnd = seg . offset + len ;
start = segEnd - 1 ;
while ( start > = seg . offset & & isValidChar ( seg . array [ start ] ) ) {
start - - ;
}
start + + ;
len = segEnd - start ;
return len = = 0 ? EMPTY_STRING : new String ( seg . array , start , len ) ;
}
/ * *
* { @inheritDoc }
* /
public List < Completion > getCompletionsAt ( JTextComponent tc , Point p ) {
int offset = tc . viewToModel ( p ) ;
if ( offset < 0 | | offset > = tc . getDocument ( ) . getLength ( ) ) {
lastCompletionsAtText = null ;
return lastParameterizedCompletionsAt = null ;
}
Segment s = new Segment ( ) ;
Document doc = tc . getDocument ( ) ;
Element root = doc . getDefaultRootElement ( ) ;
int line = root . getElementIndex ( offset ) ;
Element elem = root . getElement ( line ) ;
int start = elem . getStartOffset ( ) ;
int end = elem . getEndOffset ( ) - 1 ;
try {
doc . getText ( start , end - start , s ) ;
// Get the valid chars before the specified offset.
int startOffs = s . offset + ( offset - start ) - 1 ;
while ( startOffs > = s . offset & & isValidChar ( s . array [ startOffs ] ) ) {
startOffs - - ;
}
// Get the valid chars at and after the specified offset.
int endOffs = s . offset + ( offset - start ) ;
while ( endOffs < s . offset + s . count & & isValidChar ( s . array [ endOffs ] ) ) {
endOffs + + ;
}
int len = endOffs - startOffs - 1 ;
if ( len < = 0 ) {
return lastParameterizedCompletionsAt = null ;
}
String text = new String ( s . array , startOffs + 1 , len ) ;
if ( text . equals ( lastCompletionsAtText ) ) {
return lastParameterizedCompletionsAt ;
}
// Get a list of all Completions matching the text.
List < Completion > list = getCompletionByInputText ( text ) ;
lastCompletionsAtText = text ;
return lastParameterizedCompletionsAt = list ;
} catch ( BadLocationException ble ) {
// Never happens
}
lastCompletionsAtText = null ;
return lastParameterizedCompletionsAt = null ;
}
/ * *
* { @inheritDoc }
* /
public List < ParameterizedCompletion > getParameterizedCompletions (
JTextComponent tc ) {
List < ParameterizedCompletion > list = null ;
// If this provider doesn't support parameterized completions,
// bail out now.
char paramListStart = getParameterListStart ( ) ;
if ( paramListStart = = 0 ) {
return list ; // null
}
int dot = tc . getCaretPosition ( ) ;
Segment s = new Segment ( ) ;
Document doc = tc . getDocument ( ) ;
Element root = doc . getDefaultRootElement ( ) ;
int line = root . getElementIndex ( dot ) ;
Element elem = root . getElement ( line ) ;
int offs = elem . getStartOffset ( ) ;
int len = dot - offs - 1 /*paramListStart.length()*/ ;
if ( len < = 0 ) { // Not enough chars on line for a method.
return list ; // null
}
try {
doc . getText ( offs , len , s ) ;
// Get the identifier preceding the '(', ignoring any whitespace
// between them.
offs = s . offset + len - 1 ;
while ( offs > = s . offset & & Character . isWhitespace ( s . array [ offs ] ) ) {
offs - - ;
}
int end = offs ;
while ( offs > = s . offset & & isValidChar ( s . array [ offs ] ) ) {
offs - - ;
}
String text = new String ( s . array , offs + 1 , end - offs ) ;
// Get a list of all Completions matching the text, but then
// narrow it down to just the ParameterizedCompletions.
List < Completion > l = getCompletionByInputText ( text ) ;
if ( l ! = null & & ! l . isEmpty ( ) ) {
for ( int i = 0 ; i < l . size ( ) ; i + + ) {
Object o = l . get ( i ) ;
if ( o instanceof ParameterizedCompletion ) {
if ( list = = null ) {
list = new ArrayList < ParameterizedCompletion > ( 1 ) ;
}
list . add ( ( ParameterizedCompletion ) o ) ;
}
}
}
} catch ( BadLocationException ble ) {
// Never happens
}
return list ;
}
/ * *
* Initializes this completion provider .
* /
protected void init ( ) {
completions = new ArrayList < Completion > ( ) ;
seg = new Segment ( ) ;
}
/ * *
* Returns whether the specified character is valid in an auto - completion .
* The default implementation is equivalent to
* "<code>Character.isLetterOrDigit(ch) || ch=='_'</code>" . Subclasses
* can override this method to change what characters are matched .
*
* @param ch The character .
* @return Whether the character is valid .
* /
protected boolean isValidChar ( char ch ) {
return Character . isLetterOrDigit ( ch ) | | ch = = '_' ;
}
/ * *
* Loads completions from an XML file . The XML should validate against
* < code > CompletionXml . dtd < / code > .
*
* @param file An XML file to load from .
* @throws IOException If an IO error occurs .
* /
public void loadFromXML ( File file ) throws IOException {
BufferedInputStream bin = new BufferedInputStream (
new FileInputStream ( file ) ) ;
try {
loadFromXML ( bin ) ;
} finally {
bin . close ( ) ;
}
}
/ * *
* Loads completions from an XML input stream . The XML should validate
* against < code > CompletionXml . dtd < / code > .
*
* @param in The input stream to read from .
* @throws IOException If an IO error occurs .
* /
public void loadFromXML ( InputStream in ) throws IOException {
loadFromXML ( in , null ) ;
}
/ * *
* Loads completions from an XML input stream . The XML should validate
* against < code > CompletionXml . dtd < / code > .
*
* @param in The input stream to read from .
* @param cl The class loader to use when loading any extra classes defined
* in the XML , such as custom { @link FunctionCompletion } s . This
* may be < code > null < / code > if the default is to be used , or if no
* custom completions are defined in the XML .
* @throws IOException If an IO error occurs .
* /
public void loadFromXML ( InputStream in , ClassLoader cl ) throws IOException {
//long start = System.currentTimeMillis();
SAXParserFactory factory = SAXParserFactory . newInstance ( ) ;
factory . setValidating ( true ) ;
CompletionXMLParser handler = new CompletionXMLParser ( this , cl ) ;
BufferedInputStream bin = new BufferedInputStream ( in ) ;
try {
SAXParser saxParser = factory . newSAXParser ( ) ;
saxParser . parse ( bin , handler ) ;
List < Completion > completions = handler . getCompletions ( ) ;
addCompletions ( completions ) ;
char startChar = handler . getParamStartChar ( ) ;
if ( startChar ! = 0 ) {
char endChar = handler . getParamEndChar ( ) ;
String sep = handler . getParamSeparator ( ) ;
if ( endChar ! = 0 & & sep ! = null & & sep . length ( ) > 0 ) { // Sanity
setParameterizedCompletionParams ( startChar , sep , endChar ) ;
}
}
} catch ( SAXException se ) {
throw new IOException ( se . toString ( ) ) ;
} catch ( ParserConfigurationException pce ) {
throw new IOException ( pce . toString ( ) ) ;
} finally {
//long time = System.currentTimeMillis() - start;
//System.out.println("XML loaded in: " + time + "ms");
bin . close ( ) ;
}
}
/ * *
* Loads completions from an XML file . The XML should validate against
* < code > CompletionXml . dtd < / code > .
*
* @param resource A resource the current ClassLoader can get to .
* @throws IOException If an IO error occurs .
* /
public void loadFromXML ( String resource ) throws IOException {
ClassLoader cl = getClass ( ) . getClassLoader ( ) ;
InputStream in = cl . getResourceAsStream ( resource ) ;
if ( in = = null ) {
File file = new File ( resource ) ;
if ( file . isFile ( ) ) {
in = new FileInputStream ( file ) ;
}
else {
throw new IOException ( "No such resource: " + resource ) ;
}
}
BufferedInputStream bin = new BufferedInputStream ( in ) ;
try {
loadFromXML ( bin ) ;
} finally {
bin . close ( ) ;
}
}
protected Segment seg ;
/ * *
* Used to speed up { @link # getCompletionsAt ( JTextComponent , Point ) } .
* /
private String lastCompletionsAtText ;
/ * *
* Used to speed up { @link # getCompletionsAt ( JTextComponent , Point ) } ,
* since this may be called multiple times in succession ( this is usually
* called by < tt > JTextComponent . getToolTipText ( ) < / tt > , and if the user
* wiggles the mouse while a tool tip is displayed , this method gets
* repeatedly called . It can be costly so we try to speed it up a tad ) .
* /
private List < Completion > lastParameterizedCompletionsAt ;
/ * *
* Constructor . The returned provider will not be aware of any completions .
*
* @see # addCompletion ( Completion )
* /
public DefaultCompletionProvider ( ) {
init ( ) ;
}
/ * *
* Creates a completion provider that provides completion for a simple
* list of words .
*
* @param words The words to offer as completion suggestions . If this is
* < code > null < / code > , no completions will be known .
* @see BasicCompletion
* /
public DefaultCompletionProvider ( String [ ] words ) {
init ( ) ;
addWordCompletions ( words ) ;
}
/ * *
* Returns the text just before the current caret position that could be
* the start of something auto - completable . < p >
* < p >
* This method returns all characters before the caret that are matched
* by { @link # isValidChar ( char ) } .
* < p >
* { @inheritDoc }
* /
public String getAlreadyEnteredText ( JTextComponent comp ) {
Document doc = comp . getDocument ( ) ;
int dot = comp . getCaretPosition ( ) ;
Element root = doc . getDefaultRootElement ( ) ;
int index = root . getElementIndex ( dot ) ;
Element elem = root . getElement ( index ) ;
int start = elem . getStartOffset ( ) ;
int len = dot - start ;
try {
doc . getText ( start , len , seg ) ;
} catch ( BadLocationException ble ) {
return EMPTY_STRING ;
}
int segEnd = seg . offset + len ;
start = segEnd - 1 ;
while ( start > = seg . offset & & isValidChar ( seg . array [ start ] ) ) {
start - - ;
}
start + + ;
len = segEnd - start ;
return len = = 0 ? EMPTY_STRING : new String ( seg . array , start , len ) ;
}
/ * *
* { @inheritDoc }
* /
public List < Completion > getCompletionsAt ( JTextComponent tc , Point p ) {
int offset = tc . viewToModel ( p ) ;
if ( offset < 0 | | offset > = tc . getDocument ( ) . getLength ( ) ) {
lastCompletionsAtText = null ;
return lastParameterizedCompletionsAt = null ;
}
Segment s = new Segment ( ) ;
Document doc = tc . getDocument ( ) ;
Element root = doc . getDefaultRootElement ( ) ;
int line = root . getElementIndex ( offset ) ;
Element elem = root . getElement ( line ) ;
int start = elem . getStartOffset ( ) ;
int end = elem . getEndOffset ( ) - 1 ;
try {
doc . getText ( start , end - start , s ) ;
// Get the valid chars before the specified offset.
int startOffs = s . offset + ( offset - start ) - 1 ;
while ( startOffs > = s . offset & & isValidChar ( s . array [ startOffs ] ) ) {
startOffs - - ;
}
// Get the valid chars at and after the specified offset.
int endOffs = s . offset + ( offset - start ) ;
while ( endOffs < s . offset + s . count & & isValidChar ( s . array [ endOffs ] ) ) {
endOffs + + ;
}
int len = endOffs - startOffs - 1 ;
if ( len < = 0 ) {
return lastParameterizedCompletionsAt = null ;
}
String text = new String ( s . array , startOffs + 1 , len ) ;
if ( text . equals ( lastCompletionsAtText ) ) {
return lastParameterizedCompletionsAt ;
}
// Get a list of all Completions matching the text.
List < Completion > list = getCompletionByInputText ( text ) ;
lastCompletionsAtText = text ;
return lastParameterizedCompletionsAt = list ;
} catch ( BadLocationException ble ) {
// Never happens
}
lastCompletionsAtText = null ;
return lastParameterizedCompletionsAt = null ;
}
/ * *
* { @inheritDoc }
* /
public List < ParameterizedCompletion > getParameterizedCompletions (
JTextComponent tc ) {
List < ParameterizedCompletion > list = null ;
// If this provider doesn't support parameterized completions,
// bail out now.
char paramListStart = getParameterListStart ( ) ;
if ( paramListStart = = 0 ) {
return list ; // null
}
int dot = tc . getCaretPosition ( ) ;
Segment s = new Segment ( ) ;
Document doc = tc . getDocument ( ) ;
Element root = doc . getDefaultRootElement ( ) ;
int line = root . getElementIndex ( dot ) ;
Element elem = root . getElement ( line ) ;
int offs = elem . getStartOffset ( ) ;
int len = dot - offs - 1 /*paramListStart.length()*/ ;
if ( len < = 0 ) { // Not enough chars on line for a method.
return list ; // null
}
try {
doc . getText ( offs , len , s ) ;
// Get the identifier preceding the '(', ignoring any whitespace
// between them.
offs = s . offset + len - 1 ;
while ( offs > = s . offset & & Character . isWhitespace ( s . array [ offs ] ) ) {
offs - - ;
}
int end = offs ;
while ( offs > = s . offset & & isValidChar ( s . array [ offs ] ) ) {
offs - - ;
}
String text = new String ( s . array , offs + 1 , end - offs ) ;
// Get a list of all Completions matching the text, but then
// narrow it down to just the ParameterizedCompletions.
List < Completion > l = getCompletionByInputText ( text ) ;
if ( l ! = null & & ! l . isEmpty ( ) ) {
for ( int i = 0 ; i < l . size ( ) ; i + + ) {
Object o = l . get ( i ) ;
if ( o instanceof ParameterizedCompletion ) {
if ( list = = null ) {
list = new ArrayList < ParameterizedCompletion > ( 1 ) ;
}
list . add ( ( ParameterizedCompletion ) o ) ;
}
}
}
} catch ( BadLocationException ble ) {
// Never happens
}
return list ;
}
/ * *
* Initializes this completion provider .
* /
protected void init ( ) {
completions = new ArrayList < Completion > ( ) ;
seg = new Segment ( ) ;
}
/ * *
* Returns whether the specified character is valid in an auto - completion .
* The default implementation is equivalent to
* "<code>Character.isLetterOrDigit(ch) || ch=='_'</code>" . Subclasses
* can override this method to change what characters are matched .
*
* @param ch The character .
* @return Whether the character is valid .
* /
protected boolean isValidChar ( char ch ) {
return Character . isLetterOrDigit ( ch ) | | ch = = '_' ;
}
/ * *
* Loads completions from an XML file . The XML should validate against
* < code > CompletionXml . dtd < / code > .
*
* @param file An XML file to load from .
* @throws IOException If an IO error occurs .
* /
public void loadFromXML ( File file ) throws IOException {
BufferedInputStream bin = new BufferedInputStream (
new FileInputStream ( file ) ) ;
try {
loadFromXML ( bin ) ;
} finally {
bin . close ( ) ;
}
}
/ * *
* Loads completions from an XML input stream . The XML should validate
* against < code > CompletionXml . dtd < / code > .
*
* @param in The input stream to read from .
* @throws IOException If an IO error occurs .
* /
public void loadFromXML ( InputStream in ) throws IOException {
loadFromXML ( in , null ) ;
}
/ * *
* Loads completions from an XML input stream . The XML should validate
* against < code > CompletionXml . dtd < / code > .
*
* @param in The input stream to read from .
* @param cl The class loader to use when loading any extra classes defined
* in the XML , such as custom { @link FunctionCompletion } s . This
* may be < code > null < / code > if the default is to be used , or if no
* custom completions are defined in the XML .
* @throws IOException If an IO error occurs .
* /
public void loadFromXML ( InputStream in , ClassLoader cl ) throws IOException {
//long start = System.currentTimeMillis();
SAXParserFactory factory = SAXParserFactory . newInstance ( ) ;
try {
factory . setFeature ( "http://apache.org/xml/features/disallow-doctype-decl" , true ) ;
factory . setFeature ( "http://xml.org/sax/features/external-general-entities" , false ) ;
factory . setFeature ( "http://xml.org/sax/features/external-parameter-entities" , false ) ;
factory . setFeature ( "http://apache.org/xml/features/nonvalidating/load-external-dtd" , false ) ;
} catch ( ParserConfigurationException | SAXNotSupportedException | SAXNotRecognizedException e ) {
FineLoggerFactory . getLogger ( ) . warn ( e . getMessage ( ) , e ) ;
}
factory . setValidating ( true ) ;
CompletionXMLParser handler = new CompletionXMLParser ( this , cl ) ;
BufferedInputStream bin = new BufferedInputStream ( in ) ;
try {
SAXParser saxParser = factory . newSAXParser ( ) ;
saxParser . parse ( bin , handler ) ;
List < Completion > completions = handler . getCompletions ( ) ;
addCompletions ( completions ) ;
char startChar = handler . getParamStartChar ( ) ;
if ( startChar ! = 0 ) {
char endChar = handler . getParamEndChar ( ) ;
String sep = handler . getParamSeparator ( ) ;
if ( endChar ! = 0 & & sep ! = null & & sep . length ( ) > 0 ) { // Sanity
setParameterizedCompletionParams ( startChar , sep , endChar ) ;
}
}
} catch ( SAXException se ) {
throw new IOException ( se . toString ( ) ) ;
} catch ( ParserConfigurationException pce ) {
throw new IOException ( pce . toString ( ) ) ;
} finally {
//long time = System.currentTimeMillis() - start;
//System.out.println("XML loaded in: " + time + "ms");
bin . close ( ) ;
}
}
/ * *
* Loads completions from an XML file . The XML should validate against
* < code > CompletionXml . dtd < / code > .
*
* @param resource A resource the current ClassLoader can get to .
* @throws IOException If an IO error occurs .
* /
public void loadFromXML ( String resource ) throws IOException {
ClassLoader cl = getClass ( ) . getClassLoader ( ) ;
InputStream in = cl . getResourceAsStream ( resource ) ;
if ( in = = null ) {
File file = new File ( resource ) ;
if ( file . isFile ( ) ) {
in = new FileInputStream ( file ) ;
} else {
throw new IOException ( "No such resource: " + resource ) ;
}
}
BufferedInputStream bin = new BufferedInputStream ( in ) ;
try {
loadFromXML ( bin ) ;
} finally {
bin . close ( ) ;
}
}
}