/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun * Microsystems, Inc. All Rights Reserved. */ package org.netbeans.editor.ext; import org.netbeans.editor.BaseDocument; import org.netbeans.editor.SyntaxSupport; import javax.swing.*; import javax.swing.text.BadLocationException; import javax.swing.text.JTextComponent; import java.awt.*; import java.util.Iterator; import java.util.List; /** * Code completion querying SPI and support. * * @author Miloslav Metelka * @version 1.01 */ public interface CompletionQuery { /** Perform the query on the given component. The query usually * gets the component's document, the caret position and searches back * to find the last command start. Then it inspects the text up to the caret * position and returns the result. * <p>Implementations must be thread safe (also reentrant) because it can be * called speculatively from multiple threads. This requirement can be removed in future SPI * by passing additional flag marking speculative query. Skeletal implementation * could handle multithreading based on the flag. * @param component the component to use in this query. * @param offset position in the component's document to which the query will * be performed. Usually it's a caret position. * @param support syntax-support that will be used during resolving of the query. * @return result of the query or null if there's no result. */ public Result query(JTextComponent component, int offset, SyntaxSupport support); /** * Marker interface that should implement all providers that are * compatible with #13768 semantics. It requires thread safe and reentrant * completion query provider implementation. It's performance gain * to implement as it allows asynchronous speculative queries. * * @deprecated It is a workaround. It's suggested that providers * should wait for new completion query SPI that should better * support speculative queries, partial results, result * cancellation and result narrowing. Implement only if it's simple. * * @since CompletionQuery version 1.01 */ public interface SupportsSpeculativeInvocation { // marker interface } /** Result of the query or expression evaluation. Simply said it consists * of the list of the data and title and an internal information about * how to substitute the text. */ public interface Result { /** Get the list with the items satisfying the query. The list * must always be non-null. If there are no data it will have a zero size. * @return List of objects implementing ResultItem. */ public List getData(); /** Get the title describing the result or null if there's no title. */ public String getTitle(); /** Substitute the text in the document if the user picks * the item from the data with the given index either by * pressing ENTER or doubleclicking the item by mouse. * @param dataIndex current selected item index in the current data list. * It can be used for making the substitution. * @param shift indicates request for some kind of different behaviour, * means that e.g. user hold shift while pressing ENTER. * @return whether the text was substituted or not */ public boolean substituteText(int dataIndex, boolean shift); /** Substitute the text that is common for all the data entries. * This is used to update the document with * the common text when the user presses the TAB key. * @param dataIndex current selected item index in the current data list. * Although normally it shouldn't be necessary for making * the substitution, the completion implementations * can use it for customized behavior. * @return whether the text was substituted or not */ public boolean substituteCommonText(int dataIndex); } /** * The very basic funztionality of Result is implemented by this class, * but parts general enough to not need to be overriden. */ public static abstract class AbstractResult implements Result { /** The List of the ResultItem instances - the content of the result */ private List data; /** The title of the result */ private String title; public AbstractResult(List data, String title) { this.data = data; this.title = title; } public List getData() { return data; } public String getTitle() { return title; } } /** Full implementation of Result, managing substitution of the text and * finding and substituting common prefix of items */ public static class DefaultResult extends AbstractResult { private JTextComponent component; private int offset; private int len; /** Constructor for DefaultResult * @param component the JTextComponent the result is tightened with, * used for operations on its Document, caret, selection and so. * @param title the title displayed in header of completion window * @param data the list of ResultItem instances to be displayed in * completion window, may be null. * @param the offset in the document corresponding to the start * of the text occassionally replaced by the result. * @param the length of the text to be replaced. */ public DefaultResult(JTextComponent component, String title, List data, int offset, int len ) { super(data, title); this.component = component; this.offset = offset; this.len = len; } /** Internal method used to find longest common prefix of two Strings. * it is made private, because I'm going to change its interface * for better performance. */ private int getCommonPrefixLength( char[] commonPrefix, int len, String s ) { char[] c = s.toCharArray(); int i=0; if( len > c.length ) len = c.length; for( ; i<len; i++ ) { if( commonPrefix[i] != c[i] ) break; } return i; } /** Update the text in response to pressing TAB key. Searches through * all items of this result looking for longest common prefix and then * calls the substitution method on selected item providing it with * the length of common part. * @return whether the text was successfully updated */ public boolean substituteCommonText( int dataIndex ) { List data = getData(); if( data.size() == 0 ) return false; Iterator i = data.iterator(); char[] commonPrefix = ((CompletionQuery.ResultItem)i.next()).getItemText().toCharArray(); int commonLength = commonPrefix.length; for( ; i.hasNext(); ) { String second = ((CompletionQuery.ResultItem)i.next()).getItemText(); commonLength = getCommonPrefixLength( commonPrefix, commonLength, second ); } CompletionQuery.ResultItem actData = (CompletionQuery.ResultItem)data.get(dataIndex); return actData.substituteCommonText( component, offset, len, commonLength ); } /** Update the text in response to pressing ENTER. * @return whether the text was successfully updated */ public boolean substituteText(int dataIndex, boolean shift ) { Object actData = getData().get( dataIndex ); return ((CompletionQuery.ResultItem)actData).substituteText( component, offset, len, shift ); } } /** An interface used as an item of List returned by CompletionQuery.Result.getData() * Such items are then able to their part in Completion process themselves */ public static interface ResultItem { /** Update the text in response to pressing TAB key (or any key mapped to * this function) on this element * @param c the text component to operate on, enables implementation to * do things like movement of caret. * @param offset the offset where the item should be placed * @param len the length of recognized text which should be replaced * @param subLen the length of common part - the length of text that should * be inserted after removal of recognized text * @return whether the text was successfully updated */ public boolean substituteCommonText( JTextComponent c, int offset, int len, int subLen ); /** Update the text in response to pressing ENTER on this element. * @param c the text component to operate on, enables implementation to * do things like movement of caret. * @param offset the offset where the item should be placed * @param len the length of recognized text which should be replaced * @param shift the flag that instructs completion to behave somehow * differently - enables more kinds of invocation of substituteText * @return whether the text was successfully updated */ public boolean substituteText( JTextComponent c, int offset, int len, boolean shift ); /** Says what text would this Element use if substituteText is called. * @return the substitution text, usable e.g. for finding common text/its' length */ public String getItemText(); /** Prepare proper component for painting value of <CODE>this</CODE>. * @param JList the list this item will be drawn into, usefull e.g. for * obtaining preferred colors. * @param isSelected tells if this item is just selected, for using * proper color scheme. * @param cellHasFocus tells it this item is just focused. * @return the component usable for painting this value */ public Component getPaintComponent( JList list, boolean isSelected, boolean cellHasFocus); } /** A class providing generic, nearly full implementation of ResultItem */ public abstract static class AbstractResultItem implements CompletionQuery.ResultItem { /* The text this item would expand to */ protected String text; /** Create new ResultItem for given text, should be used in subclass constructors */ public AbstractResultItem( String text ) { this.text = text; } /** Generic implementation, behaves just as described in specification * in substituteCommonText() - removes <CODE>len</CODE> * characters at <CODE>offset</CODE> out of document and then inserts * <CODE>subLen<CODE> characters from the <CODE>text</CODE> */ public boolean substituteCommonText( JTextComponent c, int offset, int len, int subLen ) { BaseDocument doc = (BaseDocument)c.getDocument(); try { doc.atomicLock(); try { doc.remove( offset, len ); doc.insertString( offset, text.substring( 0, subLen ), null); } finally { doc.atomicUnlock(); } } catch( BadLocationException exc ) { return false; //not sucessfull } return true; } /** Generic implementation, behaves just as described in specification * in substituteText() - removes <CODE>len</CODE> characters * at <CODE>offset</CODE> out of document and then inserts * whole <CODE>text</CODE>. Ignores <CODE>shift</CODE> argument. */ public boolean substituteText( JTextComponent c, int offset, int len, boolean shift ) { BaseDocument doc = (BaseDocument)c.getDocument(); try { doc.atomicLock(); try { doc.remove( offset, len ); doc.insertString( offset, text, null); } finally { doc.atomicUnlock(); } } catch( BadLocationException exc ) { return false; //not sucessfull } return true; } /** @return the text this item would expand to. */ public String getItemText() { return text; } } public static class DefaultResultItem extends CompletionQuery.AbstractResultItem { /** The cache for component used for painting value of <CODE>this</CODE> * this component is reused, on every call to getPaintComponent it is * set up and then painted. By default, this component is hold opaque. */ static JLabel rubberStamp = new JLabel(); static { rubberStamp.setOpaque( true ); } /** Color used for painting text of non-selected item */ protected Color foreColor; public DefaultResultItem( String text, Color foreColor ) { super( text ); this.foreColor = foreColor; } public Component getPaintComponent( JList list, boolean isSelected, boolean cellHasFocus ) { rubberStamp.setText( " " + text ); if (isSelected) { rubberStamp.setBackground(list.getSelectionBackground()); rubberStamp.setForeground(list.getSelectionForeground()); } else { rubberStamp.setBackground(list.getBackground()); rubberStamp.setForeground( foreColor ); } return rubberStamp; } } }