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    
020    package org.crsh.processor.jline;
021    
022    import jline.console.ConsoleReader;
023    import jline.console.completer.Completer;
024    import org.crsh.cli.impl.completion.CompletionMatch;
025    import org.crsh.cli.impl.Delimiter;
026    import org.crsh.cli.spi.Completion;
027    import org.crsh.shell.Shell;
028    import org.crsh.shell.ShellProcess;
029    import org.crsh.shell.ShellResponse;
030    
031    import java.io.IOException;
032    import java.io.PrintWriter;
033    import java.util.List;
034    import java.util.Map;
035    import java.util.concurrent.atomic.AtomicReference;
036    
037    public 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    }