001/*
002 * (C) Copyright 2006-2010 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 */
019package org.nuxeo.shell.swing;
020
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.HashSet;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Set;
027
028import javax.swing.JOptionPane;
029
030import jline.CompletionHandler;
031import jline.ConsoleReader;
032import jline.CursorBuffer;
033
034import org.nuxeo.shell.Shell;
035
036/**
037 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
038 */
039@SuppressWarnings({ "unchecked", "rawtypes" })
040public class SwingCompletionHandler implements CompletionHandler {
041
042    protected Console console;
043
044    public SwingCompletionHandler(Console console) {
045        this.console = console;
046    }
047
048    public boolean complete(ConsoleReader reader, List candidates, int position) throws IOException {
049        CursorBuffer buf = reader.getCursorBuffer();
050
051        // if there is only one completion, then fill in the buffer
052        if (candidates.size() == 1) {
053            String value = candidates.get(0).toString();
054
055            // fail if the only candidate is the same as the current buffer
056            if (value.equals(buf.toString())) {
057                console.beep();
058                return false;
059            }
060
061            position = console.getCmdLine().setCompletionWord(value);
062            console.setCaretPosition(console.getCmdLine().getCmdStart() + position);
063            return true;
064        } else if (candidates.size() > 1) {
065            String value = getUnambiguousCompletions(candidates);
066            position = console.getCmdLine().setCompletionWord(value);
067        } else if (candidates.isEmpty()) {
068            console.beep();
069            return false;
070        }
071        String text = console.getCmdLine().getText();
072        console.append("\n");
073        printCandidates(candidates);
074        console.append("\n");
075        console.append(Shell.get().getActiveRegistry().getPrompt(Shell.get()));
076        console.cline = null;
077        console.getCmdLine().setText(text);
078        console.setCaretPosition(console.getCmdLine().getCmdStart() + position);
079
080        return true;
081    }
082
083    /**
084     * Returns a root that matches all the {@link String} elements of the specified {@link List}, or null if there are
085     * no commalities. For example, if the list contains <i>foobar</i>, <i>foobaz</i>, <i>foobuz</i>, the method will
086     * return <i>foob</i>.
087     */
088    private final String getUnambiguousCompletions(final List candidates) {
089        if ((candidates == null) || (candidates.size() == 0)) {
090            return null;
091        }
092
093        // convert to an array for speed
094        String[] strings = (String[]) candidates.toArray(new String[candidates.size()]);
095
096        String first = strings[0];
097        StringBuffer candidate = new StringBuffer();
098
099        for (int i = 0; i < first.length(); i++) {
100            if (startsWith(first.substring(0, i + 1), strings)) {
101                candidate.append(first.charAt(i));
102            } else {
103                break;
104            }
105        }
106
107        return candidate.toString();
108    }
109
110    /**
111     * @return true is all the elements of <i>candidates</i> start with <i>starts</i>
112     */
113    private final boolean startsWith(final String starts, final String[] candidates) {
114        for (int i = 0; i < candidates.length; i++) {
115            if (!candidates[i].startsWith(starts)) {
116                return false;
117            }
118        }
119
120        return true;
121    }
122
123    protected void printCandidates(List<String> candidates) {
124        Set<String> distinct = new HashSet<String>(candidates);
125
126        if (distinct.size() > console.reader.getAutoprintThreshhold()) {
127            // if (!eagerNewlines)
128            if (JOptionPane.showConfirmDialog(console, "Display all " + distinct.size() + " possibilities? (y or n) ",
129                    "Completion Warning", JOptionPane.YES_NO_OPTION) == 1) {
130                return;
131            }
132        }
133
134        // copy the values and make them distinct, without otherwise
135        // affecting the ordering. Only do it if the sizes differ.
136        if (distinct.size() != candidates.size()) {
137            List<String> copy = new ArrayList<String>();
138
139            for (Iterator<String> i = candidates.iterator(); i.hasNext();) {
140                String next = i.next();
141
142                if (!(copy.contains(next))) {
143                    copy.add(next);
144                }
145            }
146
147            candidates = copy;
148        }
149
150        console.printColumns(candidates);
151
152    }
153}