Package org.jruby.ext

Source Code of org.jruby.ext.Readline$ProcCompletor

/***** BEGIN LICENSE BLOCK *****
* Version: CPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Common Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/cpl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2006 Ola Bini <ola@ologix.com>
* Copyright (C) 2006 Damian Steer <pldms@mac.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the CPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the CPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.ext;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Iterator;
import java.util.Collections;

import org.jruby.Ruby;
import org.jruby.RubyModule;
import org.jruby.RubyArray;
import org.jruby.runtime.Block;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.load.Library;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.anno.JRubyModule;

import jline.ConsoleReader;
import jline.Completor;
import jline.FileNameCompletor;
import jline.CandidateListCompletionHandler;
import jline.History;
import org.jruby.RubyString;
import org.jruby.anno.JRubyMethod;
import org.jruby.javasupport.util.RuntimeHelpers;
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.Visibility;
import org.jruby.util.ByteList;

/**
* @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
* @author <a href="mailto:pldms@mac.com">Damian Steer</a>
*/
@JRubyModule(name = "Readline", include = "Enumerable")
public class Readline {

    public static class Service implements Library {

        public void load(final Ruby runtime, boolean wrap) throws IOException {
            createReadline(runtime);
        }
    }

    public static class ConsoleHolder {

        public ConsoleReader readline;
        public Completor currentCompletor;
        public History history;
    }

    public static void createReadline(Ruby runtime) throws IOException {
        ConsoleHolder holder = new ConsoleHolder();
        holder.history = new History();
        holder.currentCompletor = null;

        RubyModule mReadline = runtime.defineModule("Readline");

        mReadline.dataWrapStruct(holder);

        mReadline.defineAnnotatedMethods(Readline.class);
        IRubyObject hist = runtime.getObject().callMethod(runtime.getCurrentContext(), "new");
        mReadline.fastSetConstant("HISTORY", hist);
        hist.getSingletonClass().includeModule(runtime.getEnumerable());
        hist.getSingletonClass().defineAnnotatedMethods(HistoryMethods.class);

        // MRI does similar thing on MacOS X with 'EditLine wrapper'.
        mReadline.fastSetConstant("VERSION", runtime.newString("JLine wrapper"));
    }

    // We lazily initialize this in case Readline.readline has been overridden in ruby (s_readline)
    protected static void initReadline(Ruby runtime, ConsoleHolder holder) throws IOException {
        holder.readline = new ConsoleReader();
        holder.readline.setUseHistory(false);
        holder.readline.setUsePagination(true);
        holder.readline.setBellEnabled(false);
        ((CandidateListCompletionHandler) holder.readline.getCompletionHandler()).setAlwaysIncludeNewline(false);
        if (holder.currentCompletor == null) {
            holder.currentCompletor = new RubyFileNameCompletor();
        }
        holder.readline.addCompletor(holder.currentCompletor);
        holder.readline.setHistory(holder.history);
    }

    public static History getHistory(ConsoleHolder holder) {
        return holder.history;
    }

    public static ConsoleHolder getHolder(Ruby runtime) {
        return (ConsoleHolder) (runtime.fastGetModule("Readline").dataGetStruct());
    }

    public static void setCompletor(ConsoleHolder holder, Completor completor) {
        if (holder.readline != null) {
            holder.readline.removeCompletor(holder.currentCompletor);
        }
        holder.currentCompletor = completor;
        if (holder.readline != null) {
            holder.readline.addCompletor(holder.currentCompletor);
        }
    }

    public static Completor getCompletor(ConsoleHolder holder) {
        return holder.currentCompletor;
    }

