001/*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019
020package org.crsh.processor.jline;
021
022import jline.console.ConsoleReader;
023import jline.console.completer.Completer;
024import org.crsh.cli.impl.completion.CompletionMatch;
025import org.crsh.cli.impl.Delimiter;
026import org.crsh.cli.spi.Completion;
027import org.crsh.shell.Shell;
028import org.crsh.shell.ShellProcess;
029import org.crsh.shell.ShellResponse;
030
031import java.io.IOException;
032import java.io.PrintWriter;
033import java.util.List;
034import java.util.Map;
035import java.util.concurrent.atomic.AtomicReference;
036
037public class JLineProcessor implements Runnable, Completer {
038
039  /** . */
040  private final Shell shell;
041
042  /** . */
043  final ConsoleReader reader;
044
045  /** . */
046  final PrintWriter writer;
047
048  /** . */
049  final AtomicReference<ShellProcess> current;
050
051  /** Whether or not we switched on the alternate screen. */
052  boolean useAlternate;
053
054  public JLineProcessor(Shell shell, ConsoleReader reader, PrintWriter writer) {
055    this.shell = shell;
056    this.reader = reader;
057    this.writer = writer;
058    this.current = new AtomicReference<ShellProcess>();
059    this.useAlternate = false;
060  }
061
062  public void run() {
063    String welcome = shell.getWelcome();
064    writer.println(welcome);
065    writer.flush();
066    loop();
067  }
068
069  private String readLine() {
070    StringBuilder buffer = new StringBuilder();
071    String prompt = getPrompt();
072    writer.println();
073    writer.flush();
074    while (true) {
075      try {
076        String chunk;
077        if ((chunk = reader.readLine(prompt)) == null) {
078          return null;
079        }
080        if (chunk.length() > 0 && chunk.charAt(chunk.length() - 1) == '\\') {
081          prompt = "> ";
082          buffer.append(chunk, 0, chunk.length() - 1);
083        } else {
084          buffer.append(chunk);
085          return buffer.toString();
086        }
087      }
088      catch (IOException e) {
089        // What should we do other than that ?
090        return null;
091      }
092    }
093  }
094
095  private void loop() {
096    while (true) {
097      String line = readLine();
098
099      //
100      ShellProcess process = shell.createProcess(line);
101      JLineProcessContext context = new JLineProcessContext(this);
102      current.set(process);
103      try {
104        process.execute(context);
105        try {
106          context.latch.await();
107        }
108        catch (InterruptedException ignore) {
109          // At the moment
110        }
111      }
112      finally {
113        current.set(null);
114      }
115
116      //
117      ShellResponse response = context.resp.get();
118
119      // Write message
120      boolean flushed = false;
121      String msg = response.getMessage();
122      if (msg.length() > 0) {
123        writer.write(msg);
124        writer.flush();
125        flushed = true;
126      }
127
128      //
129      if (response instanceof ShellResponse.Cancelled) {
130        // Do nothing
131      } else if (response instanceof ShellResponse.Close) {
132        break;
133      } else {
134        if (!flushed) {
135          writer.flush();
136        }
137      }
138    }
139  }
140
141  public int complete(String buffer, int cursor, List<CharSequence> candidates) {
142    String prefix = buffer.substring(0, cursor);
143    CompletionMatch completion = shell.complete(prefix);
144    Completion vc = completion.getValue();
145    if (vc.isEmpty()) {
146      return -1;
147    }
148    Delimiter delimiter = completion.getDelimiter();
149    for (Map.Entry<String, Boolean> entry : vc) {
150      StringBuilder sb = new StringBuilder();
151      sb.append(vc.getPrefix());
152      try {
153        delimiter.escape(entry.getKey(), sb);
154        if (entry.getValue()) {
155          sb.append(completion.getDelimiter().getValue());
156        }
157        candidates.add(sb.toString());
158      }
159      catch (IOException ignore) {
160      }
161    }
162    return cursor - vc.getPrefix().length();
163  }
164
165  public void cancel() {
166    ShellProcess process = current.get();
167    if (process != null) {
168      process.cancel();
169    } else {
170      // Do nothing
171    }
172  }
173
174  String getPrompt() {
175    String prompt = shell.getPrompt();
176    return prompt == null ? "% " : prompt;
177  }
178}