001: /***** BEGIN LICENSE BLOCK *****
002: * Version: CPL 1.0/GPL 2.0/LGPL 2.1
003: *
004: * The contents of this file are subject to the Common Public
005: * License Version 1.0 (the "License"); you may not use this file
006: * except in compliance with the License. You may obtain a copy of
007: * the License at http://www.eclipse.org/legal/cpl-v10.html
008: *
009: * Software distributed under the License is distributed on an "AS
010: * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
011: * implied. See the License for the specific language governing
012: * rights and limitations under the License.
013: *
014: * Copyright (C) 2006 Ola Bini <ola@ologix.com>
015: * Copyright (C) 2006 Damian Steer <pldms@mac.com>
016: *
017: * Alternatively, the contents of this file may be used under the terms of
018: * either of the GNU General Public License Version 2 or later (the "GPL"),
019: * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
020: * in which case the provisions of the GPL or the LGPL are applicable instead
021: * of those above. If you wish to allow use of your version of this file only
022: * under the terms of either the GPL or the LGPL, and not to allow others to
023: * use your version of this file under the terms of the CPL, indicate your
024: * decision by deleting the provisions above and replace them with the notice
025: * and other provisions required by the GPL or the LGPL. If you do not delete
026: * the provisions above, a recipient may use your version of this file under
027: * the terms of any one of the CPL, the GPL or the LGPL.
028: ***** END LICENSE BLOCK *****/package org.jruby.ext;
029:
030: import java.io.IOException;
031: import java.util.List;
032: import java.util.Iterator;
033: import java.util.Collections;
034:
035: import org.jruby.Ruby;
036: import org.jruby.RubyModule;
037: import org.jruby.RubyArray;
038: import org.jruby.runtime.Block;
039: import org.jruby.runtime.CallbackFactory;
040: import org.jruby.runtime.ThreadContext;
041: import org.jruby.runtime.load.Library;
042: import org.jruby.runtime.builtin.IRubyObject;
043:
044: import jline.ConsoleReader;
045: import jline.Completor;
046: import jline.FileNameCompletor;
047: import jline.CandidateListCompletionHandler;
048: import jline.History;
049: import org.jruby.runtime.MethodIndex;
050:
051: /**
052: * @author <a href="mailto:ola.bini@ki.se">Ola Bini</a>
053: * @author <a href="mailto:pldms@mac.com">Damian Steer</a>
054: */
055: public class Readline {
056: public static class Service implements Library {
057: public void load(final Ruby runtime) throws IOException {
058: createReadline(runtime);
059: }
060: }
061:
062: private static ConsoleReader readline;
063: private static Completor currentCompletor;
064: private static History history;
065:
066: public static void createReadline(Ruby runtime) throws IOException {
067: history = new History();
068: currentCompletor = null;
069:
070: RubyModule mReadline = runtime.defineModule("Readline");
071: CallbackFactory readlinecb = runtime
072: .callbackFactory(Readline.class);
073: mReadline.defineMethod("readline", readlinecb
074: .getFastSingletonMethod("s_readline",
075: IRubyObject.class, IRubyObject.class));
076: mReadline.module_function(new IRubyObject[] { runtime
077: .newSymbol("readline") });
078: mReadline.defineMethod("completion_append_character=",
079: readlinecb.getFastSingletonMethod(
080: "s_set_completion_append_character",
081: IRubyObject.class));
082: mReadline.module_function(new IRubyObject[] { runtime
083: .newSymbol("completion_append_character=") });
084: mReadline.defineMethod("completion_proc=", readlinecb
085: .getFastSingletonMethod("s_set_completion_proc",
086: IRubyObject.class));
087: mReadline.module_function(new IRubyObject[] { runtime
088: .newSymbol("completion_proc=") });
089: IRubyObject hist = runtime.getObject().callMethod(
090: runtime.getCurrentContext(), "new");
091: mReadline.setConstant("HISTORY", hist);
092: hist.getSingletonClass().includeModule(
093: runtime.getModule("Enumerable"));
094: hist.getSingletonClass().defineMethod("push",
095: readlinecb.getFastOptSingletonMethod("s_push"));
096: hist.getSingletonClass().defineMethod("pop",
097: readlinecb.getFastSingletonMethod("s_pop"));
098: hist.getSingletonClass().defineMethod("to_a",
099: readlinecb.getFastSingletonMethod("s_hist_to_a"));
100: hist.getSingletonClass().defineMethod("to_s",
101: readlinecb.getFastSingletonMethod("s_hist_to_s"));
102: hist.getSingletonClass().defineMethod(
103: "[]",
104: readlinecb.getFastSingletonMethod("s_hist_get",
105: IRubyObject.class));
106: hist.getSingletonClass().defineMethod(
107: "[]=",
108: readlinecb.getFastSingletonMethod("s_hist_set",
109: IRubyObject.class, IRubyObject.class));
110: hist.getSingletonClass().defineMethod("<<",
111: readlinecb.getFastOptSingletonMethod("s_push"));
112: hist.getSingletonClass().defineMethod("shift",
113: readlinecb.getFastSingletonMethod("s_hist_shift"));
114: hist.getSingletonClass().defineMethod("each",
115: readlinecb.getSingletonMethod("s_hist_each"));
116: hist.getSingletonClass().defineMethod("length",
117: readlinecb.getFastSingletonMethod("s_hist_length"));
118: hist.getSingletonClass().defineMethod("size",
119: readlinecb.getFastSingletonMethod("s_hist_length"));
120: hist.getSingletonClass().defineMethod("empty?",
121: readlinecb.getFastSingletonMethod("s_hist_empty_p"));
122: hist.getSingletonClass().defineMethod(
123: "delete_at",
124: readlinecb.getFastSingletonMethod("s_hist_delete_at",
125: IRubyObject.class));
126: }
127:
128: // We lazily initialise this in case Readline.readline has been overriden in ruby (s_readline)
129: protected static void initReadline() throws IOException {
130: readline = new ConsoleReader();
131: readline.setUseHistory(false);
132: readline.setUsePagination(true);
133: readline.setBellEnabled(false);
134: ((CandidateListCompletionHandler) readline
135: .getCompletionHandler()).setAlwaysIncludeNewline(false);
136: if (currentCompletor == null)
137: currentCompletor = new RubyFileNameCompletor();
138: readline.addCompletor(currentCompletor);
139: readline.setHistory(history);
140: }
141:
142: public static History getHistory() {
143: return history;
144: }
145:
146: public static void setCompletor(Completor completor) {
147: if (readline != null)
148: readline.removeCompletor(currentCompletor);
149: currentCompletor = completor;
150: if (readline != null)
151: readline.addCompletor(currentCompletor);
152: }
153:
154: public static Completor getCompletor() {
155: return currentCompletor;
156: }
157:
158: public static IRubyObject s_readline(IRubyObject recv,
159: IRubyObject prompt, IRubyObject add_to_hist)
160: throws IOException {
161: if (readline == null)
162: initReadline(); // not overridden, let's go
163: IRubyObject line = recv.getRuntime().getNil();
164: String v = readline.readLine(prompt.toString());
165: if (null != v) {
166: if (add_to_hist.isTrue())
167: readline.getHistory().addToHistory(v);
168: line = recv.getRuntime().newString(v);
169: }
170: return line;
171: }
172:
173: public static IRubyObject s_push(IRubyObject recv,
174: IRubyObject[] lines) throws Exception {
175: for (int i = 0; i < lines.length; i++) {
176: history.addToHistory(lines[i].toString());
177: }
178: return recv.getRuntime().getNil();
179: }
180:
181: public static IRubyObject s_pop(IRubyObject recv) throws Exception {
182: return recv.getRuntime().getNil();
183: }
184:
185: public static IRubyObject s_hist_to_a(IRubyObject recv)
186: throws Exception {
187: RubyArray histList = recv.getRuntime().newArray();
188: for (Iterator i = history.getHistoryList().iterator(); i
189: .hasNext();) {
190: histList.append(recv.getRuntime().newString(
191: (String) i.next()));
192: }
193: return histList;
194: }
195:
196: public static IRubyObject s_hist_to_s(IRubyObject recv) {
197: return recv.getRuntime().newString("HISTORY");
198: }
199:
200: public static IRubyObject s_hist_get(IRubyObject recv,
201: IRubyObject index) {
202: int i = (int) index.convertToInteger().getLongValue();
203: return recv.getRuntime().newString(
204: (String) history.getHistoryList().get(i));
205: }
206:
207: public static IRubyObject s_hist_set(IRubyObject recv,
208: IRubyObject index, IRubyObject val) {
209: throw recv.getRuntime().newNotImplementedError(
210: "the []=() function is unimplemented on this machine");
211: }
212:
213: public static IRubyObject s_hist_shift(IRubyObject recv) {
214: throw recv.getRuntime().newNotImplementedError(
215: "the shift function is unimplemented on this machine");
216: }
217:
218: public static IRubyObject s_hist_length(IRubyObject recv) {
219: return recv.getRuntime().newFixnum(history.size());
220: }
221:
222: public static IRubyObject s_hist_empty_p(IRubyObject recv) {
223: return recv.getRuntime().newBoolean(history.size() == 0);
224: }
225:
226: public static IRubyObject s_hist_delete_at(IRubyObject recv,
227: IRubyObject index) {
228: throw recv
229: .getRuntime()
230: .newNotImplementedError(
231: "the delete_at function is unimplemented on this machine");
232: }
233:
234: public static IRubyObject s_hist_each(IRubyObject recv, Block block) {
235: for (Iterator i = history.getHistoryList().iterator(); i
236: .hasNext();) {
237: block.yield(recv.getRuntime().getCurrentContext(), recv
238: .getRuntime().newString((String) i.next()));
239: }
240: return recv;
241: }
242:
243: public static IRubyObject s_set_completion_append_character(
244: IRubyObject recv, IRubyObject achar) throws Exception {
245: return recv.getRuntime().getNil();
246: }
247:
248: public static IRubyObject s_set_completion_proc(IRubyObject recv,
249: IRubyObject proc) throws Exception {
250: if (!proc.respondsTo("call"))
251: throw recv.getRuntime().newArgumentError(
252: "argument must respond to call");
253: setCompletor(new ProcCompletor(proc));
254: return recv.getRuntime().getNil();
255: }
256:
257: // Complete using a Proc object
258: public static class ProcCompletor implements Completor {
259: IRubyObject procCompletor;
260:
261: public ProcCompletor(IRubyObject procCompletor) {
262: this .procCompletor = procCompletor;
263: }
264:
265: public int complete(String buffer, int cursor, List candidates) {
266: buffer = buffer.substring(0, cursor);
267: int index = buffer.lastIndexOf(" ");
268: if (index != -1)
269: buffer = buffer.substring(index + 1);
270: ThreadContext context = procCompletor.getRuntime()
271: .getCurrentContext();
272:
273: IRubyObject comps = procCompletor.callMethod(
274: context,
275: "call",
276: new IRubyObject[] { procCompletor.getRuntime()
277: .newString(buffer) }).callMethod(context,
278: MethodIndex.TO_A, "to_a");
279: if (comps instanceof List) {
280: for (Iterator i = ((List) comps).iterator(); i
281: .hasNext();) {
282: Object obj = i.next();
283: if (obj != null)
284: candidates.add(obj.toString());
285: }
286: Collections.sort(candidates);
287: }
288: return cursor - buffer.length();
289: }
290: }
291:
292: // Fix FileNameCompletor to work mid-line
293: public static class RubyFileNameCompletor extends FileNameCompletor {
294: public int complete(String buffer, int cursor, List candidates) {
295: buffer = buffer.substring(0, cursor);
296: int index = buffer.lastIndexOf(" ");
297: if (index != -1)
298: buffer = buffer.substring(index + 1);
299: return index + 1
300: + super .complete(buffer, cursor, candidates);
301: }
302: }
303:
304: }// Readline
|