    @JRubyMethod(name = "readline", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject s_readline(IRubyObject recv, IRubyObject prompt, IRubyObject add_to_hist) throws IOException {
        ConsoleHolder holder = getHolder(recv.getRuntime());
        if (holder.readline == null) {
            initReadline(recv.getRuntime(), holder); // not overridden, let's go

        }
        IRubyObject line = recv.getRuntime().getNil();
        holder.readline.getTerminal().disableEcho();
        String v = holder.readline.readLine(prompt.toString());
        holder.readline.getTerminal().enableEcho();
        if (null != v) {
            if (add_to_hist.isTrue()) {
                holder.readline.getHistory().addToHistory(v);
            }

            /* Explicitly use UTF-8 here. c.f. history.addToHistory using line.asUTF8() */
            line = RubyString.newUnicodeString(recv.getRuntime(), v);
        }
        return line;
    }

    @JRubyMethod(name = "completion_append_character=", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject s_set_completion_append_character(IRubyObject recv, IRubyObject achar) throws Exception {
        return recv.getRuntime().getNil();
    }

    @JRubyMethod(name = "completion_proc=", module = true, visibility = Visibility.PRIVATE)
    public static IRubyObject s_set_completion_proc(IRubyObject recv, IRubyObject proc) throws Exception {
        if (!proc.respondsTo("call")) {
            throw recv.getRuntime().newArgumentError("argument must respond to call");
        }
        setCompletor(getHolder(recv.getRuntime()), new ProcCompletor(proc));
        return recv.getRuntime().getNil();
    }

    public static class HistoryMethods {
        @JRubyMethod(name = {"push", "<<"}, rest = true)
        public static IRubyObject s_push(IRubyObject recv, IRubyObject[] lines) throws Exception {
            ConsoleHolder holder = getHolder(recv.getRuntime());
            for (int i = 0; i < lines.length; i++) {
                RubyString line = lines[i].convertToString();
                holder.history.addToHistory(line.getUnicodeValue());
            }
            return recv.getRuntime().getNil();
        }

        @JRubyMethod(name = "pop")
        @SuppressWarnings("unchecked")
        public static IRubyObject s_pop(IRubyObject recv) throws Exception {
            Ruby runtime = recv.getRuntime();
            ConsoleHolder holder = getHolder(runtime);
            List histList = holder.history.getHistoryList();

            // TODO: Not fully implemented. We just return the last value,
            // without really popping it.
            String current = (String)histList.get(histList.size() - 1);
            return runtime.newString(current);
        }

        @JRubyMethod(name = "to_a")
        public static IRubyObject s_hist_to_a(IRubyObject recv) throws Exception {
            ConsoleHolder holder = getHolder(recv.getRuntime());
            RubyArray histList = recv.getRuntime().newArray();
            for (Iterator i = holder.history.getHistoryList().iterator(); i.hasNext();) {
                histList.append(recv.getRuntime().newString((String) i.next()));
            }
            return histList;
        }

        @JRubyMethod(name = "to_s")
        public static IRubyObject s_hist_to_s(IRubyObject recv) {
            return recv.getRuntime().newString("HISTORY");
        }

        @JRubyMethod(name = "[]")
        public static IRubyObject s_hist_get(IRubyObject recv, IRubyObject index) {
            Ruby runtime = recv.getRuntime();
            ConsoleHolder holder = getHolder(runtime);
            int i = (int) index.convertToInteger().getLongValue();
            try {
                // TODO: MRI behavior is more complicated than that,
                // there is some magic when dealing with negative indexes.
                return runtime.newString((String) holder.history.getHistoryList().get(i));
            } catch (IndexOutOfBoundsException ioobe) {
                throw runtime.newIndexError("invalid history index: " + i);
            }
        }

        @JRubyMethod(name = "[]=")
        public static IRubyObject s_hist_set(IRubyObject recv, IRubyObject index, IRubyObject val) {
            throw recv.getRuntime().newNotImplementedError("the []=() function is unimplemented on this machine");
        }

        @JRubyMethod(name = "shift")
        public static IRubyObject s_hist_shift(IRubyObject recv) {
            throw recv.getRuntime().newNotImplementedError("the shift function is unimplemented on this machine");
        }

        @JRubyMethod(name = {"length", "size"})
        public static IRubyObject s_hist_length(IRubyObject recv) {
            ConsoleHolder holder = getHolder(recv.getRuntime());
            return recv.getRuntime().newFixnum(holder.history.size());
        }

        @JRubyMethod(name = "empty?")
        public static IRubyObject s_hist_empty_p(IRubyObject recv) {
            ConsoleHolder holder = getHolder(recv.getRuntime());
            return recv.getRuntime().newBoolean(holder.history.size() == 0);
        }

        @JRubyMethod(name = "delete_at")
        public static IRubyObject s_hist_delete_at(IRubyObject recv, IRubyObject index) {
            throw recv.getRuntime().newNotImplementedError("the delete_at function is unimplemented on this machine");
        }

        @JRubyMethod(name = "each")
        public static IRubyObject s_hist_each(IRubyObject recv, Block block) {
            ConsoleHolder holder = getHolder(recv.getRuntime());
            for (Iterator i = holder.history.getHistoryList().iterator(); i.hasNext();) {
                block.yield(recv.getRuntime().getCurrentContext(), recv.getRuntime().newString((String) i.next()));
            }
            return recv;
        }
    }

    // Complete using a Proc object
    public static class ProcCompletor implements Completor {

        IRubyObject procCompletor;

        public ProcCompletor(IRubyObject procCompletor) {
            this.procCompletor = procCompletor;
        }

        public int complete(String buffer, int cursor, List candidates) {
            buffer = buffer.substring(0, cursor);
            int index = buffer.lastIndexOf(" ");
            if (index != -1) {
                buffer = buffer.substring(index + 1);
            }
            ThreadContext context = procCompletor.getRuntime().getCurrentContext();

            IRubyObject comps = RuntimeHelpers
                    .invoke(context, procCompletor, "call", procCompletor.getRuntime().newString(buffer))
                    .callMethod(context, "to_a");
            if (comps instanceof List) {
                for (Iterator i = ((List) comps).iterator(); i.hasNext();) {
                    Object obj = i.next();
                    if (obj != null) {
                        candidates.add(obj.toString());
                    }
                }
                Collections.sort(candidates);
            }
            return cursor - buffer.length();
        }
    }

    // Fix FileNameCompletor to work mid-line
    public static class RubyFileNameCompletor extends FileNameCompletor {

        public int complete(String buffer, int cursor, List candidates) {
            buffer = buffer.substring(0, cursor);
            int index = buffer.lastIndexOf(" ");
            if (index != -1) {
                buffer = buffer.substring(index + 1);
            }
            return index + 1 + super.complete(buffer, cursor, candidates);
        }
    }
}// Readline
TOP

Related Classes of org.jruby.ext.Readline$ProcCompletor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.